diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b9e8ec1..0f7d663 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,67 +1,150 @@ -name: CI Matrix +name: CI on: push: - branches: [ "master", "release" ] + branches: [master, release] pull_request: - branches: [ "master", "release" ] + branches: [master, release] permissions: contents: read jobs: - build-and-test: - runs-on: ${{ startsWith(matrix.platform, 'arm') && 'ubuntu-24.04-arm' || format('{0}-latest', matrix.os) }} # GitHub-hosted runners + # ─── Host: build + run unittest exes on every supported host arch/OS ─── + host: + name: host ${{ matrix.os }} ${{ matrix.arch }} ${{ matrix.compiler }} + runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || format('{0}-latest', matrix.os) }} strategy: + fail-fast: false matrix: - os: [windows, ubuntu] + os: [ubuntu, windows] compiler: [dmd, ldc] - platform: [x86, x86_64, arm, arm64, riscv64] + arch: [x86, x86_64, arm64] exclude: - # No point cross-compiling on Windows; linux is so much faster! - - os: windows - platform: arm - - os: windows - platform: arm64 - - os: windows - platform: riscv64 - # TODO: DMD ARM64 support is WIP; remove when it releases... - - compiler: dmd - platform: arm64 - - compiler: dmd - platform: arm - - platform: arm # TODO: not yet working... - - platform: riscv64 # TODO: not yet working... - fail-fast: false + - { os: windows, arch: arm64 } # windows-arm64 runner not in free tier + - { compiler: dmd, arch: arm64 } # DMD arm64 still WIP + env: + MAKE_OS: ${{ matrix.os == 'ubuntu' && 'linux' || matrix.os }} steps: - uses: actions/checkout@v4 - - name: Setup D Compiler + - name: Setup D compiler uses: dlang-community/setup-dlang@v2 with: compiler: ${{ matrix.compiler == 'dmd' && 'dmd-master' || matrix.compiler }} - # 32-bit linux needs libs to link and run tests... - - name: Install 32-bit Toolchains (Linux) - if: ${{ matrix.os == 'ubuntu' && matrix.platform == 'x86' }} - run: sudo apt-get update && sudo apt-get install -y gcc-multilib - - name: Install 32-bit ARM Toolchains (Linux) - if: ${{ matrix.os == 'ubuntu' && matrix.platform == 'arm' }} - run: sudo dpkg --add-architecture armhf && sudo apt-get update && sudo apt-get install -y libstdc++6:armhf - - - name: Build release - if: ${{ github.ref == 'refs/heads/release' }} - run: make PLATFORM=${{ matrix.platform }} CONFIG=release OS=${{ matrix.os }} D_COMPILER=${{ matrix.compiler }} + - name: Install mbedtls (Linux host) + if: matrix.os == 'ubuntu' + run: sudo apt-get update && sudo apt-get install -y libmbedtls-dev + + - name: Install 32-bit deps (Linux x86) + if: matrix.os == 'ubuntu' && matrix.arch == 'x86' + run: | + sudo dpkg --add-architecture i386 + sudo apt-get update + sudo apt-get install -y gcc-multilib libmbedtls-dev:i386 + - name: Build unittest - run: make PLATFORM=${{ matrix.platform }} CONFIG=unittest OS=${{ matrix.os }} D_COMPILER=${{ matrix.compiler }} + run: make ARCH=${{ matrix.arch }} OS=${{ env.MAKE_OS }} CONFIG=unittest COMPILER=${{ matrix.compiler }} + + - name: Run unittest + run: ./bin/${{ matrix.arch }}_${{ env.MAKE_OS }}_unittest/urt_test${{ matrix.os == 'windows' && '.exe' || '' }} - - name: Test - if: ${{ success() && (matrix.platform == 'x86_64' || matrix.platform == 'x86' || matrix.platform == 'arm64') }} - run: ./bin/${{ matrix.platform }}_unittest/urt_test + - name: Build release library + if: github.ref == 'refs/heads/release' + run: make ARCH=${{ matrix.arch }} OS=${{ env.MAKE_OS }} CONFIG=release COMPILER=${{ matrix.compiler }} - name: Upload release library - if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/release' && matrix.compiler == 'ldc' }} + if: github.event_name == 'push' && github.ref == 'refs/heads/release' && matrix.compiler == 'ldc' + uses: actions/upload-artifact@v4 + with: + name: urt-lib_${{ matrix.os }}_${{ matrix.arch }} + path: ./bin/${{ matrix.arch }}_${{ env.MAKE_OS }}_release/ + + # ─── Cross-bare: build flashable urt_test.bin for every embedded target ─── + # CI runner can't execute these — they're flash images. Artifacts are + # uploaded so anyone can grab one and flash it to real hardware for testing. + cross-bare: + name: cross ${{ matrix.target.name }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + target: + - { name: bl618, toolchain: riscv, args: PLATFORM=bl618 } + - { name: bl808-d0, toolchain: riscv, args: 'PLATFORM=bl808 PROCESSOR=c906' } + - { name: bl808-m0, toolchain: riscv, args: 'PLATFORM=bl808 PROCESSOR=e907' } + - { name: rp2350, toolchain: arm, args: PLATFORM=rp2350 } + - { name: bk7231n, toolchain: arm, args: PLATFORM=bk7231n } + - { name: bk7231t, toolchain: arm, args: PLATFORM=bk7231t } + - { name: stm4xx, toolchain: arm, args: PLATFORM=stm4xx } + - { name: stm7xx, toolchain: arm, args: PLATFORM=stm7xx } + steps: + - uses: actions/checkout@v4 + + - uses: dlang-community/setup-dlang@v2 + with: + compiler: ldc + + - name: Install ARM cross-toolchain + if: matrix.target.toolchain == 'arm' + run: | + sudo apt-get update + sudo apt-get install -y gcc-arm-none-eabi picolibc-arm-none-eabi + + - name: Install RISC-V cross-toolchain + if: matrix.target.toolchain == 'riscv' + run: | + sudo apt-get update + sudo apt-get install -y gcc-riscv64-unknown-elf picolibc-riscv64-unknown-elf + + - name: Build flashable unittest image + run: make ${{ matrix.target.args }} CONFIG=unittest + + - name: Upload flash image + uses: actions/upload-artifact@v4 + with: + name: urt_test_${{ matrix.target.name }} + path: | + bin/*/urt_test + bin/*/urt_test.bin + + # ─── Cross-ESP: pre-link artifacts only (no ESP-IDF on build slave) ─── + # esp32, esp32-s3 (Xtensa): emit LLVM bitcode (.bc) for downstream Espressif + # llc / ESP-IDF link. Requires LDC with Xtensa target enabled. + # esp32-p4 (RISC-V): plain compile to object (.o); link needs ESP-IDF. + cross-esp: + name: cross ${{ matrix.target.name }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + target: + - { name: esp32, arch: xtensa, ext: bc } # LX6 + - { name: esp32-s3, arch: xtensa, ext: bc } # LX7 + - { name: esp32-p4, arch: riscv, ext: o } # RV32IMAFDCV + steps: + - uses: actions/checkout@v4 + + - uses: dlang-community/setup-dlang@v2 + with: + compiler: ldc + + - name: Install RISC-V toolchain + if: matrix.target.arch == 'riscv' + run: | + sudo apt-get update + sudo apt-get install -y gcc-riscv64-unknown-elf picolibc-riscv64-unknown-elf + + # TODO: Xtensa needs LDC built with Xtensa target. Recent upstream LDC + # has experimental support; if dlang-community/setup-dlang's LDC build + # lacks it, install Espressif's LDC fork here. + + - name: Build + run: make PLATFORM=${{ matrix.target.name }} CONFIG=unittest + + - name: Upload artifact uses: actions/upload-artifact@v4 with: - name: urt-lib_${{ matrix.os }}_${{ matrix.platform }} - path: ./bin/${{ matrix.platform }}_release/${{ matrix.os == 'windows' && 'urt.lib' || 'liburt.a' }} + name: urt_test_${{ matrix.target.name }} + path: obj/*/urt_test.${{ matrix.target.ext }} diff --git a/Makefile b/Makefile index 924a71e..39cb25e 100644 --- a/Makefile +++ b/Makefile @@ -1,126 +1,165 @@ -OS ?= ubuntu -PLATFORM ?= x86_64 -CONFIG ?= debug -D_COMPILER ?= dmd -DC ?= dmd - -SRCDIR := src -TARGET_SUBDIR := $(PLATFORM)_$(CONFIG) -OBJDIR := obj/$(TARGET_SUBDIR) -TARGETDIR := bin/$(TARGET_SUBDIR) -TARGETNAME := urt - -# unittest config adjustments -ifeq ($(CONFIG),unittest) - TARGETNAME := $(TARGETNAME)_test - BUILD_TYPE := exe -else - BUILD_TYPE := lib -endif +URT_SRCDIR := src -DEPFILE := $(OBJDIR)/$(TARGETNAME).d +include platforms.mk -DFLAGS := $(DFLAGS) -preview=bitfields -preview=rvaluerefparam -preview=nosharedaccess -preview=in +# ======================================================================= +# Build mode +# +# Host (windows/linux/freebsd): +# default -> static lib (liburt.a / urt.lib) +# CONFIG=unittest -> standalone test exe +# +# Cross (freertos/baremetal): +# CONFIG=unittest -> flashable test image (.bin via objcopy), using URT's +# in-tree linker script (platforms//.ld) +# default -> compile-only (.o for non-ESP, .bc for ESP) +# ESP -> always .bc, no link possible without ESP-IDF +# ======================================================================= -ifeq ($(OS),windows) -SOURCES := $(shell dir /s /b $(SRCDIR)\\*.d) -else -SOURCES := $(shell find "$(SRCDIR)" -type f -name '*.d') +OBJDIR := obj/$(BUILDNAME)_$(CONFIG) +TARGETDIR := bin/$(BUILDNAME)_$(CONFIG) + +# Linker script selection for cross-target unittest builds (ESP excluded -- +# no ESP-IDF on build slave). platforms.mk falls back to compile-only when +# BAREMETAL_LD isn't set. +ifeq ($(CONFIG),unittest) + ifeq ($(PLATFORM),bl808) + ifeq ($(PROCESSOR),c906) + BAREMETAL_LD := platforms/bl808/bl808_d0.ld + else ifeq ($(PROCESSOR),e907) + BAREMETAL_LD := platforms/bl808/bl808_m0.ld + endif + else ifeq ($(PLATFORM),bl618) + BAREMETAL_LD := platforms/bl618/bl618.ld + else ifneq ($(filter bk7231n bk7231t,$(PLATFORM)),) + BAREMETAL_LD := platforms/bk7231/$(PLATFORM).ld + else ifeq ($(PLATFORM),rp2350) + BAREMETAL_LD := platforms/rp2350/rp2350.ld + else ifdef STM32_VARIANT + BAREMETAL_LD := platforms/stm32/stm32_$(STM32_VARIANT).ld + endif + ifdef BAREMETAL_LD + DFLAGS := $(DFLAGS) -L-T$(BAREMETAL_LD) -main + endif endif -# Set target file based on build type and OS -ifeq ($(BUILD_TYPE),exe) +# Resolve build mode + target file +ifneq ($(filter freertos baremetal,$(OS)),) + ifdef BAREMETAL_LD + BUILD_MODE := embedded-exe + TARGETNAME := urt_test + TARGET := $(TARGETDIR)/$(TARGETNAME) BUILD_CMD_FLAGS := - ifeq ($(OS),windows) - TARGET = $(TARGETDIR)/$(TARGETNAME).exe - else - TARGET = $(TARGETDIR)/$(TARGETNAME) - endif -else # lib - BUILD_CMD_FLAGS := -lib - ifeq ($(OS),windows) - TARGET = $(TARGETDIR)/$(TARGETNAME).lib - else - TARGET = $(TARGETDIR)/lib$(TARGETNAME).a - endif + else ifeq ($(ARCH),xtensa) + # Xtensa: bitcode for downstream Espressif llc / ESP-IDF + BUILD_MODE := bitcode + TARGETNAME := urt$(if $(filter unittest,$(CONFIG)),_test) + TARGET := $(OBJDIR)/$(TARGETNAME).bc + BUILD_CMD_FLAGS := + else + # No linker script + non-Xtensa cross: compile-only object + BUILD_MODE := compile-only + TARGETNAME := urt$(if $(filter unittest,$(CONFIG)),_test) + TARGET := $(OBJDIR)/$(TARGETNAME).o + BUILD_CMD_FLAGS := + endif +else ifeq ($(CONFIG),unittest) + BUILD_MODE := exe + TARGETNAME := urt_test + BUILD_CMD_FLAGS := -main + ifeq ($(OS),windows) + TARGET := $(TARGETDIR)/$(TARGETNAME).exe + else + TARGET := $(TARGETDIR)/$(TARGETNAME) + endif +else + BUILD_MODE := lib + TARGETNAME := urt + BUILD_CMD_FLAGS := -lib + ifeq ($(OS),windows) + TARGET := $(TARGETDIR)/$(TARGETNAME).lib + else + TARGET := $(TARGETDIR)/lib$(TARGETNAME).a + endif endif -ifeq ($(D_COMPILER),ldc) - DFLAGS := $(DFLAGS) -I $(SRCDIR) - - ifeq ($(PLATFORM),x86_64) -# DFLAGS := $(DFLAGS) -mtriple=x86_64-linux-gnu - else ifeq ($(PLATFORM),x86) - ifeq ($(OS),windows) - DFLAGS := $(DFLAGS) -mtriple=i686-windows-msvc - else - DFLAGS := $(DFLAGS) -mtriple=i686-linux-gnu - endif - else ifeq ($(PLATFORM),arm64) - DFLAGS := $(DFLAGS) -mtriple=aarch64-linux-gnu - else ifeq ($(PLATFORM),arm) - DFLAGS := $(DFLAGS) -mtriple=arm-linux-eabihf -mcpu=cortex-a7 - else ifeq ($(PLATFORM),riscv64) - # we are building the Sipeed M1s device... which is BL808 as I understand - DFLAGS := $(DFLAGS) -mtriple=riscv64-unknown-elf -mcpu=c906 -mattr=+m,+a,+f,+c,+v - else - $(error "Unsupported platform: $(PLATFORM)") - endif +DEPFILE := $(OBJDIR)/$(TARGETNAME).d - ifeq ($(CONFIG),release) - DFLAGS := $(DFLAGS) -release -O3 -enable-inlining - else - DFLAGS := $(DFLAGS) -g -d-debug - endif -else ifeq ($(D_COMPILER),dmd) - DFLAGS := $(DFLAGS) -I=$(SRCDIR) - - ifeq ($(PLATFORM),x86_64) -# DFLAGS := $(DFLAGS) -m64 - else ifeq ($(PLATFORM),x86) - DFLAGS := $(DFLAGS) -m32 - else - $(error "Unsupported platform: $(PLATFORM)") - endif +# objcopy for embedded-exe -> .bin (derive from cross-gcc path) +ifeq ($(BUILD_MODE),embedded-exe) + ifneq ($(filter arm thumb,$(ARCH)),) + BAREMETAL_OBJCOPY := arm-none-eabi-objcopy + OBJCOPY_FLAGS := -R .bss -R .tbss -R '.tbss.*' -R .ARM.attributes -R '.debug*' + else + BAREMETAL_OBJCOPY := riscv64-unknown-elf-objcopy + OBJCOPY_FLAGS := + endif - ifeq ($(CONFIG),release) - DFLAGS := $(DFLAGS) -release -O -inline - else - DFLAGS := $(DFLAGS) -g -debug - endif -else - $(error "Unknown D compiler: $(D_COMPILER)") -endif + BAREMETAL_OBJS := $(patsubst %.S,$(OBJDIR)/%.o,$(patsubst %.c,$(OBJDIR)/%.o,$(BAREMETAL_SRCS))) + BAREMETAL_CFLAGS := $(BAREMETAL_CFLAGS) -ffreestanding -O2 -ifeq ($(CONFIG),unittest) - DFLAGS := $(DFLAGS) -unittest +$(OBJDIR)/%.o: $(BAREMETAL_DIR)/%.S + @mkdir -p $(OBJDIR) + $(BAREMETAL_GCC) $(BAREMETAL_CFLAGS) -c -o $@ $< + +$(OBJDIR)/%.o: $(BAREMETAL_DIR)/%.c + @mkdir -p $(OBJDIR) + $(BAREMETAL_GCC) $(BAREMETAL_CFLAGS) -c -o $@ $< endif --include $(DEPFILE) +# ======================================================================= +# Build rule +# ======================================================================= -$(TARGET): -ifeq ($(OS),windows) - @if not exist "obj" mkdir "obj" > nul 2>&1 - @if not exist "$(subst /,\,$(OBJDIR))" mkdir "$(subst /,\,$(OBJDIR))" > nul 2>&1 - @if not exist "bin" mkdir "bin" > nul 2>&1 - @if not exist "$(subst /,\,$(TARGETDIR))" mkdir "$(subst /,\,$(TARGETDIR))" > nul 2>&1 -else +$(TARGET): $(BAREMETAL_OBJS) mkdir -p $(OBJDIR) $(TARGETDIR) -endif -ifeq ($(D_COMPILER),ldc) - "$(DC)" $(DFLAGS) $(BUILD_CMD_FLAGS) -of$(TARGET) -od$(OBJDIR) -deps=$(DEPFILE) $(SOURCES) -else ifeq ($(D_COMPILER),dmd) -ifeq ($(BUILD_TYPE),lib) - "$(DC)" $(DFLAGS) $(BUILD_CMD_FLAGS) -of$(notdir $(TARGET)) -od$(OBJDIR) -makedeps $(SOURCES) > $(DEPFILE) -ifeq ($(OS),windows) - move "$(subst /,\,$(OBJDIR))\\$(notdir $(TARGET))" "$(subst /,\,$(TARGETDIR))" > nul -else +ifeq ($(COMPILER),ldc) + "$(DC)" $(DFLAGS) $(BUILD_CMD_FLAGS) -of$(TARGET) -od$(OBJDIR) -deps=$(DEPFILE) $(BAREMETAL_OBJS) $(URT_SOURCES) +else ifeq ($(COMPILER),dmd) +ifeq ($(BUILD_MODE),lib) + "$(DC)" $(DFLAGS) $(BUILD_CMD_FLAGS) -of$(OBJDIR)/$(notdir $(TARGET)) -od$(OBJDIR) -makedeps $(URT_SOURCES) > $(DEPFILE) mv "$(OBJDIR)/$(notdir $(TARGET))" "$(TARGETDIR)" +else + "$(DC)" $(DFLAGS) $(BUILD_CMD_FLAGS) -of$(TARGET) -od$(OBJDIR) -makedeps $(URT_SOURCES) > $(DEPFILE) endif -else # exe - "$(DC)" $(DFLAGS) $(BUILD_CMD_FLAGS) -of$(TARGET) -od$(OBJDIR) -makedeps $(SOURCES) > $(DEPFILE) endif +ifeq ($(BUILD_MODE),embedded-exe) + $(BAREMETAL_OBJCOPY) -O binary $(OBJCOPY_FLAGS) $(TARGET) $(TARGETDIR)/$(TARGETNAME).bin endif +# ======================================================================= +# CI: build the full cross-target matrix +# +# Embedded targets produce flashable urt_test images (.bin); ESP variants +# produce LLVM bitcode (no ESP-IDF on build slave). Catches submodule-bump +# breakage before downstream projects update. +# ======================================================================= + +CI_PLATFORMS := \ + esp32 esp32-s2 esp32-s3 esp32-c2 esp32-c3 esp32-c5 esp32-c6 esp32-h2 esp32-p4 \ + bl618 bk7231n bk7231t rp2350 stm4xx stm7xx bl808-d0 bl808-m0 + +.PHONY: ci-build +ci-build: + @set -e; for p in $(CI_PLATFORMS); do \ + case $$p in \ + bl808-d0) args="PLATFORM=bl808 PROCESSOR=c906" ;; \ + bl808-m0) args="PLATFORM=bl808 PROCESSOR=e907" ;; \ + *) args="PLATFORM=$$p" ;; \ + esac; \ + echo "=== ci-build: $$p ($$args) ==="; \ + $(MAKE) --no-print-directory $$args CONFIG=unittest || exit 1; \ + done + @echo "" + @echo "=== ci-build complete ===" + @find bin obj -type f \( -name 'urt_test*.bin' -o -name 'urt_test*.bc' -o -name 'urt_test*.o' \) 2>/dev/null | sort + +# ======================================================================= +# Clean +# ======================================================================= + clean: rm -rf $(OBJDIR) $(TARGETDIR) + +clean-all: + rm -rf obj bin diff --git a/platforms.mk b/platforms.mk new file mode 100644 index 0000000..379d2f5 --- /dev/null +++ b/platforms.mk @@ -0,0 +1,675 @@ +# ======================================================================= +# URT platform/processor/toolchain configuration +# +# Shared between URT's own Makefile and downstream consumers (OpenWatt etc.). +# Anything that depends on which SoC/core/toolchain we're targeting lives here. +# +# Inputs (set by caller before include): +# PLATFORM - SoC/board (esp32-s3, bl808, bl618, bk7231n, rp2350, stm4xx, ...) +# or OS name (windows, linux, ubuntu, freebsd) for host builds. +# Auto-detected from `uname` if undefined. +# PROCESSOR - CPU core (e907, c906, lx7, cortex-m33, ...). Usually derived +# from PLATFORM; override only for multi-core SoCs (e.g. +# PROCESSOR=e907 for the BL808 M0 core). +# CONFIG - debug | release | unittest +# COMPILER - dmd | ldc | gdc. Auto-promoted to ldc for cross-compile. +# URT_SRCDIR - path to URT's src/ tree. Default `src` (URT in-tree); outer +# projects set this to e.g. `third_party/urt/src`. +# +# Outputs (resolved variables for caller to consume): +# ARCH, OS, MARCH, MATTR, MABI, PROCESSOR, BUILDNAME, COMPILER, DC +# DFLAGS - augmented with triple, mattr, platform version flags +# URT_SOURCES - urt/**.d + urt/driver//**.d (+ mbedtls.c on host) +# BAREMETAL_DIR - dir containing start.S etc. (empty on host targets) +# BAREMETAL_SRCS - basenames of asm/c sources for cross-gcc +# BAREMETAL_GCC - cross-gcc path +# BAREMETAL_CFLAGS - cross-gcc cflags (mcpu/march/mabi/mfpu) +# BAREMETAL_LIBC/M/GCC - resolved newlib/picolibc/libgcc archive paths +# ESPRESSIF_PATH, ESPRESSIF_XTENSA_BIN, ESPRESSIF_RISCV32_BIN +# XTENSA_TWO_STAGE, ESPRESSIF_LLC, XTENSA_MATTR +# +# Caller still owns (NOT set here -- these are app-specific): +# - BAREMETAL_LD: linker script (app memory map lives in the consumer's +# platforms//ld/*.ld tree). +# - `-J` string-imports for app config dirs (consumer's platforms//). +# - Vendor SDK roots and blob paths (BK_SDK_ROOT, ESP_PROJECT_DIR, ...). +# - Final link/objcopy/packaging (containers, .bin, flashing, OTA). +# - The build rule that actually produces $(TARGET). +# ======================================================================= + +URT_SRCDIR ?= src +CONFIG ?= debug +COMPILER ?= dmd + +# Windows always sets env OS=Windows_NT -- normalize so OS-conditional blocks +# (driver/windows source selection, host triple) recognize it. Plain `:=` (not +# `override`) so platform blocks can still set OS=baremetal/freertos for cross. +ifeq ($(OS),Windows_NT) + OS := windows +endif + +# ======================================================================= +# PLATFORM -- SoC/board identity +# +# Sets: BUILDNAME, PROCESSOR (default), OS, vendor version flags, +# platform source paths, vendor-specific GCC wrappers (Xtensa). +# ======================================================================= + +# Normalize CI-friendly platform aliases before platform detection +ifeq ($(PLATFORM),bl808_m0) + override PLATFORM := bl808 + PROCESSOR := e907 +endif + +ifeq ($(PLATFORM),esp8266) + # ESP8266 has no FPU! + BUILDNAME := esp8266 + PROCESSOR := l106 + OS = freertos + XTENSA_GCC := xtensa-lx106-elf-gcc +else ifeq ($(PLATFORM),esp32) + BUILDNAME := esp32 + PROCESSOR := lx6 + OS = freertos + XTENSA_GCC := xtensa-esp32-elf-gcc +else ifeq ($(PLATFORM),esp32-s2) + # Single-core LX7, 240MHz -- NO FPU, NO loops + BUILDNAME := esp32-s2 + PROCESSOR := lx7 + OS = freertos + XTENSA_GCC := xtensa-esp32s2-elf-gcc +else ifeq ($(PLATFORM),esp32-s3) + # Dual-core LX7, 240MHz -- FPU, loops, hardware unaligned access + BUILDNAME := esp32-s3 + PROCESSOR := lx7 + OS = freertos + XTENSA_GCC := xtensa-esp32s3-elf-gcc + MATTR = +fp,+loop + DFLAGS := $(DFLAGS) -d-version=SupportUnaligned +else ifeq ($(PLATFORM),esp32-h2) + BUILDNAME := esp32-h2 + PROCESSOR := e906 + OS = freertos +else ifeq ($(PLATFORM),esp32-c2) + BUILDNAME := esp32-c2 + PROCESSOR := e906 + OS = freertos +else ifeq ($(PLATFORM),esp32-c3) + BUILDNAME := esp32-c3 + PROCESSOR := e906 + OS = freertos +else ifeq ($(PLATFORM),esp32-c5) + # RV32IMAC, 240MHz -- has atomics + BUILDNAME := esp32-c5 + PROCESSOR := e907 + OS = freertos +else ifeq ($(PLATFORM),esp32-c6) + # RV32IMAC, 160MHz -- has atomics + BUILDNAME := esp32-c6 + PROCESSOR := e907 + OS = freertos +else ifeq ($(PLATFORM),esp32-p4) + # HP core: RV32IMAFDCV, 400MHz + BUILDNAME := esp32-p4 + PROCESSOR := esp32p4 + OS = freertos +else ifeq ($(PLATFORM),bl808) + # BL808 multi-core SoC -- default to D0 (C906 RV64GC) + # Override with PROCESSOR=e907 for M0 core (E907 RV32IMAFC) + PROCESSOR ?= c906 + OS = baremetal + ifeq ($(PROCESSOR),c906) + BUILDNAME := bl808-d0 + else ifeq ($(PROCESSOR),e907) + BUILDNAME := bl808-m0 + else + $(error "BL808: unsupported PROCESSOR=$(PROCESSOR) (expected c906 or e907)") + endif +else ifeq ($(PLATFORM),bl618) + # Sipeed M0P -- Bouffalo BL618, single-core T-Head E907 RV32IMAFC, 320MHz + BUILDNAME := bl618 + PROCESSOR := e907 + OS = baremetal +else ifneq ($(filter bk7231n bk7231t,$(PLATFORM)),) + # Beken BK7231 family -- ARM968E-S (ARMv5TE), 120MHz, 256KB SRAM, 2MB SPI flash + # BK7231N adds BLE 5.0; BK7231T is Wi-Fi only. Same MCU peripherals. + BUILDNAME := $(PLATFORM) + PROCESSOR := arm968e-s + OS = baremetal +else ifeq ($(PLATFORM),rp2350) + # Raspberry Pi RP2350 -- dual Cortex-M33, 150MHz, 520KB SRAM, XIP QSPI flash + BUILDNAME := rp2350 + PROCESSOR := cortex-m33 + OS = baremetal +else ifeq ($(PLATFORM),stm7xx) + BUILDNAME := stm7xx + PROCESSOR := cortex-m7 + OS = baremetal + STM32_VARIANT = f7 + MFPU = fpv5-d16 +else ifeq ($(PLATFORM),stm4xx) + BUILDNAME := stm4xx + PROCESSOR := cortex-m4 + OS = baremetal + STM32_VARIANT = f4 + MFPU = fpv4-sp-d16 +else ifeq ($(PLATFORM),routeros) + # MikroTik RouterOS container (ARM64 Linux). Packaging is consumer-side. + BUILDNAME := routeros + PROCESSOR := aarch64-generic + OS := linux +else + ifeq ($(origin PLATFORM),undefined) + # No platform specified -- auto-detect host + UNAME_S := $(shell uname -s 2>/dev/null || echo Unknown) + UNAME_M := $(shell uname -m 2>/dev/null || echo unknown) + + ifneq ($(findstring MINGW,$(UNAME_S)),) + PLATFORM := windows + OS := windows + else ifneq ($(findstring MSYS,$(UNAME_S)),) + PLATFORM := windows + OS := windows + else ifneq ($(findstring CYGWIN,$(UNAME_S)),) + PLATFORM := windows + OS := windows + else ifeq ($(UNAME_S),Unknown) + # no uname, probably native Windows - assume x86_64 + PLATFORM := windows + OS := windows + UNAME_M := x86_64 + else ifeq ($(UNAME_S),) + # cmd.exe / PowerShell: shell couldn't honour `|| echo Unknown` either + OS := windows + UNAME_M := x86_64 + else + OS ?= linux + endif + + PLATFORM := $(OS) + + ifndef ARCH + ifeq ($(UNAME_M),x86_64) + ARCH := x86_64 + else ifeq ($(UNAME_M),amd64) + ARCH := x86_64 + else ifeq ($(UNAME_M),i686) + ARCH := x86 + else ifeq ($(UNAME_M),i386) + ARCH := x86 + else ifeq ($(UNAME_M),aarch64) + ARCH := arm64 + else ifeq ($(UNAME_M),arm64) + ARCH := arm64 + else ifeq ($(UNAME_M),armv7l) + ARCH := arm + else ifeq ($(UNAME_M),riscv64) + ARCH := riscv64 + endif + endif + endif +endif + +# Bare-processor fallback: if PLATFORM was set but didn't match any known +# platform above, treat it as a raw processor name (e.g., make PLATFORM=e906). +ifndef PROCESSOR + ifdef PLATFORM + ifneq ($(PLATFORM),$(OS)) + PROCESSOR := $(PLATFORM) + endif + endif +endif + +# ======================================================================= +# PROCESSOR -- CPU core identity +# +# Sets: ARCH, MARCH, MABI. Pure ISA/compiler-target config. +# OS is set with ?= only as a fallback for bare-processor builds; +# PLATFORM always takes precedence. +# ======================================================================= + +ifdef PROCESSOR + ifeq ($(PROCESSOR),aarch64-generic) + ARCH = arm64 + else ifeq ($(PROCESSOR),cortex-a7) + ARCH = arm + MARCH = cortex-a7 + else ifeq ($(PROCESSOR),cortex-m4) + ARCH = thumb + MARCH = cortex-m4 + else ifeq ($(PROCESSOR),arm968e-s) + ARCH = arm + MARCH = arm968e-s + MABI = soft + OS ?= baremetal + else ifeq ($(PROCESSOR),cortex-m33) + ARCH = thumb + MARCH = cortex-m33 + else ifeq ($(PROCESSOR),cortex-m7) + ARCH = thumb + MARCH = cortex-m7 + else ifeq ($(PROCESSOR),l106) + ARCH = xtensa + else ifeq ($(PROCESSOR),lx6) + ARCH = xtensa + MATTR = +fp,+loop,+mac16,+dfpaccel + else ifeq ($(PROCESSOR),lx7) + ARCH = xtensa + else ifeq ($(PROCESSOR),k210) + ARCH = riscv64 + MARCH = rv64imafdc + MATTR = +m,+a,+f,+d,+c,+zicsr,+zifencei + MABI = lp64d + OS ?= baremetal + else ifeq ($(PROCESSOR),c906) + ARCH = riscv64 + MARCH = rv64imafdc + MATTR = +m,+a,+f,+d,+c,+unaligned-scalar-mem,+xtheadba,+xtheadbb,+xtheadbs,+xtheadcmo,+xtheadcondmov,+xtheadfmemidx,+xtheadmac,+xtheadmemidx,+xtheadsync + MABI = lp64d + OS ?= baremetal + else ifeq ($(PROCESSOR),e902) + ARCH = riscv + MARCH = rv32emc + MATTR = +e,+m,+c + MABI = ilp32e + OS ?= baremetal + else ifeq ($(PROCESSOR),e906) + ARCH = riscv + MARCH = rv32imc + MATTR = +m,+c + MABI = ilp32 + OS ?= freertos + else ifeq ($(PROCESSOR),e907) + ARCH = riscv + MARCH = rv32imafc + MATTR = +m,+a,+f,+c + MABI = ilp32f + OS ?= freertos + else ifeq ($(PROCESSOR),esp32p4) + ARCH = riscv + MARCH = rv32imafdcv + MATTR = +m,+a,+f,+d,+c,+v + MABI = ilp32f + OS ?= freertos + endif +endif + +ifndef BUILDNAME + ifdef PROCESSOR + BUILDNAME := $(PROCESSOR) + else + BUILDNAME := $(ARCH)_$(OS) + endif +endif + +# ======================================================================= +# Compiler auto-selection: cross-compilation targets use LDC +# ======================================================================= + +ifeq ($(COMPILER),dmd) +ifdef ARCH +ifneq ($(ARCH),x86_64) +ifneq ($(ARCH),x86) + COMPILER = ldc +endif +endif +endif +endif + +# ======================================================================= +# Toolchain discovery (Espressif) +# ======================================================================= + +ESPRESSIF_PATH ?= $(wildcard $(HOME)/.espressif) +ifdef ESPRESSIF_PATH + ESPRESSIF_XTENSA_BIN := $(lastword $(sort $(wildcard $(ESPRESSIF_PATH)/tools/xtensa-esp-elf/*/xtensa-esp-elf/bin))) + ESPRESSIF_RISCV32_BIN := $(lastword $(sort $(wildcard $(ESPRESSIF_PATH)/tools/riscv32-esp-elf/*/riscv32-esp-elf/bin))) +endif + +# ======================================================================= +# URT_SOURCES -- urt/**.d + urt/driver//**.d +# +# Caller appends app sources separately. C glue (mbedtls) is host-only. +# ======================================================================= + +URT_SOURCES := $(shell find "$(URT_SRCDIR)" -type f -name '*.d' -not -path '$(URT_SRCDIR)/urt/driver/*') +URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/urt/driver" -maxdepth 1 -type f -name '*.d') +URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/urt/driver/baremetal" -type f -name '*.d') + +# mbedtls C glue needs host mbedtls headers -- exclude for embedded targets +ifeq ($(filter freertos baremetal,$(OS)),) + URT_SOURCES := $(URT_SOURCES) $(URT_SRCDIR)/urt/internal/mbedtls.c +endif + +ifeq ($(PLATFORM),bl808) + ifeq ($(PROCESSOR),c906) + URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/urt/driver/bl808" -type f -name '*.d') + else ifeq ($(PROCESSOR),e907) + # BL808 M0 core -- E907 uses same peripheral drivers as BL618 + URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/urt/driver/bl618" -type f -name '*.d') + endif +endif +ifeq ($(PLATFORM),bl618) + URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/urt/driver/bl618" -type f -name '*.d') +endif +ifeq ($(PLATFORM),rp2350) + URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/urt/driver/rp2350" -type f -name '*.d') +endif +ifneq ($(filter bk7231n bk7231t,$(PLATFORM)),) + URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/urt/driver/bk7231" -type f -name '*.d' 2>/dev/null) +endif +ifneq ($(filter esp%,$(PLATFORM)),) + URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/urt/driver/esp32" -type f -name '*.d') +endif +ifdef STM32_VARIANT + URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/urt/driver/stm32" -type f -name '*.d') +endif +ifeq ($(OS),windows) + URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/urt/driver/windows" -type f -name '*.d') +endif +ifneq ($(filter linux ubuntu freebsd,$(OS)),) + URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/urt/driver/posix" -type f -name '*.d') +endif +ifeq ($(OS),freertos) + URT_SOURCES := $(URT_SOURCES) $(shell find "$(URT_SRCDIR)/urt/driver/freertos" -type f -name '*.d') +endif + +# ======================================================================= +# DFLAGS -- version flags + previews +# +# Only platform/processor *version* flags live here (so URT's urt/driver/ code +# can `version (BL808)` etc.). Consumer-side `-J platforms/` string +# imports stay in the consumer Makefile. +# ======================================================================= + +DFLAGS := $(DFLAGS) -preview=bitfields -preview=rvaluerefparam -preview=in #-preview=nosharedaccess <- TODO + +# OS-level versions +ifeq ($(OS),freertos) + DFLAGS := $(DFLAGS) -d-version=FreeRTOS +endif +ifeq ($(OS),baremetal) + DFLAGS := $(DFLAGS) -d-version=BareMetal +endif +ifneq ($(filter freertos baremetal,$(OS)),) + DFLAGS := $(DFLAGS) -d-version=Embedded +endif + +# "Tiny" targets: <~350KB RAM and <2MB flash, or no external memory at all. +# Code gates nice-to-haves (verbose help, optional protocols) behind +# `version (Tiny)` to minimise binary size. Override with TINY=1/0. +ifneq ($(filter esp8266 bk7231n bk7231t esp32-c2 esp32-h2 esp32-s2,$(PLATFORM)),) + TINY ?= 1 +endif +ifeq ($(PLATFORM),bl808) + ifeq ($(PROCESSOR),e907) + TINY ?= 1 + endif +endif +ifeq ($(TINY),1) + DFLAGS := $(DFLAGS) -d-version=Tiny +endif + +# Vendor/family versions consumed by URT's urt/driver/ code +ifneq ($(filter esp%,$(PLATFORM)),) + DFLAGS := $(DFLAGS) -d-version=Espressif -d-version=lwIP -d-version=CRuntime_Picolibc +endif +ifeq ($(PLATFORM),bl808) + ifeq ($(PROCESSOR),c906) + DFLAGS := $(DFLAGS) -d-version=BL808 -d-version=Bouffalo -d-version=CRuntime_Picolibc + else ifeq ($(PROCESSOR),e907) + DFLAGS := $(DFLAGS) -d-version=BL808 -d-version=BL808_M0 -d-version=Bouffalo -d-version=CRuntime_Picolibc + endif +endif +ifeq ($(PLATFORM),bl618) + DFLAGS := $(DFLAGS) -d-version=BL618 -d-version=Bouffalo -d-version=CRuntime_Picolibc +endif +ifeq ($(PLATFORM),rp2350) + DFLAGS := $(DFLAGS) -d-version=RP2350 -d-version=CRuntime_Picolibc +endif +ifdef STM32_VARIANT + DFLAGS := $(DFLAGS) -d-version=STM32 -d-version=CRuntime_Picolibc + ifeq ($(STM32_VARIANT),f4) + DFLAGS := $(DFLAGS) -d-version=STM32F4 + else ifeq ($(STM32_VARIANT),f7) + DFLAGS := $(DFLAGS) -d-version=STM32F7 + endif +endif +ifneq ($(filter bk7231n bk7231t,$(PLATFORM)),) + DFLAGS := $(DFLAGS) -d-version=Beken -d-version=CRuntime_Picolibc + ifeq ($(PLATFORM),bk7231n) + DFLAGS := $(DFLAGS) -d-version=BK7231N + else + DFLAGS := $(DFLAGS) -d-version=BK7231T + endif +endif + +# Chip-specific versions +ifeq ($(PLATFORM),esp8266) + DFLAGS := $(DFLAGS) -d-version=ESP8266 +else ifeq ($(PLATFORM),esp32) + DFLAGS := $(DFLAGS) -d-version=ESP32 +else ifeq ($(PLATFORM),esp32-s2) + DFLAGS := $(DFLAGS) -d-version=ESP32_S2 +else ifeq ($(PLATFORM),esp32-s3) + DFLAGS := $(DFLAGS) -d-version=ESP32_S3 +else ifeq ($(PLATFORM),esp32-c2) + DFLAGS := $(DFLAGS) -d-version=ESP32_C2 +else ifeq ($(PLATFORM),esp32-c3) + DFLAGS := $(DFLAGS) -d-version=ESP32_C3 +else ifeq ($(PLATFORM),esp32-c5) + DFLAGS := $(DFLAGS) -d-version=ESP32_C5 +else ifeq ($(PLATFORM),esp32-c6) + DFLAGS := $(DFLAGS) -d-version=ESP32_C6 +else ifeq ($(PLATFORM),esp32-h2) + DFLAGS := $(DFLAGS) -d-version=ESP32_H2 +else ifeq ($(PLATFORM),esp32-p4) + DFLAGS := $(DFLAGS) -d-version=ESP32_P4 +endif + +ifeq ($(CONFIG),unittest) + DFLAGS := $(DFLAGS) -unittest +endif + +# ======================================================================= +# Compiler configuration -- triple, mattr, link flags +# ======================================================================= + +ifeq ($(COMPILER),ldc) + # Prefer dlang-installer LDC (avoids system package conflicts with cross-compile) + DC := $(lastword $(sort $(wildcard $(HOME)/dlang/ldc-*/bin/ldc2))) + DC := $(if $(DC),$(DC),ldc2) + + # Strip druntime/phobos -- URT brings its own object.d and runtime support. + # OpenWatt's ldc2.conf does the same via `switches = ["-defaultlib="]` for + # builds invoked from its tree; setting it here too means URT-standalone + # builds (e.g. URT's own CI) don't need their own ldc2.conf. + # + # -frame-pointer=all: keep the frame pointer in every function. URT's + # exception/unwind machinery walks EBP/RBP chains directly (notably on + # x86 Windows SEH); leaf-FPO would leave gaps and crash the walker. + DFLAGS := $(DFLAGS) -defaultlib= -frame-pointer=all -I $(URT_SRCDIR) + + ifeq ($(ARCH),x86_64) +# DFLAGS := $(DFLAGS) -mtriple=x86_64-linux-gnu + else ifeq ($(ARCH),x86) + ifeq ($(OS),windows) + DFLAGS := $(DFLAGS) -mtriple=i686-windows-msvc + else + DFLAGS := $(DFLAGS) -mtriple=i686-linux-gnu + endif + else ifeq ($(ARCH),arm64) + ifeq ($(OS),freertos) + DFLAGS := $(DFLAGS) -mtriple=aarch64-none-elf + else ifeq ($(OS),windows) + DFLAGS := $(DFLAGS) -mtriple=aarch64-windows-msvc + else + DFLAGS := $(DFLAGS) -mtriple=aarch64-linux-gnu + # Cross-compile linker, fully static for minimal container size + DFLAGS := $(DFLAGS) -gcc=aarch64-linux-gnu-gcc -static -L-static + endif + else ifeq ($(ARCH),thumb) + ifeq ($(MARCH),cortex-m33) + DFLAGS := $(DFLAGS) -mtriple=thumbv8m.main-none-eabihf -gcc=arm-none-eabi-gcc + else + DFLAGS := $(DFLAGS) -mtriple=thumbv7em-none-eabihf -gcc=arm-none-eabi-gcc + endif + ifdef MARCH + DFLAGS := $(DFLAGS) -mcpu=$(MARCH) + endif + else ifeq ($(ARCH),arm) + ifeq ($(OS),baremetal) + ifeq ($(MABI),soft) + # ARMv5TE has no atomic instructions -- single-thread model + # makes LLVM lower atomics to plain loads/stores + DFLAGS := $(DFLAGS) -mtriple=armv5te-none-eabi -float-abi=soft --thread-model=single + else + DFLAGS := $(DFLAGS) -mtriple=arm-none-eabihf + endif + else ifeq ($(OS),freertos) + DFLAGS := $(DFLAGS) -mtriple=arm-none-eabihf + else ifeq ($(OS),windows) + DFLAGS := $(DFLAGS) -mtriple=armv7-windows-msvc + else + DFLAGS := $(DFLAGS) -mtriple=armv7-linux-gnueabihf + endif + DFLAGS := $(DFLAGS) -gcc=arm-none-eabi-gcc + ifdef MARCH + DFLAGS := $(DFLAGS) -mcpu=$(MARCH) + endif + else ifeq ($(ARCH),riscv64) + DFLAGS := $(DFLAGS) -mtriple=riscv64-unknown-elf -gcc=riscv64-unknown-elf-gcc -code-model=medium + DFLAGS := $(DFLAGS) -mattr=$(MATTR) + # ImportC needs picolibc headers for C imports (stdio.h etc.) + PICOLIBC_INCLUDE := $(firstword $(wildcard /usr/riscv64-unknown-elf/include /usr/lib/picolibc/riscv64-unknown-elf/include)) + DFLAGS := $(DFLAGS) $(if $(PICOLIBC_INCLUDE),-P=-isystem -P=$(PICOLIBC_INCLUDE)) + else ifeq ($(ARCH),riscv) + RISCV32_GCC ?= $(or $(if $(ESPRESSIF_RISCV32_BIN),$(ESPRESSIF_RISCV32_BIN)/riscv32-esp-elf-gcc),$(shell which riscv32-esp-elf-gcc 2>/dev/null),riscv64-unknown-elf-gcc) + DFLAGS := $(DFLAGS) -mtriple=riscv32-unknown-elf -gcc=$(RISCV32_GCC) + DFLAGS := $(DFLAGS) -mattr=$(MATTR) -mabi=$(MABI) + ifeq ($(PROCESSOR),e902) + DFLAGS := $(DFLAGS) -d-version=RISCV32E + endif + else ifeq ($(ARCH),xtensa) + # Xtensa -- requires Espressif toolchain (chip-specific GCC wrappers). + # Two-stage codegen: LDC emits bitcode, Espressif's llc does codegen + # (upstream LLVM Xtensa backend crashes on invoke+landingpad at -O1+). + XTENSA_GCC_DIR ?= $(or $(if $(ESPRESSIF_XTENSA_BIN),$(ESPRESSIF_XTENSA_BIN)/),$(dir $(shell which xtensa-esp-elf-gcc 2>/dev/null))) + # Base features common to all ESP32 Xtensa cores (LX6, S2 LX7, S3 LX7). + # +fp and +loop are NOT universal -- S2 lacks both. + XTENSA_MATTR := -mattr=+density,+mul16,+mul32,+mul32high,+div32 \ + -mattr=+sext,+nsa,+clamps,+minmax,+bool \ + -mattr=+windowed,+threadptr \ + -mattr=+exception,+interrupt,+highpriinterrupts,+debug + ifdef MATTR + XTENSA_MATTR := $(XTENSA_MATTR) -mattr=$(MATTR) + endif + # Workarounds: + # - single-thread model: no atomic instructions, lower atomics to plain loads/stores + # - emulated TLS: @TPOFF symbol suffixes incompatible with GNU ld + # - align-all-functions=2: ensures 4-byte alignment for l32r literal targets + DFLAGS := $(DFLAGS) -mtriple=xtensa-none-elf --thread-model=single -emulated-tls \ + --align-all-functions=2 $(XTENSA_MATTR) \ + -gcc=$(XTENSA_GCC_DIR)$(XTENSA_GCC) + ESPRESSIF_LLC := $(lastword $(sort $(wildcard $(HOME)/.espressif/tools/esp-clang/*/esp-clang/bin/llc))) + XTENSA_TWO_STAGE := 1 + else + $(error "Unsupported ARCH: $(ARCH)") + endif + + # Embedded baremetal: assemble cross-gcc flags + libc/libm/libgcc paths. + # Caller supplies BAREMETAL_LD (linker script) -- without it we emit `-c` + # (compile-only; sufficient for CI link-check of URT-only builds). + ifneq ($(filter freertos baremetal,$(OS)),) + ifeq ($(PLATFORM),bl808) + ifeq ($(PROCESSOR),c906) + BAREMETAL_DIR := $(URT_SRCDIR)/urt/driver/bl808 + BAREMETAL_SRCS := start.S hbn_ram.c + else ifeq ($(PROCESSOR),e907) + BAREMETAL_DIR := $(URT_SRCDIR)/urt/driver/bl618 + BAREMETAL_SRCS := start.S + endif + else ifeq ($(PLATFORM),bl618) + BAREMETAL_DIR := $(URT_SRCDIR)/urt/driver/bl618 + BAREMETAL_SRCS := start.S + else ifneq ($(filter bk7231n bk7231t,$(PLATFORM)),) + BAREMETAL_DIR := $(URT_SRCDIR)/urt/driver/bk7231 + BAREMETAL_SRCS := start.S + else ifeq ($(PLATFORM),rp2350) + BAREMETAL_DIR := $(URT_SRCDIR)/urt/driver/rp2350 + BAREMETAL_SRCS := start.S boot2.S + else ifdef STM32_VARIANT + BAREMETAL_DIR := $(URT_SRCDIR)/urt/driver/stm32 + BAREMETAL_SRCS := start.S + endif + + ifdef BAREMETAL_DIR + ifeq ($(ARCH),arm) + BAREMETAL_GCC := arm-none-eabi-gcc + BAREMETAL_CFLAGS := -mcpu=$(MARCH) -marm -mfloat-abi=$(MABI) + else ifeq ($(ARCH),thumb) + BAREMETAL_GCC := arm-none-eabi-gcc + MFPU ?= fpv5-sp-d16 + BAREMETAL_CFLAGS := -mcpu=$(MARCH) -mthumb -mfloat-abi=hard -mfpu=$(MFPU) + else + BAREMETAL_GCC := riscv64-unknown-elf-gcc + BAREMETAL_CFLAGS := -march=$(MARCH) -mabi=$(MABI) + endif + BAREMETAL_LIBGCC := $(shell $(BAREMETAL_GCC) $(BAREMETAL_CFLAGS) --print-libgcc-file-name) + # picolibc/newlib via --specs=picolibc.specs first, then plain gcc, then multilib fallback + PICOLIBC_MULTIDIR := $(shell $(BAREMETAL_GCC) $(BAREMETAL_CFLAGS) --print-multi-directory 2>/dev/null) + BAREMETAL_LIBC := $(or $(filter /%,$(shell $(BAREMETAL_GCC) --specs=picolibc.specs $(BAREMETAL_CFLAGS) --print-file-name=libc.a 2>/dev/null)),$(filter /%,$(shell $(BAREMETAL_GCC) $(BAREMETAL_CFLAGS) --print-file-name=libc.a 2>/dev/null)),$(wildcard /usr/lib/picolibc/riscv64-unknown-elf/lib/$(PICOLIBC_MULTIDIR)/libc.a)) + BAREMETAL_LIBM := $(or $(filter /%,$(shell $(BAREMETAL_GCC) --specs=picolibc.specs $(BAREMETAL_CFLAGS) --print-file-name=libm.a 2>/dev/null)),$(filter /%,$(shell $(BAREMETAL_GCC) $(BAREMETAL_CFLAGS) --print-file-name=libm.a 2>/dev/null)),$(wildcard /usr/lib/picolibc/riscv64-unknown-elf/lib/$(PICOLIBC_MULTIDIR)/libm.a)) + # Caller adds: -L-T + any vendor blob archives + DFLAGS := $(DFLAGS) -L--gc-sections --link-internally -L-z -Lnorelro -L$(BAREMETAL_LIBC) -L$(BAREMETAL_LIBM) -L$(BAREMETAL_LIBGCC) + else ifeq ($(ARCH),xtensa) + # Xtensa: emit LLVM bitcode for two-stage codegen via Espressif's llc + # (upstream LLVM Xtensa backend crashes on invoke+landingpad at -O1+). + DFLAGS := $(DFLAGS) --output-bc + else + # No link script wired up (e.g. ESP RISC-V variants without ESP-IDF): + # compile-only object output. + DFLAGS := $(DFLAGS) -c + endif + endif + + ifeq ($(CONFIG),release) + ifeq ($(ARCH),xtensa) + DFLAGS := $(DFLAGS) -release --enable-asserts -Oz -enable-inlining + else + DFLAGS := $(DFLAGS) -release --enable-asserts -O3 -enable-inlining + endif + else ifdef BAREMETAL_DIR + # Embedded debug/unittest: still optimize to fit in firmware partition + DFLAGS := $(DFLAGS) --enable-asserts -O2 -enable-inlining + else ifeq ($(ARCH),xtensa) + # Xtensa: -Oz to fit in flash; bitcode emission set above + DFLAGS := $(DFLAGS) --enable-asserts -Oz -enable-inlining -d-debug + else + DFLAGS := $(DFLAGS) -g -d-debug + endif + +else ifeq ($(COMPILER),dmd) + DC ?= dmd + + # Strip druntime/phobos, use URT's own object.d. + # Consumers may need to prepend their own -I to shadow druntime's + # __importc_builtins.di (e.g. OpenWatt's third_party/dmd/ for MSVC va_list). + DFLAGS := $(DFLAGS) -defaultlib= -I=$(URT_SRCDIR) + + ifeq ($(ARCH),x86_64) +# DFLAGS := $(DFLAGS) -m64 + else ifeq ($(ARCH),x86) + DFLAGS := $(DFLAGS) -m32 + else + $(error "DMD: unsupported ARCH=$(ARCH) for PLATFORM=$(PLATFORM) (use COMPILER=ldc)") + endif + + ifeq ($(CONFIG),release) + DFLAGS := $(DFLAGS) -release -O -inline + else + DFLAGS := $(DFLAGS) -g -debug + endif +else + $(error "Unknown D compiler: $(COMPILER)") +endif diff --git a/platforms/bk7231/README.txt b/platforms/bk7231/README.txt new file mode 100644 index 0000000..e10ad6d --- /dev/null +++ b/platforms/bk7231/README.txt @@ -0,0 +1,90 @@ +BK7231 (Beken) -- ARM968E-S @ 120 MHz, ARMv5TE +============================================== + +Two variants in this directory: + + bk7231n Wi-Fi 802.11b/g/n + BLE 5.0 + bk7231t Wi-Fi only (no BLE) + +Both share the same MCU core and most peripherals. SRAM layout differs +(N reserves TCM regions for the BLE stack; T uses full SRAM). They are +NOT interchangeable -- flashing the wrong image will not boot. + +Setup +----- + +Toolchain (Ubuntu 22.04+): + + # D compiler -- LDC with the official upstream build. + curl -fsS https://dlang.org/install.sh | bash -s ldc + source ~/dlang/ldc-*/activate + + # ARM cross-toolchain and picolibc. + sudo apt-get install gcc-arm-none-eabi picolibc-arm-none-eabi + +Flash tool (Python; bundled with OpenBK7231T_App): + + git clone https://github.com/openshwprojects/OpenBK7231T_App + pip install pyserial # only dependency hid_download.py needs + +The flasher is at OpenBK7231T_App/scripts/hid_download.py -- run it +directly, no install step. + +Windows: LDC installer from https://github.com/ldc-developers/ldc/releases; +ARM GNU Toolchain installer from +https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads. + +Build the unittest image +------------------------ + +From the URT root: + + make PLATFORM=bk7231n CONFIG=unittest + make PLATFORM=bk7231t CONFIG=unittest + +Outputs: + + bin/bk7231n_unittest/urt_test.bin + bin/bk7231t_unittest/urt_test.bin + +Toolchain required: ldc, gcc-arm-none-eabi, picolibc-arm-none-eabi. +Linker scripts: bk7231n.ld (192 KB SRAM, BLE TCM carve-out) and +bk7231t.ld (256 KB SRAM, no carve-out). + +This URT-only build does NOT include the Beken SDK -- no Wi-Fi, no BLE, +no SDK-provided UART driver beyond what URT ships. The full OpenWatt +build links against libbeken.a + WiFi/BLE blobs; the URT unittest does +not. The image will boot, run unittests, print results to UART, halt. + +Flash +----- + +Use hid_download_py from the OpenBK7231T_App project: + + git clone https://github.com/openshwprojects/OpenBK7231T_App + cd OpenBK7231T_App/scripts + python hid_download.py -d COM5 -f bin/bk7231n_unittest/urt_test.bin \ + -c bk7231n -a 0x011000 + +(Use bk7231t / -a 0x011000 for the T variant.) + +The bootloader expects the application at offset 0x11000 in flash. Hold +the CEN/RESET button while invoking the tool to enter download mode. + +Console +------- + +UART1 (the SDK calls it "uart_print"), 921600 baud, 8N1. Pins are board- +specific; on most modules UART1 is broken out to a header. The unittest +harness prints results then halts. + +Notes +----- + +* ARMv5TE has no atomic instructions. URT's start.S sets the LLVM + thread-model to "single" so atomics lower to plain loads/stores -- + fine for single-core unittests, NOT safe if you ever bring up + the SDK's RTOS scheduler alongside URT code. +* Vector table lives in the bootloader (no VTOR on ARM968E-S) -- IRQs + must be installed via SDK API in real applications. The URT unittest + runs polled, so this is not exercised. diff --git a/platforms/bk7231/bk7231n.ld b/platforms/bk7231/bk7231n.ld new file mode 100644 index 0000000..411821e --- /dev/null +++ b/platforms/bk7231/bk7231n.ld @@ -0,0 +1,139 @@ +/* BK7231N (ARM968E-S, ARMv5TE) linker script + * + * Beken BK7231N -- ARM968E-S @ 120MHz, no FPU, no MMU. + * Wi-Fi 802.11b/g/n + BLE 5.0. + * + * Flash partition table (from BkDriverFlash.c): + * Bootloader: 0x000000, 68KB (0x11000) + * Application: 0x011000, 1156KB (0x121000) + * OTA: 0x12A000, 664KB (0xA6000) + * RF Firmware: 0x1D0000, 4KB + * NET Param: 0x1D1000, 4KB + * + * SRAM layout (OTA build, from SDK bk7231_ota.ld): + * TCM: 0x003F0000, 60KB - 512 (BLE/WiFi critical paths) + * ITCM: 0x003FEE00, 4608 bytes (interrupt entry) + * RAM: 0x00400100, 192KB - 256 (application) + * + * The BLE 5.x stack claims 64KB from general SRAM as TCM regions. + * Application gets 192KB minus the 256-byte reserved header. + * + * Non-OTA: set FLASH LENGTH to 1788K (up to RF firmware at 0x1D0000). + */ + +ENTRY(Reset_Handler) + +EXTERN(_write _read _close _lseek _fstat _isatty _sbrk _exit _kill _getpid) +EXTERN(_Dmodule_ref __errno_location stdout) + +MEMORY +{ + FLASH (rx) : ORIGIN = 0x00011000, LENGTH = 4M /* TODO: restore to 1156K */ + SRAM (rwx) : ORIGIN = 0x00400100, LENGTH = 192K - 0x100 +} + +SECTIONS +{ + .text : ALIGN(4) + { + KEEP(*(.vector_table)) + *(.text .text.*) + } > FLASH + + .rodata : ALIGN(8) + { + *(.rodata .rodata.*) + } > FLASH + + .eh_frame : ALIGN(8) + { + __eh_frame_start = .; + KEEP(*(.eh_frame)) + KEEP(*(.eh_frame_hdr)) + } > FLASH + + .gcc_except_table : ALIGN(4) + { + *(.gcc_except_table .gcc_except_table.*) + } > FLASH + + .init_array : ALIGN(4) + { + __init_array_start = .; + KEEP(*(.init_array)) + __init_array_end = .; + } > FLASH + + .ARM.exidx : ALIGN(4) + { + __exidx_start = .; + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + __exidx_end = .; + } > FLASH + + .ARM.extab : ALIGN(4) + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > FLASH + + .got : ALIGN(4) + { + _got_load = LOADADDR(.got); + _got_start = .; + *(.got .got.*) + *(.got.plt) + _got_end = .; + } > SRAM AT > FLASH + + .data : ALIGN(4) + { + _data_start = .; + *(.data .data.*) + _data_end = .; + } > SRAM AT > FLASH + + _data_load = LOADADDR(.data); + __ram_load_addr = _data_load; + __ram_data_start__ = _data_start; + __ram_data_end__ = _data_end; + + .tdata : ALIGN(4) + { + _tdata_start = .; + *(.tdata .tdata.*) + _tdata_end = .; + } > SRAM AT > FLASH + + _tdata_load = LOADADDR(.tdata); + + .tbss (NOLOAD) : ALIGN(4) + { + _tbss_start = .; + *(.tbss .tbss.*) + _tbss_end = .; + } > SRAM + + .bss (NOLOAD) : ALIGN(4) + { + __bss_start__ = .; + _bss_start = .; + *(.bss .bss.*) + *(COMMON) + _bss_end = .; + __bss_end__ = .; + } > SRAM + + __heap_start = _bss_end; + __heap_end = ORIGIN(SRAM) + LENGTH(SRAM) - 4K; + + _stack_top = ORIGIN(SRAM) + LENGTH(SRAM); + __StackTop = _stack_top; + + /DISCARD/ : + { + *(.comment) + *(.note.*) + *(.gnu.hash) + *(.gnu.linkonce.*) + } +} diff --git a/platforms/bk7231/bk7231t.ld b/platforms/bk7231/bk7231t.ld new file mode 100644 index 0000000..73b9222 --- /dev/null +++ b/platforms/bk7231/bk7231t.ld @@ -0,0 +1,136 @@ +/* BK7231T (ARM968E-S, ARMv5TE) linker script + * + * Beken BK7231T -- ARM968E-S @ 120MHz, no FPU, no MMU. + * Wi-Fi 802.11b/g/n only (no BLE). + * + * Flash partition table (from BkDriverFlash.c): + * Bootloader: 0x000000, 68KB (0x11000) + * Application: 0x011000, 1156KB (0x121000) + * OTA: 0x132000, 600KB (0x96000) + * RF Firmware: 0x1E0000, 4KB + * NET Param: 0x1E1000, 4KB + * + * SRAM layout (OTA build, from SDK bk7231_ota.ld): + * RAM: 0x00400020, 256KB - 32 (full SRAM minus 32-byte header) + * + * No BLE stack means no TCM carve-out -- application gets full 256KB SRAM. + * + * Non-OTA: set FLASH LENGTH to 1812K (up to RF firmware at 0x1E0000). + */ + +ENTRY(Reset_Handler) + +EXTERN(_write _read _close _lseek _fstat _isatty _sbrk _exit _kill _getpid) +EXTERN(_Dmodule_ref __errno_location stdout) + +MEMORY +{ + FLASH (rx) : ORIGIN = 0x00011000, LENGTH = 1156K + SRAM (rwx) : ORIGIN = 0x00400020, LENGTH = 256K - 0x20 +} + +SECTIONS +{ + .text : ALIGN(4) + { + KEEP(*(.vector_table)) + *(.text .text.*) + } > FLASH + + .rodata : ALIGN(8) + { + *(.rodata .rodata.*) + } > FLASH + + .eh_frame : ALIGN(8) + { + __eh_frame_start = .; + KEEP(*(.eh_frame)) + KEEP(*(.eh_frame_hdr)) + } > FLASH + + .gcc_except_table : ALIGN(4) + { + *(.gcc_except_table .gcc_except_table.*) + } > FLASH + + .init_array : ALIGN(4) + { + __init_array_start = .; + KEEP(*(.init_array)) + __init_array_end = .; + } > FLASH + + .ARM.exidx : ALIGN(4) + { + __exidx_start = .; + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + __exidx_end = .; + } > FLASH + + .ARM.extab : ALIGN(4) + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > FLASH + + .got : ALIGN(4) + { + _got_load = LOADADDR(.got); + _got_start = .; + *(.got .got.*) + *(.got.plt) + _got_end = .; + } > SRAM AT > FLASH + + .data : ALIGN(4) + { + _data_start = .; + *(.data .data.*) + _data_end = .; + } > SRAM AT > FLASH + + _data_load = LOADADDR(.data); + __ram_load_addr = _data_load; + __ram_data_start__ = _data_start; + __ram_data_end__ = _data_end; + + .tdata : ALIGN(4) + { + _tdata_start = .; + *(.tdata .tdata.*) + _tdata_end = .; + } > SRAM AT > FLASH + + _tdata_load = LOADADDR(.tdata); + + .tbss (NOLOAD) : ALIGN(4) + { + _tbss_start = .; + *(.tbss .tbss.*) + _tbss_end = .; + } > SRAM + + .bss (NOLOAD) : ALIGN(4) + { + __bss_start__ = .; + _bss_start = .; + *(.bss .bss.*) + *(COMMON) + _bss_end = .; + __bss_end__ = .; + } > SRAM + + __heap_start = _bss_end; + __heap_end = ORIGIN(SRAM) + LENGTH(SRAM) - 4K; + + _stack_top = ORIGIN(SRAM) + LENGTH(SRAM); + __StackTop = _stack_top; + + /DISCARD/ : + { + *(.comment) + *(.note.*) + *(.gnu.hash) + *(.gnu.linkonce.*) + } +} diff --git a/platforms/bl618/README.txt b/platforms/bl618/README.txt new file mode 100644 index 0000000..74b12e2 --- /dev/null +++ b/platforms/bl618/README.txt @@ -0,0 +1,74 @@ +BL618 (Bouffalo Lab) -- T-Head E907 RV32IMAFC @ 320 MHz +======================================================== + +Reference board: Sipeed M0P Dock. + +Setup +----- + +Toolchain (Ubuntu 22.04+): + + # D compiler -- LDC with the official upstream build (includes RISC-V). + # The Ubuntu apt 'ldc' package also works but may lag the latest release. + curl -fsS https://dlang.org/install.sh | bash -s ldc + source ~/dlang/ldc-*/activate + + # RISC-V cross-toolchain and picolibc + sudo apt-get install gcc-riscv64-unknown-elf picolibc-riscv64-unknown-elf + +Flash tool (Python, host-side; no target dependency): + + pip install bflb-mcu-tool + +Windows: LDC installer from https://github.com/ldc-developers/ldc/releases. +RISC-V toolchain from the xpack distribution +(https://xpack.github.io/dev-tools/riscv-none-elf-gcc/) -- put its bin +directory on PATH; picolibc is bundled. Or use WSL2 with the Ubuntu +instructions above. + +Build the unittest image +------------------------ + +From the URT root: + + make PLATFORM=bl618 CONFIG=unittest + +Outputs (in bin/bl618_unittest/): + + urt_test ELF with debug symbols + urt_test.bin Raw binary, ready to flash + +Toolchain required: ldc, gcc-riscv64-unknown-elf, picolibc-riscv64-unknown-elf. +Linker script: third_party/urt/platforms/bl618/bl618.ld (uses flash XIP from +0xA0000000, OCRAM at 0x22020000, DTCM at 0x20000000). + +Flash +----- + +Bouffalo's official tool is BLDevCube (GUI) or bflb-mcu-tool (CLI): + + pip install bflb-mcu-tool + bflb-mcu-tool --chipname bl616 --interface uart \ + --port /dev/ttyUSB0 --baudrate 2000000 \ + --firmware bin/bl618_unittest/urt_test.bin \ + --addr 0x0 + +Hold BOOT, tap RESET, release BOOT to enter the ROM bootloader before +running the command. The chipname is "bl616" -- BL618 is a pin/package +variant of the same die, the tool only knows the bl616 family identifier. + +Console +------- + +UART0 on the default pins (GPIO14 TX, GPIO15 RX on the M0P Dock, exposed +on the onboard USB-serial bridge). 2 Mbaud, 8N1. The unittest harness +prints test results to stdout, then halts; tap RESET to re-run. + +Notes +----- + +* The memory map in bl618.ld is from the BL616 reference manual and may + need tweaking for non-Sipeed boards. Verify against the vendor BSP for + your specific carrier. +* No FreeRTOS, no SDK -- bare metal. URT brings its own start.S, syscall + stubs, UART driver, and IRQ table. diff --git a/platforms/bl618/bl618.ld b/platforms/bl618/bl618.ld new file mode 100644 index 0000000..fd8d9c6 --- /dev/null +++ b/platforms/bl618/bl618.ld @@ -0,0 +1,136 @@ +/* BL618 (T-Head E907 RV32IMAFC) linker script + * + * Single-core Bouffalo BL616/BL618 — no M0/D0 split. + * Code executes from flash via XIP or from OCRAM. + * + * Memory layout: + * FLASH XIP: 0xA0000000, 8MB (execute-in-place, read-only) + * OCRAM: 0x62020000, 480KB (on-chip RAM, uncached) + * OCRAM$: 0x22020000, 480KB (same RAM, cached alias) + * DTCM: 0x20000000, 64KB (tightly-coupled data memory) + * + * TODO: Verify memory map against actual BL618 hardware. + * The addresses below are from the BL616 reference manual + * and may need adjustment for the Sipeed M0P board. + */ + +ENTRY(_start) + +/* Force-link newlib syscall stubs and D runtime symbols */ +EXTERN(_write _read _close _lseek _fstat _isatty _sbrk _exit _kill _getpid) +EXTERN(_Dmodule_ref __errno_location stdout) + +MEMORY +{ + /* Flash XIP — code and read-only data */ + FLASH (rx) : ORIGIN = 0xA0000000, LENGTH = 8M + /* DTCM — fast tightly-coupled memory for stack and small data */ + DTCM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K + /* OCRAM — main writable RAM (cached alias for performance) */ + RAM (rwx) : ORIGIN = 0x22020000, LENGTH = 480K +} + +SECTIONS +{ + .text : ALIGN(4) + { + KEEP(*(.text.entry)) + . = ALIGN(4); + *(.text .text.*) + } > FLASH + + .rodata : ALIGN(8) + { + *(.rodata .rodata.*) + } > FLASH + + .eh_frame : ALIGN(8) + { + __eh_frame_start = .; + KEEP(*(.eh_frame)) + KEEP(*(.eh_frame_hdr)) + } > FLASH + + .gcc_except_table : ALIGN(4) + { + *(.gcc_except_table .gcc_except_table.*) + } > FLASH + + .init_array : ALIGN(4) + { + __init_array_start = .; + KEEP(*(.init_array)) + __init_array_end = .; + } > FLASH + + /* GOT and small data in DTCM for fast access. + * LMA in FLASH, start.S copies to DTCM at boot. */ + .got : ALIGN(4) + { + PROVIDE(__global_pointer$ = . + 0x800); + *(.got .got.*) + *(.got.plt) + *(.sdata .sdata.*) + *(.srodata .srodata.*) + } > DTCM AT > FLASH + + _got_load = LOADADDR(.got); + _got_start = ADDR(.got); + _got_end = _got_start + SIZEOF(.got); + + .data : ALIGN(4) + { + *(.data .data.*) + } > RAM AT > FLASH + + _data_load = LOADADDR(.data); + _data_start = ADDR(.data); + _data_end = _data_start + SIZEOF(.data); + __ram_load_addr = _data_load; + __ram_data_start__ = _data_start; + __ram_data_end__ = _data_end; + + .bss (NOLOAD) : ALIGN(4) + { + __bss_start__ = .; + _bss_start = .; + *(.bss .bss.*) + *(.sbss .sbss.*) + *(COMMON) + _bss_end = .; + __bss_end__ = .; + } > RAM + + /* Thread-local data/BSS in DTCM */ + .tdata : ALIGN(4) + { + *(.tdata .tdata.*) + } > DTCM AT > FLASH + + _tdata_load = LOADADDR(.tdata); + _tdata_start = ADDR(.tdata); + _tdata_end = _tdata_start + SIZEOF(.tdata); + + .tbss (NOLOAD) : ALIGN(4) + { + _tbss_start = .; + *(.tbss .tbss.*) + _tbss_end = .; + } > DTCM + + /* Heap in OCRAM after BSS */ + __heap_start = _bss_end; + __heap_end = ORIGIN(RAM) + LENGTH(RAM); + + /* Stack in DTCM (fast access) */ + _stack_top = ORIGIN(DTCM) + LENGTH(DTCM); + __StackTop = _stack_top; + + /DISCARD/ : + { + *(.comment) + *(.note.*) + *(.gnu.hash) + *(.gnu.linkonce.*) + } +} diff --git a/platforms/bl808/README.txt b/platforms/bl808/README.txt new file mode 100644 index 0000000..b615895 --- /dev/null +++ b/platforms/bl808/README.txt @@ -0,0 +1,94 @@ +BL808 (Bouffalo Lab) -- multi-core SoC +====================================== + +Two heterogeneous cores in one package: + + D0 T-Head C906 RV64GC @ 480 MHz (application/multimedia) + M0 T-Head E907 RV32IMAFC @ 320 MHz (boot core, MCU domain) + +Reference board: Sipeed M1s Dock. + +There is also an LP core (E902) in the LP domain; URT does not target it. + +Setup +----- + +Toolchain (Ubuntu 22.04+): + + # D compiler -- LDC with the official upstream build. + curl -fsS https://dlang.org/install.sh | bash -s ldc + source ~/dlang/ldc-*/activate + + # RISC-V cross-toolchain and picolibc (covers both D0 RV64 and M0 RV32). + sudo apt-get install gcc-riscv64-unknown-elf picolibc-riscv64-unknown-elf + +Flash tool (Python, host-side): + + pip install bflb-mcu-tool + +The C906 (D0) core uses the T-Head xthead extensions; recent +gcc-riscv64-unknown-elf and LDC both accept the +xthead* mattr flags. +If you see "unsupported mattr" errors, your toolchain is too old -- +upgrade to Ubuntu 24.04 or install LDC 1.36+ from the upstream tarball. + +Windows: LDC installer from https://github.com/ldc-developers/ldc/releases; +xpack RISC-V toolchain (https://xpack.github.io/dev-tools/riscv-none-elf-gcc/); +or use WSL2 with the Ubuntu instructions above. Older xpack builds may +not include the xthead extensions needed for C906; if so, use the +T-Head toolchain from https://www.xrvm.cn/community/download. + +Build the unittest image +------------------------ + +From the URT root: + + make PLATFORM=bl808 PROCESSOR=c906 CONFIG=unittest (D0 image) + make PLATFORM=bl808 PROCESSOR=e907 CONFIG=unittest (M0 image) + +Outputs: + + bin/bl808-d0_unittest/urt_test.bin D0 firmware, loads to PSRAM + bin/bl808-m0_unittest/urt_test.bin M0 firmware, runs XIP from flash + +Linker scripts: bl808_d0.ld (D0 expects to run from PSRAM at 0x50100000; +M0 firmware copies it from flash at boot). bl808_m0.ld (M0 runs XIP from +flash at 0x58000000). + +Boot dependency +--------------- + +D0 cannot boot standalone -- only M0 starts at power-on. Before D0 can +fetch its first instruction, M0 must: + + 1. Initialize clocks and PLLs. + 2. Bring up the flash controller and PSRAM controller. + 3. Copy the D0 firmware from flash (typically 0x580F0000) to PSRAM. + 4. Release D0 from reset by writing its boot-address register. + +This means a D0-only urt_test.bin is a valid build artifact but is NOT +flashable on its own. To run the D0 unittests on hardware, flash both +images together: an M0 firmware that performs the handoff, plus the D0 +image at the address M0 expects. + +Flash +----- + +Same tool as BL618, with chipname=bl808: + + pip install bflb-mcu-tool + bflb-mcu-tool --chipname bl808 --interface uart \ + --port /dev/ttyUSB0 --baudrate 2000000 \ + --firmware bin/bl808-m0_unittest/urt_test.bin \ + --addr 0x58000000 + +For a paired D0+M0 image, flash D0 at 0x580F0000 in the same command +(refer to BLDevCube's partition table editor for the proper layout). + +Hold BOOT, tap RESET, release BOOT to enter ROM bootloader. + +Console +------- + +UART0 on the default pins (exposed via the onboard USB-serial bridge on +the M1s Dock). 2 Mbaud, 8N1. M0 brings up UART early; D0 prints once it +has been released and reaches main(). diff --git a/platforms/bl808/bl808_d0.ld b/platforms/bl808/bl808_d0.ld new file mode 100644 index 0000000..5332f76 --- /dev/null +++ b/platforms/bl808/bl808_d0.ld @@ -0,0 +1,160 @@ +/* BL808 D0 core (T-Head C906 RV64GC) linker script + * + * M0 copies D0 firmware from Flash (0x580F0000) to PSRAM and starts D0 + * executing from PSRAM at 0x50100000. This is NOT XIP — code runs from RAM. + * + * Memory layout: + * CODE PSRAM: 0x50100000, 2MB (D0 firmware, copied by M0 from Flash) + * DATA PSRAM: 0x50300000, 61MB (writable data, BSS, heap) + * SRAM: 0x3eff8000, 64KB (stack, GOT, fast data) + */ + +ENTRY(_start) + +/* Force-link newlib syscall stubs and D runtime symbols + * (otherwise --gc-sections strips them before newlib resolves) */ +EXTERN(_write _read _close _lseek _fstat _isatty _sbrk _exit _kill _getpid) +EXTERN(_Dmodule_ref __errno_location stdout) +EXTERN(socket close poll _accept _recv _recvfrom _sendmsg) +EXTERN(_shutdown _bind _listen _connect setsockopt getsockname getpeername) +EXTERN(getaddrinfo freeaddrinfo) + +MEMORY +{ + /* D0 firmware is loaded by M0 to PSRAM at this address */ + CODE (rx) : ORIGIN = 0x50100000, LENGTH = 2M + /* Writable data starts after the 2MB firmware region */ + DATA (rwx) : ORIGIN = 0x50300000, LENGTH = 61M + SRAM (rwx) : ORIGIN = 0x3eff8000, LENGTH = 64K + /* HBN RAM: 4KB, survives hibernate if VBAT maintained */ + HBNRAM (rw) : ORIGIN = 0x20010000, LENGTH = 4K +} + +SECTIONS +{ + .text : ALIGN(4) + { + KEEP(*(.text.entry)) + . = ALIGN(4); + *(.text.psram_early) + *(.text .text.*) + } > CODE + + .rodata : ALIGN(8) + { + *(.rodata .rodata.*) + } > CODE + + /* DWARF exception handling — required for D throw/catch (fibre abort, etc.). + * eh_frame: stack frame descriptions for the unwinder. + * gcc_except_table: LSDA (landing pads, catch clause type filters). */ + .eh_frame : ALIGN(8) + { + __eh_frame_start = .; + KEEP(*(.eh_frame)) + KEEP(*(.eh_frame_hdr)) + } > CODE + + .gcc_except_table : ALIGN(4) + { + *(.gcc_except_table .gcc_except_table.*) + } > CODE + + /* Init array — LDC module info registration functions. + * Called from start.S before main(). */ + .init_array : ALIGN(8) + { + __init_array_start = .; + KEEP(*(.init_array)) + __init_array_end = .; + } > CODE + + /* GOT and small data in fast SRAM. + * LMA is in CODE (PSRAM), start.S copies to SRAM at boot. */ + .got : ALIGN(8) + { + PROVIDE(__global_pointer$ = . + 0x800); + *(.got .got.*) + *(.got.plt) + *(.sdata .sdata.*) + *(.srodata .srodata.*) + } > SRAM AT > CODE + + _got_load = LOADADDR(.got); + _got_start = ADDR(.got); + _got_end = _got_start + SIZEOF(.got); + + .data : ALIGN(8) + { + *(.data .data.*) + } > DATA AT > CODE + + _data_load = LOADADDR(.data); + _data_start = ADDR(.data); + _data_end = _data_start + SIZEOF(.data); + __ram_load_addr = _data_load; + __ram_data_start__ = _data_start; + __ram_data_end__ = _data_end; + + .bss (NOLOAD) : ALIGN(8) + { + __bss_start__ = .; + _bss_start = .; + *(.bss .bss.*) + *(.sbss .sbss.*) + *(COMMON) + _bss_end = .; + __bss_end__ = .; + } > DATA + + /* Thread-local data/BSS in SRAM */ + .tdata : ALIGN(8) + { + *(.tdata .tdata.*) + } > SRAM AT > CODE + + _tdata_load = LOADADDR(.tdata); + _tdata_start = ADDR(.tdata); + _tdata_end = _tdata_start + SIZEOF(.tdata); + + .tbss (NOLOAD) : ALIGN(8) + { + _tbss_start = .; + *(.tbss .tbss.*) + _tbss_end = .; + } > SRAM + + /* HBN RAM: persistent across hibernate, not initialized by startup */ + .hbn_ram (NOLOAD) : ALIGN(4) + { + *(.hbn_ram) + } > HBNRAM + + /* Heap in DATA PSRAM after BSS */ + __heap_start = _bss_end; + __heap_end = ORIGIN(DATA) + LENGTH(DATA); + + /* Stack in fast on-chip SRAM */ + _stack_top = ORIGIN(SRAM) + LENGTH(SRAM); + __StackTop = _stack_top; + + /* Vendor start_load.c expects these symbols for ITCM/DTCM/system RAM + * copy loops. We don't use those sections, so define them as empty. */ + PROVIDE(__itcm_load_addr = 0); + PROVIDE(__tcm_code_start__ = 0); + PROVIDE(__tcm_code_end__ = 0); + PROVIDE(__dtcm_load_addr = 0); + PROVIDE(__tcm_data_start__ = 0); + PROVIDE(__tcm_data_end__ = 0); + PROVIDE(__system_ram_load_addr = 0); + PROVIDE(__system_ram_data_start__ = 0); + PROVIDE(__system_ram_data_end__ = 0); + + /DISCARD/ : + { + *(.comment) + *(.note.*) + *(.gnu.hash) + *(.gnu.linkonce.*) + } +} diff --git a/platforms/bl808/bl808_m0.ld b/platforms/bl808/bl808_m0.ld new file mode 100644 index 0000000..2d09302 --- /dev/null +++ b/platforms/bl808/bl808_m0.ld @@ -0,0 +1,126 @@ +/* BL808 M0 core (T-Head E907 RV32IMAFC) linker script + * + * The M0 core is the first to boot on BL808. It initializes clocks, + * GPIO muxing, and peripherals before optionally starting the D0 core. + * + * Memory layout (MCU domain): + * FLASH XIP: 0x58000000, 2MB (M0 firmware, execute-in-place) + * OCRAM: 0x22020000, 64KB (on-chip RAM, cached alias) + * DTCM: 0x20000000, 64KB (tightly-coupled data memory) + * + * TODO: Verify memory map on actual hardware. The M0's view of memory + * differs from D0's — M0 has its own PLIC, own UART IRQ domain, + * and cannot directly access the MM-domain PSRAM used by D0. + */ + +ENTRY(_start) + +EXTERN(_write _read _close _lseek _fstat _isatty _sbrk _exit _kill _getpid) +EXTERN(_Dmodule_ref __errno_location stdout) + +MEMORY +{ + FLASH (rx) : ORIGIN = 0x58000000, LENGTH = 2M + DTCM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K + RAM (rwx) : ORIGIN = 0x22020000, LENGTH = 64K +} + +SECTIONS +{ + .text : ALIGN(4) + { + KEEP(*(.text.entry)) + . = ALIGN(4); + *(.text .text.*) + } > FLASH + + .rodata : ALIGN(8) + { + *(.rodata .rodata.*) + } > FLASH + + .eh_frame : ALIGN(8) + { + __eh_frame_start = .; + KEEP(*(.eh_frame)) + KEEP(*(.eh_frame_hdr)) + } > FLASH + + .gcc_except_table : ALIGN(4) + { + *(.gcc_except_table .gcc_except_table.*) + } > FLASH + + .init_array : ALIGN(4) + { + __init_array_start = .; + KEEP(*(.init_array)) + __init_array_end = .; + } > FLASH + + .got : ALIGN(4) + { + PROVIDE(__global_pointer$ = . + 0x800); + *(.got .got.*) + *(.got.plt) + *(.sdata .sdata.*) + *(.srodata .srodata.*) + } > DTCM AT > FLASH + + _got_load = LOADADDR(.got); + _got_start = ADDR(.got); + _got_end = _got_start + SIZEOF(.got); + + .data : ALIGN(4) + { + *(.data .data.*) + } > RAM AT > FLASH + + _data_load = LOADADDR(.data); + _data_start = ADDR(.data); + _data_end = _data_start + SIZEOF(.data); + __ram_load_addr = _data_load; + __ram_data_start__ = _data_start; + __ram_data_end__ = _data_end; + + .bss (NOLOAD) : ALIGN(4) + { + __bss_start__ = .; + _bss_start = .; + *(.bss .bss.*) + *(.sbss .sbss.*) + *(COMMON) + _bss_end = .; + __bss_end__ = .; + } > RAM + + .tdata : ALIGN(4) + { + *(.tdata .tdata.*) + } > DTCM AT > FLASH + + _tdata_load = LOADADDR(.tdata); + _tdata_start = ADDR(.tdata); + _tdata_end = _tdata_start + SIZEOF(.tdata); + + .tbss (NOLOAD) : ALIGN(4) + { + _tbss_start = .; + *(.tbss .tbss.*) + _tbss_end = .; + } > DTCM + + __heap_start = _bss_end; + __heap_end = ORIGIN(RAM) + LENGTH(RAM); + + _stack_top = ORIGIN(DTCM) + LENGTH(DTCM); + __StackTop = _stack_top; + + /DISCARD/ : + { + *(.comment) + *(.note.*) + *(.gnu.hash) + *(.gnu.linkonce.*) + } +} diff --git a/platforms/rp2350/README.txt b/platforms/rp2350/README.txt new file mode 100644 index 0000000..f0212e7 --- /dev/null +++ b/platforms/rp2350/README.txt @@ -0,0 +1,90 @@ +RP2350 (Raspberry Pi) -- dual Cortex-M33 @ 150 MHz +================================================== + +520 KB SRAM, XIP from external QSPI flash. URT targets the Arm cores +(the chip also has dual Hazard3 RISC-V cores selectable at boot; URT +does not currently support that mode). + +Reference board: Raspberry Pi Pico 2. + +Setup +----- + +Toolchain (Ubuntu 22.04+): + + # D compiler -- LDC with the official upstream build. + curl -fsS https://dlang.org/install.sh | bash -s ldc + source ~/dlang/ldc-*/activate + + # ARM cross-toolchain and picolibc. + sudo apt-get install gcc-arm-none-eabi picolibc-arm-none-eabi + +Flash tool (picotool, for ELF -> UF2 conversion): + + sudo apt-get install build-essential cmake libusb-1.0-0-dev + git clone https://github.com/raspberrypi/picotool + cd picotool && mkdir build && cd build && cmake .. && make + sudo cp picotool /usr/local/bin/ + +Pre-built picotool binaries are also published at +https://github.com/raspberrypi/pico-sdk-tools/releases for those who +don't want to build from source. + +Windows: LDC installer from https://github.com/ldc-developers/ldc/releases; +ARM GNU Toolchain installer from +https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads; +picotool prebuilt from the pico-sdk-tools releases page noted above. + +Build the unittest image +------------------------ + +From the URT root: + + make PLATFORM=rp2350 CONFIG=unittest + +Outputs (in bin/rp2350_unittest/): + + urt_test ELF with debug symbols + urt_test.bin Raw binary + +Toolchain required: ldc, gcc-arm-none-eabi, picolibc-arm-none-eabi. +Linker script: rp2350.ld. + +Flash +----- + +The RP2350 has a USB mass-storage bootloader -- hold BOOTSEL while +plugging in USB to enter it. Drag-and-drop a .uf2 file onto the +mounted RPI-RP2 volume. + +Convert urt_test (the ELF, not the .bin) to UF2: + + picotool uf2 convert bin/rp2350_unittest/urt_test \ + bin/rp2350_unittest/urt_test.uf2 \ + --family rp2350-arm-s + +(Install picotool from https://github.com/raspberrypi/picotool.) + +Then drop urt_test.uf2 onto the RPI-RP2 volume. The board reboots and +runs immediately. + +Alternatively, flash directly via SWD with openocd + the Pi-supplied +config files (slower; UF2 is the path of least resistance). + +Console +------- + +UART0 (GP0=TX, GP1=RX on the Pico 2 header), 115200 baud, 8N1. Or use +the picoprobe firmware on a second Pico for SWD + virtual UART. + +The unittest harness prints results and halts. Tap RESET (or unplug/ +replug USB) to re-run. + +Notes +----- + +* boot2.S is included in URT's start sources -- a second-stage bootloader + that configures XIP for the QSPI flash chip on the Pico 2 board. Other + boards with different flash chips may need a different boot2. +* The RP2350 has secure/non-secure split (TrustZone-M). URT runs + entirely in secure mode; non-secure callable veneers are not set up. diff --git a/platforms/rp2350/rp2350.ld b/platforms/rp2350/rp2350.ld new file mode 100644 index 0000000..f423ae1 --- /dev/null +++ b/platforms/rp2350/rp2350.ld @@ -0,0 +1,154 @@ +/* RP2350 (ARM Cortex-M33) linker script + * + * Raspberry Pi RP2350B — dual Cortex-M33 @ 150MHz, 520KB SRAM, XIP QSPI flash. + * + * Memory layout: + * BOOT2: 0x10000000, 256B (second-stage bootloader, copied to SRAM by ROM) + * FLASH: 0x10000100, ~4MB (XIP execute-in-place, read-only) + * SRAM: 0x20000000, 520KB (8 banks x 64KB + 8KB scratch) + * + * The RP2350 ROM loads the 256-byte boot2 block from the start of flash, + * executes it from SRAM to configure the QSPI interface, then jumps to + * the vector table at the start of .text. + */ + +ENTRY(Reset_Handler) + +/* Force-link newlib syscall stubs and D runtime symbols */ +EXTERN(_write _read _close _lseek _fstat _isatty _sbrk _exit _kill _getpid) +EXTERN(_Dmodule_ref __errno_location stdout) + +MEMORY +{ + /* Second-stage bootloader — first 256 bytes of flash */ + BOOT2 (rx) : ORIGIN = 0x10000000, LENGTH = 256 + /* Application flash — XIP, code and read-only data */ + FLASH (rx) : ORIGIN = 0x10000100, LENGTH = 4M - 256 + /* SRAM — all 8 banks contiguous (banks 0-7: 8 x 64KB = 512KB) + 8KB scratch */ + SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 520K +} + +SECTIONS +{ + /* Boot2 — second-stage bootloader for QSPI flash init */ + .boot2 : ALIGN(4) + { + KEEP(*(.boot2)) + } > BOOT2 + + /* Vector table must be first in FLASH (right after boot2) */ + .vector_table : ALIGN(4) + { + KEEP(*(.vector_table)) + } > FLASH + + .text : ALIGN(4) + { + *(.text .text.*) + } > FLASH + + .rodata : ALIGN(8) + { + *(.rodata .rodata.*) + } > FLASH + + .eh_frame : ALIGN(8) + { + __eh_frame_start = .; + KEEP(*(.eh_frame)) + KEEP(*(.eh_frame_hdr)) + } > FLASH + + .gcc_except_table : ALIGN(4) + { + *(.gcc_except_table .gcc_except_table.*) + } > FLASH + + .init_array : ALIGN(4) + { + __init_array_start = .; + KEEP(*(.init_array)) + __init_array_end = .; + } > FLASH + + /* ARM exception unwind tables */ + .ARM.exidx : ALIGN(4) + { + __exidx_start = .; + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + __exidx_end = .; + } > FLASH + + .ARM.extab : ALIGN(4) + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > FLASH + + /* GOT in SRAM for fast access — LMA in FLASH, copied at startup */ + .got : ALIGN(4) + { + _got_load = LOADADDR(.got); + _got_start = .; + *(.got .got.*) + *(.got.plt) + _got_end = .; + } > SRAM AT > FLASH + + /* Initialized data — LMA in FLASH, copied to SRAM at startup */ + .data : ALIGN(4) + { + _data_start = .; + *(.data .data.*) + _data_end = .; + } > SRAM AT > FLASH + + _data_load = LOADADDR(.data); + __ram_load_addr = _data_load; + __ram_data_start__ = _data_start; + __ram_data_end__ = _data_end; + + /* Thread-local initialized data — LMA in FLASH, copied to SRAM at startup */ + .tdata : ALIGN(4) + { + _tdata_start = .; + *(.tdata .tdata.*) + _tdata_end = .; + } > SRAM AT > FLASH + + _tdata_load = LOADADDR(.tdata); + + /* Thread-local zero-initialized data */ + .tbss (NOLOAD) : ALIGN(4) + { + _tbss_start = .; + *(.tbss .tbss.*) + _tbss_end = .; + } > SRAM + + /* Zero-initialized data */ + .bss (NOLOAD) : ALIGN(4) + { + __bss_start__ = .; + _bss_start = .; + *(.bss .bss.*) + *(COMMON) + _bss_end = .; + __bss_end__ = .; + } > SRAM + + /* Heap grows up from end of BSS */ + __heap_start = _bss_end; + __heap_end = ORIGIN(SRAM) + LENGTH(SRAM) - 4K; + + /* Stack at top of SRAM (4KB reserved) */ + _stack_top = ORIGIN(SRAM) + LENGTH(SRAM); + __StackTop = _stack_top; + + /DISCARD/ : + { + *(.comment) + *(.note.*) + *(.gnu.hash) + *(.gnu.linkonce.*) + } +} diff --git a/platforms/stm32/README.txt b/platforms/stm32/README.txt new file mode 100644 index 0000000..58161de --- /dev/null +++ b/platforms/stm32/README.txt @@ -0,0 +1,105 @@ +STM32 (STMicroelectronics) -- Cortex-M4F / Cortex-M7F +===================================================== + +Two PLATFORM aliases supported: + + stm4xx STM32F4 family (Cortex-M4F, FPv4-SP-D16) + stm7xx STM32F7 family (Cortex-M7F, FPv5-D16) + +Linker scripts: stm32_f4.ld and stm32_f7.ld respectively. Each is a +generic-ish memory map -- override flash/RAM sizes in the script if +your specific part has more or less than the defaults. + +Setup +----- + +Toolchain (Ubuntu 22.04+): + + # D compiler -- LDC with the official upstream build. + curl -fsS https://dlang.org/install.sh | bash -s ldc + source ~/dlang/ldc-*/activate + + # ARM cross-toolchain and picolibc. + sudo apt-get install gcc-arm-none-eabi picolibc-arm-none-eabi + +Flash tools (pick one; you do not need all three): + + # ST-LINK + STM32CubeProgrammer -- official ST tool, GUI + CLI. + # Download from https://www.st.com/en/development-tools/stm32cubeprog.html + # (free with registration). Adds STM32_Programmer_CLI to PATH. + + # OpenOCD with ST-LINK or J-Link -- open-source. + sudo apt-get install openocd + + # dfu-util for the ROM bootloader path. + sudo apt-get install dfu-util + +Windows: LDC installer from https://github.com/ldc-developers/ldc/releases; +ARM GNU Toolchain installer from +https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads; +STM32CubeProgrammer Windows installer (it also ships the ST-LINK USB +drivers, which you will need separately on first connect even with +OpenOCD). + +Build the unittest image +------------------------ + +From the URT root: + + make PLATFORM=stm4xx CONFIG=unittest + make PLATFORM=stm7xx CONFIG=unittest + +Outputs: + + bin/stm4xx_unittest/urt_test.bin + bin/stm7xx_unittest/urt_test.bin + +Toolchain required: ldc, gcc-arm-none-eabi, picolibc-arm-none-eabi. + +Flash +----- + +Three common paths: + +1. ST-LINK + STM32CubeProgrammer (GUI or CLI): + + STM32_Programmer_CLI -c port=SWD -d bin/stm4xx_unittest/urt_test.bin 0x08000000 -rst + +2. openocd + ST-LINK or J-Link: + + openocd -f interface/stlink.cfg -f target/stm32f4x.cfg \ + -c "program bin/stm4xx_unittest/urt_test.bin 0x08000000 verify reset exit" + + (Use target/stm32f7x.cfg for stm7xx.) + +3. DFU bootloader (ROM-resident on most parts): + + dfu-util -a 0 -s 0x08000000 -D bin/stm4xx_unittest/urt_test.bin + + Requires entering DFU mode -- typically BOOT0 high at reset. + +The reset vector and image base are at 0x08000000 in all three cases +(internal flash bank 1). + +Console +------- + +USART2 by default (PA2=TX, PA3=RX on most Nucleo and Discovery boards; +exposed via the ST-LINK USB-CDC bridge). 115200 baud, 8N1. Some boards +mux the ST-LINK VCP to USART3 instead; check the board user manual. + +The unittest harness prints results and halts. Tap RESET to re-run. + +Notes +----- + +* The default linker script assumes 1 MB flash + 192 KB RAM (a common + F4/F7 mid-range part). Edit the MEMORY block in stm32_f4.ld or + stm32_f7.ld for your specific MCU. +* No HAL, no LL drivers, no CubeMX -- URT brings its own start.S, + vector table stub, and minimal UART driver. If you need on-chip + peripherals beyond UART you will need to write them or pull in the + vendor headers manually. +* MFPU defaults: F4 = fpv4-sp-d16 (single-precision only), F7 = + fpv5-d16 (double-precision). The Makefile picks these per + STM32_VARIANT; override with MFPU=... if your part differs. diff --git a/platforms/stm32/stm32_f4.ld b/platforms/stm32/stm32_f4.ld new file mode 100644 index 0000000..b719f72 --- /dev/null +++ b/platforms/stm32/stm32_f4.ld @@ -0,0 +1,139 @@ +/* STM32F4 (ARM Cortex-M4) linker script + * + * Default memory map for STM32F407VG (Discovery board): + * FLASH: 1MB at 0x08000000 + * RAM: 128KB at 0x20000000 (SRAM1 112KB + SRAM2 16KB contiguous) + * + * STM32F4 also has 64KB CCM RAM at 0x10000000 (no DMA access). + * Not used here -- add a DTCM region if you need it for stack/GOT. + * + * Adjust MEMORY sizes for your specific F4 variant. + */ + +ENTRY(Reset_Handler) + +EXTERN(_write _read _close _lseek _fstat _isatty _sbrk _exit _kill _getpid) +EXTERN(_Dmodule_ref __errno_location stdout) + +MEMORY +{ + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1M + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K +} + +SECTIONS +{ + .vector_table : ALIGN(4) + { + KEEP(*(.vector_table)) + } > FLASH + + .text : ALIGN(4) + { + *(.text .text.*) + } > FLASH + + .rodata : ALIGN(8) + { + *(.rodata .rodata.*) + } > FLASH + + .eh_frame : ALIGN(8) + { + __eh_frame_start = .; + KEEP(*(.eh_frame)) + KEEP(*(.eh_frame_hdr)) + } > FLASH + + .gcc_except_table : ALIGN(4) + { + *(.gcc_except_table .gcc_except_table.*) + } > FLASH + + .init_array : ALIGN(4) + { + __init_array_start = .; + KEEP(*(.init_array)) + __init_array_end = .; + } > FLASH + + .ARM.exidx : ALIGN(4) + { + __exidx_start = .; + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + __exidx_end = .; + } > FLASH + + .ARM.extab : ALIGN(4) + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > FLASH + + /* GOT in RAM for fast access -- LMA in FLASH, copied at startup */ + .got : ALIGN(4) + { + _got_load = LOADADDR(.got); + _got_start = .; + *(.got .got.*) + *(.got.plt) + _got_end = .; + } > RAM AT > FLASH + + /* Initialized data -- LMA in FLASH, copied to RAM at startup */ + .data : ALIGN(4) + { + _data_start = .; + *(.data .data.*) + _data_end = .; + } > RAM AT > FLASH + + _data_load = LOADADDR(.data); + __ram_load_addr = _data_load; + __ram_data_start__ = _data_start; + __ram_data_end__ = _data_end; + + /* Thread-local initialized data -- LMA in FLASH, copied to RAM at startup */ + .tdata : ALIGN(4) + { + _tdata_start = .; + *(.tdata .tdata.*) + _tdata_end = .; + } > RAM AT > FLASH + + _tdata_load = LOADADDR(.tdata); + + /* Thread-local zero-initialized data */ + .tbss (NOLOAD) : ALIGN(4) + { + _tbss_start = .; + *(.tbss .tbss.*) + _tbss_end = .; + } > RAM + + /* Zero-initialized data */ + .bss (NOLOAD) : ALIGN(4) + { + __bss_start__ = .; + _bss_start = .; + *(.bss .bss.*) + *(COMMON) + _bss_end = .; + __bss_end__ = .; + } > RAM + + /* Heap grows up from end of BSS */ + __heap_start = _bss_end; + __heap_end = ORIGIN(RAM) + LENGTH(RAM) - 4K; + + /* Stack at top of RAM (4KB reserved) */ + _stack_top = ORIGIN(RAM) + LENGTH(RAM); + __StackTop = _stack_top; + + /DISCARD/ : + { + *(.comment) + *(.note.*) + *(.gnu.hash) + *(.gnu.linkonce.*) + } +} diff --git a/platforms/stm32/stm32_f7.ld b/platforms/stm32/stm32_f7.ld new file mode 100644 index 0000000..8a4ae48 --- /dev/null +++ b/platforms/stm32/stm32_f7.ld @@ -0,0 +1,141 @@ +/* STM32F7 (ARM Cortex-M7) linker script + * + * Default memory map for STM32F746NG (Discovery board): + * FLASH: 1MB at 0x08000000 + * DTCM: 64KB at 0x20000000 (tightly-coupled, fast, no DMA) + * RAM: 256KB at 0x20010000 (SRAM1 240KB + SRAM2 16KB contiguous) + * + * DTCM is used for stack and GOT (zero-wait-state single-cycle access). + * Main RAM is used for .data, .bss, and heap. + * + * Adjust MEMORY sizes for your specific F7 variant. + */ + +ENTRY(Reset_Handler) + +EXTERN(_write _read _close _lseek _fstat _isatty _sbrk _exit _kill _getpid) +EXTERN(_Dmodule_ref __errno_location stdout) + +MEMORY +{ + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1M + DTCM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K + RAM (rwx) : ORIGIN = 0x20010000, LENGTH = 256K +} + +SECTIONS +{ + .vector_table : ALIGN(4) + { + KEEP(*(.vector_table)) + } > FLASH + + .text : ALIGN(4) + { + *(.text .text.*) + } > FLASH + + .rodata : ALIGN(8) + { + *(.rodata .rodata.*) + } > FLASH + + .eh_frame : ALIGN(8) + { + __eh_frame_start = .; + KEEP(*(.eh_frame)) + KEEP(*(.eh_frame_hdr)) + } > FLASH + + .gcc_except_table : ALIGN(4) + { + *(.gcc_except_table .gcc_except_table.*) + } > FLASH + + .init_array : ALIGN(4) + { + __init_array_start = .; + KEEP(*(.init_array)) + __init_array_end = .; + } > FLASH + + .ARM.exidx : ALIGN(4) + { + __exidx_start = .; + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + __exidx_end = .; + } > FLASH + + .ARM.extab : ALIGN(4) + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > FLASH + + /* GOT in DTCM for fast access -- LMA in FLASH, copied at startup */ + .got : ALIGN(4) + { + _got_load = LOADADDR(.got); + _got_start = .; + *(.got .got.*) + *(.got.plt) + _got_end = .; + } > DTCM AT > FLASH + + /* Initialized data -- LMA in FLASH, copied to RAM at startup */ + .data : ALIGN(4) + { + _data_start = .; + *(.data .data.*) + _data_end = .; + } > RAM AT > FLASH + + _data_load = LOADADDR(.data); + __ram_load_addr = _data_load; + __ram_data_start__ = _data_start; + __ram_data_end__ = _data_end; + + /* Thread-local initialized data in DTCM -- LMA in FLASH */ + .tdata : ALIGN(4) + { + _tdata_start = .; + *(.tdata .tdata.*) + _tdata_end = .; + } > DTCM AT > FLASH + + _tdata_load = LOADADDR(.tdata); + + /* Thread-local zero-initialized data */ + .tbss (NOLOAD) : ALIGN(4) + { + _tbss_start = .; + *(.tbss .tbss.*) + _tbss_end = .; + } > DTCM + + /* Zero-initialized data */ + .bss (NOLOAD) : ALIGN(4) + { + __bss_start__ = .; + _bss_start = .; + *(.bss .bss.*) + *(COMMON) + _bss_end = .; + __bss_end__ = .; + } > RAM + + /* Heap grows up from end of BSS in RAM */ + __heap_start = _bss_end; + __heap_end = ORIGIN(RAM) + LENGTH(RAM); + + /* Stack at top of DTCM (fast access) */ + _stack_top = ORIGIN(DTCM) + LENGTH(DTCM); + __StackTop = _stack_top; + + /DISCARD/ : + { + *(.comment) + *(.note.*) + *(.gnu.hash) + *(.gnu.linkonce.*) + } +} diff --git a/src/core/atomic.d b/src/core/atomic.d new file mode 100644 index 0000000..5d5eb04 --- /dev/null +++ b/src/core/atomic.d @@ -0,0 +1,15 @@ +// Shadows LDC's core.atomic with non-intrinsic implementations. +// LDC's version uses LLVM atomic intrinsics (llvm_atomic_rmw_add etc.) which +// require hardware atomic support. Targets without native atomics (e.g. Xtensa) +// crash in LLVM instruction selection. This module provides plain load/store +// equivalents that are correct for single-core / cooperative-multitasking targets. +// +// Only included when uRT's import path (-I) precedes LDC's (post-switches). + +module core.atomic; + +nothrow @nogc @safe: + +public import urt.atomic : MemoryOrder, cas, + atomicLoad, atomicStore, atomicOp, atomicExchange, + atomicFetchAdd, atomicFetchSub, TailShared; diff --git a/src/object.d b/src/object.d new file mode 100644 index 0000000..5a0360a --- /dev/null +++ b/src/object.d @@ -0,0 +1,1796 @@ +// Minimal object.d - replaces druntime's implicit root module. +// +// This file is the auditing frontier: every symbol added here is a +// symbol the compiler or linker demanded. Keep it as small as possible. +module object; + +static assert(__VERSION__ >= 2112, + "uRT requires DMD frontend 2.112+ (DMD ≥2.112, LDC ≥1.42). " ~ + "Older frontends use TypeInfo-based AA hooks incompatible with uRT's template-based AAs."); + +// ────────────────────────────────────────────────────────────────────── +// Platform-dependent ABI flags (must match druntime's detection) +// ────────────────────────────────────────────────────────────────────── + +version (X86_64) +{ + version (DigitalMars) version = WithArgTypes; + else version (Windows) {} // Win64 ABI doesn't need argTypes + else version = WithArgTypes; +} +else version (AArch64) +{ + version (OSX) {} + else version (iOS) {} + else version (TVOS) {} + else version (WatchOS) {} + else version = WithArgTypes; +} + +// ────────────────────────────────────────────────────────────────────── +// Fundamental type aliases (compiler hardcodes references to these) +// ────────────────────────────────────────────────────────────────────── + +alias size_t = typeof(int.sizeof); +alias ptrdiff_t = typeof(cast(void*)0 - cast(void*)0); +alias nullptr_t = typeof(null); +alias noreturn = typeof(*null); + +// needed so druntime's core.stdc.stdio compiles on AArch64 +version (AArch64) +{ + extern(C++, std) struct __va_list + { + void* __stack; + void* __gr_top; + void* __vr_top; + int __gr_offs; + int __vr_offs; + } +} + +version (Windows) + alias wchar wchar_t; +else version (Posix) + alias dchar wchar_t; +else version (WASI) + alias dchar wchar_t; +else version (FreeStanding) + alias dchar wchar_t; + +alias string = immutable(char)[]; +alias wstring = immutable(wchar)[]; +alias dstring = immutable(dchar)[]; + +alias hash_t = size_t; + +// Required by __importc_builtins.di for lazy module references in ImportC. +template imported(string name) +{ + mixin("import imported = " ~ name ~ ";"); +} + +// ────────────────────────────────────────────────────────────────────── +// Primitive tools +// ────────────────────────────────────────────────────────────────────── + +// TODO: move the functions here so object doesn't import these modules... +public import urt.lifetime : move, forward; +public import urt.meta : Alias, AliasSeq; +public import urt.util : min, max, swap; + +//alias Alias(alias a) = a; +//alias Alias(T) = T; +// +//alias AliasSeq(TList...) = TList; +// +//T min(T)(T a, T b) pure nothrow @nogc @safe +// => b < a ? b : a; +// +//T max(T)(T a, T b) pure nothrow @nogc @safe +// => b > a ? b : a; +// +//ref T swap(T)(ref T a, return ref T b) +//{ +// import urt.lifetime : move, moveEmplace; +// +// T t = a.move; +// b.move(a); +// t.move(b); +// return b; +//} +// +//T swap(T)(ref T a, T b) +//{ +// import urt.lifetime : move, moveEmplace; +// +// auto t = a.move; +// b.move(a); +// return t.move; +//} + +// ────────────────────────────────────────────────────────────────────── +// ^^ helpers +// ────────────────────────────────────────────────────────────────────── + +static if (__VERSION__ >= 2113) +{ + static import urt.atomic; + alias _d_atomicOp = urt.atomic.atomic_op; + + static import urt.math; + alias _d_pow = urt.math.pow; + + auto _d_sqrt(T)(T x) + { + // TODO: should we have a `float` one? + return urt.math.sqrt(x); + } +} + +// ────────────────────────────────────────────────────────────────────── +// Object - root of the class hierarchy +// ────────────────────────────────────────────────────────────────────── + +class Object +{ +@nogc: + static if (__VERSION__ >= 2113) + void* __monitor; + + size_t toHash() @trusted nothrow + { + size_t addr = cast(size_t) cast(void*) this; + return addr ^ (addr >>> 4); + } + + bool opEquals(Object rhs) + => this is rhs; + + int opCmp(Object rhs) + => 0; + + ptrdiff_t toString(char[] buffer) const nothrow + => try_copy_string(buffer, "Object"); +} + +// Free-function opEquals for class types - the compiler lowers `a == b` +// on class objects to a call to this function. +bool opEquals(LHS, RHS)(LHS lhs, RHS rhs) + if ((is(LHS : const Object) || is(LHS : const shared Object)) && + (is(RHS : const Object) || is(RHS : const shared Object))) +{ + static if (__traits(compiles, lhs.opEquals(rhs)) && __traits(compiles, rhs.opEquals(lhs))) + { + if (lhs is rhs) + return true; + if (lhs is null || rhs is null) + return false; + if (!lhs.opEquals(rhs)) + return false; + if (typeid(lhs) is typeid(rhs) || !__ctfe && typeid(lhs).opEquals(typeid(rhs))) + return true; + return rhs.opEquals(lhs); + } + else + return .opEquals!(Object, Object)(*cast(Object*) &lhs, *cast(Object*) &rhs); +} + +// ────────────────────────────────────────────────────────────────────── +// TypeInfo - compiler generates references for typeid, AAs, etc. +// ────────────────────────────────────────────────────────────────────── + +class TypeInfo +{ +@nogc: + size_t getHash(scope const void* p) @trusted nothrow const + => 0; + + bool equals(scope const void* p1, scope const void* p2) @trusted const + => p1 == p2; + + int compare(scope const void* p1, scope const void* p2) @trusted const + => 0; + + @property size_t tsize() nothrow pure const @safe @nogc + => 0; + + const(TypeInfo) next() nothrow pure const @nogc + => null; + + size_t[] offTi() nothrow const + => null; + + @property uint flags() nothrow pure const @safe @nogc + => 0; + + @property size_t talign() nothrow pure const @safe @nogc + => tsize; + + const(void)[] initializer() nothrow pure const @safe @nogc + => null; + + @property immutable(void)* rtInfo() nothrow pure const @safe @nogc + => null; + + version (WithArgTypes) + { + int argTypes(out TypeInfo arg1, out TypeInfo arg2) @safe nothrow + { + arg1 = null; + arg2 = null; + return 0; + } + } + + override size_t toHash() @trusted nothrow const + => 0; + + override bool opEquals(Object rhs) + => false; + + override int opCmp(Object rhs) + => 0; + + override ptrdiff_t toString(char[] buffer) const nothrow + => try_copy_string(buffer, "TypeInfo"); +} + +struct Interface +{ + TypeInfo_Class classinfo; + void*[] vtbl; + size_t offset; +} + +struct OffsetTypeInfo +{ + size_t offset; + TypeInfo ti; +} + +class TypeInfo_Class : TypeInfo +{ +@nogc: + byte[] m_init; + string name; + void*[] vtbl; + Interface[] interfaces; + TypeInfo_Class base; + void* destructor; + void function(Object) nothrow @nogc classInvariant; + + enum ClassFlags : ushort + { + isCOMclass = 0x1, + noPointers = 0x2, + hasOffTi = 0x4, + hasCtor = 0x8, + hasGetMembers = 0x10, + hasTypeInfo = 0x20, + isAbstract = 0x40, + isCPPclass = 0x80, + hasDtor = 0x100, + hasNameSig = 0x200, + } + ClassFlags m_flags; + ushort depth; + void* deallocator; + OffsetTypeInfo[] m_offTi; + void function(Object) @nogc defaultConstructor; + + immutable(void)* m_RTInfo; + override @property immutable(void)* rtInfo() nothrow pure const @safe { return m_RTInfo; } + + uint[4] nameSig; + + override @property size_t tsize() nothrow pure const @safe + { + return (void*).sizeof; + } +} + +// TypeInfo_Struct - compiler generates static instances for every struct type. +// Field layout must exactly match what the compiler emits. +class TypeInfo_Struct : TypeInfo +{ +@nogc: + override ptrdiff_t toString(char[] buffer) const nothrow + => try_copy_string(buffer, name); + + override size_t toHash() @trusted nothrow const + => hashOf(mangledName); + + override bool opEquals(Object o) + { + if (this is o) return true; + auto s = cast(const TypeInfo_Struct) o; + return s && this.mangledName == s.mangledName; + } + + override size_t getHash(scope const void* p) @trusted pure nothrow const + { + if (xtoHash) + return (*xtoHash)(p); + return 0; + } + + override bool equals(scope const void* p1, scope const void* p2) @trusted pure nothrow const + { + if (!p1 || !p2) return false; + if (xopEquals) + { + const dg = _member_func(p1, xopEquals); + return dg.xopEquals(p2); + } + return p1 == p2; + } + + override int compare(scope const void* p1, scope const void* p2) @trusted pure nothrow const + { + if (p1 == p2) return 0; + if (!p1) return -1; + if (!p2) return 1; + if (xopCmp) + { + const dg = _member_func(p1, xopCmp); + return dg.xopCmp(p2); + } + return 0; + } + + override @property size_t tsize() nothrow pure const + => initializer().length; + + override const(void)[] initializer() nothrow pure const @safe @nogc + => m_init; + + override @property uint flags() nothrow pure const + => m_flags; + + override @property size_t talign() nothrow pure const + => m_align; + + string mangledName; + + @property string name() nothrow const @trusted + => mangledName; // no demangling - avoids pulling in core.demangle + + void[] m_init; + + @safe pure nothrow + { + size_t function(in void*) @nogc xtoHash; + bool function(in void*, in void*) @nogc xopEquals; + int function(in void*, in void*) @nogc xopCmp; + ptrdiff_t function(in void*, const(char)[]) @nogc xtoString; + + enum StructFlags : uint + { + hasPointers = 0x1, + isDynamicType = 0x2, + } + StructFlags m_flags; + } + union + { + void function(void*) @nogc xdtor; + void function(void*, const TypeInfo_Struct) @nogc xdtorti; + } + void function(void*) xpostblit; + + uint m_align; + + version (WithArgTypes) + { + override int argTypes(out TypeInfo arg1, out TypeInfo arg2) + { + arg1 = m_arg1; + arg2 = m_arg2; + return 0; + } + + TypeInfo m_arg1; + TypeInfo m_arg2; + } + + override @property immutable(void)* rtInfo() nothrow pure const @safe @nogc + => m_RTInfo; + + immutable(void)* m_RTInfo; +} + +class TypeInfo_Enum : TypeInfo +{ +@nogc: + override size_t getHash(scope const void* p) const { return base.getHash(p); } + override bool equals(scope const void* p1, scope const void* p2) const { return base.equals(p1, p2); } + override int compare(scope const void* p1, scope const void* p2) const { return base.compare(p1, p2); } + override @property size_t tsize() nothrow pure const { return base.tsize; } + override const(TypeInfo) next() nothrow pure const @nogc { return base.next; } + override @property uint flags() nothrow pure const { return base.flags; } + override @property size_t talign() nothrow pure const { return base.talign; } + override @property immutable(void)* rtInfo() nothrow pure const @safe @nogc { return base.rtInfo; } + + override const(void)[] initializer() nothrow pure const @nogc + => m_init.length ? m_init : base.initializer(); + + version (WithArgTypes) + { + override int argTypes(out TypeInfo arg1, out TypeInfo arg2) + => base.argTypes(arg1, arg2); + } + + TypeInfo base; + string name; + void[] m_init; +} + +class TypeInfo_Pointer : TypeInfo +{ +@nogc: + override @property size_t tsize() nothrow pure const + => (void*).sizeof; + + override const(TypeInfo) next() nothrow pure const @nogc + => m_next; + + TypeInfo m_next; +} + +class TypeInfo_Array : TypeInfo +{ +@nogc: + override @property size_t tsize() nothrow pure const + => (void[]).sizeof; + + override const(TypeInfo) next() nothrow pure const @nogc + => value; + + TypeInfo value; +} + +// Built-in array TypeInfo subclasses - the compiler generates references to +// these for common array types. Empty subclasses are sufficient; the +// compiler fills in the `value` field. +class TypeInfo_Ah : TypeInfo_Array {} // ubyte[] +class TypeInfo_Ag : TypeInfo_Array {} // byte[] +class TypeInfo_Aa : TypeInfo_Array {} // char[] +class TypeInfo_Aaya : TypeInfo_Array {} // string[] +class TypeInfo_At : TypeInfo_Array {} // ushort[] +class TypeInfo_As : TypeInfo_Array {} // short[] +class TypeInfo_Au : TypeInfo_Array {} // wchar[] +class TypeInfo_Ak : TypeInfo_Array {} // uint[] +class TypeInfo_Ai : TypeInfo_Array {} // int[] +class TypeInfo_Aw : TypeInfo_Array {} // dchar[] +class TypeInfo_Am : TypeInfo_Array {} // ulong[] +class TypeInfo_Al : TypeInfo_Array {} // long[] +class TypeInfo_Af : TypeInfo_Array {} // float[] +class TypeInfo_Ad : TypeInfo_Array {} // double[] +class TypeInfo_Av : TypeInfo_Array {} // void[] + +class TypeInfo_StaticArray : TypeInfo +{ +@nogc: + override @property size_t tsize() nothrow pure const + => len * value.tsize; + + override const(TypeInfo) next() nothrow pure const @nogc + => value; + + TypeInfo value; + size_t len; +} + +class TypeInfo_Vector : TypeInfo +{ +@nogc: + override @property size_t tsize() nothrow pure const + => base.tsize; + + TypeInfo base; +} + +class TypeInfo_Function : TypeInfo +{ +@nogc: + override @property size_t tsize() nothrow pure const + => 0; + + TypeInfo next; + string deco; +} + +class TypeInfo_Delegate : TypeInfo +{ +@nogc: + override @property size_t tsize() nothrow pure const + { + alias dg = int delegate(); + return dg.sizeof; + } + + TypeInfo next; + string deco; +} + +class TypeInfo_Interface : TypeInfo +{ +@nogc: + override @property size_t tsize() nothrow pure const + => (void*).sizeof; + + TypeInfo_Class info; +} + +class TypeInfo_Tuple : TypeInfo +{ + TypeInfo[] elements; +} + +class TypeInfo_AssociativeArray : TypeInfo +{ +@nogc: + override ptrdiff_t toString(char[] buffer) const nothrow + { + if (!buffer.ptr) + return value.toString(null) + key.toString(null) + 2; + ptrdiff_t l = value.toString(buffer); + if (l < 0) + return l; + if (buffer.length < l + 2) + return -1; + buffer[l] = '['; + ptrdiff_t k = key.toString(buffer[l + 1 .. $]); + if (k < 0) + return k; + l += k + 2; + if (buffer.length < l) + return -1; + buffer[l - 1] = ']'; + return l; + } + + override bool opEquals(Object o) + { + if (this is o) return true; + auto c = cast(const TypeInfo_AssociativeArray) o; + return c && this.key == c.key && this.value == c.value; + } + + override bool equals(scope const void* p1, scope const void* p2) @trusted const + => xopEquals(p1, p2); + + override hash_t getHash(scope const void* p) nothrow @trusted const + => xtoHash(p); + + override @property size_t tsize() nothrow pure const + => (char[int]).sizeof; + + override const(void)[] initializer() const @trusted + => (cast(void*)null)[0 .. (char[int]).sizeof]; + + override const(TypeInfo) next() nothrow pure const @nogc { return value; } + override @property uint flags() nothrow pure const { return 1; } + + private static import urt.internal.aa; + alias Entry(K, V) = urt.internal.aa.Entry!(K, V); + + TypeInfo value; + TypeInfo key; + TypeInfo entry; + + bool function(scope const void* p1, scope const void* p2) nothrow @safe xopEquals; + hash_t function(scope const void*) nothrow @safe xtoHash; + + alias aaOpEqual(K, V) = urt.internal.aa._aaOpEqual!(K, V); + alias aaGetHash(K, V) = urt.internal.aa._aaGetHash!(K, V); + + override @property size_t talign() nothrow pure const + => (char[int]).alignof; +} + +class TypeInfo_Const : TypeInfo +{ +@nogc: + override size_t getHash(scope const void* p) const { return base.getHash(p); } + override bool equals(scope const void* p1, scope const void* p2) const { return base.equals(p1, p2); } + override int compare(scope const void* p1, scope const void* p2) const { return base.compare(p1, p2); } + override @property size_t tsize() nothrow pure const { return base.tsize; } + override const(TypeInfo) next() nothrow pure const @nogc { return base.next; } + override @property uint flags() nothrow pure const { return base.flags; } + override const(void)[] initializer() nothrow pure const @nogc { return base.initializer(); } + override @property size_t talign() nothrow pure const { return base.talign; } + + TypeInfo base; +} + +class TypeInfo_Invariant : TypeInfo_Const +{ +} + +class TypeInfo_Shared : TypeInfo_Const +{ +} + +class TypeInfo_Inout : TypeInfo_Const +{ +} + +// ────────────────────────────────────────────────────────────────────── +// TypeInfo for built-in types - compiler references these by mangled name +// (e.g. TypeInfo_k for uint). Single-character suffixes follow D's type +// encoding: a=char, b=bool, d=double, f=float, g=byte, h=ubyte, +// i=int, k=uint, l=long, m=ulong, o=dchar, s=short, t=wchar, +// u=ushort, v=void, x=const, y=immutable. +// ────────────────────────────────────────────────────────────────────── + +class TypeInfo_a : TypeInfo { override @property size_t tsize() nothrow pure const @safe @nogc { return char.sizeof; } } +class TypeInfo_b : TypeInfo { override @property size_t tsize() nothrow pure const @safe @nogc { return bool.sizeof; } } +class TypeInfo_d : TypeInfo { override @property size_t tsize() nothrow pure const @safe @nogc { return double.sizeof; } } +class TypeInfo_f : TypeInfo { override @property size_t tsize() nothrow pure const @safe @nogc { return float.sizeof; } } +class TypeInfo_g : TypeInfo { override @property size_t tsize() nothrow pure const @safe @nogc { return byte.sizeof; } } +class TypeInfo_h : TypeInfo { override @property size_t tsize() nothrow pure const @safe @nogc { return ubyte.sizeof; } } +class TypeInfo_i : TypeInfo { override @property size_t tsize() nothrow pure const @safe @nogc { return int.sizeof; } } +class TypeInfo_k : TypeInfo { override @property size_t tsize() nothrow pure const @safe @nogc { return uint.sizeof; } } +class TypeInfo_l : TypeInfo { override @property size_t tsize() nothrow pure const @safe @nogc { return long.sizeof; } } +class TypeInfo_m : TypeInfo { override @property size_t tsize() nothrow pure const @safe @nogc { return ulong.sizeof; } } +class TypeInfo_o : TypeInfo { override @property size_t tsize() nothrow pure const @safe @nogc { return dchar.sizeof; } } +class TypeInfo_s : TypeInfo { override @property size_t tsize() nothrow pure const @safe @nogc { return short.sizeof; } } +class TypeInfo_t : TypeInfo { override @property size_t tsize() nothrow pure const @safe @nogc { return wchar.sizeof; } } +class TypeInfo_u : TypeInfo { override @property size_t tsize() nothrow pure const @safe @nogc { return ushort.sizeof; } } +class TypeInfo_v : TypeInfo { override @property size_t tsize() nothrow pure const @safe @nogc { return 0; } } + +// Helper for TypeInfo_Struct delegate dispatch. +// Uses a union to overlay raw delegate ABI (ptr + funcptr) with typed delegate fields. +private struct _member_func +{ + union + { + struct + { + const void* ptr; + const void* funcptr; + } + + @safe pure nothrow + { + bool delegate(in void*) @nogc xopEquals; + int delegate(in void*) @nogc xopCmp; + } + } +} + +// ────────────────────────────────────────────────────────────────────── +// __ArrayDtor - compiler lowers dynamic array destruction to this +// ────────────────────────────────────────────────────────────────────── + +void __ArrayDtor(T)(scope T[] a) +{ + foreach_reverse (ref T e; a) + e.__xdtor(); +} + +// ────────────────────────────────────────────────────────────────────── +// Compiler hook templates - array & AA literal lowering +// These are only called at runtime; CTFE evaluates literals directly. +// ────────────────────────────────────────────────────────────────────── + +void* _d_arrayliteralTX(T)(size_t length) @trusted pure nothrow +{ + assert(false, "Array literals require druntime"); +} + +alias AssociativeArray(Key, Value) = Value[Key]; + +public import urt.internal.aa : _d_aaIn, _d_aaDel, _d_aaNew, _d_aaEqual, _d_assocarrayliteralTX; +public import urt.internal.aa : _d_aaLen, _d_aaGetY, _d_aaGetRvalueX, _d_aaApply, _d_aaApply2; + +private import urt.internal.aa : makeAA; + +// Lower an AA to a newaa struct for static initialization. +auto _aaAsStruct(K, V)(V[K] aa) @safe +{ + assert(__ctfe); + return makeAA!(K, V)(aa); +} + +Tret _d_arraycatnTX(Tret, Tarr...)(auto ref Tarr froms) @trusted +{ + assert(false, "Array concatenation requires druntime"); +} + + +T _d_newclassT(T)() @trusted + if (is(T == class)) +{ + assert(false, "new class requires druntime"); +/+ + if (__ctfe) + assert(false, "new class not supported at CTFE without druntime"); + + import urt.mem : alloc, memcpy; + + enum sz = __traits(classInstanceSize, T); + auto p = alloc(sz).ptr; + if (p is null) + assert(false, "out of memory in _d_newclassT"); + + auto initSym = __traits(initSymbol, T); + memcpy(p, initSym.ptr, sz); + return cast(T)cast(void*)p; ++/ +} + +ref Tarr _d_arrayappendcTX(Tarr : T[], T)(return ref scope Tarr px, size_t n) @trusted +{ + assert(false, "Array append requires druntime"); +} + +ref Tarr _d_arrayappendT(Tarr : T[], T)(return ref scope Tarr x, scope Tarr y) @trusted +{ + assert(false, "Array append requires druntime"); +} + +// ────────────────────────────────────────────────────────────────────── +// Compiler hook templates - array operations, construction, etc. +// These are lowered by the compiler for various language constructs. +// ────────────────────────────────────────────────────────────────────── + +T[] _d_newarrayT(T)(size_t length, bool isShared = false) @trusted +{ + import urt.mem.alloc : alloc; +// assert(false, "new array requires druntime"); + return cast(T[])alloc(T.sizeof * length); +} + +Tarr _d_newarraymTX(Tarr : U[], T, U)(size_t[] dims, bool isShared = false) @trusted +{ + assert(false, "new multi-dim array requires druntime"); +} + +T* _d_newitemT(T)() @trusted +{ + import urt.mem.alloc : alloc; +// assert(false, "new item requires druntime"); + return cast(T*)alloc(T.sizeof).ptr; +} + +T _d_newThrowable(T)() @trusted + if (is(T : Throwable)) +{ + assert(false, "new throwable requires druntime"); +} + +int __cmp(T)(scope const T[] lhs, scope const T[] rhs) @trusted +{ + immutable len = lhs.length <= rhs.length ? lhs.length : rhs.length; + foreach (i; 0 .. len) + { + if (lhs.ptr[i] < rhs.ptr[i]) + return -1; + if (lhs.ptr[i] > rhs.ptr[i]) + return 1; + } + return (lhs.length > rhs.length) - (lhs.length < rhs.length); +} + +bool __equals(T1, T2)(scope const T1[] lhs, scope const T2[] rhs) +nothrow @nogc pure @trusted +if (__traits(isScalar, T1) && __traits(isScalar, T2)) +{ + if (lhs.length != rhs.length) + return false; + static if (T1.sizeof == T2.sizeof + && (T1.sizeof >= 4 || __traits(isUnsigned, T1) == __traits(isUnsigned, T2)) + && !__traits(isFloating, T1) && !__traits(isFloating, T2)) + { + if (__ctfe) + { + foreach (i; 0 .. lhs.length) + if (lhs.ptr[i] != rhs.ptr[i]) return false; + return true; + } + else + { + import urt.mem : memcmp; + return !lhs.length || 0 == memcmp(cast(const void*) lhs.ptr, cast(const void*) rhs.ptr, lhs.length * T1.sizeof); + } + } + else + { + foreach (i; 0 .. lhs.length) + if (lhs.ptr[i] != rhs.ptr[i]) return false; + return true; + } +} + +bool __equals(T1, T2)(scope T1[] lhs, scope T2[] rhs) +if (!__traits(isScalar, T1) || !__traits(isScalar, T2)) +{ + if (lhs.length != rhs.length) + return false; + foreach (i; 0 .. lhs.length) + if (lhs[i] != rhs[i]) return false; + return true; +} + +TTo[] __ArrayCast(TFrom, TTo)(return scope TFrom[] from) @nogc pure @trusted +{ + const fromSize = from.length * TFrom.sizeof; + if (fromSize % TTo.sizeof != 0) + assert(false, "Array cast misalignment"); + return (cast(TTo*) from.ptr)[0 .. fromSize / TTo.sizeof]; +} + +Tarr _d_arrayctor(Tarr : T[], T)(return scope Tarr to, scope Tarr from) @trusted +{ + assert(false, "Array ctor requires druntime"); +} + +void _d_arraysetctor(Tarr : T[], T)(scope Tarr p, scope ref T value) @trusted +{ + assert(false, "Array set-ctor requires druntime"); +} + +Tarr _d_arrayassign_l(Tarr : T[], T)(return scope Tarr to, scope Tarr from) @trusted +{ + assert(false, "Array assign requires druntime"); +} + +Tarr _d_arrayassign_r(Tarr : T[], T)(return scope Tarr to, scope Tarr from) @trusted +{ + assert(false, "Array assign requires druntime"); +} + +void _d_arraysetassign(Tarr : T[], T)(return scope Tarr to, scope ref T value) @trusted +{ + assert(false, "Array set-assign requires druntime"); +} + +size_t _d_arraysetlengthT(Tarr : T[], T)(return ref scope Tarr arr, size_t newlength) @trusted +{ + assert(false, "Array setlength requires druntime"); +} + +// LDC wraps the above in a template namespace +template _d_arraysetlengthTImpl(Tarr : T[], T) +{ + size_t _d_arraysetlengthT(return scope ref Tarr arr, size_t newlength) @trusted pure nothrow + { + assert(false, "Array setlength requires druntime"); + } +} + +string _d_assert_fail(A...)(const scope string comp, auto ref const scope A a) +{ + return "assertion failure"; +} + +// ────────────────────────────────────────────────────────────────────── +// Runtime hooks: assertions, array bounds checks +// These are referenced by compiler-generated code even in debug builds. +// ────────────────────────────────────────────────────────────────────── + +extern(C) void _d_assert_msg(string msg, string file, uint line) nothrow @nogc +{ + import urt.exception : assert_handler; + assert_handler()(file, line, msg); +} + +extern(C) void _d_assertp(immutable(char)* file, uint line) nothrow @nogc @trusted +{ + import urt.mem : strlen; + import urt.exception : assert_handler; + auto f = file[0 .. file ? strlen(file) : 0]; + assert_handler()(f, line, null); +} + +extern(C) void _d_assert(string file, uint line) nothrow @nogc +{ + import urt.exception : assert_handler; + assert_handler()(file, line, null); +} + +extern(C) void _d_arraybounds_indexp(string file, uint line, size_t index, size_t length) nothrow @nogc +{ + import urt.exception : assert_handler; + assert_handler()(file, line, "array index out of bounds"); +} + +extern(C) void _d_arraybounds_slicep(string file, uint line, size_t lower, size_t upper, size_t length) nothrow @nogc +{ + import urt.exception : assert_handler; + assert_handler()(file, line, "array slice out of bounds"); +} + +extern(C) void _d_arrayboundsp(string file, uint line) nothrow @nogc +{ + import urt.exception : assert_handler; + assert_handler()(file, line, "array index out of bounds"); +} + +extern(C) void _d_arraybounds(string file, uint line) nothrow @nogc +{ + import urt.exception : assert_handler; + assert_handler()(file, line, "array index out of bounds"); +} + +// Unittest assert hooks - the compiler generates these for assert() inside +// unittest blocks instead of the regular _d_assertp/_d_assert_msg. +extern(C) void _d_unittestp(immutable(char)* file, uint line) nothrow @nogc @trusted +{ + import urt.mem : strlen; + import urt.exception : assert_handler; + auto f = file[0 .. file ? strlen(file) : 0]; + assert_handler()(f, line, "unittest assertion failure"); +} + +extern(C) void _d_unittest_msg(string msg, string file, uint line) nothrow @nogc @trusted +{ + import urt.exception : assert_handler; + assert_handler()(file, line, msg); +} + +extern(C) void _d_unittest(string file, uint line) nothrow @nogc +{ + import urt.exception : assert_handler; + assert_handler()(file, line, "unittest assertion failure"); +} + +// GC allocation hook - compiler lowers `new` to this. In our @nogc world +// it should never be called from production code; provided so unittest +// blocks that accidentally use `new` can at least link. +extern(C) void* _d_allocmemory(size_t sz) nothrow @nogc @trusted +{ + import urt.mem.alloc : alloc; + return alloc(sz).ptr; +} + +// ────────────────────────────────────────────────────────────────────── +// LDC extern(C) runtime hooks +// LDC's codegen emits calls to old-style extern(C) functions for many +// operations where DMD uses template-based hooks. These stubs satisfy +// the linker. Implementations are provided where feasible; others +// assert(false) and must be fleshed out if the code path is hit. +// ────────────────────────────────────────────────────────────────────── + +version (LDC) +{ + +// --- Bounds checks (LDC variants without 'p' suffix) ------------------- + +extern(C) void _d_arraybounds_index(string file, uint line, size_t index, size_t length) nothrow @nogc +{ + import urt.exception : assert_handler; + if (auto handler = assert_handler) + handler(file, line, "array index out of bounds"); + else + _halt(); +} + +extern(C) void _d_arraybounds_slice(string file, uint line, size_t lower, size_t upper, size_t length) nothrow @nogc +{ + import urt.exception : assert_handler; + if (auto handler = assert_handler) + handler(file, line, "array slice out of bounds"); + else + _halt(); +} + +// --- Array slice copy (LDC emits this for non-elaborate arr[] = other[]) - +// Bounds-checked memcpy: verifies dstlen == srclen, then copies raw bytes. + +extern(C) void _d_array_slice_copy(void* dst, size_t dstlen, void* src, size_t srclen, size_t elemsize) nothrow @nogc @trusted +{ + assert(dstlen == srclen, "array slice lengths don't match for copy"); + import urt.mem : memcpy; + memcpy(dst, src, dstlen * elemsize); +} + +// --- Class allocation and casting (old-style extern C) ----------------- + +extern(C) void* _d_allocclass(TypeInfo_Class ci) nothrow @nogc @trusted +{ + import urt.mem : alloc, memcpy; + auto init = ci.initializer; + auto p = alloc(init.length).ptr; + if (p !is null) + memcpy(p, init.ptr, init.length); + return p; +} + +extern(C) Object _d_dynamic_cast(Object o, TypeInfo_Class c) nothrow @nogc @trusted +{ + // Traverse classinfo chain to check if o is-a c + if (o is null) return null; + auto oc = typeid(o); + while (oc !is null) + { + if (oc is c) + return o; + oc = oc.base; + } + return null; +} + +extern(C) void* _d_interface_cast(void* p, TypeInfo_Class c) nothrow @nogc @trusted +{ + // TODO: full interface casting requires traversing the Interface[] table + // in the target object's classinfo to find the right vtable offset. + // For now, return null (cast fails). + return null; +} + +// --- Struct array equality (old-style) --------------------------------- + +extern(C) int _adEq2(void[] a1, void[] a2, TypeInfo ti) nothrow @nogc @trusted +{ + if (a1.length != a2.length) return 0; + import urt.mem : memcmp; + return memcmp(a1.ptr, a2.ptr, a1.length) == 0 ? 1 : 0; +} + +} // version (LDC) + +// --- Old-style array allocation (extern C) ------------------------------ + +extern(C) void[] _d_newarrayT(const TypeInfo ti, size_t length) nothrow @nogc @trusted +{ + import urt.mem.alloc : alloc; + auto elemsize = ti.next ? ti.next.tsize : 1; + return alloc(length * elemsize); +} + +extern(C) void[] _d_newarrayiT(const TypeInfo ti, size_t length) nothrow @nogc @trusted +{ + import urt.mem.alloc : alloc; + auto elemsize = ti.next ? ti.next.tsize : 1; + return alloc(length * elemsize); +} + +extern(C) void[] _d_newarrayU(const TypeInfo ti, size_t length) nothrow @nogc @trusted +{ + import urt.mem.alloc : alloc; + auto elemsize = ti.next ? ti.next.tsize : 1; + return alloc(length * elemsize); +} + +// Unconditional halt - avoids circular dependency with assert. +private void _halt() nothrow @nogc @trusted +{ + version (D_InlineAsm_X86_64) + asm nothrow @nogc { hlt; } + else version (D_InlineAsm_X86) + asm nothrow @nogc { hlt; } + else + *(cast(int*) null) = 0; // fallback: null deref +} + +void __move_post_blt(S)(ref S newLocation, ref S oldLocation) nothrow + if (is(S == struct)) +{ + static if (__traits(hasMember, S, "__xpostblit")) + newLocation.__xpostblit(); +} + +void __ArrayPostblit(T)(T[] a) +{ + foreach (ref e; a) + static if (__traits(hasMember, T, "__xpostblit")) + e.__xpostblit(); +} + +int __switch(T, caseLabels...)(const scope T[] condition) pure nothrow @safe @nogc +{ + foreach (i, s; caseLabels) + { + static if (is(typeof(s) : typeof(condition))) + { + if (condition == s) + return cast(int) i; + } + } + return -1; +} + +void __switch_error()(string file = __FILE__, size_t line = __LINE__) +{ + assert(false, "Final switch error"); +} + +template _d_delstructImpl(T) +{ + void _d_delstruct(ref T p) @trusted + { + p = null; + } +} + +nothrow @nogc @trusted pure extern(C) void _d_delThrowable(scope Throwable) {} + +// ────────────────────────────────────────────────────────────────────── +// _arrayOp - compiler hook for vectorized array slice operations. +// DMD lowers `dest[] = a[] ^ b[]` to `_arrayOp!(T[], T[], T[], "^", "=")(dest, a, b)`. +// Args are in Reverse Polish Notation (RPN). +// ────────────────────────────────────────────────────────────────────── + +template _arrayOp(Args...) +{ + static if (is(Args[0] == E[], E)) + { + alias T = E; + + T[] _arrayOp(T[] res, _Filter!(_is_type, Args[1 .. $]) args) nothrow @nogc @trusted + { + foreach (pos; 0 .. res.length) + mixin(_scalar_exp!(Args[1 .. $]) ~ ";"); + return res; + } + } +} + +// ---- _arrayOp helpers (all private, CTFE-only) ---- + +private template _Filter(alias pred, args...) +{ + static if (args.length == 0) + alias _Filter = AliasSeq!(); + else static if (pred!(args[0])) + alias _Filter = AliasSeq!(args[0], _Filter!(pred, args[1 .. $])); + else + alias _Filter = _Filter!(pred, args[1 .. $]); +} + +private enum _is_type(T) = true; +private enum _is_type(alias a) = false; + +private bool _is_unary_op(string op) pure nothrow @safe @nogc + => op.length > 0 && op[0] == 'u'; + +private bool _is_binary_op(string op) pure nothrow @safe @nogc +{ + if (op == "^^") return true; + if (op.length != 1) return false; + switch (op[0]) + { + case '+', '-', '*', '/', '%', '|', '&', '^': + return true; + default: + return false; + } +} + +private bool _is_binary_assign_op(string op) + => op.length >= 2 && op[$ - 1] == '=' && _is_binary_op(op[0 .. $ - 1]); + +// Convert size_t to string at CTFE. +private string _size_to_str(size_t num) pure @safe +{ + enum digits = "0123456789"; + if (num < 10) return digits[num .. num + 1]; + return _size_to_str(num / 10) ~ digits[num % 10 .. num % 10 + 1]; +} + +// Generate element-wise mixin expression from RPN args (CTFE-evaluated enum). +// Uses a fixed-size stack with depth counter to avoid dynamic array operations +// (which would require _d_arraysetlengthT at semantic analysis time). +private enum _scalar_exp(Args...) = () { + string[Args.length] stack; + size_t depth; + size_t args_idx; + + static if (is(Args[0] == U[], U)) + alias Type = U; + else + alias Type = Args[0]; + + foreach (i, arg; Args) + { + static if (is(arg == E[], E)) + { + stack[depth] = "args[" ~ _size_to_str(args_idx++) ~ "][pos]"; + ++depth; + } + else static if (is(arg)) + { + stack[depth] = "args[" ~ _size_to_str(args_idx++) ~ "]"; + ++depth; + } + else static if (_is_unary_op(arg)) + { + auto op = arg[0] == 'u' ? arg[1 .. $] : arg; + static if (is(Type : int)) + stack[depth - 1] = "cast(typeof(" ~ stack[depth - 1] ~ "))" ~ op ~ "cast(int)(" ~ stack[depth - 1] ~ ")"; + else + stack[depth - 1] = op ~ stack[depth - 1]; + } + else static if (arg == "=") + { + stack[depth - 1] = "res[pos] = cast(T)(" ~ stack[depth - 1] ~ ")"; + } + else static if (_is_binary_assign_op(arg)) + { + stack[depth - 1] = "res[pos] " ~ arg ~ " cast(T)(" ~ stack[depth - 1] ~ ")"; + } + else static if (_is_binary_op(arg)) + { + stack[depth - 2] = "(" ~ stack[depth - 2] ~ " " ~ arg ~ " " ~ stack[depth - 1] ~ ")"; + --depth; + } + } + return stack[0]; +}(); + +// ────────────────────────────────────────────────────────────────────── +// _d_cast - dynamic class cast, walks TypeInfo_Class.base chain +// ────────────────────────────────────────────────────────────────────── + +void* _d_cast(To, From)(From o) @trusted + if (is(From == class) && is(To == class)) +{ + if (o is null) return null; + auto ci = typeid(o); + auto target = typeid(To); + do + { + if (ci is target) return cast(void*) o; + ci = ci.base; + } + while (ci !is null); + return null; +} + +void* _d_cast(To, From)(From o) @trusted + if (is(From == class) && is(To == interface)) +{ + if (o is null) return null; + // Walk interface list - not implemented yet, fall back to null + assert(false, "Interface cast not yet implemented"); +} + +// ────────────────────────────────────────────────────────────────────── +// Throwable / Exception / Error - exception hierarchy +// ────────────────────────────────────────────────────────────────────── + +class Throwable : Object +{ + string msg; + Throwable next; + string file; + size_t line; + + private uint _refcount; + + @nogc @safe pure nothrow this(string msg, Throwable next = null) + { + this.msg = msg; + this.next = next; + } + + @nogc @safe pure nothrow this(string msg, string file, size_t line, Throwable next = null) + { + this.msg = msg; + this.file = file; + this.line = line; + this.next = next; + } + + const(char)[] message() const @nogc @safe pure nothrow + => msg; + + @system @nogc final pure nothrow ref uint refcount() return + => _refcount; + + static Throwable chainTogether(return scope Throwable e1, return scope Throwable e2) nothrow @nogc + { + if (!e1) + return e2; + if (!e2) + return e1; + for (auto e = e1; ; e = e.next) + { + if (!e.next) + { + e.next = e2; + break; + } + } + return e1; + } +} + +class Exception : Throwable +{ + @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) + { + super(msg, file, line, next); + } +} + +class Error : Throwable +{ + Throwable bypassedException; + + @nogc @safe pure nothrow this(string msg, Throwable next = null) + { + super(msg, next); + } + + @nogc @safe pure nothrow this(string msg, string file, size_t line, Throwable next = null) + { + super(msg, file, line, next); + } +} + +// ────────────────────────────────────────────────────────────────────── +// destroy - compiler generates calls for scope guards, etc. +// ────────────────────────────────────────────────────────────────────── + +void destroy(bool initialize = true, T)(ref T obj) if (is(T == struct)) +{ + destruct_recurse(obj); + static if (initialize) + { + static if (__traits(isZeroInit, T)) + (cast(ubyte*)&obj)[0 .. T.sizeof] = 0; + else + { + auto init = __traits(initSymbol, T); + (cast(ubyte*)&obj)[0 .. T.sizeof] = (cast(const ubyte*)init.ptr)[0 .. T.sizeof]; + } + } +} + +// Destruct a struct by calling its __xdtor, but only if it truly belongs +// to this type (Bugzilla 14746). +void destruct_recurse(S)(ref S s) if (is(S == struct)) +{ + static if (__traits(hasMember, S, "__xdtor") && __traits(isSame, S, __traits(parent, s.__xdtor))) + s.__xdtor(); +} + +void destruct_recurse(E, size_t n)(ref E[n] arr) +{ + foreach_reverse (ref elem; arr) + destruct_recurse(elem); +} + +void destroy(bool initialize = true, T)(T obj) if (is(T == class)) +{ + static if (__traits(hasMember, T, "__xdtor")) + obj.__xdtor(); +} + +void destroy(bool initialize = true, T)(ref T obj) if (!is(T == struct) && !is(T == class)) +{ + static if (initialize) + obj = T.init; +} + +// ────────────────────────────────────────────────────────────────────── +// .dup / .idup - array duplication properties +// ────────────────────────────────────────────────────────────────────── + +@property immutable(T)[] idup(T)(T[] a) @trusted +{ + assert(__ctfe, "idup is only supported at compile time"); + // CTFE-compatible: the interpreter handles ~= natively. + // At runtime this would assert via _d_arrayappendcTX. + immutable(T)[] r; + foreach (ref e; a) + r ~= cast(immutable(T)) e; + return r; +} + +@property T[] dup(T)(const(T)[] a) @trusted +{ + assert(__ctfe, "dup is only supported at compile time"); + T[] r; + foreach (ref e; a) + r ~= cast(T) e; + return r; +} + +// ────────────────────────────────────────────────────────────────────── +// Compiler-generated struct equality/comparison fallbacks +// ────────────────────────────────────────────────────────────────────── + +bool _xopEquals(in void*, in void*) + => false; + +bool _xopCmp(in void*, in void*) + => false; + +// ────────────────────────────────────────────────────────────────────── +// hashOf - used by AAs and anywhere .toHash is needed +// ────────────────────────────────────────────────────────────────────── + +size_t hashOf(T)(auto ref T val, size_t seed = 0) pure nothrow @nogc @trusted +{ + import urt.hash : fnv1a, fnv1a64, fnv1_initial; + + static if (is(T : const(char)[])) + { + static if (is(size_t == uint)) + return fnv1a(cast(ubyte[])val, seed ? seed : fnv1_initial!uint); + else + return fnv1a64(cast(ubyte[])val, seed ? seed : fnv1_initial!ulong); + } + else static if (is(T V : V*)) + { + // Pointers - CTFE compatible + if (__ctfe) + { + if (val is null) + return seed; + assert(0, "Unable to hash non-null pointer at compile time"); + } + size_t v = cast(size_t)val; + return _fnv(v ^ (v >> 4), seed); + } + else static if (__traits(isIntegral, T)) + { + // Integers - CTFE compatible, no reinterpreting cast + static if (T.sizeof <= size_t.sizeof) + return _fnv(cast(size_t)val, seed); + else + return _fnv(cast(size_t)(val ^ (val >>> (size_t.sizeof * 8))), seed); + } + else static if (__traits(isFloating, T)) + { + // At CTFE we cannot reinterpret float bits; use lossy integer cast + // it'd be better if we could work out the magnitude and multipley the significant bits into an integer + if (__ctfe) + return _fnv(cast(size_t)cast(long)val, seed); + static if (is(size_t == uint)) + return fnv1a((cast(ubyte*)&val)[0..T.sizeof], seed ? seed : fnv1_initial!uint); + else + return fnv1a64((cast(ubyte*)&val)[0..T.sizeof], seed ? seed : fnv1_initial!uint); + } + else static if (is(T == struct)) + { + // Structs - hash each field (CTFE compatible) + size_t h = seed; + foreach (ref field; val.tupleof) + h = hashOf(field, h); + return h; + } + else static if (is(T == enum)) + { + static if (is(T EType == enum)) + return hashOf(cast(EType)val, seed); + else + return _fnv(0, seed); + } + else + return seed; +} + +private size_t _fnv(size_t val, size_t seed) pure nothrow @nogc @trusted +{ + import urt.hash : fnv1a, fnv1a64, fnv1_initial; + + // maybe it's better to write out the algorithm inline...? + if (__ctfe) + { + static if (is(size_t == uint)) + { + ubyte[4] bytes = [ val & 0xFF, (val >> 8) & 0xFF, (val >> 16) & 0xFF, (val >> 24) & 0xFF ]; + return fnv1a(bytes, seed ? seed : fnv1_initial!uint); + } + else + { + ubyte[8] bytes = [ val & 0xFF, (val >> 8) & 0xFF, (val >> 16) & 0xFF, (val >> 24) & 0xFF, + (val >> 32) & 0xFF, (val >> 40) & 0xFF, (val >> 48) & 0xFF, (val >> 56) & 0xFF ]; + return fnv1a64(bytes, seed ? seed : fnv1_initial!ulong); + } + } + static if (is(size_t == uint)) + return fnv1a((cast(ubyte*)&val)[0..4], seed ? seed : fnv1_initial!uint); + else + return fnv1a64((cast(ubyte*)&val)[0..8], seed ? seed : fnv1_initial!ulong); +} + +// ────────────────────────────────────────────────────────────────────── +// ModuleInfo - compiler emits one per module with ctor/dtor/unittest info. +// Variable-sized: fields are packed after the header based on flag bits. +// ────────────────────────────────────────────────────────────────────── + +enum +{ + MIctorstart = 0x1, + MIctordone = 0x2, + MIstandalone = 0x4, + MItlsctor = 0x8, + MItlsdtor = 0x10, + MIctor = 0x20, + MIdtor = 0x40, + MIxgetMembers = 0x80, + MIictor = 0x100, + MIunitTest = 0x200, + MIimportedModules = 0x400, + MIlocalClasses = 0x800, + MIname = 0x1000, +} + +struct ModuleInfo +{ + uint _flags; + uint _index; + +const: + private void* addr_of(int flag) return nothrow pure @nogc @trusted + { + void* p = cast(void*)&this + ModuleInfo.sizeof; + + if (flags & MItlsctor) + { + if (flag == MItlsctor) + return p; + p += (void function()).sizeof; + } + if (flags & MItlsdtor) + { + if (flag == MItlsdtor) + return p; + p += (void function()).sizeof; + } + if (flags & MIctor) + { + if (flag == MIctor) + return p; + p += (void function()).sizeof; + } + if (flags & MIdtor) + { + if (flag == MIdtor) + return p; + p += (void function()).sizeof; + } + if (flags & MIxgetMembers) + { + if (flag == MIxgetMembers) + return p; + p += (void*).sizeof; + } + if (flags & MIictor) + { + if (flag == MIictor) + return p; + p += (void function()).sizeof; + } + if (flags & MIunitTest) + { + if (flag == MIunitTest) + return p; + p += (void function()).sizeof; + } + if (flags & MIimportedModules) + { + if (flag == MIimportedModules) + return p; + p += size_t.sizeof + *cast(size_t*)p * (immutable(ModuleInfo)*).sizeof; + } + if (flags & MIlocalClasses) + { + if (flag == MIlocalClasses) + return p; + p += size_t.sizeof + *cast(size_t*)p * (TypeInfo_Class).sizeof; + } + if (true || flags & MIname) + { + if (flag == MIname) + return p; + } + assert(0); + } + + @property uint flags() nothrow pure @nogc + => _flags; + + @property void function() tlsctor() nothrow pure @nogc @trusted + => flags & MItlsctor ? *cast(typeof(return)*)addr_of(MItlsctor) : null; + + @property void function() tlsdtor() nothrow pure @nogc @trusted + => flags & MItlsdtor ? *cast(typeof(return)*)addr_of(MItlsdtor) : null; + + @property void function() ctor() nothrow pure @nogc @trusted + => flags & MIctor ? *cast(typeof(return)*)addr_of(MIctor) : null; + + @property void function() dtor() nothrow pure @nogc @trusted + => flags & MIdtor ? *cast(typeof(return)*)addr_of(MIdtor) : null; + + @property void function() ictor() nothrow pure @nogc @trusted + => flags & MIictor ? *cast(typeof(return)*)addr_of(MIictor) : null; + + @property void function() unitTest() nothrow pure @nogc @trusted + => flags & MIunitTest ? *cast(typeof(return)*)addr_of(MIunitTest) : null; + + @property immutable(ModuleInfo*)[] importedModules() return nothrow pure @nogc @trusted + { + if (flags & MIimportedModules) + { + auto p = cast(size_t*)addr_of(MIimportedModules); + return (cast(immutable(ModuleInfo*)*)(p + 1))[0 .. *p]; + } + return null; + } + + @property string name() nothrow @nogc @trusted + { + // LDC always emits the name after the packed fields, even without + // setting MIname. addr_of(MIname) handles this (see `true ||` guard). + auto p = cast(immutable char*)addr_of(MIname); + size_t len = 0; + while (p[len] != 0) ++len; + return p[0 .. len]; + } +} + +// ────────────────────────────────────────────────────────────────────── +// Module registration - LDC uses _Dmodule_ref linked list +// +// On ELF targets the compiler generates .init_array entries that chain +// ModuleReference structs into _Dmodule_ref. On Linux, glibc's crt0 +// calls .init_array automatically. On bare-metal, start.S does it. +// ────────────────────────────────────────────────────────────────────── + +version (Windows) {} +else + extern(C) __gshared void* _Dmodule_ref = null; + +version (linux) +{ + struct CompilerDSOData + { + size_t _version; + void** _slot; + immutable(ModuleInfo*)* _minfo_beg, _minfo_end; + } + + extern(C) __gshared immutable(ModuleInfo*)* _elf_minfo_beg; + extern(C) __gshared immutable(ModuleInfo*)* _elf_minfo_end; + + extern(C) void _d_dso_registry(CompilerDSOData* data) nothrow @nogc + { + if (data._version < 1) + return; + + if (*data._slot is null) + { + *data._slot = cast(void*)data; + _elf_minfo_beg = data._minfo_beg; + _elf_minfo_end = data._minfo_end; + } + else + { + *data._slot = null; + _elf_minfo_beg = null; + _elf_minfo_end = null; + } + } +} + +// ────────────────────────────────────────────────────────────────────── +// TypeInfo for const/immutable char[] - compiler references by name +// ────────────────────────────────────────────────────────────────────── + +class TypeInfo_Axa : TypeInfo_Array {} // const(char)[] +class TypeInfo_Aya : TypeInfo_Array {} // immutable(char)[] = string + +// ────────────────────────────────────────────────────────────────────── +// _d_invariant - contract invariant hook (matches rt.invariant_ mangling) +// ────────────────────────────────────────────────────────────────────── + +pragma(mangle, "_D2rt10invariant_12_d_invariantFC6ObjectZv") +void _d_invariant_impl(Object o) nothrow @nogc +{ + assert(o !is null); + auto c = typeid(o); + do + { + if (c.classInvariant) + c.classInvariant(o); + c = c.base; + } + while (c); +} + +// ────────────────────────────────────────────────────────────────────── +// Compiler-generated memset intrinsics (struct initialization) +// ────────────────────────────────────────────────────────────────────── + +private struct Bits128 { ulong[2] v; } +private struct Bits80 { ubyte[real.sizeof] v; } +private struct Bits160 { ubyte[2 * real.sizeof] v; } + +extern(C) nothrow @nogc @trusted +{ + short* _memset16(short* p, short value, size_t count) + { + foreach (i; 0 .. count) + p[i] = value; + return p; + } + + int* _memset32(int* p, int value, size_t count) + { + foreach (i; 0 .. count) + p[i] = value; + return p; + } + + long* _memset64(long* p, long value, size_t count) + { + foreach (i; 0 .. count) + p[i] = value; + return p; + } + + Bits80* _memset80(Bits80* p, Bits80 value, size_t count) + { + foreach (i; 0 .. count) + p[i] = value; + return p; + } + + Bits128* _memset128(Bits128* p, Bits128 value, size_t count) + { + foreach (i; 0 .. count) + p[i] = value; + return p; + } + + void[]* _memset128ii(void[]* p, void[] value, size_t count) + { + foreach (i; 0 .. count) + p[i] = value; + return p; + } + + Bits160* _memset160(Bits160* p, Bits160 value, size_t count) + { + foreach (i; 0 .. count) + p[i] = value; + return p; + } + + float* _memsetFloat(float* p, float value, size_t count) + { + foreach (i; 0 .. count) + p[i] = value; + return p; + } + + double* _memsetDouble(double* p, double value, size_t count) + { + foreach (i; 0 .. count) + p[i] = value; + return p; + } + + void* _memsetn(void* p, void* value, int count, size_t sizelem) + { + auto dst = cast(ubyte*) p; + auto src = cast(ubyte*) value; + foreach (_; 0 .. count) + { + foreach (j; 0 .. sizelem) + dst[j] = src[j]; + dst += sizelem; + } + return p; + } +} + +private ptrdiff_t try_copy_string(char[] buffer, const(char)[] src) pure nothrow @nogc +{ + if (buffer.ptr) + { + if (buffer.length < src.length) + return -1; + buffer[0 .. src.length] = src[]; + } + return src.length; +} diff --git a/src/std/math.d b/src/std/math.d new file mode 100644 index 0000000..0690df8 --- /dev/null +++ b/src/std/math.d @@ -0,0 +1,8 @@ +/// Minimal std.math stub - provides just the `pow` function that DMD's +/// `^^` operator lowering requires. Nothing else from Phobos is pulled in. +module std.math; + +// DMD lowers `a ^^ b` to `std.math.pow(a, b)`. +public import urt.math : pow; + +// TODO: do we need sqrt for `^^ 0.5`? diff --git a/src/urt/algorithm.d b/src/urt/algorithm.d index 14ecc1f..00090d7 100644 --- a/src/urt/algorithm.d +++ b/src/urt/algorithm.d @@ -1,6 +1,7 @@ module urt.algorithm; -import urt.traits : lvalueOf; +import urt.meta : AliasSeq; +import urt.traits : is_some_function, lvalue_of, Parameters, ReturnType, Unqual; import urt.util : swap; version = SmallSize; @@ -10,36 +11,44 @@ nothrow @nogc: auto compare(T, U)(auto ref T a, auto ref U b) { - static if (__traits(compiles, lvalueOf!T.opCmp(lvalueOf!U))) + static if (__traits(compiles, a.opCmp(b))) return a.opCmp(b); - else static if (__traits(compiles, lvalueOf!U.opCmp(lvalueOf!T))) + else static if (__traits(compiles, b.opCmp(a))) return -b.opCmp(a); - else static if (is(T : A[], A)) + else static if (is(T : A[], A) || is(U : B[], B)) { - import urt.traits : isPrimitive; + import urt.traits : is_primitive; + + static assert(is(T : A[], A) && is(U : B[], B), "TODO: compare an array with a not-array?"); auto ai = a.ptr; auto bi = b.ptr; - size_t len = a.length < b.length ? a.length : b.length; - static if (isPrimitive!A) + + // first compere the pointers... + if (ai !is bi) { - // compare strings - foreach (i; 0 .. len) + size_t len = a.length < b.length ? a.length : b.length; + static if (is_primitive!A) { - if (ai[i] != bi[i]) - return ai[i] < bi[i] ? -1 : 1; + // compare strings + foreach (i; 0 .. len) + { + if (ai[i] != bi[i]) + return ai[i] < bi[i] ? -1 : 1; + } } - } - else - { - // compare arrays - foreach (i; 0 .. len) + else { - auto cmp = compare(ai[i], bi[i]); - if (cmp != 0) - return cmp; + // compare arrays + foreach (i; 0 .. len) + { + if (auto cmp = compare(ai[i], bi[i])) + return cmp; + } } } + + // finally, compare the lengths if (a.length == b.length) return 0; return a.length < b.length ? -1 : 1; @@ -48,8 +57,39 @@ auto compare(T, U)(auto ref T a, auto ref U b) return a < b ? -1 : (a > b ? 1 : 0); } -size_t binarySearch(alias pred = void, T, Cmp...)(T[] arr, auto ref Cmp cmpArgs) +size_t binary_search(Pred)(const void[] arr, size_t stride, const void* value, auto ref Pred pred) + if (is_some_function!Pred && is(ReturnType!Pred == int) && is(Parameters!Pred == AliasSeq!(const void*, const void*))) { + debug assert(arr.length % stride == 0, "array length must be a multiple of stride"); + const count = arr.length / stride; + + const void* p = arr.ptr; + size_t low = 0; + size_t high = count; + while (low < high) + { + size_t mid = low + (high - low) / 2; + + // should we chase the first in a sequence of same values? + int cmp = pred(p + mid*stride, value); + if (cmp < 0) + low = mid + 1; + else + high = mid; + } + if (low == count) + return count; + if (pred(p + low*stride, value) == 0) + return low; + return count; +} + +size_t binary_search(alias pred = void, bool insert_pos = false, T, Cmp...)(T[] arr, auto ref Cmp cmp_args) + if (!is(Unqual!T == void)) +{ + static if (is(pred == void)) + static assert (cmp_args.length == 1, "binary_search without a predicate requires exactly one comparison argument"); + T* p = arr.ptr; size_t low = 0; size_t high = arr.length; @@ -59,7 +99,7 @@ size_t binarySearch(alias pred = void, T, Cmp...)(T[] arr, auto ref Cmp cmpArgs) static if (is(pred == void)) { // should we chase the first in a sequence of same values? - if (p[mid] < cmpArgs[0]) + if (p[mid] < cmp_args[0]) low = mid + 1; else high = mid; @@ -67,82 +107,104 @@ size_t binarySearch(alias pred = void, T, Cmp...)(T[] arr, auto ref Cmp cmpArgs) else { // should we chase the first in a sequence of same values? - int cmp = pred(p[mid], cmpArgs); + auto cmp = pred(p[mid], cmp_args); if (cmp < 0) low = mid + 1; else high = mid; } } - static if (is(pred == void)) + static if (insert_pos) { - if (p[low] == cmpArgs[0]) - return low; + return low; } else { - if (pred(p[low], cmpArgs) == 0) - return low; + if (low == arr.length) + return arr.length; + static if (is(pred == void)) + { + if (p[low] == cmp_args[0]) + return low; + } + else + { + if (pred(p[low], cmp_args) == 0) + return low; + } + return arr.length; } - return arr.length; } -void qsort(alias pred = void, T)(T[] arr) +void qsort(alias pred = void, T)(T[] arr) pure { + if (arr.length <= 1) + return; + version (SmallSize) + enum use_small_size_impl = true; + else + enum use_small_size_impl = false; + + if (!__ctfe && use_small_size_impl) { static if (is(pred == void)) - static if (__traits(compiles, lvalueOf!T.opCmp(lvalueOf!T))) - static int compare(const void* a, const void* b) nothrow @nogc + static if (__traits(compiles, lvalue_of!T.opCmp(lvalue_of!T))) + static int compare(const void* a, const void* b) pure nothrow @nogc => (*cast(const T*)a).opCmp(*cast(const T*)b); else - static int compare(const void* a, const void* b) nothrow @nogc + static int compare(const void* a, const void* b) pure nothrow @nogc => *cast(const T*)a < *cast(const T*)b ? -1 : *cast(const T*)a > *cast(const T*)b ? 1 : 0; else - static int compare(const void* a, const void* b) nothrow @nogc + static int compare(const void* a, const void* b) pure nothrow @nogc => pred(*cast(T*)a, *cast(T*)b); - qsort(arr[], T.sizeof, &compare, (void* a, void* b) nothrow @nogc { + qsort(arr[], T.sizeof, &compare, (void* a, void* b) pure nothrow @nogc { swap(*cast(T*)a, *cast(T*)b); }); } else { T* p = arr.ptr; - if (arr.length > 1) - { - size_t pivotIndex = arr.length / 2; - T* pivot = &p[pivotIndex]; + const n = cast(ptrdiff_t)arr.length; + + size_t pivotIndex = n / 2; + T* pivot = p + pivotIndex; - size_t i = 0; - size_t j = arr.length - 1; + size_t i = 0; + ptrdiff_t j = n - 1; - while (i <= j) + while (i <= j) + { + static if (is(pred == void)) { - static if (is(pred == void)) - { - while (p[i] < *pivot) i++; - while (p[j] > *pivot) j--; - } - else - { - while (pred(*cast(T*)&p[i], *cast(T*)pivot) < 0) i++; - while (pred(*cast(T*)&p[j], *cast(T*)pivot) > 0) j--; - } - if (i <= j) - { - swap(p[i], p[j]); - i++; - j--; - } + while (p[i] < *pivot) ++i; + while (p[j] > *pivot) --j; } + else + { + while (pred(p[i], *pivot) < 0) ++i; + while (pred(p[j], *pivot) > 0) --j; + } + if (i <= j) + { + // track pivot value across swaps + if (p + i == pivot) + pivot = p + j; + else if (p + j == pivot) + pivot = p + i; - if (j > 0) - qsort(p[0 .. j + 1]); - if (i < arr.length) - qsort(p[i .. arr.length]); + swap(p[i], p[j]); + ++i; + --j; + } } + + if (j >= 0) + qsort!pred(p[0 .. j + 1]); + if (i < n) + qsort!pred(p[i .. n]); } } @@ -151,7 +213,7 @@ unittest struct S { int x; - int opCmp(ref const S rh) const nothrow @nogc + int opCmp(ref const S rh) pure const nothrow @nogc => x < rh.x ? -1 : x > rh.x ? 1 : 0; } @@ -159,18 +221,18 @@ unittest qsort(arr); assert(arr == [-1, 3, 17, 30, 100]); - S[5] arr2 = [ S(3), S(100), S(-1), S(17), S(30)]; + S[5] arr2 = [ S(3), S(100), S(-1), S(17), S(30) ]; qsort(arr2); foreach (i, ref s; arr2) assert(s.x == arr[i]); // test binary search, not that they're sorted... - assert(binarySearch(arr, -1) == 0); - assert(binarySearch!(s => s.x < 30 ? -1 : s.x > 30 ? 1 : 0)(arr2) == 3); - assert(binarySearch(arr, 0) == arr.length); + assert(binary_search(arr, -1) == 0); + assert(binary_search!(s => s.x < 30 ? -1 : s.x > 30 ? 1 : 0)(arr2) == 3); + assert(binary_search(arr, 0) == arr.length); int[10] rep = [1, 10, 10, 10, 10, 10, 10, 10, 10, 100]; - assert(binarySearch(rep, 10) == 1); + assert(binary_search(rep, 10) == 1); } @@ -181,34 +243,36 @@ version (SmallSize) // just one generic implementation to minimise the code... // kinda slow though... look at all those multiplies! // maybe there's some way to make this faster :/ - void qsort(void[] arr, size_t elementSize, int function(const void* a, const void* b) nothrow @nogc compare, void function(void* a, void* b) nothrow @nogc swap) + void qsort(void[] arr, size_t element_size, int function(const void* a, const void* b) pure nothrow @nogc compare, void function(void* a, void* b) pure nothrow @nogc swap) pure { void* p = arr.ptr; - size_t length = arr.length / elementSize; - if (length > 1) - { - size_t pivotIndex = length / 2; - void* pivot = p + pivotIndex*elementSize; + size_t length = arr.length / element_size; + if (length <= 1) + return; - size_t i = 0; - size_t j = length - 1; + size_t pivot_index = length / 2; + size_t last = length - 1; + swap(p + pivot_index*element_size, p + last*element_size); - while (i <= j) + void* pivot = p + last*element_size; + + size_t partition = 0; + for (size_t k = 0; k < last; ++k) + { + void* elem = p + k*element_size; + if (compare(elem, pivot) < 0) { - while (compare(p + i*elementSize, pivot) < 0) i++; - while (compare(p + j*elementSize, pivot) > 0) j--; - if (i <= j) - { - swap(p + i*elementSize, p + j*elementSize); - i++; - j--; - } + if (k != partition) + swap(elem, p + partition*element_size); + ++partition; } - - if (j > 0) - qsort(p[0 .. (j + 1)*elementSize], elementSize, compare, swap); - if (i < length) - qsort(p[i*elementSize .. length*elementSize], elementSize, compare, swap); } + + swap(p + partition*element_size, p + last*element_size); + + if (partition > 1) + qsort(p[0 .. partition*element_size], element_size, compare, swap); + if (partition + 1 < length) + qsort(p[(partition + 1)*element_size .. length*element_size], element_size, compare, swap); } } diff --git a/src/urt/array.d b/src/urt/array.d index 8d9858c..9f2897c 100644 --- a/src/urt/array.d +++ b/src/urt/array.d @@ -1,7 +1,10 @@ module urt.array; -import urt.mem; - +import urt.lifetime : move, emplace, moveEmplace; +import urt.mem : memcpy, memmove, memset; +import urt.mem.alloc; +import urt.mem.allocator : NoGCAllocator; +import urt.traits : is_some_char, is_primitive, is_trivial, Unqual; nothrow @nogc: @@ -79,40 +82,29 @@ ref inout(T)[N] takeBack(size_t N, T)(ref inout(T)[] arr) pure return t[0..N]; } -bool exists(T)(const(T)[] arr, auto ref const T el, size_t *pIndex = null) -{ - foreach (i, ref e; arr) - { - if (e.elCmp(el)) - { - if (pIndex) - *pIndex = i; - return true; - } - } - return false; -} - // TODO: I'd like it if these only had one arg (T) somehow... size_t findFirst(T, U)(const(T)[] arr, auto ref const U el) + if (!is_some_char!T) { size_t i = 0; - while (i < arr.length && !arr[i].elCmp(el)) + while (i < arr.length && !arr[i].el_cmp(el)) ++i; return i; } // TODO: I'd like it if these only had one arg (T) somehow... size_t findLast(T, U)(const(T)[] arr, auto ref const U el) + if (!is_some_char!T) { - ptrdiff_t last = length-1; - while (last >= 0 && !arr[last].elCmp(el)) + ptrdiff_t last = arr.length-1; + while (last >= 0 && !arr[last].el_cmp(el)) --last; - return last < 0 ? length : last; + return last < 0 ? arr.length : last; } // TODO: I'd like it if these only had one arg (T) somehow... size_t findFirst(T, U)(const(T)[] arr, U[] seq) + if (!is_some_char!T) { if (seq.length == 0) return 0; @@ -129,6 +121,7 @@ size_t findFirst(T, U)(const(T)[] arr, U[] seq) // TODO: I'd like it if these only had one arg (T) somehow... size_t findLast(T, U)(const(T)[] arr, U[] seq) + if (!is_some_char!T) { if (seq.length == 0) return arr.length; @@ -143,7 +136,8 @@ size_t findLast(T, U)(const(T)[] arr, U[] seq) return arr.length; } -size_t findFirst(T)(const(T)[] arr, bool delegate(auto ref const T) nothrow @nogc pred) +size_t findFirst(T)(const(T)[] arr, bool delegate(ref const T) nothrow @nogc pred) + if (!is_some_char!T) { size_t i = 0; while (i < arr.length && !pred(arr[i])) @@ -151,6 +145,39 @@ size_t findFirst(T)(const(T)[] arr, bool delegate(auto ref const T) nothrow @nog return i; } +bool contains(T, U)(const(T)[] arr, auto ref const U el, size_t *index = null) + if (!is_some_char!T) +{ + size_t i = findFirst(arr, el); + if (i == arr.length) + return false; + if (index) + *index = i; + return true; +} + +bool contains(T, U)(const(T)[] arr, U[] seq, size_t *index = null) + if (!is_some_char!T) +{ + size_t i = findFirst(arr, seq); + if (i == arr.length) + return false; + if (index) + *index = i; + return true; +} + +bool contains(T)(const(T)[] arr, bool delegate(ref const T) nothrow @nogc pred, size_t *index = null) + if (!is_some_char!T) +{ + size_t i = findFirst(arr, pred); + if (i == arr.length) + return false; + if (index) + *index = i; + return true; +} + ptrdiff_t indexOfElement(T, U)(const(T)[] arr, const(U)* el) { if (el < arr.ptr || el >= arr.ptr + arr.length) @@ -168,17 +195,153 @@ inout(T)* search(T)(inout(T)[] arr, bool delegate(ref const T) nothrow @nogc pre return null; } -U[] copyTo(T, U)(T[] arr, U[] dest) +// TODO: use is_trivially_copy_constructible! +private enum copy_use_memcpy(T, U) = is_trivial!T && is(Unqual!T == U); +private enum copy_use_memcpy(T) = copy_use_memcpy!(T, T); +// TODO: use is_trivially_move_constructible! +private enum move_use_memcpy(T, U) = is_trivial!T && is(Unqual!T == U); +private enum move_use_memcpy(T) = move_use_memcpy!(T, T); + +U[] copy_to(bool overlapping = false, bool move = false, T, U)(T[] arr, U[] dest) { + alias UT = Unqual!T; + enum same_type = is(UT == U); + enum both_byte = (is(UT == ubyte) || is(UT == byte) || is(UT == char) || is(UT == void)) && + (is(U == ubyte) || is(U == byte) || is(U == char) || is(U == void)); + + static assert (!overlapping || same_type, "overlapping copy must be same type"); + assert(dest.length >= arr.length); - dest[0 .. arr.length] = arr[]; + + if (__ctfe) + { + static if (same_type) + { + // handle overlapping copies correctly where src < dst + if (arr.ptr < dest.ptr) + { + for (ptrdiff_t i = ptrdiff_t(arr.length) - 1; i >= 0; --i) + dest[i] = arr[i]; + return dest[0 .. arr.length]; + } + } + for (size_t i = 0; i < arr.length; ++i) + dest[i] = arr[i]; + } + else + { + static if (!same_type && both_byte) + return copy_to!(overlapping, move)(cast(ubyte[])arr, cast(ubyte[])dest); + static if ((!move && copy_use_memcpy!(T, U)) || (move && move_use_memcpy!(T, U))) + { + static if (move && copy_use_memcpy!(T, U)) + return copy_to!(overlapping, false, T, U)(arr, dest); + else static if (overlapping) + memmove(dest.ptr, arr.ptr, arr.length * T.sizeof); + else + memcpy(dest.ptr, arr.ptr, arr.length * T.sizeof); + } + else static if (move) + { +// pragma(msg, "move: ", T, " --> ", U, is(T == class) ? " *** class" : ""); + static if (overlapping) + { + if (arr.ptr < dest.ptr && dest.ptr < arr.ptr + arr.length) + { + for (ptrdiff_t i = ptrdiff_t(arr.length) - 1; i >= 0; --i) + .move(arr[i], dest[i]); + return dest[0 .. arr.length]; + } + } + for (size_t i = 0; i < arr.length; ++i) + .move(arr[i], dest[i]); + } + else + { +// pragma(msg, "copy: ", T, " --> ", U, is(T == class) ? " *** class" : ""); + static if (overlapping) + { + if (arr.ptr < dest.ptr && dest.ptr < arr.ptr + arr.length) + { + for (ptrdiff_t i = ptrdiff_t(arr.length) - 1; i >= 0; --i) + dest[i] = arr[i]; + return dest[0 .. arr.length]; + } + } + else static if (same_type) + dest[] = arr[]; + else for (size_t i = 0; i < arr.length; ++i) + dest[i] = arr[i]; + } + } return dest[0 .. arr.length]; } +U[] move_to(bool overlapping = false, T, U)(T[] arr, U[] dest) + => copy_to!(overlapping, true, T, U)(arr, dest); + +void emplace_all(bool move = false, T, U)(T[] arr, U[] dest) +{ + assert(!__ctfe, "emplace_all should not be called at compile time"); + + static if (move && move_use_memcpy!(T, U)) + move_to(cast(Unqual!T[])arr, dest); + else static if ((!move && copy_use_memcpy!(T, U)) || (is(T : U) && is_trivial!U)) + copy_to(cast(Unqual!T[])arr, dest); + else static if (move) + { +// pragma(msg, "move-emplace: ", T, " --> ", U); + for (size_t i = 0; i < arr.length; ++i) + moveEmplace(arr[i], dest[i]); + } + else + { +// pragma(msg, "emplace: ", T, " --> ", U); + for (size_t i = 0; i < arr.length; ++i) + emplace!T(&dest[i], arr[i]); + } +} +void move_emplace_all(T, U)(T[] arr, U[] dest) + => emplace_all!(true, T, U)(arr, dest); -T[] duplicate(T)(const T[] src, NoGCAllocator allocator) nothrow @nogc +void destroy_all(bool initialize = true, T)(T[] arr) { - T[] r = cast(T[])allocator.alloc(src.length * T.sizeof); - r[] = src[]; + assert(!__ctfe, "destroy_all should not be called at compile time"); + + static if (is_trivial!T) + { + static if (initialize) + init_all(arr); + } + else + { + for (uint i = 0; i < arr.length; ++i) + destroy!initialize(arr[i]); + } +} + +void init_all(T)(T[] arr) +{ + assert(!__ctfe, "init_all should not be called at compile time"); + + static if (is_trivial!T) + { + static if (__traits(isZeroInit, T)) + memset(arr.ptr, 0, arr.length * T.sizeof); + else + arr[] = T.init; + } + else for (size_t i = 0; i < arr.length; ++i) + emplace!T(&arr[i]); +} + +T[] duplicate(T)(const T[] src, NoGCAllocator allocator = null) nothrow @nogc +{ + T[] r; + if (allocator) + r = cast(T[])allocator.alloc(src.length * T.sizeof); + else + r = cast(T[])alloc(src.length * T.sizeof); + emplace_all(src, r); return r; } @@ -189,6 +352,10 @@ struct Array(T, size_t EmbedCount = 0) { static assert(EmbedCount == 0, "Not without move semantics!"); + alias This = typeof(this); + + T* ptr; + // constructors // TODO: DELETE POSTBLIT! @@ -201,7 +368,7 @@ struct Array(T, size_t EmbedCount = 0) this = t[]; } - this(ref typeof(this) val) + this(ref This val) { this(val[]); } @@ -218,8 +385,7 @@ struct Array(T, size_t EmbedCount = 0) { debug assert(arr.length <= uint.max); reserve(arr.length); - for (uint i = 0; i < arr.length; ++i) - emplace!T(&ptr[i], arr.ptr[i]); + emplace_all(arr[], ptr[0..arr.length]); _length = cast(uint)arr.length; } @@ -247,7 +413,7 @@ struct Array(T, size_t EmbedCount = 0) ~this() { clear(); - if (hasAllocation()) + if (has_allocation()) free(ptr); } @@ -273,11 +439,12 @@ nothrow @nogc: void opAssign(U)(U[] arr) if (is(U : T)) { + // TODO: WHAT IF arr IS A SLICE OF THIS?!!? + debug assert(arr.length <= uint.max); clear(); reserve(arr.length); - for (uint i = 0; i < arr.length; ++i) - emplace!T(&ptr[i], arr.ptr[i]); + emplace_all(arr[], ptr[0 .. arr.length]); _length = cast(uint)arr.length; } @@ -288,7 +455,98 @@ nothrow @nogc: } // manipulation - ref Array!(T, EmbedCount) concat(Things...)(auto ref Things things); + static if (!is_some_char!T) + { + alias concat = append; // TODO: REMOVE THIS ALIAS, we phase out the old name... + + ref Array!(T, EmbedCount) append(Things...)(auto ref Things things) + { + size_t ext_len = 0; + static foreach (i; 0 .. things.length) + { + static if (is(Things[i] == U[], U) && is(U : T)) + ext_len += things[i].length; + else static if (is(Things[i] == U[N], U, size_t N) && is(U : T)) + ext_len += N; + else static if (is(Things[i] : T)) + ext_len += 1; + else + static assert(false, "Invalid type for concat"); + } + reserve(_length + ext_len); + static foreach (i; 0 .. things.length) + { + static if (is(Things[i] == V[], V) && is(V : T)) + { + emplace_all(things[i][], ptr[_length .. _length + things[i].length]); + _length += cast(uint)things[i].length; + } + else static if (is(Things[i] == V[M], V, size_t M) && is(V : T)) + { + emplace_all(things[i][], ptr[_length .. _length + M]); + _length += cast(uint)M; + } + else static if (is(Things[i] : T)) + { + static if (is_trivial!T) + ptr[_length++] = things[i]; + else + emplace!T(&ptr[_length++], forward!(things[i])); + } + } + return this; + } + } + else + { + // char arrays are really just a string buffer, and so we'll expand the capability of concat to match what `MutableString` accepts... + static assert(is(T == char), "TODO: wchar and dchar"); // needs buffer length counting helpers + + // TODO: string's have this function `concat` which clears the string first, and that's different than Array + // we need to tighten this up! + ref Array!(T, EmbedCount) concat(Things...)(auto ref Things things) + { + pragma(inline, true); + clear(); + return append(forward!things); + } + + ref Array!(T, EmbedCount) append(Things...)(auto ref Things things) + { + import urt.string.format : _concat = concat_impl, normalise_args; + + auto args = normalise_args(things); + + size_t ext_len = _concat(null, args).length; + reserve(_length + ext_len); + _concat(ptr[_length .. _length + ext_len], args); + _length += ext_len; + return this; + } + + ref Array!(T, EmbedCount) append_format(Things...)(const(char)[] format, auto ref Things args) + { + import urt.string.format : _format = format; + + size_t ext_len = _format(null, format, args).length; + reserve(_length + ext_len); + _format(ptr[_length .. _length + ext_len], format, forward!args); + _length += ext_len; + return this; + } + } + + T[] extend(bool do_init = true)(size_t length) + { + assert(_length + length <= uint.max); + + size_t old_len = _length; + reserve(_length + length); + static if (do_init) + init_all(ptr[_length .. _length + length]); + _length += cast(uint)length; + return ptr[old_len .. _length]; + } bool empty() const => _length == 0; @@ -306,7 +564,7 @@ nothrow @nogc: return ptr[_length - 1]; } - ref T pushFront() + ref T pushFront()() { static if (is(T == class) || is(T == interface)) return pushFront(null); @@ -316,32 +574,24 @@ nothrow @nogc: ref T pushFront(U)(auto ref U item) if (is(U : T)) { - static if (is(T == class) || is(T == interface)) - { - uint len = _length; - reserve(len + 1); - _length = len + 1; - for (uint i = len; i > 0; --i) - ptr[i] = ptr[i-1]; - ptr[0] = item; - return ptr[0]; - } + reserve(_length + 1); + static if (is_trivial!T) + memmove(ptr + 1, ptr, _length++ * T.sizeof); else { - uint len = _length; - reserve(len + 1); - _length = len + 1; - for (uint i = len; i > 0; --i) + for (uint i = _length++; i > 0; --i) { moveEmplace!T(ptr[i-1], ptr[i]); destroy!false(ptr[i-1]); } - emplace!T(&ptr[0], forward!item); - return ptr[0]; } + static if (is(T == class) || is(T == interface)) + return (ptr[0] = item); + else + return *emplace!T(&ptr[0], forward!item); } - ref T pushBack() + ref T pushBack()() { static if (is(T == class) || is(T == interface)) return pushBack(null); @@ -351,156 +601,90 @@ nothrow @nogc: ref T pushBack(U)(auto ref U item) if (is(U : T)) { + reserve(_length + 1); static if (is(T == class) || is(T == interface)) - { - uint len = _length; - reserve(len + 1); - _length = len + 1; - ptr[len] = item; - return ptr[len]; - } + return (ptr[_length++] = item); else - { - uint len = _length; - reserve(len + 1); - _length = len + 1; - emplace!T(&ptr[len], forward!item); - return ptr[len]; - } + return *emplace!T(&ptr[_length++], forward!item); } ref T emplaceFront(Args...)(auto ref Args args) + if (!is(T == class) && !is(T == interface)) { - uint len = _length; - reserve(len + 1); - _length = len + 1; - for (uint i = len; i > 0; --i) - { - moveEmplace(ptr[i-1], ptr[i]); - destroy!false(ptr[i-1]); - } - emplace!T(&ptr[0], forward!args); - return ptr[0]; - } - - ref T emplaceBack(Args...)(auto ref Args args) - { - uint len = _length; - reserve(len + 1); - _length = len + 1; - emplace!T(&ptr[len], forward!args); - return ptr[len]; - } - - T popFront() - { - debug assert(_length > 0); - - // TODO: this should be removed and uses replaced with a queue container - static if (is(T == class) || is(T == interface)) - { - T copy = ptr[0]; - for (uint i = 1; i < _length; ++i) - ptr[i-1] = ptr[i]; - ptr[--_length] = null; - return copy; - } + reserve(_length + 1); + static if (is_trivial!T) + memmove(ptr + 1, ptr, _length++ * T.sizeof); else { - T copy = ptr[0].move; - for (uint i = 1; i < _length; ++i) + for (uint i = _length++; i > 0; --i) { + moveEmplace!T(ptr[i-1], ptr[i]); destroy!false(ptr[i-1]); - moveEmplace(ptr[i], ptr[i-1]); } - destroy!false(ptr[--_length]); - return copy.move; } + return *emplace!T(&ptr[0], forward!args); } - T popBack() + ref T emplaceBack(Args...)(auto ref Args args) + if (!is(T == class) && !is(T == interface)) { - debug assert(_length > 0); - - static if (is(T == class) || is(T == interface)) - { - uint last = _length-1; - T copy = ptr[last]; - ptr[last] = null; - _length = last; - return copy; - } - else - { - uint last = _length-1; - T copy = ptr[last].move; - destroy!false(ptr[last]); - _length = last; - return copy.move; - } + reserve(_length + 1); + return *emplace!T(&ptr[_length++], forward!args); } - Array!T takeFront(size_t count) + T popFront() { - assert(false, "TODO"); + debug assert(_length > 0, "Range error"); + T r = ptr[0].move; + remove(0); + return r; } - Array!(T, N) takeFront(size_t N)() + T popBack() { - assert(false, "TODO"); + debug assert(_length > 0, "Range error"); + T r = ptr[_length - 1].move; + destroy!false(ptr[--_length]); + return r; } - Array!T takeBack(size_t count) + Array!T takeFront()(size_t count) { - assert(false, "TODO"); + auto r = Array!T(Reserve, count); + move_emplace_all(ptr[0 .. count], r.ptr[0 .. count]); + r._length = cast(uint)count; + remove(0, count); + return r; } - Array!(T, N) takeBack(size_t N)() + Array!T takeBack()(size_t count) { - assert(false, "TODO"); + auto r = Array!T(Reserve, count); + move_emplace_all(ptr[_length - count .. _length], r.ptr[0 .. count]); + r._length = cast(uint)count; + remove(_length - count, count); + return r; } - void remove(size_t i) + void remove(size_t i, size_t count = 1) { - debug assert(i < _length); - - static if (is(T == class) || is(T == interface)) - { - for (size_t j = i + 1; j < _length; ++j) - ptr[j-1] = ptr[j]; - ptr[--_length] = null; - } - else - { - destroy!false(ptr[i]); - for (size_t j = i + 1; j < _length; ++j) - { - emplace!T(&ptr[j-1], ptr[j].move); - destroy!false(ptr[j]); - } - --_length; - } + debug assert(i + count <= _length, "Range error"); + if (i < _length - count) + move_to!true(ptr[i + count .. _length], ptr[i .. _length - count]); + destroy_all!false(ptr[_length - count .. length]); + _length -= cast(uint)count; } - void remove(const(T)* pItem) { remove(ptr[0 .. _length].indexOfElement(pItem)); } void removeFirst(U)(ref const U item) { remove(ptr[0 .. _length].findFirst(item)); } - void removeSwapLast(size_t i) + void removeSwapLast(size_t i, size_t count = 1) { - assert(i < _length, "Range error"); - static if (is(T == class) || is(T == interface)) - { - ptr[i] = ptr[--_length]; - ptr[_length] = null; - } - else - { - destroy!false(ptr[i]); - emplace!T(&ptr[i], ptr[--_length].move); - destroy!false(ptr[_length]); - } + debug assert(i + count <= _length, "Range error"); + if (i < _length - count) + move_to!true(ptr[_length - count .. _length], ptr[i .. count]); + destroy_all!false(ptr[_length - count .. length]); + _length -= cast(uint)count; } - void removeSwapLast(const(T)* pItem) { removeSwapLast(ptr[0 .. _length].indexOfElement(pItem)); } void removeFirstSwapLast(U)(ref const U item) { removeSwapLast(ptr[0 .. _length].findFirst(item)); } @@ -513,154 +697,125 @@ nothrow @nogc: inout(void)[] getBuffer() inout { static if (EmbedCount > 0) - return ptr ? ptr[0 .. allocCount()] : embed[]; + return ptr ? ptr[0 .. alloc_count()] : embed[]; else - return ptr ? ptr[0 .. allocCount()] : null; + return ptr ? ptr[0 .. alloc_count()] : null; } - bool opCast(T : bool)() const + bool opCast(T : bool)() const pure => _length != 0; - size_t opDollar() const + size_t opDollar() const pure => _length; // full slice: arr[] - inout(T)[] opIndex() inout + inout(T)[] opIndex() inout pure => ptr[0 .. _length]; + void opIndexAssign(U)(U[] rh) + { + debug assert(rh.length == _length, "Range error"); + ptr[0 .. _length] = rh[]; + } + // array indexing: arr[i] - ref inout(T) opIndex(size_t i) inout + ref inout(T) opIndex(size_t i) inout pure { debug assert(i < _length, "Range error"); return ptr[i]; } // array slicing: arr[x .. y] - inout(T)[] opIndex(uint[2] i) inout + inout(T)[] opIndex(size_t[2] i) inout pure => ptr[i[0] .. i[1]]; - uint[2] opSlice(size_t dim : 0)(size_t x, size_t y) + void opIndexAssign(U)(U[] rh, size_t[2] i) { - debug assert(y <= _length, "Range error"); - return [cast(uint)x, cast(uint)y]; + debug assert(i[1] <= _length && i[1] - i[0] == rh.length, "Range error"); + ptr[i[0] .. i[1]] = rh[]; } - void opOpAssign(string op : "~", U)(auto ref U el) - if (is(U : T)) + size_t[2] opSlice(size_t dim : 0)(size_t x, size_t y) const pure { - pushBack(forward!el); + debug assert(y <= _length, "Range error"); + return [x, y]; } - void opOpAssign(string op : "~", U)(U[] arr) - if (is(U : T)) + void opOpAssign(string op : "~", U)(auto ref U rh) { - reserve(_length + arr.length); - foreach (ref e; arr) - pushBack(e); + append(forward!rh); } void reserve(size_t count) { - if (count > allocCount()) + if (count > alloc_count()) { debug assert(count <= uint.max, "Exceed maximum size"); - T* newArray = allocate(cast(uint)count); - // TODO: POD should memcpy... (including class) - - static if (is(T == class) || is(T == interface)) - { - for (uint i = 0; i < _length; ++i) - newArray[i] = ptr[i]; - } - else - { - for (uint i = 0; i < _length; ++i) - { - moveEmplace(ptr[i], newArray[i]); - destroy!false(ptr[i]); - } - } + T* new_array = allocate(cast(uint)count); + move_emplace_all(ptr[0 .. _length], new_array[0 .. _length]); + destroy_all!false(ptr[0 .. _length]); - if (hasAllocation()) + if (has_allocation()) free(ptr); - - ptr = newArray; + ptr = new_array; } } - void alloc(size_t count) - { - assert(false); - } - void resize(size_t count) { if (count == _length) return; - if (count < _length) { - static if (is(T == class) || is(T == interface)) - { - for (ptrdiff_t i = _length - 1; i >= count; --i) - ptr[i] = null; - } - else - { - for (ptrdiff_t i = _length - 1; i >= count; --i) - destroy!false(ptr[i]); - } + destroy_all!false(ptr[count .. _length]); _length = cast(uint)count; } else { reserve(count); - static if (is(T == class) || is(T == interface)) - { - foreach (i; _length .. count) - ptr[i] = null; - } - else - { - foreach (i; _length .. count) - emplace!T(&ptr[i]); - } + init_all(ptr[_length .. count]); _length = cast(uint)count; } } void clear() { - static if (!is(T == class) && !is(T == interface)) - for (uint i = 0; i < _length; ++i) - destroy!false(ptr[i]); + destroy_all!false(ptr[0 .. _length]); _length = 0; } + import urt.string.format : FormatArg, formatValue; + ptrdiff_t toString()(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const + => formatValue(ptr[0 .. _length], buffer, format, formatArgs); + + ptrdiff_t fromString()(const(char)[] s) + { + assert(false, "TODO"); + } + private: - T* ptr; uint _length; static if (EmbedCount > 0) T[EmbedCount] embed = void; - bool hasAllocation() const + bool has_allocation() const pure { static if (EmbedCount > 0) return ptr && ptr != embed.ptr; else return ptr !is null; } - uint allocCount() const - => hasAllocation() ? (cast(uint*)ptr)[-1] : EmbedCount; + uint alloc_count() const pure + => has_allocation() ? (cast(uint*)ptr)[-1] : EmbedCount; T* allocate(uint count) { enum AllocAlignment = T.alignof < 4 ? 4 : T.alignof; enum AllocPrefixBytes = T.sizeof < 4 ? 4 : T.sizeof; - void[] mem = defaultAllocator().alloc(AllocPrefixBytes + T.sizeof * count, AllocAlignment); + void[] mem = .alloc(AllocPrefixBytes + T.sizeof * count, AllocAlignment); T* array = cast(T*)(mem.ptr + AllocPrefixBytes); (cast(uint*)array)[-1] = count; return array; @@ -669,17 +824,19 @@ private: { enum AllocPrefixBytes = T.sizeof < 4 ? 4 : T.sizeof; - defaultAllocator().free((cast(void*)ptr - AllocPrefixBytes)[0 .. AllocPrefixBytes + T.sizeof * allocCount()]); + .free((cast(void*)ptr - AllocPrefixBytes)[0 .. AllocPrefixBytes + T.sizeof * alloc_count()]); } pragma(inline, true) - static uint numToAlloc(uint i) + static uint num_to_alloc(uint i) pure { // TODO: i'm sure we can imagine a better heuristic... return i > 16 ? i * 2 : 16; } - - auto __debugExpanded() const pure => ptr[0 .. _length]; + version (Windows) + { + auto __debugExpanded() const pure => ptr[0 .. _length]; + } } // SharedArray is a ref-counted array which can be distributed @@ -689,7 +846,7 @@ struct SharedArray(T) this(this) { if (ptr) - incRef(); + inc_ref(); } this(typeof(null)) {} @@ -699,7 +856,7 @@ struct SharedArray(T) ptr = val.ptr; _length = val._length; if (ptr) - incRef(); + inc_ref(); } this(U)(U[] arr) @@ -721,11 +878,6 @@ struct SharedArray(T) nothrow @nogc: - void opAssign(typeof(null)) - { - clear(); - } - void opAssign(ref typeof(this) val) { clear(); @@ -733,23 +885,21 @@ nothrow @nogc: ptr = val.ptr; _length = val._length; if (ptr) - incRef(); + inc_ref(); } void opAssign(U)(U[] arr) if (is(U : T)) { debug assert(arr.length <= uint.max); - uint len = cast(uint)arr.length; + _length = cast(uint)arr.length; clear(); - _length = len; if (len > 0) { - ptr = allocate(len); - for (uint i = 0; i < len; ++i) - emplace!T(&ptr[i], arr.ptr[i]); + ptr = allocate(_length); + emplace_all(arr[], ptr[0 .. _length]); } else ptr = null; @@ -780,9 +930,9 @@ nothrow @nogc: // inout(void)[] getBuffer() inout // { // static if (EmbedCount > 0) -// return ptr ? ptr[0 .. allocCount()] : embed[]; +// return ptr ? ptr[0 .. alloc_count()] : embed[]; // else -// return ptr ? ptr[0 .. allocCount()] : null; +// return ptr ? ptr[0 .. alloc_count()] : null; // } bool opCast(T : bool)() const @@ -814,12 +964,9 @@ nothrow @nogc: void clear() { - static if (!is(T == class) && !is(T == interface)) - for (uint i = 0; i < _length; ++i) - ptr[i].destroy!false(); - + destroy_all!false(ptr[0 .. _length]); if (ptr) - decRef(); + dec_ref(); ptr = null; _length = 0; } @@ -828,12 +975,12 @@ private: T* ptr; uint _length; - void incRef() + void inc_ref() { ++(cast(uint*)ptr)[-1]; } - void decRef() + void dec_ref() { uint* rc = cast(uint*)ptr - 1; if (*rc == 0) @@ -846,7 +993,7 @@ private: { enum AllocAlignment = T.alignof < uint.alignof ? uint.alignof : T.alignof; - void[] mem = defaultAllocator().alloc(AllocAlignment + T.sizeof * count, AllocAlignment); + void[] mem = .alloc(AllocAlignment + T.sizeof * count, AllocAlignment); T* array = cast(T*)(mem.ptr + AllocPrefixBytes); (cast(uint*)array)[-1] = 0; return array; @@ -855,7 +1002,7 @@ private: { enum AllocAlignment = T.alignof < uint.alignof ? uint.alignof : T.alignof; - defaultAllocator().free((cast(void*)ptr - AllocAlignment)[0 .. AllocAlignment + T.sizeof * allocCount()]); + .free((cast(void*)ptr - AllocAlignment)[0 .. AllocAlignment + T.sizeof * alloc_count()]); } } @@ -863,21 +1010,21 @@ private: private: pragma(inline, true) -bool elCmp(T)(const T a, const T b) +bool el_cmp(T)(const T a, const T b) if (is(T == class) || is(T == interface)) { return a is b; } pragma(inline, true) -bool elCmp(T)(const T a, const T b) +bool el_cmp(T)(const T a, const T b) if (is(T == U[], U)) { return a[] == b[]; } pragma(inline, true) -bool elCmp(T)(auto ref const T a, auto ref const T b) +bool el_cmp(T)(auto ref const T a, auto ref const T b) if (!is(T == class) && !is(T == interface) && !is(T == U[], U)) { return a == b; diff --git a/src/urt/async.d b/src/urt/async.d index fee575f..557f24a 100644 --- a/src/urt/async.d +++ b/src/urt/async.d @@ -20,7 +20,7 @@ Promise!(ReturnType!Fun)* async(alias Fun, size_t stackSize = DefaultStackSize, // TODO: nice to rework this; maybe make stackSize a not-template-arg, and receive a function call/closure object which stores the args Promise!(ReturnType!Fun)* async(size_t stackSize = DefaultStackSize, Fun, Args...)(Fun fun, auto ref Args args) - if (isSomeFunction!Fun) + if (is_some_function!Fun) { alias Result = ReturnType!Fun; Promise!Result* r = cast(Promise!Result*)defaultAllocator().alloc(Promise!Result.sizeof, Promise!Result.alignof); @@ -60,7 +60,7 @@ Promise!(ReturnType!Fun)* async(size_t stackSize = DefaultStackSize, Fun, Args.. void freePromise(T)(ref Promise!T* promise) { - assert(promise.state() != Promise!T.State.Pending, "Promise still pending!"); + assert(promise.state() != PromiseState.Pending, "Promise still pending!"); defaultAllocator().freeT(promise); promise = null; } @@ -79,20 +79,20 @@ void asyncUpdate() if (!t.event.ready()) continue; } - t.resume(); + t.call.fibre.resume(); } } -struct Promise(Result) +enum PromiseState { - enum State - { - Pending, - Ready, - Failed, - } + Pending, + Ready, + Failed +} +struct Promise(Result) +{ // construct using `async()` functions... this() @disable; this(ref typeof(this)) @disable; // disable copy constructor @@ -117,29 +117,29 @@ struct Promise(Result) } } - State state() const + PromiseState state() const { if (async.fibre.wasAborted()) - return State.Failed; + return PromiseState.Failed; else if (async.fibre.isFinished()) - return State.Ready; + return PromiseState.Ready; else - return State.Pending; + return PromiseState.Pending; } bool finished() const - => state() != State.Pending; + => state() != PromiseState.Pending; ref Result result() { - assert(state() == State.Ready, "Promise not fulfilled!"); + assert(state() == PromiseState.Ready, "Promise not fulfilled!"); static if (!is(Result == void)) return value; } void abort() { - assert(state() == State.Pending, "Promise already fulfilled!"); + assert(state() == PromiseState.Pending, "Promise already fulfilled!"); async.fibre.abort(); } @@ -180,11 +180,11 @@ unittest } auto p = async!fun(1, 2); - assert(p.state() == p.State.Ready); + assert(p.state() == PromiseState.Ready); assert(p.result() == 3); freePromise(p); p = async!fun(10, 20); - assert(p.state() == p.State.Ready); + assert(p.state() == PromiseState.Ready); assert(p.result() == 30); freePromise(p); @@ -201,13 +201,13 @@ unittest } auto p_yield = async(&fun_yield); - assert(p_yield.state() == p_yield.State.Pending); + assert(p_yield.state() == PromiseState.Pending); assert(val == 1); asyncUpdate(); - assert(p_yield.state() == p_yield.State.Pending); + assert(p_yield.state() == PromiseState.Pending); assert(val == 2); asyncUpdate(); - assert(p_yield.state() == p_yield.State.Ready); + assert(p_yield.state() == PromiseState.Ready); assert(val == 3); assert(p_yield.result() == 4); freePromise(p_yield); diff --git a/src/urt/atomic.d b/src/urt/atomic.d new file mode 100644 index 0000000..2b6c3a0 --- /dev/null +++ b/src/urt/atomic.d @@ -0,0 +1,869 @@ +module urt.atomic; + +enum MemoryOrder +{ + relaxed = 0, + consume = 1, + acquire = 2, + release = 3, + acq_rel = 4, + seq_cst = 5, + seq = 5, +} + +// DMD lowers to these names... +alias atomicLoad = atomic_load; +alias atomicStore = atomic_store; +alias atomicOp = atomic_op; +alias atomicExchange = atomic_exchange; +alias atomicFetchAdd = atomic_fetch_add; +alias atomicFetchSub = atomic_fetch_sub; + + +version (LDC) +{ + // ----------------------------------------------------------------------- + // LDC: LLVM intrinsics - architecture-generic + // ----------------------------------------------------------------------- + + nothrow @nogc @safe: + pragma(inline, true): + + T atomic_load(MemoryOrder ms = MemoryOrder.seq, T)(ref const T val) pure @trusted + if (!is(T == shared)) + { + alias A = _AtomicType!T; + A result = llvm_atomic_load!A(cast(shared A*)&val, _ordering!ms); + return *cast(inout(T)*)&result; + } + + TailShared!T atomic_load(MemoryOrder ms = MemoryOrder.seq, T)(auto ref shared const T val) pure @trusted + { + alias A = _AtomicType!T; + A result = llvm_atomic_load!A(cast(shared A*)&val, _ordering!ms); + return *cast(TailShared!T*)&result; + } + + void atomic_store(MemoryOrder ms = MemoryOrder.seq, T, V)(ref shared T val, V newval) pure @trusted + if (__traits(compiles, { *cast(T*)&val = newval; })) + { + alias A = _AtomicType!T; + T tmp = newval; + llvm_atomic_store!A(*cast(A*)&tmp, cast(shared A*)&val, _ordering!ms); + } + + TailShared!T atomic_op(string op, T, V1)(ref shared T val, V1 mod) pure @trusted + if (__traits(compiles, mixin("*cast(T*)&val" ~ op ~ "mod"))) + { + // use LLVM atomic RMW where possible + static if (__traits(isIntegral, T) && T.sizeof <= size_t.sizeof) + { + alias A = _AtomicType!T; + static if (op == "+=") + { + auto old = llvm_atomic_rmw_add!A(cast(shared A*)&val, cast(A)mod, _ordering!(MemoryOrder.seq)); + auto result = cast(T)(old + cast(A)mod); + return *cast(TailShared!T*)&result; + } + else static if (op == "-=") + { + auto old = llvm_atomic_rmw_sub!A(cast(shared A*)&val, cast(A)mod, _ordering!(MemoryOrder.seq)); + auto result = cast(T)(old - cast(A)mod); + return *cast(TailShared!T*)&result; + } + else + { + // CAS loop for other ops + return _cas_op_loop!(op, T, V1)(val, mod); + } + } + else + return _cas_op_loop!(op, T, V1)(val, mod); + } + + bool cas(T, V1, V2)(shared(T)* here, V1 ifThis, V2 writeThis) pure @trusted + { + alias A = _AtomicType!T; + T cmp = cast(T)ifThis; + T desired = cast(T)writeThis; + auto result = llvm_atomic_cmp_xchg!A(cast(shared A*)here, *cast(A*)&cmp, *cast(A*)&desired, + _ordering!(MemoryOrder.seq), _ordering!(MemoryOrder.seq), false); + return result.exchanged; + } + + T atomic_exchange(MemoryOrder ms = MemoryOrder.seq, T, V)(shared(T)* here, V exchangeWith) pure @trusted + { + alias A = _AtomicType!T; + T tmp = cast(T)exchangeWith; + A result = llvm_atomic_rmw_xchg!A(cast(shared A*)here, *cast(A*)&tmp, _ordering!ms); + return *cast(T*)&result; + } + + T atomic_fetch_add(MemoryOrder ms = MemoryOrder.seq, T)(ref shared T val, T mod) pure @trusted + if (__traits(isIntegral, T)) + { + alias A = _AtomicType!T; + return cast(T)llvm_atomic_rmw_add!A(cast(shared A*)&val, cast(A)mod, _ordering!ms); + } + + T atomic_fetch_sub(MemoryOrder ms = MemoryOrder.seq, T)(ref shared T val, T mod) pure @trusted + if (__traits(isIntegral, T)) + { + alias A = _AtomicType!T; + return cast(T)llvm_atomic_rmw_sub!A(cast(shared A*)&val, cast(A)mod, _ordering!ms); + } + + void atomicFence(MemoryOrder ms = MemoryOrder.seq)() pure @trusted + { + llvm_memory_fence(_ordering!ms); + } + + void pause() pure @trusted + { + version (X86) + enum inst = "pause"; + else version (X86_64) + enum inst = "pause"; + else version (ARM) + { + static if (__traits(targetHasFeature, "v6k")) + enum inst = "yield"; + else + enum inst = null; + } + else version (AArch64) + enum inst = "yield"; + else + enum inst = null; + + static if (inst !is null) + asm pure nothrow @nogc @trusted { (inst); } + } + + // --- LDC private helpers --- + + private TailShared!T _cas_op_loop(string op, T, V1)(ref shared T val, V1 mod) pure @trusted + { + alias A = _AtomicType!T; + A current = llvm_atomic_load!A(cast(shared A*)&val, _ordering!(MemoryOrder.seq)); + while (true) + { + auto tmp = *cast(T*)¤t; + mixin("tmp " ~ op ~ " mod;"); + auto result = llvm_atomic_cmp_xchg!A(cast(shared A*)&val, current, *cast(A*)&tmp, + _ordering!(MemoryOrder.seq), _ordering!(MemoryOrder.seq), false); + if (result.exchanged) + return *cast(TailShared!T*)&tmp; + current = result.previousValue; + } + } + + private template _ordering(MemoryOrder ms) + { + static if (ms == MemoryOrder.acquire) + enum _ordering = AtomicOrdering.Acquire; + else static if (ms == MemoryOrder.release) + enum _ordering = AtomicOrdering.Release; + else static if (ms == MemoryOrder.acq_rel) + enum _ordering = AtomicOrdering.AcquireRelease; + else static if (ms == MemoryOrder.seq) + enum _ordering = AtomicOrdering.SequentiallyConsistent; + else // raw/relaxed/consume + enum _ordering = AtomicOrdering.Monotonic; + } + + private template _AtomicType(T) + { + static if (T.sizeof == ubyte.sizeof) + alias _AtomicType = ubyte; + else static if (T.sizeof == ushort.sizeof) + alias _AtomicType = ushort; + else static if (T.sizeof == uint.sizeof) + alias _AtomicType = uint; + else static if (T.sizeof == ulong.sizeof) + alias _AtomicType = ulong; + else + static assert(false, "Cannot atomically load/store type of size " ~ T.sizeof.stringof); + } + + // LLVM atomic intrinsic declarations + private: + + enum AtomicOrdering + { + Monotonic = 2, + Acquire = 4, + Release = 5, + AcquireRelease = 6, + SequentiallyConsistent = 7, + } + + enum SynchronizationScope + { + SingleThread = 0, + CrossThread = 1, + } + + struct CmpXchgResult(T) + { + T previousValue; + bool exchanged; + } + + pragma(LDC_atomic_load) + T llvm_atomic_load(T)(in shared T* ptr, AtomicOrdering ordering) pure @trusted; + + pragma(LDC_atomic_store) + void llvm_atomic_store(T)(T val, shared T* ptr, AtomicOrdering ordering) pure @trusted; + + pragma(LDC_atomic_rmw, "xchg") + T llvm_atomic_rmw_xchg(T)(shared T* ptr, T val, AtomicOrdering ordering) pure @trusted; + + pragma(LDC_atomic_rmw, "add") + T llvm_atomic_rmw_add(T)(in shared T* ptr, T val, AtomicOrdering ordering) pure @trusted; + + pragma(LDC_atomic_rmw, "sub") + T llvm_atomic_rmw_sub(T)(in shared T* ptr, T val, AtomicOrdering ordering) pure @trusted; + + pragma(LDC_atomic_cmp_xchg) + CmpXchgResult!T llvm_atomic_cmp_xchg(T)(shared T* ptr, T cmp, T val, + AtomicOrdering successOrdering, AtomicOrdering failureOrdering, bool weak) pure @trusted; + + pragma(LDC_fence) + void llvm_memory_fence(AtomicOrdering ordering) pure @trusted; +} +else version (D_InlineAsm_X86_64) +{ + // ----------------------------------------------------------------------- + // DMD x86_64: inline assembly + // ----------------------------------------------------------------------- + + nothrow @nogc @safe: + + T atomic_load(MemoryOrder ms = MemoryOrder.seq, T)(ref const T val) pure @trusted + if (!is(T == shared)) + { + static if (ms == MemoryOrder.seq) + { + // seq_cst load: use lock cmpxchg to get full barrier + size_t storage = void; + auto srcPtr = cast(size_t)&val; + + enum ValReg = SizedReg!(DX, T); + enum ResReg = SizedReg!(AX, T); + + mixin(simpleFormat!(q{ + asm pure nothrow @nogc @trusted + { + mov RCX, srcPtr; + mov %0, 0; + mov %1, 0; + lock; cmpxchg [RCX], %0; + lea RCX, storage; + mov [RCX], %1; + } + }, [ValReg, ResReg])); + + return *cast(T*)&storage; + } + else + return val; + } + + TailShared!T atomic_load(MemoryOrder ms = MemoryOrder.seq, T)(auto ref shared const T val) pure @trusted + { + return atomic_load!ms(*cast(const T*)&val); + } + + void atomic_store(MemoryOrder ms = MemoryOrder.seq, T, V)(ref shared T val, V newval) pure @trusted + if (__traits(compiles, { *cast(T*)&val = newval; })) + { + static if (ms == MemoryOrder.seq) + { + // seq_cst store: use xchg (has implicit lock) + auto destPtr = cast(size_t)cast(T*)&val; + T tmp = newval; + + enum ValReg = SizedReg!(AX, T); + + mixin(simpleFormat!(q{ + asm pure nothrow @nogc @trusted + { + mov %0, tmp; + mov RCX, destPtr; + lock; xchg [RCX], %0; + } + }, [ValReg])); + } + else + *cast(T*)&val = newval; + } + + TailShared!T atomic_op(string op, T, V1)(ref shared T val, V1 mod) pure @trusted + if (__traits(compiles, mixin("*cast(T*)&val" ~ op ~ "mod"))) + { + static if ((op == "+=" || op == "-=") && __traits(isIntegral, T) && T.sizeof <= size_t.sizeof) + { + auto ptr = cast(T*)&val; + static if (op == "+=") + { + T old = _asm_fetch_add!T(ptr, cast(T)mod); + return cast(TailShared!T)(old + cast(T)mod); + } + else + { + T old = _asm_fetch_add!T(ptr, cast(T)-cast(IntOrLong!T)mod); + return cast(TailShared!T)(old - cast(T)mod); + } + } + else + { + // CAS loop + auto ptr = cast(T*)&val; + while (true) + { + T current = *ptr; + T desired = current; + mixin("desired " ~ op ~ " mod;"); + if (_asm_cas!T(ptr, ¤t, desired)) + return *cast(TailShared!T*)&desired; + } + } + } + + bool cas(T, V1, V2)(shared(T)* here, V1 ifThis, V2 writeThis) pure @trusted + { + T cmp = cast(T)ifThis; + return _asm_cas!T(cast(T*)here, &cmp, cast(T)writeThis); + } + + T atomic_exchange(MemoryOrder ms = MemoryOrder.seq, T, V)(shared(T)* here, V exchangeWith) pure @trusted + { + auto ptr = cast(T*)here; + T tmp = cast(T)exchangeWith; + size_t storage = void; + auto destPtr = cast(size_t)ptr; + + enum DestReg = SizedReg!CX; + enum ValReg = SizedReg!(AX, T); + + mixin(simpleFormat!(q{ + asm pure nothrow @nogc @trusted + { + mov %1, tmp; + mov %0, destPtr; + lock; xchg [%0], %1; + lea %0, storage; + mov [%0], %1; + } + }, [DestReg, ValReg])); + + return *cast(T*)&storage; + } + + T atomic_fetch_add(MemoryOrder ms = MemoryOrder.seq, T)(ref shared T val, T mod) pure @trusted + if (__traits(isIntegral, T)) + { + return _asm_fetch_add!T(cast(T*)&val, mod); + } + + T atomic_fetch_sub(MemoryOrder ms = MemoryOrder.seq, T)(ref shared T val, T mod) pure @trusted + if (__traits(isIntegral, T)) + { + return _asm_fetch_add!T(cast(T*)&val, cast(T)-cast(IntOrLong!T)mod); + } + + void atomicFence(MemoryOrder ms = MemoryOrder.seq)() pure @trusted + { + static if (ms != MemoryOrder.relaxed) + { + asm pure nothrow @nogc @trusted + { + mfence; + } + } + } + + void pause() pure @trusted + { + asm pure nothrow @nogc @trusted + { + pause; + } + } + + // --- DMD x86_64 private helpers --- + private: + + T _asm_fetch_add(T)(T* dest, T value) pure @trusted + { + size_t storage = void; + auto destPtr = cast(size_t)dest; + + enum DestReg = SizedReg!DX; + enum ValReg = SizedReg!(AX, T); + + mixin(simpleFormat!(q{ + asm pure nothrow @nogc @trusted + { + mov %1, value; + mov %0, destPtr; + lock; xadd [%0], %1; + lea %0, storage; + mov [%0], %1; + } + }, [DestReg, ValReg])); + + return *cast(T*)&storage; + } + + bool _asm_cas(T)(T* dest, T* compare, T value) pure @trusted + { + bool success; + auto destPtr = cast(size_t)dest; + auto cmpPtr = cast(size_t)compare; + + enum SrcReg = SizedReg!CX; + enum ValueReg = SizedReg!(DX, T); + enum CompareReg = SizedReg!(AX, T); + + mixin(simpleFormat!(q{ + asm pure nothrow @nogc @trusted + { + mov %1, value; + mov %0, cmpPtr; + mov %2, [%0]; + + mov %0, destPtr; + lock; cmpxchg [%0], %1; + + setz success; + mov %0, cmpPtr; + mov [%0], %2; + } + }, [SrcReg, ValueReg, CompareReg])); + + return success; + } +} +else version (D_InlineAsm_X86) +{ + // ----------------------------------------------------------------------- + // DMD x86 (32-bit): inline assembly + // ----------------------------------------------------------------------- + + nothrow @nogc @safe: + + T atomic_load(MemoryOrder ms = MemoryOrder.seq, T)(ref const T val) pure @trusted + if (!is(T == shared)) + { + static assert(T.sizeof <= 4, "64-bit atomicLoad not supported on 32-bit target"); + + static if (ms == MemoryOrder.seq) + { + size_t storage = void; + + enum ValReg = SizedReg!(DX, T); + enum ResReg = SizedReg!(AX, T); + + mixin(simpleFormat!(q{ + asm pure nothrow @nogc @trusted + { + mov ECX, val; + mov %0, 0; + mov %1, 0; + lock; cmpxchg [ECX], %0; + lea ECX, storage; + mov [ECX], %1; + } + }, [ValReg, ResReg])); + + return *cast(T*)&storage; + } + else + return val; + } + + TailShared!T atomic_load(MemoryOrder ms = MemoryOrder.seq, T)(auto ref shared const T val) pure @trusted + { + return atomic_load!ms(*cast(const T*)&val); + } + + void atomic_store(MemoryOrder ms = MemoryOrder.seq, T, V)(ref shared T val, V newval) pure @trusted + if (__traits(compiles, { *cast(T*)&val = newval; })) + { + static assert(T.sizeof <= 4, "64-bit atomicStore not supported on 32-bit target"); + + static if (ms == MemoryOrder.seq) + { + T tmp = newval; + enum ValReg = SizedReg!(AX, T); + + mixin(simpleFormat!(q{ + asm pure nothrow @nogc @trusted + { + mov %0, tmp; + mov ECX, val; + lock; xchg [ECX], %0; + } + }, [ValReg])); + } + else + *cast(T*)&val = newval; + } + + TailShared!T atomic_op(string op, T, V1)(ref shared T val, V1 mod) pure @trusted + if (__traits(compiles, mixin("*cast(T*)&val" ~ op ~ "mod"))) + { + static assert(T.sizeof <= 4, "64-bit atomicOp not supported on 32-bit target"); + + static if ((op == "+=" || op == "-=") && __traits(isIntegral, T) && T.sizeof <= 4) + { + auto ptr = cast(T*)&val; + static if (op == "+=") + { + T old = _asm_fetch_add!T(ptr, cast(T)mod); + return cast(TailShared!T)(old + cast(T)mod); + } + else + { + T old = _asm_fetch_add!T(ptr, cast(T)-cast(IntOrLong!T)mod); + return cast(TailShared!T)(old - cast(T)mod); + } + } + else + { + auto ptr = cast(T*)&val; + while (true) + { + T current = *ptr; + T desired = current; + mixin("desired " ~ op ~ " mod;"); + if (_asm_cas!T(ptr, ¤t, desired)) + return *cast(TailShared!T*)&desired; + } + } + } + + bool cas(T, V1, V2)(shared(T)* here, V1 ifThis, V2 writeThis) pure @trusted + { + static assert(T.sizeof <= 4, "64-bit cas not supported on 32-bit target"); + T cmp = cast(T)ifThis; + return _asm_cas!T(cast(T*)here, &cmp, cast(T)writeThis); + } + + T atomic_exchange(MemoryOrder ms = MemoryOrder.seq, T, V)(shared(T)* here, V exchangeWith) pure @trusted + { + static assert(T.sizeof <= 4, "64-bit atomicExchange not supported on 32-bit target"); + + auto ptr = cast(T*)here; + T tmp = cast(T)exchangeWith; + size_t storage = void; + + enum ValReg = SizedReg!(AX, T); + + mixin(simpleFormat!(q{ + asm pure nothrow @nogc @trusted + { + mov %0, tmp; + mov ECX, ptr; + lock; xchg [ECX], %0; + lea ECX, storage; + mov [ECX], %0; + } + }, [ValReg])); + + return *cast(T*)&storage; + } + + T atomic_fetch_add(MemoryOrder ms = MemoryOrder.seq, T)(ref shared T val, T mod) pure @trusted + if (__traits(isIntegral, T)) + { + static assert(T.sizeof <= 4, "64-bit atomicFetchAdd not supported on 32-bit target"); + return _asm_fetch_add!T(cast(T*)&val, mod); + } + + T atomic_fetch_sub(MemoryOrder ms = MemoryOrder.seq, T)(ref shared T val, T mod) pure @trusted + if (__traits(isIntegral, T)) + { + static assert(T.sizeof <= 4, "64-bit atomicFetchSub not supported on 32-bit target"); + return _asm_fetch_add!T(cast(T*)&val, cast(T)-cast(IntOrLong!T)mod); + } + + void atomicFence(MemoryOrder ms = MemoryOrder.seq)() pure @trusted + { + static if (ms != MemoryOrder.relaxed) + { + // x86 without guaranteed SSE2 - mfence may not exist. + // lock; add is a full barrier on all x86. + asm pure nothrow @nogc @trusted + { + push EAX; + lock; add [ESP], 0; + pop EAX; + } + } + } + + void pause() pure @trusted + { + asm pure nothrow @nogc @trusted + { + pause; + } + } + + // --- DMD x86 private helpers --- + private: + + T _asm_fetch_add(T)(T* dest, T value) pure @trusted + { + size_t storage = void; + + enum DestReg = SizedReg!DX; + enum ValReg = SizedReg!(AX, T); + + mixin(simpleFormat!(q{ + asm pure nothrow @nogc @trusted + { + mov %1, value; + mov %0, dest; + lock; xadd [%0], %1; + lea %0, storage; + mov [%0], %1; + } + }, [DestReg, ValReg])); + + return *cast(T*)&storage; + } + + bool _asm_cas(T)(T* dest, T* compare, T value) pure @trusted + { + bool success; + + enum SrcReg = SizedReg!CX; + enum ValueReg = SizedReg!(DX, T); + enum CompareReg = SizedReg!(AX, T); + + mixin(simpleFormat!(q{ + asm pure nothrow @nogc @trusted + { + mov %1, value; + mov %0, compare; + mov %2, [%0]; + + mov %0, dest; + lock; cmpxchg [%0], %1; + + setz success; + mov %0, compare; + mov [%0], %2; + } + }, [SrcReg, ValueReg, CompareReg])); + + return success; + } +} +else +{ + // ----------------------------------------------------------------------- + // Fallback: plain load/store for single-core / cooperative-multitasking + // targets (e.g. Xtensa, bare-metal RISC-V) + // ----------------------------------------------------------------------- + + nothrow @nogc @safe: + + T atomic_load(MemoryOrder ms = MemoryOrder.seq, T)(ref const T val) pure @trusted + if (!is(T == shared)) + { + return val; + } + + TailShared!T atomic_load(MemoryOrder ms = MemoryOrder.seq, T)(auto ref shared const T val) pure @trusted + { + return *cast(TailShared!T*)&val; + } + + void atomic_store(MemoryOrder ms = MemoryOrder.seq, T, V)(ref shared T val, V newval) pure @trusted + if (__traits(compiles, { *cast(T*)&val = newval; })) + { + *cast(T*)&val = newval; + } + + TailShared!T atomic_op(string op, T, V1)(ref shared T val, V1 mod) pure @trusted + if (__traits(compiles, mixin("*cast(T*)&val" ~ op ~ "mod"))) + { + auto ptr = cast(T*)&val; + mixin("*ptr " ~ op ~ " mod;"); + return *cast(TailShared!T*)ptr; + } + + bool cas(T, V1, V2)(shared(T)* here, V1 ifThis, V2 writeThis) pure @trusted + { + auto ptr = cast(T*)here; + if (*ptr == ifThis) + { + *ptr = writeThis; + return true; + } + return false; + } + + T atomic_exchange(MemoryOrder ms = MemoryOrder.seq, T, V)(shared(T)* here, V exchangeWith) pure @trusted + { + auto ptr = cast(T*)here; + T old = *ptr; + *ptr = exchangeWith; + return old; + } + + T atomic_fetch_add(MemoryOrder ms = MemoryOrder.seq, T)(ref shared T val, T mod) pure @trusted + if (__traits(isIntegral, T)) + { + auto ptr = cast(T*)&val; + T old = *ptr; + *ptr += mod; + return old; + } + + T atomic_fetch_sub(MemoryOrder ms = MemoryOrder.seq, T)(ref shared T val, T mod) pure @trusted + if (__traits(isIntegral, T)) + { + auto ptr = cast(T*)&val; + T old = *ptr; + *ptr -= mod; + return old; + } + + void atomicFence(MemoryOrder ms = MemoryOrder.seq)() pure @trusted {} + void pause() pure @trusted {} +} + + +// ----------------------------------------------------------------------- +// Shared helpers +// ----------------------------------------------------------------------- + +template TailShared(U) if (!is(U == shared)) +{ + alias TailShared = .TailShared!(shared U); +} + +template TailShared(S) if (is(S == shared)) +{ + static if (is(S U == shared U)) + { + static if (is(S : U)) + alias TailShared = U; + else + alias TailShared = S; + } + else + static assert(false); +} + +private: + +template IntOrLong(T) +{ + static if (T.sizeof > 4) + alias IntOrLong = long; + else + alias IntOrLong = int; +} + +// DMD inline-asm helpers (shared between x86 and x86_64) +version (D_InlineAsm_X86) + enum _have_dmd_asm = true; +else version (D_InlineAsm_X86_64) + enum _have_dmd_asm = true; +else + enum _have_dmd_asm = false; + +static if (_have_dmd_asm) +{ + enum : int + { + AX, BX, CX, DX, DI, SI, R8, R9 + } + + immutable string[4][8] registerNames = [ + ["AL", "AX", "EAX", "RAX"], + ["BL", "BX", "EBX", "RBX"], + ["CL", "CX", "ECX", "RCX"], + ["DL", "DX", "EDX", "RDX"], + ["DIL", "DI", "EDI", "RDI"], + ["SIL", "SI", "ESI", "RSI"], + ["R8B", "R8W", "R8D", "R8"], + ["R9B", "R9W", "R9D", "R9"], + ]; + + template RegIndex(T) + { + static if (T.sizeof == 1) + enum RegIndex = 0; + else static if (T.sizeof == 2) + enum RegIndex = 1; + else static if (T.sizeof == 4) + enum RegIndex = 2; + else static if (T.sizeof == 8) + enum RegIndex = 3; + else + static assert(false, "Invalid type"); + } + + enum SizedReg(int reg, T = size_t) = registerNames[reg][RegIndex!T]; + + // CTFE-only helper for building asm strings. Templated so it doesn't + // inherit the module-level @nogc (string concat is fine at compile time). + template simpleFormat(string format, string[] args) + { + enum simpleFormat = _simpleFormatImpl(format, args); + } + + string _simpleFormatImpl()(string format, const(string)[] args) pure @safe + { + string result; + outer: while (format.length) + { + foreach (i; 0 .. format.length) + { + if (format[i] == '%' || format[i] == '?') + { + bool isQ = format[i] == '?'; + result ~= format[0 .. i++]; + assert(i < format.length, "Invalid format string"); + if (format[i] == '%' || format[i] == '?') + { + assert(!isQ, "Invalid format string"); + result ~= format[i++]; + } + else + { + int index = 0; + assert(format[i] >= '0' && format[i] <= '9', "Invalid format string"); + while (i < format.length && format[i] >= '0' && format[i] <= '9') + index = index * 10 + (ubyte(format[i++]) - ubyte('0')); + if (!isQ) + result ~= args[index]; + else if (!args[index]) + { + size_t j = i; + for (; j < format.length;) + { + if (format[j++] == '\n') + break; + } + i = j; + } + } + format = format[i .. $]; + continue outer; + } + } + result ~= format; + break; + } + return result; + } +} diff --git a/src/urt/attribute.d b/src/urt/attribute.d index 081f11f..5bc2807 100644 --- a/src/urt/attribute.d +++ b/src/urt/attribute.d @@ -4,9 +4,70 @@ version (GNU) public import gcc.attributes; version (LDC) public import ldc.attributes; - version (DigitalMars) { enum restrict; enum weak; } + +// The public imports above already provide the core compiler attributes: +// @section("name") explicit linker section placement +// @weak weak linkage +// @restrict pointer aliasing hint +// @optStrategy(...) optimization strategy override +// @llvmAttr(...) arbitrary LLVM function attribute (LDC) +// @assumeUsed prevent dead-stripping +// +// Aliases for commonly attributes: + +version (LDC) +{ + enum noinline = llvmAttr("noinline"); + enum always_inline = llvmAttr("alwaysinline"); + enum naked = llvmAttr("naked"); // no prologue/epilogue + enum cold = llvmAttr("cold"); // optimizer: rarely executed (layout hint) + enum hot = llvmAttr("hot"); // optimizer: frequently executed (layout hint) + enum used = assumeUsed; // prevent linker dead-stripping +} +else +{ + enum noinline; + enum always_inline; + enum naked; + enum cold; + enum hot; + enum used; +} + + +// ── Memory placement ──────────────────────────────────── + +// @critical — code that must execute from internal SRAM. +// Use for ISRs, code that runs during flash erase/write, and paths +// that need deterministic latency (no cache-miss jitter). +// Default (no attribute) = XIP from flash via instruction cache. +version (Espressif) enum critical = section(".iram1"); +else version (Bouffalo) enum critical = section(".ramfunc"); +else version (STM32) enum critical = section(".ramfunc"); +else version (BK7231) enum critical = section(".ramfunc"); +else version (RP2350) enum critical = section(".ramfunc"); +else enum critical; + +// @persist — data that survives deep sleep / hibernate. +// Not initialized at startup — the whole point is retaining prior values. +// Only available on platforms with RTC or hibernate-capable memory. +version (Espressif) enum persist = section(".rtc_noinit"); +else version (BL808) enum persist = section(".hbn_ram"); +else enum persist; + +// @fast_data — data in the fastest available RAM (TCM/DTCM/SRAM). +// Use sparingly: these regions are small and shared with stack/GOT. +// Only meaningful on platforms with distinct fast/slow data regions. +version (STM32F7) enum fast_data = section(".dtcm_data"); +else version (Bouffalo) enum fast_data = section(".sram_data"); +else enum fast_data; + +// @bulk_data — large data in slow, abundant memory (PSRAM / ext RAM). +// Use for big buffers, caches, lookup tables where latency doesn't matter. +version (Espressif) enum bulk_data = section(".ext_ram.bss"); +else enum bulk_data; diff --git a/src/urt/conv.d b/src/urt/conv.d index 91f39ab..1263bd2 100644 --- a/src/urt/conv.d +++ b/src/urt/conv.d @@ -6,49 +6,67 @@ public import urt.string.format : toString; nothrow @nogc: - -// on error or not-a-number cases, bytesTaken will contain 0 -long parseInt(const(char)[] str, size_t* bytesTaken = null, int base = 10) pure +// Workaround for LLVM bug: riscv-isel hangs when stores into a stack buffer +// and memcmp on that buffer are visible in the same function with +// +unaligned-scalar-mem. Preventing inlining keeps them in separate functions. +// See: https://github.com/llvm/llvm-project/issues/XXXXX +pragma(inline, false) private bool streq(const(char)[] a, const(char)[] b) pure { - size_t i = 0; - bool neg = false; + if (a.length != b.length) + return false; + foreach (i; 0 .. a.length) + if (a[i] != b[i]) + return false; + return true; +} - if (str.length > 0) - { - char c = str.ptr[0]; - neg = c == '-'; - if (neg || c == '+') - i++; - } +// on error or not-a-number cases, bytes_taken will contain 0 - ulong value = str.ptr[i .. str.length].parseUint(bytesTaken, base); - if (bytesTaken && *bytesTaken != 0) - *bytesTaken += i; - return neg ? -cast(long)value : cast(long)value; +long parse_int(const(char)[] str, size_t* bytes_taken = null, uint base = 10) pure +{ + const(char)* s = str.ptr, e = s + str.length, p = s; + uint neg = parse_sign(p, e); + ulong value = p[0 .. e - p].parse_uint(bytes_taken, base); + if (bytes_taken && *bytes_taken != 0) + *bytes_taken += p - s; + return neg ? -long(value) : long(value); } -long parseIntWithDecimal(const(char)[] str, out ulong fixedPointDivisor, size_t* bytesTaken = null, int base = 10) pure +long parse_int_with_base(const(char)[] str, size_t* bytes_taken = null) pure { - size_t i = 0; - bool neg = false; + const(char)* s = str.ptr, e = s + str.length, p = s; + uint neg = parse_sign(p, e); + uint base = parse_base_prefix(p, e); + ulong i = p[0 .. e - p].parse_uint(bytes_taken, base); + if (bytes_taken && *bytes_taken != 0) + *bytes_taken += p - s; + return neg ? -long(i) : long(i); +} - if (str.length > 0) - { - char c = str.ptr[0]; - neg = c == '-'; - if (neg || c == '+') - i++; - } +long parse_int_with_exponent(const(char)[] str, out int exponent, size_t* bytes_taken = null, uint base = 10) pure +{ + const(char)* s = str.ptr, e = s + str.length, p = s; + uint neg = parse_sign(p, e); + ulong value = p[0 .. e - p].parse_uint_with_exponent(exponent, bytes_taken, base); + if (bytes_taken && *bytes_taken != 0) + *bytes_taken += p - s; + return neg ? -long(value) : long(value); +} - ulong value = str[i .. str.length].parseUintWithDecimal(fixedPointDivisor, bytesTaken, base); - if (bytesTaken && *bytesTaken != 0) - *bytesTaken += i; - return neg ? -cast(long)value : cast(long)value; +long parse_int_with_exponent_and_base(const(char)[] str, out int exponent, out uint base, size_t* bytes_taken = null) pure +{ + const(char)* s = str.ptr, e = s + str.length, p = s; + uint neg = parse_sign(p, e); + base = parse_base_prefix(p, e); + ulong value = p[0 .. e - p].parse_uint_with_exponent(exponent, bytes_taken, base); + if (bytes_taken && *bytes_taken != 0) + *bytes_taken += p - s; + return neg ? -long(value) : long(value); } -ulong parseUint(const(char)[] str, size_t* bytesTaken = null, int base = 10) pure +ulong parse_uint(const(char)[] str, size_t* bytes_taken = null, uint base = 10) pure { - assert(base > 1 && base <= 36, "Invalid base"); + debug assert(base > 1 && base <= 36, "Invalid base"); ulong value = 0; @@ -60,7 +78,7 @@ ulong parseUint(const(char)[] str, size_t* bytesTaken = null, int base = 10) pur for (; s < e; ++s) { uint digit = *s - '0'; - if (digit > 9) + if (digit >= base) break; value = value*base + digit; } @@ -69,99 +87,192 @@ ulong parseUint(const(char)[] str, size_t* bytesTaken = null, int base = 10) pur { for (; s < e; ++s) { - uint digit = getDigit(*s); + uint digit = get_digit(*s); if (digit >= base) break; value = value*base + digit; } } - if (bytesTaken) - *bytesTaken = s - str.ptr; + if (bytes_taken) + *bytes_taken = s - str.ptr; return value; } -ulong parseUintWithDecimal(const(char)[] str, out ulong fixedPointDivisor, size_t* bytesTaken = null, int base = 10) pure +ulong parse_uint_with_base(const(char)[] str, size_t* bytes_taken = null) pure { - assert(base > 1 && base <= 36, "Invalid base"); + const(char)* s = str.ptr, e = s + str.length, p = s; + uint base = parse_base_prefix(p, e); + ulong i = p[0 .. e - p].parse_uint(bytes_taken, base); + if (bytes_taken && *bytes_taken != 0) + *bytes_taken += p - s; + return i; +} - ulong value = 0; - ulong divisor = 1; +ulong parse_uint_with_exponent(const(char)[] str, out int exponent, size_t* bytes_taken = null, uint base = 10) pure +{ + debug assert(base > 1 && base <= 36, "Invalid base"); const(char)* s = str.ptr; const(char)* e = s + str.length; - // TODO: we could optimise the common base <= 10 case... + ulong value = 0; + int exp = 0; + uint digits = 0; + uint zero_seq = 0; + char c = void; for (; s < e; ++s) { - char c = *s; + c = *s; if (c == '.') { + if (s == str.ptr) + goto done; ++s; + exp = zero_seq; goto parse_decimal; } + else if (c == '0') + { + ++zero_seq; + continue; + } - uint digit = getDigit(c); + uint digit = get_digit(c); if (digit >= base) break; - value = value*base + digit; + + if (digits) + { + for (uint i = 0; i <= zero_seq; ++i) + value = value * base; + digits += zero_seq; + } + value += digit; + digits += 1; + zero_seq = 0; } - goto done; + + // number has no decimal point, tail zeroes are positive exp + if (!digits) + goto nothing; + + exp = zero_seq; + goto check_exp; parse_decimal: for (; s < e; ++s) { - uint digit = getDigit(*s); - if (digit >= base) + c = *s; + + if (c == '0') { - // if i == 1, then the first char was a '.' and the next was not numeric, so this is not a number! - if (s == str.ptr + 1) - s = str.ptr; + ++zero_seq; + continue; + } + + uint digit = get_digit(c); + if (digit >= base) break; + + if (digits) + { + for (uint i = 0; i <= zero_seq; ++i) + value = value * base; + digits += zero_seq; + } + value += digit; + digits += 1; + exp -= 1 + zero_seq; + zero_seq = 0; + } + if (!digits) + goto nothing; + +check_exp: + // check for exponent part + if (s + 1 < e && ((*s | 0x20) == 'e')) + { + c = s[1]; + bool exp_neg = c == '-'; + if (exp_neg || c == '+') + { + if (s + 2 >= e || !s[2].is_numeric) + goto done; + s += 2; + } + else + { + if (!c.is_numeric) + goto done; + ++s; } - value = value*base + digit; - divisor *= base; + + int exp_value = 0; + for (; s < e; ++s) + { + uint digit = *s - '0'; + if (digit > 9) + break; + exp_value = exp_value * 10 + digit; + } + exp += exp_neg ? -exp_value : exp_value; } done: - fixedPointDivisor = divisor; - if (bytesTaken) - *bytesTaken = s - str.ptr; + exponent = exp; + if (bytes_taken) + *bytes_taken = s - str.ptr; return value; + +nothing: + exp = 0; + goto done; } -ulong parseUintWithBase(const(char)[] str, size_t* bytesTaken = null) pure +ulong parse_uint_with_exponent_and_base(const(char)[] str, out int exponent, out uint base, size_t* bytes_taken = null) pure { - const(char)* p = str.ptr; - int base = parseBasePrefix(str); - ulong i = parseUint(str, bytesTaken, base); - if (bytesTaken && *bytesTaken != 0) - *bytesTaken += str.ptr - p; - return i; + const(char)* s = str.ptr, e = s + str.length, p = s; + base = parse_base_prefix(p, e); + ulong value = p[0 .. e - p].parse_uint_with_exponent(exponent, bytes_taken, base); + if (value && *bytes_taken != 0) + *bytes_taken += p - s; + return value; } - unittest { size_t taken; - ulong divisor; - assert(parseUint("123") == 123); - assert(parseInt("+123.456") == 123); - assert(parseInt("-123.456", null, 10) == -123); - assert(parseUintWithDecimal("123.456", divisor, null, 10) == 123456 && divisor == 1000); - assert(parseIntWithDecimal("123.456.789", divisor, &taken, 16) == 1193046 && taken == 7 && divisor == 4096); - assert(parseInt("11001", null, 2) == 25); - assert(parseIntWithDecimal("-AbCdE.f", divisor, null, 16) == -11259375 && divisor == 16); - assert(parseInt("123abc", &taken, 10) == 123 && taken == 3); - assert(parseInt("!!!", &taken, 10) == 0 && taken == 0); - assert(parseInt("-!!!", &taken, 10) == 0 && taken == 0); - assert(parseInt("Wow", &taken, 36) == 42368 && taken == 3); - assert(parseUintWithBase("0x100", &taken) == 0x100 && taken == 5); + assert(parse_uint("123") == 123); + assert(parse_int("+123.456") == 123); + assert(parse_int("-123.456", null, 10) == -123); + assert(parse_int("11001", null, 2) == 25); + assert(parse_int("123abc", &taken, 10) == 123 && taken == 3); + assert(parse_int("!!!", &taken, 10) == 0 && taken == 0); + assert(parse_int("-!!!", &taken, 10) == 0 && taken == 0); + assert(parse_int("Wow", &taken, 36) == 42368 && taken == 3); + assert(parse_uint_with_base("0x100", &taken) == 0x100 && taken == 5); + assert(parse_int_with_base("-0x100", &taken) == -0x100 && taken == 6); + + int e; + assert("0001023000".parse_uint_with_exponent(e, &taken, 10) == 1023 && e == 3 && taken == 10); + assert("0.0012003000".parse_uint_with_exponent(e, &taken, 10) == 12003 && e == -7 && taken == 12); + assert("00010.23000".parse_uint_with_exponent(e, &taken, 10) == 1023 && e == -2 && taken == 11); + assert("00012300.0".parse_uint_with_exponent(e, &taken, 10) == 123 && e == 2 && taken == 10); + assert("00100.00230".parse_uint_with_exponent(e, &taken, 10) == 1000023 && e == -4 && taken == 11); + assert("0.0".parse_uint_with_exponent(e, &taken, 10) == 0 && e == 0 && taken == 3); + assert(".01".parse_uint_with_exponent(e, &taken, 10) == 0 && e == 0 && taken == 0); + assert("10e2".parse_uint_with_exponent(e, &taken, 10) == 1 && e == 3 && taken == 4); + assert("0.01E+2".parse_uint_with_exponent(e, &taken, 10) == 1 && e == 0 && taken == 7); + assert("0.01E".parse_uint_with_exponent(e, &taken, 10) == 1 && e == -2 && taken == 4); + assert("0.01Ex".parse_uint_with_exponent(e, &taken, 10) == 1 && e == -2 && taken == 4); + assert("0.01E-".parse_uint_with_exponent(e, &taken, 10) == 1 && e == -2 && taken == 4); + assert("0.01E-x".parse_uint_with_exponent(e, &taken, 10) == 1 && e == -2 && taken == 4); } -int parseIntFast(ref const(char)[] text, out bool success) pure +int parse_int_fast(ref const(char)[] text, out bool success) pure { if (!text.length) return 0; @@ -210,30 +321,38 @@ unittest { bool success; const(char)[] text = "123"; - assert(parseIntFast(text, success) == 123 && success == true && text.empty); + assert(parse_int_fast(text, success) == 123 && success == true && text.empty); text = "-2147483648abc"; - assert(parseIntFast(text, success) == -2147483648 && success == true && text.length == 3); + assert(parse_int_fast(text, success) == -2147483648 && success == true && text.length == 3); text = "2147483648"; - assert(parseIntFast(text, success) == 0 && success == false); + assert(parse_int_fast(text, success) == 0 && success == false); text = "-2147483649"; - assert(parseIntFast(text, success) == 0 && success == false); + assert(parse_int_fast(text, success) == 0 && success == false); text = "2147483650"; - assert(parseIntFast(text, success) == 0 && success == false); + assert(parse_int_fast(text, success) == 0 && success == false); } -// on error or not-a-number, result will be nan and bytesTaken will contain 0 -double parseFloat(const(char)[] str, size_t* bytesTaken = null, int base = 10) pure +// on error or not-a-number, result will be nan and bytes_taken will contain 0 +double parse_float(const(char)[] str, size_t* bytes_taken = null, uint base = 10) pure { - // TODO: E-notation... - size_t taken = void; - ulong div = void; - long value = str.parseIntWithDecimal(div, &taken, base); - if (bytesTaken) - *bytesTaken = taken; + import urt.math : pow; + + int e; + size_t taken; + long mantissa = str.parse_int_with_exponent(e, &taken, base); + if (bytes_taken) + *bytes_taken = taken; if (taken == 0) return double.nan; - return cast(double)value / div; + + // TODO: the real work needs to happen here! + // we want all the bits of precision! + + if (__ctfe) + return mantissa * double(base)^^e; + else + return mantissa * pow(double(base), e); } unittest @@ -245,26 +364,115 @@ unittest } size_t taken; - assert(fcmp(parseFloat("123.456"), 123.456)); - assert(fcmp(parseFloat("+123.456"), 123.456)); - assert(fcmp(parseFloat("-123.456.789"), -123.456)); - assert(fcmp(parseFloat("1101.11", &taken, 2), 13.75) && taken == 7); - assert(parseFloat("xyz", &taken) is double.nan && taken == 0); + assert(fcmp(parse_float("123.456"), 123.456)); + assert(fcmp(parse_float("+123.456"), 123.456)); + assert(fcmp(parse_float("-123.456.789"), -123.456)); + assert(fcmp(parse_float("-123.456e10"), -1.23456e+12)); + assert(fcmp(parse_float("1101.11", &taken, 2), 13.75) && taken == 7); + assert(parse_float("xyz", &taken) is double.nan && taken == 0); } -ptrdiff_t formatInt(long value, char[] buffer, uint base = 10, uint width = 0, char fill = ' ', bool showSign = false) pure +ptrdiff_t parse(T)(const char[] text, out T result) +{ + import urt.array : beginsWith; + import urt.traits; + + alias UT = Unqual!T; + + static if (is(UT == bool)) + { + if (text.beginsWith("true")) + { + result = true; + return 4; + } + result = false; + if (text.beginsWith("false")) + return 5; + return -1; + } + else static if (is_some_int!T) + { + size_t taken; + static if (is_signed_int!T) + long r = text.parse_int(&taken); + else + ulong r = text.parse_uint(&taken); + if (!taken) + return -1; + if (r >= T.min && r <= T.max) + { + result = cast(T)r; + return taken; + } + return -2; + } + else static if (is_some_float!T) + { + size_t taken; + double f = text.parse_float(&taken); + if (!taken) + return -1; + result = cast(T)f; + return taken; + } + else static if (is_enum!T) + { + static assert(false, "TODO: do we want to parse from enum keys?"); + // case-sensitive? + } + else static if (is(T == struct) && __traits(compiles, { result.fromString(text); })) + { + return result.fromString(text); + } + else + static assert(false, "Cannot parse " ~ T.stringof ~ " from string"); +} + +unittest +{ + { + bool r; + assert("true".parse(r) == 4 && r == true); + assert("false".parse(r) == 5 && r == false); + assert("wow".parse(r) == -1); + } + { + int r; + assert("-10".parse(r) == 3 && r == -10); + } + { + ubyte r; + assert("10".parse(r) == 2 && r == 10); + assert("-10".parse(r) == -1); + assert("257".parse(r) == -2); + } + { + float r; + assert("10".parse(r) == 2 && r == 10.0f); + assert("-2.5".parse(r) == 4 && r == -2.5f); + } + { + import urt.inet; + IPAddr r; + assert("10.0.0.1".parse(r) == 8 && r == IPAddr(10,0,0,1)); + } +} + + +ptrdiff_t format_int(long value, char[] buffer, uint base = 10, uint width = 0, char fill = ' ', bool show_sign = false) pure { const bool neg = value < 0; - showSign |= neg; + show_sign |= neg; - if (buffer.ptr && buffer.length < showSign) + if (buffer.ptr && buffer.length < show_sign) return -1; ulong i = neg ? -value : value; - ptrdiff_t r = formatUint(i, buffer.ptr ? buffer.ptr[(width == 0 ? showSign : 0) .. buffer.length] : null, base, width, fill); - if (r < 0 || !showSign) + ptrdiff_t r = format_uint(i, buffer.ptr ? buffer.ptr[(width == 0 ? show_sign : 0) .. buffer.length] : null, base, width, fill); + if (r < 0 || !show_sign) return r; if (buffer.ptr) @@ -286,10 +494,10 @@ ptrdiff_t formatInt(long value, char[] buffer, uint base = 10, uint width = 0, c if (buffer.ptr[0] == fill) { // we don't need to shift it left... - size_t sgnOffset = 0; - while (buffer.ptr[sgnOffset + 1] == fill) - ++sgnOffset; - buffer.ptr[sgnOffset] = sgn; + size_t sgn_offset = 0; + while (buffer.ptr[sgn_offset + 1] == fill) + ++sgn_offset; + buffer.ptr[sgn_offset] = sgn; return r; } @@ -309,20 +517,20 @@ ptrdiff_t formatInt(long value, char[] buffer, uint base = 10, uint width = 0, c return r + 1; } -ptrdiff_t formatUint(ulong value, char[] buffer, uint base = 10, uint width = 0, char fill = ' ') pure +ptrdiff_t format_uint(ulong value, char[] buffer, uint base = 10, uint width = 0, char fill = ' ') pure { import urt.util : max; assert(base >= 2 && base <= 36, "Invalid base"); ulong i = value; - uint numLen = 0; + uint num_len = 0; char[64] t = void; if (i == 0) { if (buffer.length > 0) t.ptr[0] = '0'; - numLen = 1; + num_len = 1; } else { @@ -334,14 +542,14 @@ ptrdiff_t formatUint(ulong value, char[] buffer, uint base = 10, uint width = 0, if (buffer.ptr) { int d = cast(int)(i % base); - t.ptr[numLen] = cast(char)((d < 10 ? '0' : 'A' - 10) + d); + t.ptr[num_len] = cast(char)((d < 10 ? '0' : 'A' - 10) + d); } - ++numLen; + ++num_len; } } - uint len = max(numLen, width); - uint padding = width > numLen ? width - numLen : 0; + uint len = max(num_len, width); + uint padding = width > num_len ? width - num_len : 0; if (buffer.ptr) { @@ -351,7 +559,7 @@ ptrdiff_t formatUint(ulong value, char[] buffer, uint base = 10, uint width = 0, size_t offset = 0; while (padding--) buffer.ptr[offset++] = fill; - for (uint j = numLen; j > 0; ) + for (uint j = num_len; j > 0; ) buffer.ptr[offset++] = t[--j]; } return len; @@ -360,51 +568,65 @@ ptrdiff_t formatUint(ulong value, char[] buffer, uint base = 10, uint width = 0, unittest { char[64] buffer; - assert(formatInt(0, null) == 1); - assert(formatInt(14, null) == 2); - assert(formatInt(14, null, 16) == 1); - assert(formatInt(-14, null) == 3); - assert(formatInt(-14, null, 16) == 2); - assert(formatInt(-14, null, 16, 3, '0') == 3); - assert(formatInt(-123, null, 10, 6) == 6); - assert(formatInt(-123, null, 10, 3) == 4); - assert(formatInt(-123, null, 10, 2) == 4); - - size_t len = formatInt(0, buffer); - assert(buffer[0 .. len] == "0"); - len = formatInt(14, buffer); - assert(buffer[0 .. len] == "14"); - len = formatInt(14, buffer, 2); - assert(buffer[0 .. len] == "1110"); - len = formatInt(14, buffer, 8, 3); - assert(buffer[0 .. len] == " 16"); - len = formatInt(14, buffer, 16, 4, '0'); - assert(buffer[0 .. len] == "000E"); - len = formatInt(-14, buffer, 16, 3, '0'); - assert(buffer[0 .. len] == "-0E"); - len = formatInt(12345, buffer, 10, 3); - assert(buffer[0 .. len] == "12345"); - len = formatInt(-123, buffer, 10, 6); - assert(buffer[0 .. len] == " -123"); + assert(format_int(0, null) == 1); + assert(format_int(14, null) == 2); + assert(format_int(14, null, 16) == 1); + assert(format_int(-14, null) == 3); + assert(format_int(-14, null, 16) == 2); + assert(format_int(-14, null, 16, 3, '0') == 3); + assert(format_int(-123, null, 10, 6) == 6); + assert(format_int(-123, null, 10, 3) == 4); + assert(format_int(-123, null, 10, 2) == 4); + + size_t len = format_int(0, buffer); + assert(streq(buffer[0 .. len], "0")); + len = format_int(14, buffer); + assert(streq(buffer[0 .. len], "14")); + len = format_int(14, buffer, 2); + assert(streq(buffer[0 .. len], "1110")); + len = format_int(14, buffer, 8, 3); + assert(streq(buffer[0 .. len], " 16")); + len = format_int(14, buffer, 16, 4, '0'); + assert(streq(buffer[0 .. len], "000E")); + len = format_int(-14, buffer, 16, 3, '0'); + assert(streq(buffer[0 .. len], "-0E")); + len = format_int(12345, buffer, 10, 3); + assert(streq(buffer[0 .. len], "12345")); + len = format_int(-123, buffer, 10, 6); + assert(streq(buffer[0 .. len], " -123")); } -ptrdiff_t formatFloat(double value, char[] buffer, const(char)[] format = null) // pure +ptrdiff_t format_float(double value, char[] buffer, const(char)[] format = null) pure { // TODO: this function should be oblitereated and implemented natively... // CRT call can't CTFE, which is a shame - import core.stdc.stdio; import urt.string.format : concat; - char[16] fmt = void; char[64] result = void; - assert(format.length <= fmt.sizeof - 3, "Format string buffer overflow"); - concat(fmt, "%", format, "g\0"); - int len = snprintf(result.ptr, result.length, fmt.ptr, value); - if (len < 0) - return -2; + // parse format; precision is '.10' => 10 digits + int digits = 6; + size_t dot = format.findFirst('.'); + if (dot < format.length) + digits = cast(int)parse_uint(format[dot + 1 .. $]); + version (Windows) + { + import urt.internal.stdc.stdlib : _gcvt_s; + int err = _gcvt_s(result.ptr, result.length, value, digits); + if (err != 0) + return -2; + } + else + { + import urt.internal.stdc.stdlib : gcvt; + if (gcvt(value, digits, result.ptr) is null) + return -2; + } + size_t len = result.ptr.strlen(); + if (result[len - 1] == '.') + --len; // trim trailing '.' if no digits follow it if (buffer.ptr) { if (len > buffer.length) @@ -414,6 +636,31 @@ ptrdiff_t formatFloat(double value, char[] buffer, const(char)[] format = null) return len; } +unittest +{ + import urt.io; + char[64] buf; + auto len = format_float(0.0, buf); + assert(buf[0..len] == "0"); + len = format_float(1.0, buf); + assert(buf[0..len] == "1"); + len = format_float(-1.0, buf); + assert(buf[0..len] == "-1"); + len = format_float(3.14159, buf); + assert(buf[0..len] == "3.14159"); + len = format_float(3.14159, buf, ".3"); + assert(buf[0..len] == "3.14"); + len = format_float(1.5, buf); + assert(buf[0..len] == "1.5"); + len = format_float(1e6, buf); + assert(buf[0..len] == "1.e+006" || buf[0..len] == "1e+06"); + len = format_float(1e6, buf, ".7"); + assert(buf[0..len] == "1000000"); + len = format_float(0.001, buf); + assert(buf[0..len] == "0.001" || buf[0..len] == "1.e-003"); // i don't know why it emits e-3 :/ + len = format_float(-0.0, buf); + assert(buf[0..len] == "-0"); // do we want to print -0? +} template to(T) @@ -424,9 +671,9 @@ template to(T) { long to(const(char)[] str) { - int base = parseBasePrefix(str); + uint base = parse_base_prefix(str); size_t taken; - long r = parseInt(str, &taken, base); + long r = parse_int(str, &taken, base); assert(taken == str.length, "String is not numeric"); return r; } @@ -435,19 +682,19 @@ template to(T) { double to(const(char)[] str) { - int base = parseBasePrefix(str); + uint base = parse_base_prefix(str); size_t taken; - double r = parseFloat(str, &taken, base); + double r = parse_float(str, &taken, base); assert(taken == str.length, "String is not numeric"); return r; } } - else static if (isSomeInt!T) // call-through for other int types; reduce instantiation bloat + else static if (is_some_int!T) // call-through for other int types; reduce instantiation bloat { T to(const(char)[] str) => cast(T)to!long(str); } - else static if (isSomeFloat!T) // call-through for other float types; reduce instantiation bloat + else static if (is_some_float!T) // call-through for other float types; reduce instantiation bloat { T to(const(char)[] str) => cast(T)to!double(str); @@ -479,38 +726,46 @@ template to(T) private: -uint getDigit(char c) pure +// valid result is 0 .. 35; result is garbage outside that bound +uint get_digit(char c) pure { - uint zeroBase = c - '0'; - if (zeroBase < 10) - return zeroBase; - uint ABase = c - 'A'; - if (ABase < 26) - return ABase + 10; - uint aBase = c - 'a'; - if (aBase < 26) - return aBase + 10; - return -1; + uint zero_base = c - '0'; + if (zero_base < 10) + return zero_base; + uint a_base = (c | 0x20) - 'a'; + return 10 + (a_base & 0xFF); } -int parseBasePrefix(ref const(char)[] str) pure +uint parse_base_prefix(ref const(char)* str, const(char)* end) pure { - int base = 10; - if (str.length >= 2) + uint base = 10; + if (str + 2 <= end && str[0] == '0') { - if (str[0..2] == "0x") - base = 16, str = str[2..$]; - else if (str[0..2] == "0b") - base = 2, str = str[2..$]; - else if (str[0..2] == "0o") - base = 8, str = str[2..$]; + if (str[1] == 'x') + base = 16, str += 2; + else if (str[1] == 'b') + base = 2, str += 2; + else if (str[1] == 'o') + base = 8, str += 2; } return base; } +uint parse_sign(ref const(char)* str, const(char)* end) pure +{ + if (str == end) + return 0; + // NOTE: ascii is '+' = 43, '-' = 45 + uint neg = *str - '+'; + if (neg > 2 || neg == 1) + return 0; + ++str; + return neg; // neg is 0 (+) or 2 (-) +} + /+ -size_t formatStruct(T)(ref T value, char[] buffer) nothrow @nogc +size_t format_struct(T)(ref T value, char[] buffer) nothrow @nogc { import urt.string.format; @@ -518,7 +773,7 @@ size_t formatStruct(T)(ref T value, char[] buffer) nothrow @nogc alias args = value.tupleof; // alias args = AliasSeq!(value.tupleof); -// alias args = InterleaveSeparator!(", ", value.tupleof); +// alias args = INTERLEAVE_SEPARATOR!(", ", value.tupleof); // pragma(msg, args); return concat(buffer, args).length; } @@ -530,7 +785,7 @@ unittest Packet p; char[1024] buffer; - size_t len = formatStruct(p, buffer); + size_t len = format_struct(p, buffer); assert(buffer[0 .. len] == "Packet()"); } diff --git a/src/urt/crc.d b/src/urt/crc.d index 5010e3f..e45b6da 100644 --- a/src/urt/crc.d +++ b/src/urt/crc.d @@ -1,51 +1,54 @@ module urt.crc; -import urt.meta : intForWidth; -import urt.traits : isUnsignedInt; +import urt.meta : IntForWidth; +import urt.traits : is_unsigned_int; nothrow @nogc: enum Algorithm : ubyte { - CRC16_USB, - CRC16_MODBUS, - CRC16_KERMIT, - CRC16_XMODEM, - CRC16_CCITT_FALSE, - CRC16_ISO_HDLC, - CRC16_DNP, - CRC32_ISO_HDLC, - CRC32_CASTAGNOLI, - - CRC16_Default_ShortPacket = CRC16_KERMIT, // good default choice for small packets - CRC32_Default = CRC32_CASTAGNOLI, // has SSE4.2 hardware implementation - - // aliases - CRC16_BLUETOOTH = CRC16_KERMIT, - CRC16_CCITT_TRUE = CRC16_KERMIT, - CRC16_CCITT = CRC16_KERMIT, - CRC16_EZSP = CRC16_CCITT_FALSE, - CRC16_IBM_SDLC = CRC16_ISO_HDLC, - CRC32_NVME = CRC32_CASTAGNOLI, + crc8_smbus, + crc16_usb, + crc16_modbus, + crc16_kermit, + crc16_xmodem, + crc16_ccitt_false, + crc16_iso_hdlc, + crc16_dnp, + crc32_iso_hdlc, + crc32_castagnoli, + + crc8_itu_t = crc8_smbus, + crc8_autosar = crc8_smbus, + crc16_default_short_packet = crc16_kermit, // good default choice for small packets + crc32_default = crc32_castagnoli, // has SSE4.2 hardware implementation + + // aliases + crc16_bluetooth = crc16_kermit, + crc16_ccitt_true = crc16_kermit, + crc16_ccitt = crc16_kermit, + crc16_ezsp = crc16_ccitt_false, + crc16_ibm_sdlc = crc16_iso_hdlc, + crc32_nvme = crc32_castagnoli, } -struct CRCParams +struct crc_params { ubyte width; bool reflect; uint poly; uint initial; - uint finalXor; + uint final_xor; uint check; } -alias CRCTable(Algorithm algo) = CRCTable!(paramTable[algo].width, paramTable[algo].poly, paramTable[algo].reflect); -alias CRCType(Algorithm algo) = intForWidth!(paramTable[algo].width); +alias CRCType(Algorithm algo) = IntForWidth!(param_table[algo].width); +alias crc_table(Algorithm algo) = crc_table!(param_table[algo].width, param_table[algo].poly, param_table[algo].reflect); // compute a CRC with runtime parameters -T calculateCRC(T = uint)(const void[] data, ref const CRCParams params, ref const T[256] table) pure - if (isUnsignedInt!T) +T calculate_crc(T = uint)(const void[] data, ref const crc_params params, ref const T[256] table) pure + if (is_unsigned_int!T) { assert(params.width <= T.sizeof*8, "T is too small for the CRC width"); @@ -64,99 +67,203 @@ T calculateCRC(T = uint)(const void[] data, ref const CRCParams params, ref cons crc = cast(T)((crc << 8) ^ table[(crc >> 8) ^ b]); } - return crc ^ cast(T)params.finalXor; + return crc ^ cast(T)params.final_xor; } // compute a CRC with hard-coded parameters -T calculateCRC(Algorithm algo, T = CRCType!algo)(const void[] data, T initial = cast(T)paramTable[algo].initial^paramTable[algo].finalXor) pure - if (isUnsignedInt!T) +T calculate_crc(Algorithm algo, T = CRCType!algo)(const void[] data, T initial = cast(T)param_table[algo].initial^param_table[algo].final_xor) pure + if (!hardware_crc_support!(algo, T) && is_unsigned_int!T) { - enum CRCParams params = paramTable[algo]; + enum crc_params params = param_table[algo]; static assert(params.width <= T.sizeof*8, "T is too small for the CRC width"); - alias table = CRCTable!algo; + alias table = crc_table!algo; const ubyte[] bytes = cast(ubyte[])data; - static if (params.finalXor) - T crc = initial ^ params.finalXor; + static if (params.final_xor) + T crc = initial ^ params.final_xor; else T crc = initial; foreach (b; bytes) { - static if (params.reflect) + static if (params.width <= 8) + crc = table[crc ^ b]; + else static if (params.reflect) crc = (crc >> 8) ^ table[cast(ubyte)crc ^ b]; else crc = cast(T)((crc << 8) ^ table[(crc >> 8) ^ b]); } - static if (params.finalXor) - return T(crc ^ params.finalXor); + static if (params.final_xor) + return T(crc ^ params.final_xor); else return crc; } +T calculate_crc(Algorithm algo, T = CRCType!algo)(const void[] data, T initial = cast(T)param_table[algo].initial^param_table[algo].final_xor) pure + if (hardware_crc_support!(algo, T) && is_unsigned_int!T) +{ + version (Espressif) + { + enum crc_params params = param_table[algo]; + enum T fxor = cast(T)params.final_xor; + + const ubyte* ptr = cast(ubyte*)data.ptr; + uint len = cast(uint)data.length; + + static if (params.width == 32) + { + static if (params.reflect) + alias rom_crc = crc32_le; + else + alias rom_crc = crc32_be; + return cast(T)(~rom_crc(cast(uint)~(initial ^ fxor), ptr, len) ^ fxor); + } + else static if (params.width == 16) + { + static if (params.reflect) + alias rom_crc = crc16_le; + else + alias rom_crc = crc16_be; + return cast(T)(cast(ushort)(~rom_crc(cast(ushort)~(initial ^ fxor), ptr, len) ^ fxor)); + } + else static if (params.width == 8) + { + static if (params.reflect) + alias rom_crc = crc8_le; + else + alias rom_crc = crc8_be; + return cast(T)(cast(ubyte)(~rom_crc(cast(ubyte)~(initial ^ fxor), ptr, len) ^ fxor)); + } + } + else + static assert(false, "Hardware CRC support claimed, but not implemented for this platform"); +} + + // computes 2 CRC's for 2 points in the data stream... -T calculateCRC_2(Algorithm algo, T = intForWidth!(paramTable[algo].width*2))(const void[] data, uint earlyOffset) pure - if (isUnsignedInt!T) +T calculate_crc_2(Algorithm algo, T = IntForWidth!(param_table[algo].width*2))(const void[] data, uint early_offset) pure + if (!hardware_crc_support!(algo, T) && is_unsigned_int!T) { - enum CRCParams params = paramTable[algo]; + enum crc_params params = param_table[algo]; static assert(params.width * 2 <= T.sizeof*8, "T is too small for the CRC width"); - alias table = CRCTable!algo; + alias CT = CRCType!algo; + alias table = crc_table!algo; const ubyte[] bytes = cast(ubyte[])data; - T highCRC = 0; - T crc = cast(T)params.initial; + CT high_crc = 0; + CT crc = cast(CT)params.initial; size_t i = 0; for (; i < bytes.length; ++i) { - if (i == earlyOffset) + if (i == early_offset) { - highCRC = crc; + high_crc = crc; goto fast_loop; // skips a redundant loop entry check } - static if (params.reflect) + static if (params.width <= 8) + crc = table[crc ^ bytes[i]]; + else static if (params.reflect) crc = (crc >> 8) ^ table[cast(ubyte)crc ^ bytes[i]]; else - crc = cast(T)((crc << 8) ^ table[(crc >> 8) ^ bytes[i]]); + crc = cast(CT)((crc << 8) ^ table[(crc >> 8) ^ bytes[i]]); } goto done; // skip over the fast loop entry check (which will fail) for (; i < bytes.length; ++i) { fast_loop: - static if (params.reflect) + static if (params.width <= 8) + crc = table[crc ^ bytes[i]]; + else static if (params.reflect) crc = (crc >> 8) ^ table[cast(ubyte)crc ^ bytes[i]]; else - crc = cast(T)((crc << 8) ^ table[(crc >> 8) ^ bytes[i]]); + crc = cast(CT)((crc << 8) ^ table[(crc >> 8) ^ bytes[i]]); } done: - static if (params.finalXor) + static if (params.final_xor) { - crc ^= cast(T)params.finalXor; - highCRC ^= cast(T)params.finalXor; + crc ^= cast(CT)params.final_xor; + high_crc ^= cast(CT)params.final_xor; } static if (params.width <= 8) - return ushort(crc | highCRC << 8); + return cast(T)(cast(uint)crc | (cast(uint)high_crc << 8)); else static if (params.width <= 16) - return uint(crc | highCRC << 16); - else if (params.width <= 32) - return ulong(crc | highCRC << 32); + return cast(T)(cast(uint)crc | (cast(uint)high_crc << 16)); + else static if (params.width <= 32) + return cast(T)(cast(ulong)crc | (cast(ulong)high_crc << 32)); +} + +T calculate_crc_2(Algorithm algo, T = IntForWidth!(param_table[algo].width*2))(const void[] data, uint early_offset) pure + if (hardware_crc_support!(algo, T) && is_unsigned_int!T) +{ + version (Espressif) + { + enum crc_params params = param_table[algo]; + + const ubyte* buf = cast(ubyte*)data.ptr; + uint len = cast(uint)data.length; + + enum uint fxor = params.final_xor; + + static if (params.width == 32) + { + static if (params.reflect) + alias rom_crc = crc32_le; + else + alias rom_crc = crc32_be; + + uint raw_init = cast(uint)~(params.initial ^ fxor); + uint raw_early = rom_crc(raw_init, buf, early_offset); + uint early = ~raw_early ^ fxor; + uint full = ~rom_crc(raw_early, buf + early_offset, len - early_offset) ^ fxor; + return cast(T)(cast(ulong)full | (cast(ulong)early << 32)); + } + else static if (params.width == 16) + { + static if (params.reflect) + alias rom_crc = crc16_le; + else + alias rom_crc = crc16_be; + + ushort raw_init = cast(ushort)~(params.initial ^ fxor); + ushort raw_early = rom_crc(raw_init, buf, early_offset); + ushort early = cast(ushort)(~raw_early ^ fxor); + ushort full = cast(ushort)(~rom_crc(raw_early, buf + early_offset, len - early_offset) ^ fxor); + return cast(T)(cast(uint)full | (cast(uint)early << 16)); + } + else static if (params.width == 8) + { + static if (params.reflect) + alias rom_crc = crc8_le; + else + alias rom_crc = crc8_be; + + ubyte raw_init = cast(ubyte)~(params.initial ^ fxor); + ubyte raw_early = rom_crc(raw_init, buf, early_offset); + ubyte early = cast(ubyte)(~raw_early ^ fxor); + ubyte full = cast(ubyte)(~rom_crc(raw_early, buf + early_offset, len - early_offset) ^ fxor); + return cast(T)(cast(ushort)full | (cast(ushort)early << 8)); + } + } + else + static assert(false, "Hardware CRC support claimed, but not implemented for this platform"); } -T[256] generateCRCTable(T)(ref const CRCParams params) pure - if (isUnsignedInt!T) +T[256] generate_crc_table(T)(ref const crc_params params) pure + if (is_unsigned_int!T) { - enum typeWidth = T.sizeof * 8; - assert(params.width <= typeWidth && params.width > typeWidth/2, "CRC width must match the size of the type"); - T topBit = cast(T)(1 << (params.width - 1)); + enum type_width = T.sizeof * 8; + assert(params.width <= type_width && params.width > type_width/2, "CRC width must match the size of the type"); + T top_bit = cast(T)(1 << (params.width - 1)); T[256] table = void; @@ -169,7 +276,7 @@ T[256] generateCRCTable(T)(ref const CRCParams params) pure crc <<= (params.width - 8); // Shift to align with the polynomial width foreach (_; 0..8) { - if ((crc & topBit) != 0) + if ((crc & top_bit) != 0) crc = cast(T)((crc << 1) ^ params.poly); else crc <<= 1; @@ -189,42 +296,126 @@ unittest { immutable ubyte[9] checkData = ['1','2','3','4','5','6','7','8','9']; - assert(calculateCRC!(Algorithm.CRC16_MODBUS)(checkData[]) == paramTable[Algorithm.CRC16_MODBUS].check); - assert(calculateCRC!(Algorithm.CRC16_EZSP)(checkData[]) == paramTable[Algorithm.CRC16_EZSP].check); - assert(calculateCRC!(Algorithm.CRC16_KERMIT)(checkData[]) == paramTable[Algorithm.CRC16_KERMIT].check); - assert(calculateCRC!(Algorithm.CRC16_USB)(checkData[]) == paramTable[Algorithm.CRC16_USB].check); - assert(calculateCRC!(Algorithm.CRC16_XMODEM)(checkData[]) == paramTable[Algorithm.CRC16_XMODEM].check); - assert(calculateCRC!(Algorithm.CRC16_ISO_HDLC)(checkData[]) == paramTable[Algorithm.CRC16_ISO_HDLC].check); - assert(calculateCRC!(Algorithm.CRC16_DNP)(checkData[]) == paramTable[Algorithm.CRC16_DNP].check); - assert(calculateCRC!(Algorithm.CRC32_ISO_HDLC)(checkData[]) == paramTable[Algorithm.CRC32_ISO_HDLC].check); - assert(calculateCRC!(Algorithm.CRC32_CASTAGNOLI)(checkData[]) == paramTable[Algorithm.CRC32_CASTAGNOLI].check); - - // check that rolling CRC works... - ushort crc = calculateCRC!(Algorithm.CRC16_MODBUS)(checkData[0 .. 5]); - assert(calculateCRC!(Algorithm.CRC16_MODBUS)(checkData[5 .. 9], crc) == paramTable[Algorithm.CRC16_MODBUS].check); - crc = calculateCRC!(Algorithm.CRC16_ISO_HDLC)(checkData[0 .. 5]); - assert(calculateCRC!(Algorithm.CRC16_ISO_HDLC)(checkData[5 .. 9], crc) == paramTable[Algorithm.CRC16_ISO_HDLC].check); - uint crc32 = calculateCRC!(Algorithm.CRC32_ISO_HDLC)(checkData[0 .. 5]); - assert(calculateCRC!(Algorithm.CRC32_ISO_HDLC)(checkData[5 .. 9], crc32) == paramTable[Algorithm.CRC32_ISO_HDLC].check); + // verify all algorithms against their check values ("123456789") + assert(calculate_crc!(Algorithm.crc8_smbus)(checkData[]) == param_table[Algorithm.crc8_smbus].check); + assert(calculate_crc!(Algorithm.crc16_modbus)(checkData[]) == param_table[Algorithm.crc16_modbus].check); + assert(calculate_crc!(Algorithm.crc16_ezsp)(checkData[]) == param_table[Algorithm.crc16_ezsp].check); + assert(calculate_crc!(Algorithm.crc16_kermit)(checkData[]) == param_table[Algorithm.crc16_kermit].check); + assert(calculate_crc!(Algorithm.crc16_usb)(checkData[]) == param_table[Algorithm.crc16_usb].check); + assert(calculate_crc!(Algorithm.crc16_xmodem)(checkData[]) == param_table[Algorithm.crc16_xmodem].check); + assert(calculate_crc!(Algorithm.crc16_iso_hdlc)(checkData[]) == param_table[Algorithm.crc16_iso_hdlc].check); + assert(calculate_crc!(Algorithm.crc16_dnp)(checkData[]) == param_table[Algorithm.crc16_dnp].check); + assert(calculate_crc!(Algorithm.crc32_iso_hdlc)(checkData[]) == param_table[Algorithm.crc32_iso_hdlc].check); + assert(calculate_crc!(Algorithm.crc32_castagnoli)(checkData[]) == param_table[Algorithm.crc32_castagnoli].check); + + // rolling CRC: split at multiple points and verify accumulation + static foreach (split; 1 .. 9) + { + assert(calculate_crc!(Algorithm.crc8_smbus)(checkData[split .. 9], + calculate_crc!(Algorithm.crc8_smbus)(checkData[0 .. split])) == param_table[Algorithm.crc8_smbus].check); + assert(calculate_crc!(Algorithm.crc16_modbus)(checkData[split .. 9], + calculate_crc!(Algorithm.crc16_modbus)(checkData[0 .. split])) == param_table[Algorithm.crc16_modbus].check); + assert(calculate_crc!(Algorithm.crc16_kermit)(checkData[split .. 9], + calculate_crc!(Algorithm.crc16_kermit)(checkData[0 .. split])) == param_table[Algorithm.crc16_kermit].check); + assert(calculate_crc!(Algorithm.crc16_iso_hdlc)(checkData[split .. 9], + calculate_crc!(Algorithm.crc16_iso_hdlc)(checkData[0 .. split])) == param_table[Algorithm.crc16_iso_hdlc].check); + assert(calculate_crc!(Algorithm.crc16_xmodem)(checkData[split .. 9], + calculate_crc!(Algorithm.crc16_xmodem)(checkData[0 .. split])) == param_table[Algorithm.crc16_xmodem].check); + assert(calculate_crc!(Algorithm.crc16_ccitt_false)(checkData[split .. 9], + calculate_crc!(Algorithm.crc16_ccitt_false)(checkData[0 .. split])) == param_table[Algorithm.crc16_ccitt_false].check); + assert(calculate_crc!(Algorithm.crc32_iso_hdlc)(checkData[split .. 9], + calculate_crc!(Algorithm.crc32_iso_hdlc)(checkData[0 .. split])) == param_table[Algorithm.crc32_iso_hdlc].check); + } + + // empty data returns the default initial value + assert(calculate_crc!(Algorithm.crc8_smbus)(null) == 0x00); + assert(calculate_crc!(Algorithm.crc32_iso_hdlc)(null) == 0x0000_0000); + assert(calculate_crc!(Algorithm.crc16_kermit)(null) == 0x0000); + assert(calculate_crc!(Algorithm.crc16_iso_hdlc)(null) == 0x0000); + assert(calculate_crc!(Algorithm.crc16_xmodem)(null) == 0x0000); + assert(calculate_crc!(Algorithm.crc16_ccitt_false)(null) == 0xFFFF); + assert(calculate_crc!(Algorithm.crc16_modbus)(null) == 0xFFFF); + + // single byte + assert(calculate_crc!(Algorithm.crc32_iso_hdlc)("A") == 0xD3D9_9E8B); + + // dual CRC: verify both halves + // reflected, final_xor=0 + uint dual16_k = calculate_crc_2!(Algorithm.crc16_kermit)(checkData[], 5); + assert((dual16_k & 0xFFFF) == param_table[Algorithm.crc16_kermit].check); + assert((dual16_k >> 16) == calculate_crc!(Algorithm.crc16_kermit)(checkData[0 .. 5])); + + // reflected, final_xor=mask + uint dual16_h = calculate_crc_2!(Algorithm.crc16_iso_hdlc)(checkData[], 5); + assert((dual16_h & 0xFFFF) == param_table[Algorithm.crc16_iso_hdlc].check); + assert((dual16_h >> 16) == calculate_crc!(Algorithm.crc16_iso_hdlc)(checkData[0 .. 5])); + + ulong dual32 = calculate_crc_2!(Algorithm.crc32_iso_hdlc)(checkData[], 5); + assert((dual32 & 0xFFFF_FFFF) == param_table[Algorithm.crc32_iso_hdlc].check); + assert((dual32 >> 32) == calculate_crc!(Algorithm.crc32_iso_hdlc)(checkData[0 .. 5])); + + // unreflected, final_xor=0 + uint dual16_x = calculate_crc_2!(Algorithm.crc16_xmodem)(checkData[], 5); + assert((dual16_x & 0xFFFF) == param_table[Algorithm.crc16_xmodem].check); + assert((dual16_x >> 16) == calculate_crc!(Algorithm.crc16_xmodem)(checkData[0 .. 5])); + + // 8-bit unreflected + ushort dual8 = calculate_crc_2!(Algorithm.crc8_smbus)(checkData[], 5); + assert((dual8 & 0xFF) == param_table[Algorithm.crc8_smbus].check); + assert((dual8 >> 8) == calculate_crc!(Algorithm.crc8_smbus)(checkData[0 .. 5])); } private: -enum CRCParams[] paramTable = [ - CRCParams(16, true, 0x8005, 0xFFFF, 0xFFFF, 0xB4C8), // CRC16_USB - CRCParams(16, true, 0x8005, 0xFFFF, 0x0000, 0x4B37), // CRC16_MODBUS - CRCParams(16, true, 0x1021, 0x0000, 0x0000, 0x2189), // CRC16_KERMIT - CRCParams(16, false, 0x1021, 0x0000, 0x0000, 0x31C3), // CRC16_XMODEM - CRCParams(16, false, 0x1021, 0xFFFF, 0x0000, 0x29B1), // CRC16_CCITT_FALSE - CRCParams(16, true, 0x1021, 0xFFFF, 0xFFFF, 0x906E), // CRC16_ISO_HDLC - CRCParams(16, true, 0x3D65, 0x0000, 0xFFFF, 0xEA82), // CRC16_DNP - CRCParams(32, true, 0x04C11DB7, 0xFFFFFFFF, 0xFFFFFFFF, 0xCBF43926), // CRC32_ISO_HDLC - CRCParams(32, true, 0x1EDC6F41, 0xFFFFFFFF, 0xFFFFFFFF, 0xE3069283), // CRC32_CASTAGNOLI +version (Espressif) +{ + // ESP32 have CRC functions in the mask rom which we'll use where applicable + + version (ESP32_S2) enum has_rom_crc_be = false; + else enum has_rom_crc_be = true; + + private extern(C) pure nothrow @nogc + { + uint crc32_le(uint crc, const ubyte* buf, uint len); + ushort crc16_le(ushort crc, const ubyte* buf, uint len); + ubyte crc8_le(ubyte crc, const ubyte* buf, uint len); + static if (has_rom_crc_be) + { + uint crc32_be(uint crc, const ubyte* buf, uint len); + ushort crc16_be(ushort crc, const ubyte* buf, uint len); + ubyte crc8_be(ubyte crc, const ubyte* buf, uint len); + } + } + + private enum rom_crc_match(Algorithm algo) = ( + (param_table[algo].width == 32 && param_table[algo].poly == 0x04C1_1DB7) || + (param_table[algo].width == 16 && param_table[algo].poly == 0x1021) || + (param_table[algo].width == 8 && param_table[algo].poly == 0x07) + ); + + enum hardware_crc_support(Algorithm algo, T) = is(T == CRCType!algo) && rom_crc_match!algo && (param_table[algo].reflect || has_rom_crc_be); +} +else +{ + enum hardware_crc_support(Algorithm algo, T) = false; +} + +enum crc_params[] param_table = [ + crc_params( 8, false, 0x07, 0x0000, 0x0000, 0xF4), // crc8_smbus + crc_params(16, true, 0x8005, 0xFFFF, 0xFFFF, 0xB4C8), // crc16_usb + crc_params(16, true, 0x8005, 0xFFFF, 0x0000, 0x4B37), // crc16_modbus + crc_params(16, true, 0x1021, 0x0000, 0x0000, 0x2189), // crc16_kermit + crc_params(16, false, 0x1021, 0x0000, 0x0000, 0x31C3), // crc16_xmodem + crc_params(16, false, 0x1021, 0xFFFF, 0x0000, 0x29B1), // crc16_ccitt_false + crc_params(16, true, 0x1021, 0xFFFF, 0xFFFF, 0x906E), // crc16_iso_hdlc + crc_params(16, true, 0x3D65, 0x0000, 0xFFFF, 0xEA82), // crc16_dnp + crc_params(32, true, 0x04C11DB7, 0xFFFFFFFF, 0xFFFFFFFF, 0xCBF43926), // crc32_iso_hdlc + crc_params(32, true, 0x1EDC6F41, 0xFFFFFFFF, 0xFFFFFFFF, 0xE3069283), // crc32_castagnoli ]; // helper function to reflect bits (reverse bit order) -T reflect(T)(T value, ubyte bits) +T reflect(T)(T value, ubyte bits) pure { T result = 0; foreach (i; 0..bits) @@ -236,7 +427,7 @@ T reflect(T)(T value, ubyte bits) } // this minimises the number of table instantiations -template CRCTable(uint width, uint poly, bool reflect) +template crc_table(uint width, uint poly, bool reflect) { - __gshared immutable CRCTable = generateCRCTable!(intForWidth!width)(CRCParams(width, reflect, poly, 0, 0, 0)); + __gshared immutable crc_table = generate_crc_table!(IntForWidth!width)(crc_params(width, reflect, poly, 0, 0, 0)); } diff --git a/src/urt/crypto/chacha.d b/src/urt/crypto/chacha.d new file mode 100644 index 0000000..2f263db --- /dev/null +++ b/src/urt/crypto/chacha.d @@ -0,0 +1,299 @@ +module urt.crypto.chacha; + +import urt.endian : littleEndianToNative, nativeToLittleEndian; +import urt.mem; + +nothrow @nogc: + + +struct ChaChaContext +{ + uint[16] state; + union { + uint[16] keystream32; + ubyte[64] keystream8; + } + uint nonceLow; + ushort position; + ushort rounds; +} + + +void chacha_init_context(ref ChaChaContext ctx, ref const ubyte[16] key, ulong nonce, ulong counter = 0, uint rounds = 20) pure +{ + chacha_init_context(ctx, key, expandNonce(nonce), counter, rounds); +} + +void chacha_init_context(ref ChaChaContext ctx, ref const ubyte[32] key, ulong nonce, ulong counter = 0, uint rounds = 20) pure +{ + chacha_init_context(ctx, key, expandNonce(nonce), counter, rounds); +} + +void chacha_init_context(ref ChaChaContext ctx, ref const ubyte[16] key, ref const ubyte[12] nonce, ulong counter = 0, uint rounds = 20) pure +{ + ubyte[32] key256 = void; + key256[0 .. 16] = key[]; + key256[16 .. 32] = key[]; + chacha_init_context(ctx, key256, nonce, counter, rounds); +} + +void chacha_init_context(ref ChaChaContext ctx, ref const ubyte[32] key, ref const ubyte[12] nonce, ulong counter = 0, uint rounds = 20) pure +{ + // the number of rounds must be 8, 12 or 20 + assert (rounds == 8 || rounds == 12 || rounds == 20); + ctx.rounds = cast(ushort)rounds / 2; + + ctx.nonceLow = nonce[0..4].littleEndianToNative!uint; + + // the string "expand 32-byte k" in little-endian + enum uint[4] magic_constant = [ 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574 ]; + + ctx.state[0] = magic_constant[0]; + ctx.state[1] = magic_constant[1]; + ctx.state[2] = magic_constant[2]; + ctx.state[3] = magic_constant[3]; + ctx.state[4] = key[0..4].littleEndianToNative!uint; + ctx.state[5] = key[4..8].littleEndianToNative!uint; + ctx.state[6] = key[8..12].littleEndianToNative!uint; + ctx.state[7] = key[12..16].littleEndianToNative!uint; + ctx.state[8] = key[16..20].littleEndianToNative!uint; + ctx.state[9] = key[20..24].littleEndianToNative!uint; + ctx.state[10] = key[24..28].littleEndianToNative!uint; + ctx.state[11] = key[28..32].littleEndianToNative!uint; + ctx.state[12] = cast(uint)counter; + ctx.state[13] = ctx.nonceLow | (counter >> 32); + ctx.state[14] = nonce[4..8].littleEndianToNative!uint; + ctx.state[15] = nonce[8..12].littleEndianToNative!uint; + + ctx.keystream32[] = 0; + + // set starting position to the end of the key buffer; there are no bytes to consume yet + ctx.position = 64; +} + +static void chacha_set_counter(ref ChaChaContext ctx, ulong counter) pure +{ + ctx.state[12] = cast(uint)counter; + ctx.state[13] = ctx.nonceLow | (counter >> 32); +} + +void chacha_free_context(ref ChaChaContext ctx) pure +{ + ctx.state[] = 0; + ctx.keystream32[] = 0; + ctx.position = 0; + ctx.nonceLow = 0; + ctx.rounds = 0; +} + +size_t chacha_update(ref ChaChaContext ctx, const ubyte[] input, ubyte[] output) pure +{ + import urt.util : min; + + size_t size = input.length; + assert(output.length >= size); + + size_t offset = 0; + if (ctx.position < 64) + { + size_t startBytes = min(size, 64 - ctx.position); + output[0 .. startBytes] = input[0 .. startBytes] ^ ctx.keystream8[ctx.position .. 64]; + ctx.position += startBytes; + size -= startBytes; + offset = startBytes; + } + while (size >= 64) + { + chacha_block_next(ctx); + output[offset .. offset + 64] = input[offset .. offset + 64] ^ ctx.keystream8[]; + offset += 64; + size -= 64; + } + if (size > 0) + { + chacha_block_next(ctx); + output[offset .. offset + size] = input[offset .. offset + size] ^ ctx.keystream8[0 .. size]; + ctx.position = cast(ushort)size; + } + + return input.length; +} + + +size_t chacha_crypt(const ubyte[] input, ubyte[] output, ref const ubyte[32] key, ulong nonce, ulong counter = 0) pure +{ + return chacha_crypt(input, output, key, expandNonce(nonce), counter); +} + +size_t chacha_crypt(const ubyte[] input, ubyte[] output, ref const ubyte[32] key, ref const ubyte[12] nonce, ulong counter = 0) pure +{ + assert(output.length >= input.length); + + ChaChaContext ctx; + ctx.chacha_init_context(key, nonce, counter); + size_t r = ctx.chacha_update(input, output); + ctx.chacha_free_context(); + return r; +} + + +private: + +ubyte[12] expandNonce(ulong nonce) pure +{ + ubyte[12] nonceBytes = void; + nonceBytes[0 .. 4] = 0; + nonceBytes[4 .. 12] = nativeToLittleEndian(nonce); + return nonceBytes; +} + +pragma(inline, true) +uint rotl32(int n)(uint x) pure + => (x << n) | (x >> (32 - n)); + +pragma(inline, true) +void chacha_quarter_round(uint a, uint b, uint c, uint d)(ref uint[16] state) pure +{ + state[a] += state[b]; state[d] = rotl32!16(state[d] ^ state[a]); + state[c] += state[d]; state[b] = rotl32!12(state[b] ^ state[c]); + state[a] += state[b]; state[d] = rotl32!8(state[d] ^ state[a]); + state[c] += state[d]; state[b] = rotl32!7(state[b] ^ state[c]); +} + +void chacha_block_next(ref ChaChaContext ctx) pure +{ + // this is where the crazy voodoo magic happens. + // mix the bytes a lot and hope that nobody finds out how to undo it. + ctx.keystream32 = ctx.state; + + // TODO: we might like to unroll this...? + foreach (i; 0 .. ctx.rounds) + { + chacha_quarter_round!(0, 4, 8, 12)(ctx.keystream32); + chacha_quarter_round!(1, 5, 9, 13)(ctx.keystream32); + chacha_quarter_round!(2, 6, 10, 14)(ctx.keystream32); + chacha_quarter_round!(3, 7, 11, 15)(ctx.keystream32); + + chacha_quarter_round!(0, 5, 10, 15)(ctx.keystream32); + chacha_quarter_round!(1, 6, 11, 12)(ctx.keystream32); + chacha_quarter_round!(2, 7, 8, 13)(ctx.keystream32); + chacha_quarter_round!(3, 4, 9, 14)(ctx.keystream32); + } + + ctx.keystream32[] += ctx.state[]; + + // increment counter + uint* counter = &ctx.state[12]; + counter[0]++; + if (counter[0] == 0) + { + // wrap around occured, increment higher 32 bits of counter + counter[1]++; + // limited to 2^64 blocks of 64 bytes each. + // if you want to process more than 1180591620717411303424 bytes, you have other problems. + // we could keep counting with counter[2] and counter[3] (nonce), but then we risk reusing the nonce which is very bad! + assert(counter[1] != 0); + } +} + + +unittest +{ + import urt.encoding; + + immutable ubyte[32] test1_key = HexDecode!"0000000000000000000000000000000000000000000000000000000000000000"; + immutable ubyte[12] test1_nonce = HexDecode!"000000000000000000000000"; + immutable ubyte[64] test1_input = 0; + immutable ubyte[64] test1_output = [ + 0x76, 0xb8, 0xe0, 0xad, 0xa0, 0xf1, 0x3d, 0x90, 0x40, 0x5d, 0x6a, 0xe5, 0x53, 0x86, 0xbd, 0x28, + 0xbd, 0xd2, 0x19, 0xb8, 0xa0, 0x8d, 0xed, 0x1a, 0xa8, 0x36, 0xef, 0xcc, 0x8b, 0x77, 0x0d, 0xc7, + 0xda, 0x41, 0x59, 0x7c, 0x51, 0x57, 0x48, 0x8d, 0x77, 0x24, 0xe0, 0x3f, 0xb8, 0xd8, 0x4a, 0x37, + 0x6a, 0x43, 0xb8, 0xf4, 0x15, 0x18, 0xa1, 0x1c, 0xc3, 0x87, 0xb6, 0x69, 0xb2, 0xee, 0x65, 0x86 + ]; + + immutable ubyte[32] test2_key = HexDecode!"0000000000000000000000000000000000000000000000000000000000000001"; + immutable ubyte[12] test2_nonce = HexDecode!"000000000000000000000002"; + immutable ubyte[375] test2_input = [ + 0x41, 0x6e, 0x79, 0x20, 0x73, 0x75, 0x62, 0x6d,0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x74, + 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x49, 0x45,0x54, 0x46, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x6e, + 0x64, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74,0x68, 0x65, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x72, + 0x69, 0x62, 0x75, 0x74, 0x6f, 0x72, 0x20, 0x66,0x6f, 0x72, 0x20, 0x70, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61,0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x6f, 0x72, + 0x20, 0x70, 0x61, 0x72, 0x74, 0x20, 0x6f, 0x66,0x20, 0x61, 0x6e, 0x20, 0x49, 0x45, 0x54, 0x46, + 0x20, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65,0x74, 0x2d, 0x44, 0x72, 0x61, 0x66, 0x74, 0x20, + 0x6f, 0x72, 0x20, 0x52, 0x46, 0x43, 0x20, 0x61,0x6e, 0x64, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x73, + 0x74, 0x61, 0x74, 0x65, 0x6d, 0x65, 0x6e, 0x74,0x20, 0x6d, 0x61, 0x64, 0x65, 0x20, 0x77, 0x69, + 0x74, 0x68, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65,0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, + 0x20, 0x6f, 0x66, 0x20, 0x61, 0x6e, 0x20, 0x49,0x45, 0x54, 0x46, 0x20, 0x61, 0x63, 0x74, 0x69, + 0x76, 0x69, 0x74, 0x79, 0x20, 0x69, 0x73, 0x20,0x63, 0x6f, 0x6e, 0x73, 0x69, 0x64, 0x65, 0x72, + 0x65, 0x64, 0x20, 0x61, 0x6e, 0x20, 0x22, 0x49,0x45, 0x54, 0x46, 0x20, 0x43, 0x6f, 0x6e, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e,0x22, 0x2e, 0x20, 0x53, 0x75, 0x63, 0x68, 0x20, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x6d, 0x65, 0x6e,0x74, 0x73, 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, + 0x64, 0x65, 0x20, 0x6f, 0x72, 0x61, 0x6c, 0x20,0x73, 0x74, 0x61, 0x74, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x49, 0x45,0x54, 0x46, 0x20, 0x73, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x73, 0x2c, 0x20, 0x61, 0x73, 0x20,0x77, 0x65, 0x6c, 0x6c, 0x20, 0x61, 0x73, 0x20, + 0x77, 0x72, 0x69, 0x74, 0x74, 0x65, 0x6e, 0x20,0x61, 0x6e, 0x64, 0x20, 0x65, 0x6c, 0x65, 0x63, + 0x74, 0x72, 0x6f, 0x6e, 0x69, 0x63, 0x20, 0x63,0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6d, 0x61,0x64, 0x65, 0x20, 0x61, 0x74, 0x20, 0x61, 0x6e, + 0x79, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x20, 0x6f,0x72, 0x20, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x2c, + 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x61,0x72, 0x65, 0x20, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x65, 0x64, 0x20, 0x74, 0x6f + ]; + immutable ubyte[375] test2_output = [ + 0xa3, 0xfb, 0xf0, 0x7d, 0xf3, 0xfa, 0x2f, 0xde,0x4f, 0x37, 0x6c, 0xa2, 0x3e, 0x82, 0x73, 0x70, + 0x41, 0x60, 0x5d, 0x9f, 0x4f, 0x4f, 0x57, 0xbd,0x8c, 0xff, 0x2c, 0x1d, 0x4b, 0x79, 0x55, 0xec, + 0x2a, 0x97, 0x94, 0x8b, 0xd3, 0x72, 0x29, 0x15,0xc8, 0xf3, 0xd3, 0x37, 0xf7, 0xd3, 0x70, 0x05, + 0x0e, 0x9e, 0x96, 0xd6, 0x47, 0xb7, 0xc3, 0x9f,0x56, 0xe0, 0x31, 0xca, 0x5e, 0xb6, 0x25, 0x0d, + 0x40, 0x42, 0xe0, 0x27, 0x85, 0xec, 0xec, 0xfa,0x4b, 0x4b, 0xb5, 0xe8, 0xea, 0xd0, 0x44, 0x0e, + 0x20, 0xb6, 0xe8, 0xdb, 0x09, 0xd8, 0x81, 0xa7,0xc6, 0x13, 0x2f, 0x42, 0x0e, 0x52, 0x79, 0x50, + 0x42, 0xbd, 0xfa, 0x77, 0x73, 0xd8, 0xa9, 0x05,0x14, 0x47, 0xb3, 0x29, 0x1c, 0xe1, 0x41, 0x1c, + 0x68, 0x04, 0x65, 0x55, 0x2a, 0xa6, 0xc4, 0x05,0xb7, 0x76, 0x4d, 0x5e, 0x87, 0xbe, 0xa8, 0x5a, + 0xd0, 0x0f, 0x84, 0x49, 0xed, 0x8f, 0x72, 0xd0,0xd6, 0x62, 0xab, 0x05, 0x26, 0x91, 0xca, 0x66, + 0x42, 0x4b, 0xc8, 0x6d, 0x2d, 0xf8, 0x0e, 0xa4,0x1f, 0x43, 0xab, 0xf9, 0x37, 0xd3, 0x25, 0x9d, + 0xc4, 0xb2, 0xd0, 0xdf, 0xb4, 0x8a, 0x6c, 0x91,0x39, 0xdd, 0xd7, 0xf7, 0x69, 0x66, 0xe9, 0x28, + 0xe6, 0x35, 0x55, 0x3b, 0xa7, 0x6c, 0x5c, 0x87,0x9d, 0x7b, 0x35, 0xd4, 0x9e, 0xb2, 0xe6, 0x2b, + 0x08, 0x71, 0xcd, 0xac, 0x63, 0x89, 0x39, 0xe2,0x5e, 0x8a, 0x1e, 0x0e, 0xf9, 0xd5, 0x28, 0x0f, + 0xa8, 0xca, 0x32, 0x8b, 0x35, 0x1c, 0x3c, 0x76,0x59, 0x89, 0xcb, 0xcf, 0x3d, 0xaa, 0x8b, 0x6c, + 0xcc, 0x3a, 0xaf, 0x9f, 0x39, 0x79, 0xc9, 0x2b,0x37, 0x20, 0xfc, 0x88, 0xdc, 0x95, 0xed, 0x84, + 0xa1, 0xbe, 0x05, 0x9c, 0x64, 0x99, 0xb9, 0xfd,0xa2, 0x36, 0xe7, 0xe8, 0x18, 0xb0, 0x4b, 0x0b, + 0xc3, 0x9c, 0x1e, 0x87, 0x6b, 0x19, 0x3b, 0xfe,0x55, 0x69, 0x75, 0x3f, 0x88, 0x12, 0x8c, 0xc0, + 0x8a, 0xaa, 0x9b, 0x63, 0xd1, 0xa1, 0x6f, 0x80,0xef, 0x25, 0x54, 0xd7, 0x18, 0x9c, 0x41, 0x1f, + 0x58, 0x69, 0xca, 0x52, 0xc5, 0xb8, 0x3f, 0xa3,0x6f, 0xf2, 0x16, 0xb9, 0xc1, 0xd3, 0x00, 0x62, + 0xbe, 0xbc, 0xfd, 0x2d, 0xc5, 0xbc, 0xe0, 0x91,0x19, 0x34, 0xfd, 0xa7, 0x9a, 0x86, 0xf6, 0xe6, + 0x98, 0xce, 0xd7, 0x59, 0xc3, 0xff, 0x9b, 0x64,0x77, 0x33, 0x8f, 0x3d, 0xa4, 0xf9, 0xcd, 0x85, + 0x14, 0xea, 0x99, 0x82, 0xcc, 0xaf, 0xb3, 0x41,0xb2, 0x38, 0x4d, 0xd9, 0x02, 0xf3, 0xd1, 0xab, + 0x7a, 0xc6, 0x1d, 0xd2, 0x9c, 0x6f, 0x21, 0xba,0x5b, 0x86, 0x2f, 0x37, 0x30, 0xe3, 0x7c, 0xfd, + 0xc4, 0xfd, 0x80, 0x6c, 0x22, 0xf2, 0x21 + ]; + + immutable ubyte[32] test3_key = HexDecode!"c46ec1b18ce8a878725a37e780dfb7351f68ed2e194c79fbc6aebee1a667975d"; + enum ulong test3_nonce = 0x218268cfd531da1a; + immutable ubyte[127] test3_input = 0; + immutable ubyte[127] test3_output = [ + 0xf6, 0x3a, 0x89, 0xb7, 0x5c, 0x22, 0x71, 0xf9,0x36, 0x88, 0x16, 0x54, 0x2b, 0xa5, 0x2f, 0x06, + 0xed, 0x49, 0x24, 0x17, 0x92, 0x30, 0x2b, 0x00,0xb5, 0xe8, 0xf8, 0x0a, 0xe9, 0xa4, 0x73, 0xaf, + 0xc2, 0x5b, 0x21, 0x8f, 0x51, 0x9a, 0xf0, 0xfd,0xd4, 0x06, 0x36, 0x2e, 0x8d, 0x69, 0xde, 0x7f, + 0x54, 0xc6, 0x04, 0xa6, 0xe0, 0x0f, 0x35, 0x3f,0x11, 0x0f, 0x77, 0x1b, 0xdc, 0xa8, 0xab, 0x92, + 0xe5, 0xfb, 0xc3, 0x4e, 0x60, 0xa1, 0xd9, 0xa9,0xdb, 0x17, 0x34, 0x5b, 0x0a, 0x40, 0x27, 0x36, + 0x85, 0x3b, 0xf9, 0x10, 0xb0, 0x60, 0xbd, 0xf1,0xf8, 0x97, 0xb6, 0x29, 0x0f, 0x01, 0xd1, 0x38, + 0xae, 0x2c, 0x4c, 0x90, 0x22, 0x5b, 0xa9, 0xea,0x14, 0xd5, 0x18, 0xf5, 0x59, 0x29, 0xde, 0xa0, + 0x98, 0xca, 0x7a, 0x6c, 0xcf, 0xe6, 0x12, 0x27,0x05, 0x3c, 0x84, 0xe4, 0x9a, 0x4a, 0x33 + ]; + + ubyte[375] output = void; + + size_t ret = test1_input.chacha_crypt(output, test1_key, test1_nonce); + assert(ret == test1_input.length); + assert(output[0 .. test1_output.length] == test1_output[]); + + ChaChaContext ctx; + ctx.chacha_init_context(test2_key, test2_nonce, 1); + ret = ctx.chacha_update(test2_input[0 .. 17], output[0 .. 17]); + ret += ctx.chacha_update(test2_input[17 .. $], output[17 .. $]); + assert(ret == test2_input.length); + assert(output[0 .. test2_output.length] == test2_output[]); + + ret = test3_input.chacha_crypt(output, test3_key, test3_nonce); + assert(ret == test3_input.length); + assert(output[0 .. test3_output.length] == test3_output[]); +} diff --git a/src/urt/crypto/der.d b/src/urt/crypto/der.d new file mode 100644 index 0000000..0ec035a --- /dev/null +++ b/src/urt/crypto/der.d @@ -0,0 +1,245 @@ +module urt.crypto.der; + +import urt.time : DateTime; + +nothrow @nogc: + + +// pre-encoded OID content bytes +static immutable ubyte[7] oid_ec_public_key = [0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01]; // 1.2.840.10045.2.1 +static immutable ubyte[8] oid_prime256v1 = [0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07]; // 1.2.840.10045.3.1.7 +static immutable ubyte[8] oid_sha256_ecdsa = [0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02]; // 1.2.840.10045.4.3.2 +static immutable ubyte[3] oid_common_name = [0x55, 0x04, 0x03]; // 2.5.4.3 + + +// Primitives... + +uint der_length_size(size_t len) pure +{ + if (len < 0x80) return 1; + if (len < 0x100) return 2; + return 3; +} + +// write tag + length header for known content size +ptrdiff_t der_header(ubyte[] buf, ubyte tag, size_t content_len) +{ + size_t n = 1 + der_length_size(content_len); + if (!buf.ptr) + return n; + if (buf.length < n) + return -1; + size_t pos = 0; + buf[pos++] = tag; + pos += put_length(buf[pos .. $], content_len); + return pos; +} + +// write tag + length + content +ptrdiff_t der_tlv(ubyte[] buf, ubyte tag, const(ubyte)[] content) +{ + size_t n = 1 + der_length_size(content.length) + content.length; + if (!buf.ptr) + return n; + if (buf.length < n) + return -1; + size_t pos = 0; + buf[pos++] = tag; + pos += put_length(buf[pos .. $], content.length); + buf[pos .. pos + content.length] = content[]; + return n; +} + +// INTEGER with leading-zero stripping and sign padding +ptrdiff_t der_integer(ubyte[] buf, const(ubyte)[] value) +{ + while (value.length > 1 && value[0] == 0) + value = value[1 .. $]; + bool pad = (value[0] & 0x80) != 0; + size_t content_len = (pad ? 1 : 0) + value.length; + size_t n = 1 + der_length_size(content_len) + content_len; + if (!buf.ptr) + return n; + if (buf.length < n) + return -1; + size_t pos = 0; + buf[pos++] = 0x02; + pos += put_length(buf[pos .. $], content_len); + if (pad) + buf[pos++] = 0; + buf[pos .. pos + value.length] = value[]; + return n; +} + +ptrdiff_t der_integer_small(ubyte[] buf, uint value) +{ + if (value == 0) + { + if (!buf.ptr) + return 3; + if (buf.length < 3) + return -1; + buf[0] = 0x02; + buf[1] = 0x01; + buf[2] = 0x00; + return 3; + } + ubyte[4] be = void; + uint n = 0; + for (uint v = value; v > 0; v >>= 8) + ++n; + for (uint i = 0; i < n; ++i) + be[i] = cast(ubyte)(value >> ((n - 1 - i) * 8)); + return der_integer(buf, be[0 .. n]); +} + + +// Composite structures... + +// SEQUENCE { INTEGER r, INTEGER s } from raw 64-byte ECDSA P-256 signature +ptrdiff_t der_ecdsa_sig(ubyte[] buf, const(ubyte)[] raw_sig) +{ + ptrdiff_t r_size = der_integer(null, raw_sig[0 .. 32]); + ptrdiff_t s_size = der_integer(null, raw_sig[32 .. 64]); + size_t content = r_size + s_size; + size_t total = 1 + der_length_size(content) + content; + if (!buf.ptr) + return total; + if (buf.length < total) + return -1; + size_t pos = 0; + buf[pos++] = 0x30; + pos += put_length(buf[pos .. $], content); + pos += der_integer(buf[pos .. $], raw_sig[0 .. 32]); + pos += der_integer(buf[pos .. $], raw_sig[32 .. 64]); + return total; +} + +ptrdiff_t der_utctime(ubyte[] buf, DateTime dt) +{ + enum size_t total = 15; // tag(1) + length(1) + content(13) + if (!buf.ptr) + return total; + if (buf.length < total) + return -1; + buf[0] = 0x17; + buf[1] = 13; + ubyte yr = cast(ubyte)(dt.year % 100); + ubyte mo = cast(ubyte)dt.month; + buf[2] = cast(ubyte)('0' + yr / 10); + buf[3] = cast(ubyte)('0' + yr % 10); + buf[4] = cast(ubyte)('0' + mo / 10); + buf[5] = cast(ubyte)('0' + mo % 10); + buf[6] = cast(ubyte)('0' + dt.day / 10); + buf[7] = cast(ubyte)('0' + dt.day % 10); + buf[8] = cast(ubyte)('0' + dt.hour / 10); + buf[9] = cast(ubyte)('0' + dt.hour % 10); + buf[10] = cast(ubyte)('0' + dt.minute / 10); + buf[11] = cast(ubyte)('0' + dt.minute % 10); + buf[12] = cast(ubyte)('0' + dt.second / 10); + buf[13] = cast(ubyte)('0' + dt.second % 10); + buf[14] = 'Z'; + return total; +} + +// X.500 Name with a single CN attribute: SEQUENCE { SET { SEQUENCE { OID, UTF8String } } } +ptrdiff_t der_name_cn(ubyte[] buf, const(char)[] cn) +{ + size_t oid_tlv = 1 + 1 + oid_common_name.length; + size_t utf8_tlv = 1 + der_length_size(cn.length) + cn.length; + size_t atv_content = oid_tlv + utf8_tlv; + size_t atv = 1 + der_length_size(atv_content) + atv_content; + size_t rdn = 1 + der_length_size(atv) + atv; + size_t total = 1 + der_length_size(rdn) + rdn; + if (!buf.ptr) + return total; + if (buf.length < total) + return -1; + size_t pos = 0; + buf[pos++] = 0x30; + pos += put_length(buf[pos .. $], rdn); + buf[pos++] = 0x31; + pos += put_length(buf[pos .. $], atv); + buf[pos++] = 0x30; + pos += put_length(buf[pos .. $], atv_content); + pos += der_tlv(buf[pos .. $], 0x06, oid_common_name[]); + pos += der_tlv(buf[pos .. $], 0x0c, cast(const(ubyte)[])cn); + return total; +} + +// SubjectPublicKeyInfo for EC P-256 uncompressed point +ptrdiff_t der_ec_pubkey_info(ubyte[] buf, const(ubyte)[] x, const(ubyte)[] y) +{ + size_t oid1_tlv = 1 + 1 + oid_ec_public_key.length; + size_t oid2_tlv = 1 + 1 + oid_prime256v1.length; + size_t alg_content = oid1_tlv + oid2_tlv; + size_t alg = 1 + der_length_size(alg_content) + alg_content; + + size_t bs_content = 1 + 1 + x.length + y.length; // unused_bits + 0x04 + x + y + size_t bs = 1 + der_length_size(bs_content) + bs_content; + + size_t spki_content = alg + bs; + size_t total = 1 + der_length_size(spki_content) + spki_content; + if (!buf.ptr) + return total; + if (buf.length < total) + return -1; + + size_t pos = 0; + buf[pos++] = 0x30; + pos += put_length(buf[pos .. $], spki_content); + + buf[pos++] = 0x30; + pos += put_length(buf[pos .. $], alg_content); + pos += der_tlv(buf[pos .. $], 0x06, oid_ec_public_key[]); + pos += der_tlv(buf[pos .. $], 0x06, oid_prime256v1[]); + + buf[pos++] = 0x03; + pos += put_length(buf[pos .. $], bs_content); + buf[pos++] = 0x00; + buf[pos++] = 0x04; + buf[pos .. pos + x.length] = x[]; + pos += x.length; + buf[pos .. pos + y.length] = y[]; + pos += y.length; + + return total; +} + +// AlgorithmIdentifier for sha256WithECDSA +ptrdiff_t der_sig_alg(ubyte[] buf) +{ + size_t oid_tlv = 1 + 1 + oid_sha256_ecdsa.length; + size_t total = 1 + der_length_size(oid_tlv) + oid_tlv; + if (!buf.ptr) + return total; + if (buf.length < total) + return -1; + size_t pos = 0; + buf[pos++] = 0x30; + pos += put_length(buf[pos .. $], oid_tlv); + pos += der_tlv(buf[pos .. $], 0x06, oid_sha256_ecdsa[]); + return total; +} + + +private: + +uint put_length(ubyte[] buf, size_t len) +{ + if (len < 0x80) + { + buf[0] = cast(ubyte)len; + return 1; + } + if (len < 0x100) + { + buf[0] = 0x81; + buf[1] = cast(ubyte)len; + return 2; + } + buf[0] = 0x82; + buf[1] = cast(ubyte)(len >> 8); + buf[2] = cast(ubyte)(len & 0xff); + return 3; +} diff --git a/src/urt/crypto/pem.d b/src/urt/crypto/pem.d new file mode 100644 index 0000000..1cc32e7 --- /dev/null +++ b/src/urt/crypto/pem.d @@ -0,0 +1,72 @@ +module urt.crypto.pem; + +import urt.array; +import urt.encoding; +import urt.mem; + +nothrow @nogc: + + +bool is_pem(const(char)[] data) + => data.length >= 11 && data[0 .. 11] == "-----BEGIN "; + +Array!char encode_pem(const(ubyte)[] der, const(char)[] label) +{ + Array!char result = "-----BEGIN "; + result ~= label; + result ~= "-----\n"; + + size_t enc_len = base64_encode_length(der.length); + if (enc_len > 256) + assert(false, "PEM encode: DER input too large for stack buffer"); + char[256] b64_buf = void; + base64_encode(der, b64_buf[0 .. enc_len]); + + size_t pos = 0; + while (pos < enc_len) + { + size_t line_len = enc_len - pos; + if (line_len > 64) + line_len = 64; + result ~= b64_buf[pos .. pos + line_len]; + result ~= "\n"; + pos += line_len; + } + + result ~= "-----END "; + result ~= label; + result ~= "-----\n"; + return result; +} + +Array!ubyte decode_pem(const(char)[] data) +{ + import urt.string; + + size_t start = data.findFirst('\n'); + if (start == data.length) + return Array!ubyte(); + data = data[start .. $].trimFront; + + size_t end = data.findFirst("-----END"); + if (end == data.length) + return Array!ubyte(); + data = data[0 .. end].trimBack; + + if (data.length == 0) + return Array!ubyte(); + + // strip whitespace from base64 content + auto b64 = Array!char(Reserve, data.length); + for (size_t i = 0; i < data.length; ++i) + if (!data[i].is_whitespace) + b64 ~= data[i]; + + auto result = Array!ubyte(Alloc, base64_decode_length(b64.length)); + ptrdiff_t decoded_len = base64_decode(b64[], result[]); + if (decoded_len < 0) + return Array!ubyte(); + + result.resize(decoded_len); + return result; +} diff --git a/src/urt/crypto/pki.d b/src/urt/crypto/pki.d new file mode 100644 index 0000000..60c73cc --- /dev/null +++ b/src/urt/crypto/pki.d @@ -0,0 +1,1212 @@ +module urt.crypto.pki; + +import urt.array; +import urt.crypto.der; +import urt.crypto.pem; +import urt.digest.sha : SHA256Context, sha_init, sha_update, sha_finalise; +import urt.mem; +import urt.result; +import urt.string; +import urt.time; + +nothrow @nogc: + + +struct KeyPair +{ +nothrow @nogc: + version (Windows) + { + BCRYPT_ALG_HANDLE halg; + BCRYPT_KEY_HANDLE hcng; + } + else version (Posix) + { + mbedtls_pk_context pk; + } + else version (FreeStanding) + { + // PKI backend not yet implemented for bare-metal + void* _stub; + } + else + static assert(false, "TODO"); + + bool valid() const pure + { + version (Windows) + return hcng !is null; + else version (Posix) + return pk.pk_info !is null; + else + return false; + } +} + +struct CertRef +{ +nothrow @nogc: + version (Windows) + { + PCCERT_CONTEXT context; + HCERTSTORE store; + NCRYPT_KEY_HANDLE hncrypt; // persisted NCrypt key for SChannel (set by associate_key) + } + else version (Posix) + { + mbedtls_x509_crt* crt; // heap-allocated via urt_x509_crt_new + } + + bool valid() const pure + { + version (Windows) + return context !is null; + else version (Posix) + return crt !is null; + else + return false; + } +} + + +Result generate_keypair(out KeyPair kp) +{ + version (Windows) + { + NTSTATUS status = BCryptOpenAlgorithmProvider(&kp.halg, BCRYPT_ECDSA_P256_ALGORITHM.ptr, null, 0); + if (status != 0) + return Result(cast(uint)status); + + status = BCryptGenerateKeyPair(kp.halg, &kp.hcng, 256, 0); + if (status != 0) + { + BCryptCloseAlgorithmProvider(kp.halg, 0); + kp.halg = null; + return Result(cast(uint)status); + } + + status = BCryptFinalizeKeyPair(kp.hcng, 0); + if (status != 0) + { + BCryptDestroyKey(kp.hcng); + kp.hcng = null; + BCryptCloseAlgorithmProvider(kp.halg, 0); + kp.halg = null; + return Result(cast(uint)status); + } + + return Result.success; + } + else version (Posix) + { + mbedtls_pk_init(&kp.pk); + int ret = urt_pk_gen_ec_p256_key(&kp.pk, &mbedtls_ctr_drbg_random, get_rng()); + if (ret != 0) + { + mbedtls_pk_free(&kp.pk); + kp.pk = mbedtls_pk_context.init; + return Result(cast(uint)ret); + } + + return Result.success; + } + else + assert(0, "PKI: generate_keypair not implemented for this platform"); +} + +void free_keypair(ref KeyPair kp) +{ + version (Windows) + { + if (kp.hcng !is null) + BCryptDestroyKey(kp.hcng); + if (kp.halg !is null) + BCryptCloseAlgorithmProvider(kp.halg, 0); + kp.hcng = null; + kp.halg = null; + } + else version (Posix) + { + mbedtls_pk_free(&kp.pk); + kp.pk = mbedtls_pk_context.init; + } +} + +Result create_self_signed(ref KeyPair key, out CertRef cert, const(char)[] cn, const(char)[] hostname = null, uint validity_days = 365) +{ + version (Windows) + return create_self_signed_win32(key, cert, cn, hostname, validity_days); + else + return create_self_signed_portable(key, cert, cn, hostname, validity_days); +} + +private Result create_self_signed_win32(ref KeyPair key, out CertRef cert, const(char)[] cn, const(char)[] hostname, uint validity_days = 365) +{ + version (Windows) + { + // create a persisted NCrypt ECDSA P-256 key (SChannel requires persisted keys) + NCRYPT_PROV_HANDLE hprov; + SECURITY_STATUS ss = NCryptOpenStorageProvider(&hprov, MS_KEY_STORAGE_PROVIDER.ptr, 0); + if (ss != 0) + return Result(cast(uint)ss); + + wchar[48] name_buf = void; + cast(void)generate_key_name(name_buf[]); + + NCRYPT_KEY_HANDLE hncrypt; + ss = NCryptCreatePersistedKey(hprov, &hncrypt, BCRYPT_ECDSA_P256_ALGORITHM.ptr, name_buf.ptr, 0, NCRYPT_OVERWRITE_KEY_FLAG); + NCryptFreeObject(hprov); + if (ss != 0) + return Result(cast(uint)ss); + + ss = NCryptFinalizeKey(hncrypt, 0); + if (ss != 0) + { + NCryptFreeObject(hncrypt); + return Result(cast(uint)ss); + } + + // encode subject name as DER + ubyte[128] name_der = void; + ptrdiff_t der_len = der_name_cn(name_der[], cn); + if (der_len <= 0) + { + NCryptDeleteKey(hncrypt, 0); + return InternalResult.data_error; + } + + CERT_NAME_BLOB subject_blob; + subject_blob.cbData = cast(DWORD)der_len; + subject_blob.pbData = name_der.ptr; + + // build SAN extension (Chrome requires SAN) + ubyte[512] san_der = void; + size_t pos = 2; // skip SEQUENCE header, fill in later + + void add_dns_name(scope const(char)[] name) + { + san_der[pos++] = 0x82; // dNSName [2] implicit + san_der[pos++] = cast(ubyte)name.length; + san_der[pos .. pos + name.length] = cast(const(ubyte)[])name[]; + pos += name.length; + } + + void add_ip(scope const(ubyte)[] addr) + { + san_der[pos++] = 0x87; // iPAddress [7] implicit + san_der[pos++] = cast(ubyte)addr.length; + san_der[pos .. pos + addr.length] = addr[]; + pos += addr.length; + } + + // CN as dNSName + add_dns_name(cn); + // localhost + loopback + add_dns_name("localhost"); + add_ip([127, 0, 0, 1]); + add_ip([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); // ::1 + + // hostname.local (mDNS) + if (hostname.length > 0 && hostname.length + 6 < 128) + { + san_der[pos++] = 0x82; + san_der[pos++] = cast(ubyte)(hostname.length + 6); + san_der[pos .. pos + hostname.length] = cast(const(ubyte)[])hostname[]; + pos += hostname.length; + san_der[pos .. pos + 6] = cast(const(ubyte)[])".local"; + pos += 6; + } + + // TODO: add iPAddress SAN entries for all IP addresses the server is bound to. + // ...needs the server's bound addresses passed in or enumerated here! + + // SEQUENCE wrapper + san_der[0] = 0x30; + san_der[1] = cast(ubyte)(pos - 2); + DWORD san_len = cast(DWORD)pos; + + CERT_EXTENSION san_ext; + san_ext.pszObjId = cast(LPSTR)"2.5.29.17".ptr; // szOID_SUBJECT_ALT_NAME2 + san_ext.fCritical = FALSE; + san_ext.Value.cbData = san_len; + san_ext.Value.pbData = san_der.ptr; + + CERT_EXTENSIONS exts; + exts.cExtension = 1; + exts.rgExtension = &san_ext; + + // CertCreateSelfSignCertificate handles key association correctly for SChannel + auto pctx = CertCreateSelfSignCertificate(hncrypt, &subject_blob, 0, null, null, null, null, &exts); + if (pctx is null) + { + auto r = getlasterror_result(); + NCryptDeleteKey(hncrypt, 0); + return r; + } + + cert.context = pctx; + cert.store = null; + cert.hncrypt = hncrypt; + + return Result.success; + } + else + assert(false); +} + +private Result create_self_signed_portable(ref KeyPair key, out CertRef cert, const(char)[] cn, const(char)[] hostname, uint validity_days = 365) +{ + if (!key.valid) + return InternalResult.invalid_parameter; + + Array!ubyte pub_x, pub_y; + auto r = export_public_key_raw(key, pub_x, pub_y); + if (!r) + return r; + + auto now = getSysTime(); + auto not_before = getDateTime(now); + auto not_after = getDateTime(now + dur!"days"(validity_days)); + + // compute TBSCertificate field sizes + ptrdiff_t ver_inner = der_integer_small(null, 2); + ptrdiff_t ver = der_header(null, 0xa0, ver_inner) + ver_inner; + ptrdiff_t serial = der_integer_small(null, 1); + ptrdiff_t sig_alg_size = der_sig_alg(null); + ptrdiff_t issuer = der_name_cn(null, cn); + ptrdiff_t val_inner = der_utctime(null, not_before) + der_utctime(null, not_after); + ptrdiff_t validity = der_header(null, 0x30, val_inner) + val_inner; + ptrdiff_t subject = der_name_cn(null, cn); + ptrdiff_t pubkey = der_ec_pubkey_info(null, pub_x[], pub_y[]); + size_t tbs_content = ver + serial + sig_alg_size + issuer + validity + subject + pubkey; + size_t tbs_total = der_header(null, 0x30, tbs_content) + tbs_content; + + // write TBSCertificate + ubyte[512] tbs_buf = void; + size_t pos = 0; + pos += der_header(tbs_buf[pos .. $], 0x30, tbs_content); + pos += der_header(tbs_buf[pos .. $], 0xa0, ver_inner); + pos += der_integer_small(tbs_buf[pos .. $], 2); + pos += der_integer_small(tbs_buf[pos .. $], 1); + pos += der_sig_alg(tbs_buf[pos .. $]); + pos += der_name_cn(tbs_buf[pos .. $], cn); + pos += der_header(tbs_buf[pos .. $], 0x30, val_inner); + pos += der_utctime(tbs_buf[pos .. $], not_before); + pos += der_utctime(tbs_buf[pos .. $], not_after); + pos += der_name_cn(tbs_buf[pos .. $], cn); + pos += der_ec_pubkey_info(tbs_buf[pos .. $], pub_x[], pub_y[]); + + // hash and sign + SHA256Context sha; + sha_init(sha); + sha_update(sha, tbs_buf[0 .. tbs_total]); + ubyte[32] hash = sha_finalise(sha); + + Array!ubyte sig; + r = sign_hash(key, hash[], sig); + if (!r) + return r; + + // Certificate: SEQUENCE { tbs, sigAlgId, BIT STRING { ecdsa_sig } } + ptrdiff_t sig_der = der_ecdsa_sig(null, sig[]); + size_t bs_content = 1 + sig_der; // unused_bits + ecdsa_sig + size_t cert_content = tbs_total + sig_alg_size + 1 + der_length_size(bs_content) + bs_content; + size_t cert_total = der_header(null, 0x30, cert_content) + cert_content; + + ubyte[640] cert_buf = void; + pos = 0; + pos += der_header(cert_buf[pos .. $], 0x30, cert_content); + cert_buf[pos .. pos + tbs_total] = tbs_buf[0 .. tbs_total]; + pos += tbs_total; + pos += der_sig_alg(cert_buf[pos .. $]); + pos += der_header(cert_buf[pos .. $], 0x03, bs_content); + cert_buf[pos++] = 0x00; // unused bits + pos += der_ecdsa_sig(cert_buf[pos .. $], sig[]); + + return cert_buf[0 .. cert_total].load_certificate(cert); +} + +Result load_certificate(const(ubyte)[] cert_data, out CertRef cert) +{ + version (Windows) + { + if (cert_data.length == 0) + return InternalResult.invalid_parameter; + + const(ubyte)[] der = cert_data; + Array!ubyte decoded; + + if (is_pem(cast(const(char)[])cert_data)) + { + decoded = decode_pem(cast(const(char)[])cert_data); + if (decoded.length == 0) + return InternalResult.data_error; + der = decoded[]; + } + + cert.store = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0, 0, null); + if (cert.store is null) + return getlasterror_result(); + + if (!CertAddEncodedCertificateToStore(cert.store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + der.ptr, cast(DWORD)der.length, CERT_STORE_ADD_REPLACE_EXISTING, cast(PCCERT_CONTEXT*)&cert.context)) + { + auto r = getlasterror_result(); + CertCloseStore(cert.store, 0); + cert.store = null; + return r; + } + + return Result.success; + } + else version (Posix) + { + if (cert_data.length == 0) + return InternalResult.invalid_parameter; + + cert.crt = urt_x509_crt_new(); + if (cert.crt is null) + return InternalResult.invalid_parameter; + + // mbedtls_x509_crt_parse handles both PEM and DER. + // for PEM, the buffer must be null-terminated. + int ret; + if (is_pem(cast(const(char)[])cert_data)) + { + // add null terminator for mbedtls PEM parser + auto pem_buf = Array!ubyte(Alloc, cert_data.length + 1); + pem_buf.ptr[0 .. cert_data.length] = cert_data[]; + pem_buf.ptr[cert_data.length] = 0; + ret = mbedtls_x509_crt_parse(cert.crt, pem_buf.ptr, pem_buf.length); + } + else + ret = mbedtls_x509_crt_parse_der(cert.crt, cert_data.ptr, cert_data.length); + + if (ret != 0) + { + urt_x509_crt_delete(cert.crt); + cert.crt = null; + return Result(cast(uint)ret); + } + + return Result.success; + } + else + assert(0, "PKI: not implemented for this platform"); +} + +Result associate_key(ref CertRef cert, ref KeyPair key) +{ + version (Windows) + { + if (!cert.valid || !key.valid) + return InternalResult.invalid_parameter; + + // already associated (e.g., by CertCreateSelfSignCertificate) + if (cert.hncrypt !is null) + return Result.success; + + // SChannel requires the private key to be a named, persisted NCrypt key. + // Ephemeral BCrypt/NCrypt keys cause SEC_E_NO_CREDENTIALS (0x8009030E). + // Strategy: BCrypt blob → NCrypt temp (for PKCS8 export) → PKCS8 re-import + // with key name → persisted key → CERT_KEY_PROV_INFO_PROP_ID so SChannel + // can locate the key by provider + name. + + NCRYPT_PROV_HANDLE hprov; + SECURITY_STATUS ss = NCryptOpenStorageProvider(&hprov, MS_KEY_STORAGE_PROVIDER.ptr, 0); + if (ss != 0) + return Result(cast(uint)ss); + + // export BCrypt key as blob + ULONG blob_size = 0; + NTSTATUS status = BCryptExportKey(key.hcng, null, BCRYPT_ECCPRIVATE_BLOB.ptr, null, 0, &blob_size, 0); + if (status != 0) + { + NCryptFreeObject(hprov); + return Result(cast(uint)status); + } + + auto blob = Array!ubyte(Alloc, blob_size); + status = BCryptExportKey(key.hcng, null, BCRYPT_ECCPRIVATE_BLOB.ptr, blob.ptr, blob_size, &blob_size, 0); + if (status != 0) + { + NCryptFreeObject(hprov); + return Result(cast(uint)status); + } + + // import BCrypt blob into NCrypt (unfinalized so we can set export policy) + NCRYPT_KEY_HANDLE htemp; + ss = NCryptImportKey(hprov, null, BCRYPT_ECCPRIVATE_BLOB.ptr, null, &htemp, blob.ptr, blob_size, NCRYPT_DO_NOT_FINALIZE_FLAG); + if (ss != 0) + { + NCryptFreeObject(hprov); + return Result(cast(uint)ss); + } + + // allow plaintext export so we can re-export as PKCS8 + DWORD export_policy = NCRYPT_ALLOW_EXPORT_FLAG | NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG; + ss = NCryptSetProperty(htemp, NCRYPT_EXPORT_POLICY_PROPERTY.ptr, cast(ubyte*)&export_policy, DWORD.sizeof, 0); + if (ss != 0) + { + NCryptFreeObject(htemp); + NCryptFreeObject(hprov); + return Result(cast(uint)ss); + } + + ss = NCryptFinalizeKey(htemp, 0); + if (ss != 0) + { + NCryptFreeObject(htemp); + NCryptFreeObject(hprov); + return Result(cast(uint)ss); + } + + // export as PKCS8 - only PKCS8 supports named key import for persistence + ULONG pkcs8_size = 0; + ss = NCryptExportKey(htemp, null, NCRYPT_PKCS8_PRIVATE_KEY_BLOB.ptr, null, null, 0, &pkcs8_size, 0); + if (ss != 0) + { + NCryptFreeObject(htemp); + NCryptFreeObject(hprov); + return Result(cast(uint)ss); + } + + auto pkcs8 = Array!ubyte(Alloc, pkcs8_size); + ss = NCryptExportKey(htemp, null, NCRYPT_PKCS8_PRIVATE_KEY_BLOB.ptr, null, pkcs8.ptr, pkcs8_size, &pkcs8_size, 0); + NCryptFreeObject(htemp); + if (ss != 0) + { + NCryptFreeObject(hprov); + return Result(cast(uint)ss); + } + + // re-import PKCS8 with key name to create a persisted key + wchar[48] name_buf = void; + size_t name_len = generate_key_name(name_buf[]); + + NCryptBuffer nbuf; + nbuf.cbBuffer = cast(uint)((name_len + 1) * 2); // include null terminator, in bytes + nbuf.BufferType = NCRYPTBUFFER_PKCS_KEY_NAME; + nbuf.pvBuffer = cast(void*)name_buf.ptr; + + NCryptBufferDesc params; + params.ulVersion = 0; // NCRYPTBUFFER_VERSION + params.cBuffers = 1; + params.pBuffers = &nbuf; + + NCRYPT_KEY_HANDLE hkey; + ss = NCryptImportKey(hprov, null, NCRYPT_PKCS8_PRIVATE_KEY_BLOB.ptr, ¶ms, &hkey, pkcs8.ptr, pkcs8_size, NCRYPT_OVERWRITE_KEY_FLAG); + NCryptFreeObject(hprov); + if (ss != 0) + return Result(cast(uint)ss); + + // clean up any previously associated NCrypt key + if (cert.hncrypt !is null) + NCryptDeleteKey(cert.hncrypt, 0); + cert.hncrypt = hkey; + + // tell SChannel where to find the key: provider name + key name (CNG mode) + CRYPT_KEY_PROV_INFO prov_info; + prov_info.pwszContainerName = name_buf.ptr; + prov_info.pwszProvName = cast(wchar*)MS_KEY_STORAGE_PROVIDER.ptr; + prov_info.dwProvType = 0; // CNG key storage provider + prov_info.dwFlags = 0; + prov_info.cProvParam = 0; + prov_info.rgProvParam = null; + prov_info.dwKeySpec = 0; + + if (!CertSetCertificateContextProperty(cert.context, CERT_KEY_PROV_INFO_PROP_ID, 0, &prov_info)) + { + auto r = getlasterror_result(); + NCryptDeleteKey(cert.hncrypt, 0); + cert.hncrypt = null; + return r; + } + + return Result.success; + } + else version (Posix) + { + // no-op: mbedtls doesn't require key-to-cert binding in a system store. + // the cert and key are passed separately to mbedtls_ssl_conf_own_cert. + if (!cert.valid || !key.valid) + return InternalResult.invalid_parameter; + return Result.success; + } + else + assert(0, "PKI: not implemented for this platform"); +} + +void free_cert(ref CertRef cert) +{ + version (Windows) + { + if (cert.hncrypt !is null) + NCryptDeleteKey(cert.hncrypt, 0); + if (cert.context !is null) + CertFreeCertificateContext(cert.context); + if (cert.store !is null) + CertCloseStore(cert.store, 0); + cert.hncrypt = null; + cert.context = null; + cert.store = null; + } + else version (Posix) + { + if (cert.crt !is null) + urt_x509_crt_delete(cert.crt); + cert.crt = null; + } +} + +SysTime cert_expiry(ref const CertRef cert) +{ + version (Windows) + { + if (cert.context is null || cert.context.pCertInfo is null) + return SysTime(); + return SysTime(*cast(ulong*)&cert.context.pCertInfo.NotAfter); + } + else version (Posix) + { + // TODO: parse cert.crt.valid_to (mbedtls_x509_time) and convert to SysTime + return SysTime(); + } + else + return SysTime(); +} + +inout(void)* native_cert_context(ref inout CertRef cert) +{ + version (Windows) + return cast(inout(void)*)cert.context; + else version (Posix) + return cast(inout(void)*)cert.crt; + else + return null; +} + + +Result sign_hash(ref KeyPair kp, const(ubyte)[] hash, out Array!ubyte signature) +{ + version (Windows) + { + if (kp.hcng is null) + return InternalResult.invalid_parameter; + + ULONG sig_size = 0; + NTSTATUS status = BCryptSignHash(kp.hcng, null, cast(PUCHAR)hash.ptr, cast(ULONG)hash.length, null, 0, &sig_size, 0); + if (status != 0) + return Result(cast(uint)status); + + signature = Array!ubyte(Alloc, sig_size); + status = BCryptSignHash(kp.hcng, null, cast(PUCHAR)hash.ptr, cast(ULONG)hash.length, signature.ptr, sig_size, &sig_size, 0); + if (status != 0) + { + signature = Array!ubyte(); + return Result(cast(uint)status); + } + + signature.resize(sig_size); + return Result.success; + } + else version (Posix) + { + if (!kp.valid) + return InternalResult.invalid_parameter; + + // mbedtls_pk_sign produces DER-encoded ECDSA signature. + // we need raw R||S format (64 bytes for P-256) to match the Windows contract. + ubyte[256] sig_buf = void; + size_t sig_len = 0; + int ret = urt_pk_sign(&kp.pk, + hash.ptr, hash.length, sig_buf.ptr, sig_buf.length, &sig_len, + &mbedtls_ctr_drbg_random, get_rng()); + if (ret != 0) + return Result(cast(uint)ret); + + // parse DER SEQUENCE { INTEGER r, INTEGER s } → raw R||S (32 bytes each) + signature = Array!ubyte(Alloc, 64); + if (!der_sig_to_raw(sig_buf[0 .. sig_len], signature.ptr[0 .. 64])) + { + signature = Array!ubyte(); + return InternalResult.data_error; + } + + return Result.success; + } + else + assert(0, "PKI: not implemented for this platform"); +} + +Result export_public_key_raw(ref KeyPair kp, out Array!ubyte x, out Array!ubyte y) +{ + version (Windows) + { + if (kp.hcng is null) + return InternalResult.invalid_parameter; + + ULONG blob_size = 0; + NTSTATUS status = BCryptExportKey(kp.hcng, null, BCRYPT_ECCPUBLIC_BLOB.ptr, null, 0, &blob_size, 0); + if (status != 0) + return Result(cast(uint)status); + + auto blob = Array!ubyte(Alloc, blob_size); + status = BCryptExportKey(kp.hcng, null, BCRYPT_ECCPUBLIC_BLOB.ptr, blob.ptr, blob_size, &blob_size, 0); + if (status != 0) + return Result(cast(uint)status); + + if (blob_size < 8) + return InternalResult.data_error; + + auto hdr = cast(BCRYPT_ECCKEY_BLOB*)blob.ptr; + ULONG key_len = hdr.cbKey; + if (blob_size < 8 + 2 * key_len) + return InternalResult.data_error; + + x = blob[8 .. 8 + key_len]; + y = blob[8 + key_len .. 8 + 2 * key_len]; + return Result.success; + } + else version (Posix) + { + if (!kp.valid) + return InternalResult.invalid_parameter; + + // export uncompressed point: 0x04 || X || Y + ubyte[65] pt_buf = void; // 1 + 32 + 32 for P-256 + size_t olen = 0; + int ret = urt_pk_export_pubkey_xy(&kp.pk, pt_buf.ptr, pt_buf.length, &olen); + if (ret != 0) + return Result(cast(uint)ret); + + if (olen != 65) // 0x04 + 32 + 32 + return InternalResult.data_error; + + x = Array!ubyte(Alloc, 32); + y = Array!ubyte(Alloc, 32); + x.ptr[0 .. 32] = pt_buf[1 .. 33]; + y.ptr[0 .. 32] = pt_buf[33 .. 65]; + return Result.success; + } + else + assert(0, "PKI: not implemented for this platform"); +} + + +Array!ubyte generate_csr(ref KeyPair kp, const(char)[] cn) +{ + if (!kp.valid) + return Array!ubyte(); + + Array!ubyte pub_x, pub_y; + if (!export_public_key_raw(kp, pub_x, pub_y)) + return Array!ubyte(); + + // compute CertificationRequestInfo field sizes + ptrdiff_t ver = der_integer_small(null, 0); + ptrdiff_t subject = der_name_cn(null, cn); + ptrdiff_t pubkey = der_ec_pubkey_info(null, pub_x[], pub_y[]); + ptrdiff_t attrs = der_header(null, 0xa0, 0); // empty attributes [0] + size_t info_content = ver + subject + pubkey + attrs; + size_t info_total = der_header(null, 0x30, info_content) + info_content; + + // write CertificationRequestInfo + ubyte[512] info_buf = void; + size_t pos = 0; + pos += der_header(info_buf[pos .. $], 0x30, info_content); + pos += der_integer_small(info_buf[pos .. $], 0); + pos += der_name_cn(info_buf[pos .. $], cn); + pos += der_ec_pubkey_info(info_buf[pos .. $], pub_x[], pub_y[]); + pos += der_header(info_buf[pos .. $], 0xa0, 0); + + // hash and sign + SHA256Context sha; + sha_init(sha); + sha_update(sha, info_buf[0 .. info_total]); + ubyte[32] hash = sha_finalise(sha); + + Array!ubyte sig; + if (!sign_hash(kp, hash[], sig)) + return Array!ubyte(); + + // CertificationRequest: SEQUENCE { info, sigAlgId, BIT STRING { ecdsa_sig } } + ptrdiff_t sig_alg_size = der_sig_alg(null); + ptrdiff_t sig_der = der_ecdsa_sig(null, sig[]); + size_t bs_content = 1 + sig_der; + size_t csr_content = info_total + sig_alg_size + 1 + der_length_size(bs_content) + bs_content; + size_t csr_total = der_header(null, 0x30, csr_content) + csr_content; + + auto csr = Array!ubyte(Alloc, csr_total); + pos = 0; + pos += der_header(csr[pos .. $], 0x30, csr_content); + csr.ptr[pos .. pos + info_total] = info_buf[0 .. info_total]; + pos += info_total; + pos += der_sig_alg(csr[pos .. $]); + pos += der_header(csr[pos .. $], 0x03, bs_content); + csr.ptr[pos++] = 0x00; + pos += der_ecdsa_sig(csr[pos .. $], sig[]); + + return csr; +} + + +Result export_private_key(ref KeyPair kp, out Array!ubyte key_out) +{ + version (Windows) + { + if (kp.hcng is null) + return InternalResult.invalid_parameter; + + // Export BCrypt blob: BCRYPT_ECCKEY_BLOB { magic, cbKey=32 } X[32] Y[32] d[32] + ULONG blob_size = 0; + NTSTATUS status = BCryptExportKey(kp.hcng, null, BCRYPT_ECCPRIVATE_BLOB.ptr, null, 0, &blob_size, 0); + if (status != 0) + return Result(cast(uint)status); + + auto blob = Array!ubyte(Alloc, blob_size); + status = BCryptExportKey(kp.hcng, null, BCRYPT_ECCPRIVATE_BLOB.ptr, blob.ptr, blob_size, &blob_size, 0); + if (status != 0) + return Result(cast(uint)status); + + if (blob_size < 8 + 32 * 3) + return InternalResult.data_error; + + // Build SEC 1 ECPrivateKey DER from the BCrypt blob components + return build_ec_sec1_der(blob[72 .. 104][0..32], blob[8 .. 40][0..32], blob[40 .. 72][0..32], key_out); + } + else version (Posix) + { + if (!kp.valid) + return InternalResult.invalid_parameter; + + // Extract raw d, X, Y from mbedtls pk context + ubyte[32] d = void; + ubyte[65] xy_buf = void; + size_t d_len = 0, xy_len = 0; + + int ret = urt_pk_export_privkey_d(&kp.pk, d.ptr, d.length, &d_len); + if (ret != 0) + return Result(cast(uint)ret); + + ret = urt_pk_export_pubkey_xy(&kp.pk, xy_buf.ptr, xy_buf.length, &xy_len); + if (ret != 0) + return Result(cast(uint)ret); + + // xy_buf is 0x04 || X[32] || Y[32] + return build_ec_sec1_der(d, xy_buf[1 .. 33], xy_buf[33 .. 65], key_out); + } + else + assert(0, "PKI: not implemented for this platform"); +} + +Result import_private_key(const(ubyte)[] key_data, out KeyPair kp) +{ + version (Windows) + { + const(ubyte)[] der = key_data; + Array!ubyte decoded; + + if (is_pem(cast(const(char)[])key_data)) + { + decoded = decode_pem(cast(const(char)[])key_data); + if (decoded.length == 0) + return InternalResult.data_error; + der = decoded[]; + } + + ubyte[32] d = void, x = void, y = void; + if (!parse_ec_sec1_der(der, d, x, y)) + return InternalResult.data_error; + + // Build BCRYPT_ECCKEY_BLOB: { magic(4), cbKey(4)=32 } X[32] Y[32] d[32] + ubyte[104] blob = void; + (cast(uint[])blob[0 .. 8])[0] = 0x34534345; // BCRYPT_ECDSA_PRIVATE_P256_MAGIC + (cast(uint[])blob[0 .. 8])[1] = 32; // cbKey + blob[8 .. 40] = x[]; + blob[40 .. 72] = y[]; + blob[72 .. 104] = d[]; + + NTSTATUS status = BCryptOpenAlgorithmProvider(&kp.halg, BCRYPT_ECDSA_P256_ALGORITHM.ptr, null, 0); + if (status == 0) + { + status = BCryptImportKeyPair(kp.halg, null, BCRYPT_ECCPRIVATE_BLOB.ptr, &kp.hcng, blob.ptr, 104, 0); + if (status == 0) + return Result.success; + BCryptCloseAlgorithmProvider(kp.halg, 0); + kp.halg = null; + } + return Result(cast(uint)status); + } + else version (Posix) + { + const(ubyte)[] der = key_data; + Array!ubyte decoded; + + if (is_pem(cast(const(char)[])key_data)) + { + decoded = decode_pem(cast(const(char)[])key_data); + if (decoded.length == 0) + return InternalResult.data_error; + der = decoded[]; + } + + ubyte[32] d = void, x = void, y = void; + if (!parse_ec_sec1_der(der, d, x, y)) + return InternalResult.data_error; + + // Build uncompressed point: 0x04 || X || Y + ubyte[65] xy = void; + xy[0] = 0x04; + xy[1 .. 33] = x[]; + xy[33 .. 65] = y[]; + + mbedtls_pk_init(&kp.pk); + int ret = urt_pk_import_ec_p256_key(&kp.pk, d.ptr, d.length, xy.ptr, xy.length); + if (ret != 0) + { + mbedtls_pk_free(&kp.pk); + kp.pk = mbedtls_pk_context.init; + return Result(cast(uint)ret); + } + + return Result.success; + } + else + assert(0, "PKI: not implemented for this platform"); +} + +private: + +// Build SEC 1 ECPrivateKey DER (RFC 5915) for P-256 from raw components. +// Both platforms use this format for portable key storage. +// +// ECPrivateKey ::= SEQUENCE { +// INTEGER 1, +// OCTET STRING d[32], +// [0] { OID secp256r1 }, +// [1] { BIT STRING 04 || X || Y } +// } +Result build_ec_sec1_der(ref const ubyte[32] d, ref const ubyte[32] x, ref const ubyte[32] y, out Array!ubyte key_out) +{ + // compute sizes (pass null to measure) + size_t ver_size = der_integer_small(null, 1); // INTEGER 1 + size_t d_size = der_tlv(null, 0x04, d[]); // OCTET STRING d + size_t oid_size = der_tlv(null, 0x06, oid_prime256v1[]); // OID secp256r1 + size_t params_size = der_header(null, 0xa0, oid_size) + oid_size; // [0] { OID } + + enum size_t pt_len = 1 + 32 + 32; // 04 || X || Y + enum size_t bs_content = 1 + pt_len; // unused_bits + point + size_t bs_size = der_header(null, 0x03, bs_content) + bs_content; // BIT STRING + size_t pubkey_size = der_header(null, 0xa1, bs_size) + bs_size; // [1] { BIT STRING } + + size_t content = ver_size + d_size + params_size + pubkey_size; + size_t total = der_header(null, 0x30, content) + content; // outer SEQUENCE + + key_out = Array!ubyte(Alloc, total); + ubyte[] buf = key_out[]; + size_t pos = 0; + + pos += der_header(buf[pos .. $], 0x30, content); + pos += der_integer_small(buf[pos .. $], 1); + pos += der_tlv(buf[pos .. $], 0x04, d[]); + + // [0] EXPLICIT { OID secp256r1 } + pos += der_header(buf[pos .. $], 0xa0, oid_size); + pos += der_tlv(buf[pos .. $], 0x06, oid_prime256v1[]); + + // [1] EXPLICIT { BIT STRING { 04 || X || Y } } + pos += der_header(buf[pos .. $], 0xa1, bs_size); + pos += der_header(buf[pos .. $], 0x03, bs_content); + buf[pos++] = 0x00; // unused bits + buf[pos++] = 0x04; // uncompressed point + buf[pos .. pos + 32] = x[]; + pos += 32; + buf[pos .. pos + 32] = y[]; + pos += 32; + + assert(pos == total); + return Result.success; +} + +// Parse SEC 1 ECPrivateKey DER (RFC 5915) for P-256, extracting raw d, X, Y. +bool parse_ec_sec1_der(const(ubyte)[] der, ref ubyte[32] d, ref ubyte[32] x, ref ubyte[32] y) +{ + if (der.length < 2 || der[0] != 0x30) + return false; + + size_t pos = 1; + size_t seq_len; + if (!read_der_length(der, pos, seq_len)) + return false; + if (pos + seq_len > der.length) + return false; + size_t seq_end = pos + seq_len; + + // INTEGER 1 (version) + if (pos + 3 > seq_end || der[pos] != 0x02 || der[pos + 1] != 0x01 || der[pos + 2] != 0x01) + return false; + pos += 3; + + // OCTET STRING (private key d) + if (pos >= seq_end || der[pos] != 0x04) + return false; + ++pos; + size_t d_len; + if (!read_der_length(der, pos, d_len)) + return false; + if (d_len == 0 || d_len > 32 || pos + d_len > seq_end) + return false; + d[] = 0; + d[32 - d_len .. 32] = der[pos .. pos + d_len]; // right-align + pos += d_len; + + // optional [0] parameters - skip + if (pos < seq_end && der[pos] == 0xa0) + { + ++pos; + size_t param_len; + if (!read_der_length(der, pos, param_len)) + return false; + pos += param_len; + } + + // optional [1] public key + if (pos < seq_end && der[pos] == 0xa1) + { + ++pos; + size_t ctx_len; + if (!read_der_length(der, pos, ctx_len)) + return false; + size_t ctx_end = pos + ctx_len; + // BIT STRING + if (pos >= ctx_end || der[pos] != 0x03) + return false; + ++pos; + size_t bs_len; + if (!read_der_length(der, pos, bs_len)) + return false; + // unused bits (0) + 04 + X + Y = 66 bytes + if (bs_len != 66 || pos + bs_len > ctx_end) + return false; + if (der[pos] != 0x00 || der[pos + 1] != 0x04) + return false; + x[] = der[pos + 2 .. pos + 34]; + y[] = der[pos + 34 .. pos + 66]; + return true; + } + + // No public key in the SEC 1 structure - can't reconstruct without EC math + return false; +} + +bool read_der_length(const(ubyte)[] der, ref size_t pos, out size_t len) +{ + if (pos >= der.length) + return false; + ubyte b = der[pos++]; + if (b < 0x80) + { + len = b; + return true; + } + uint n = b & 0x7f; + if (n == 0 || n > 2 || pos + n > der.length) + return false; + len = 0; + for (uint i = 0; i < n; ++i) + len = (len << 8) | der[pos++]; + return true; +} + +version (Windows) +{ + import core.sys.windows.bcrypt; + import core.sys.windows.ntdef : NTSTATUS; + import core.sys.windows.wincrypt; + import core.sys.windows.windef; + + pragma(lib, "Bcrypt"); + pragma(lib, "Crypt32"); + pragma(lib, "Ncrypt"); + + alias SECURITY_STATUS = int; + alias NCRYPT_PROV_HANDLE = void*; + alias NCRYPT_KEY_HANDLE = void*; + + enum NCRYPT_OVERWRITE_KEY_FLAG = 0x00000080; + enum NCRYPT_DO_NOT_FINALIZE_FLAG = 0x00000400; + enum NCRYPT_ALLOW_EXPORT_FLAG = 0x00000001; + enum NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG = 0x00000002; + enum DWORD CERT_KEY_PROV_INFO_PROP_ID = 2; + enum DWORD NCRYPTBUFFER_PKCS_KEY_NAME = 45; + + enum LPCSTR CERT_STORE_PROV_MEMORY = cast(LPCSTR)2; + enum DWORD CERT_STORE_ADD_REPLACE_EXISTING = 3; + + struct CERT_EXTENSIONS + { + DWORD cExtension; + PCERT_EXTENSION rgExtension; + } + + immutable wchar[] MS_KEY_STORAGE_PROVIDER = "Microsoft Software Key Storage Provider\0"w; + immutable wchar[] NCRYPT_PKCS8_PRIVATE_KEY_BLOB = "PKCS8_PRIVATEKEY\0"w; + immutable wchar[] NCRYPT_EXPORT_POLICY_PROPERTY = "Export Policy\0"w; + + + struct CRYPT_KEY_PROV_INFO + { + wchar* pwszContainerName; + wchar* pwszProvName; + DWORD dwProvType; + DWORD dwFlags; + DWORD cProvParam; + void* rgProvParam; + DWORD dwKeySpec; + } + + struct NCryptBuffer + { + ULONG cbBuffer; + ULONG BufferType; + void* pvBuffer; + } + + struct NCryptBufferDesc + { + ULONG ulVersion; + ULONG cBuffers; + NCryptBuffer* pBuffers; + } + + extern(Windows) @nogc nothrow + { + BOOL CertAddEncodedCertificateToStore(HCERTSTORE hCertStore, DWORD dwCertEncodingType, const(BYTE)* pbCertEncoded, DWORD cbCertEncoded, DWORD dwAddDisposition, PCCERT_CONTEXT* ppCertContext); + BOOL CertSetCertificateContextProperty(PCCERT_CONTEXT pCertContext, DWORD dwPropId, DWORD dwFlags, const(void)* pvData); + SECURITY_STATUS NCryptOpenStorageProvider(NCRYPT_PROV_HANDLE* phProvider, const(wchar)* pszProviderName, DWORD dwFlags); + SECURITY_STATUS NCryptImportKey(NCRYPT_PROV_HANDLE hProvider, NCRYPT_KEY_HANDLE hImportKey, const(wchar)* pszBlobType, void* pParameterList, NCRYPT_KEY_HANDLE* phKey, BYTE* pbData, DWORD cbData, DWORD dwFlags); + SECURITY_STATUS NCryptFreeObject(NCRYPT_PROV_HANDLE hObject); + SECURITY_STATUS NCryptSetProperty(NCRYPT_KEY_HANDLE hObject, const(wchar)* pszProperty, ubyte* pbInput, DWORD cbInput, DWORD dwFlags); + SECURITY_STATUS NCryptFinalizeKey(NCRYPT_KEY_HANDLE hKey, DWORD dwFlags); + SECURITY_STATUS NCryptExportKey(NCRYPT_KEY_HANDLE hKey, NCRYPT_KEY_HANDLE hExportKey, const(wchar)* pszBlobType, void* pParameterList, BYTE* pbOutput, DWORD cbOutput, DWORD* pcbResult, DWORD dwFlags); + SECURITY_STATUS NCryptCreatePersistedKey(NCRYPT_PROV_HANDLE hProvider, NCRYPT_KEY_HANDLE* phKey, const(wchar)* pszAlgId, const(wchar)* pszKeyName, DWORD dwLegacyKeySpec, DWORD dwFlags); + SECURITY_STATUS NCryptDeleteKey(NCRYPT_KEY_HANDLE hKey, DWORD dwFlags); + + PCCERT_CONTEXT CertCreateSelfSignCertificate( + NCRYPT_KEY_HANDLE hCryptProvOrNCryptKey, + CERT_NAME_BLOB* pSubjectIssuerBlob, + DWORD dwFlags, + void* pKeyProvInfo, // PCRYPT_KEY_PROV_INFO + void* pSignatureAlgorithm, // PCRYPT_ALGORITHM_IDENTIFIER + void* pStartTime, // PSYSTEMTIME + void* pEndTime, // PSYSTEMTIME + void* pExtensions // PCERT_EXTENSIONS + ); + } + + size_t generate_key_name(wchar[] buf) + { + import urt.mem.temp : tconcat; + import urt.string.uni : uni_convert; + __gshared uint counter = 0; + size_t len = uni_convert(tconcat("openwatt_key_", counter++), buf); + buf[len] = 0; + return len; + } +} + +version (Posix) +{ + import urt.internal.mbedtls; + + // lazily-initialized global CSPRNG for mbedtls operations + mbedtls_ctr_drbg_context* get_rng() + { + __gshared mbedtls_ctr_drbg_context* rng; + __gshared mbedtls_entropy_context* entropy; + __gshared bool initialized; + + if (initialized) + return rng; + + entropy = urt_entropy_new(); + if (entropy is null) + return null; + + rng = urt_ctr_drbg_new(); + if (rng is null) + { + urt_entropy_delete(entropy); + entropy = null; + return null; + } + + int ret = mbedtls_ctr_drbg_seed(rng, &mbedtls_entropy_func, cast(void*)entropy, null, 0); + if (ret != 0) + { + urt_ctr_drbg_delete(rng); + urt_entropy_delete(entropy); + rng = null; + entropy = null; + return null; + } + + initialized = true; + return rng; + } + + // convert DER-encoded ECDSA signature SEQUENCE { INTEGER r, INTEGER s } + // to raw R||S format (32 bytes each for P-256) + bool der_sig_to_raw(const(ubyte)[] der, ubyte[] raw) + { + if (raw.length < 64) + return false; + + raw[0 .. 64] = 0; + + size_t pos = 0; + + // SEQUENCE + if (pos >= der.length || der[pos++] != 0x30) + return false; + if (pos >= der.length) + return false; + + // sequence length (skip) + if (der[pos] & 0x80) + pos += 1 + (der[pos] & 0x7f); + else + ++pos; + + // parse two INTEGERs into raw[0..32] and raw[32..64] + foreach (i; 0 .. 2) + { + if (pos >= der.length || der[pos++] != 0x02) + return false; + if (pos >= der.length) + return false; + + size_t len = der[pos++]; + if (pos + len > der.length) + return false; + + auto integer = der[pos .. pos + len]; + pos += len; + + // strip leading zero padding (DER INTEGERs are signed, so positive values + // with high bit set get a 0x00 prefix) + while (integer.length > 32 && integer[0] == 0) + integer = integer[1 .. $]; + + if (integer.length > 32) + return false; + + // right-align into 32-byte field + size_t offset = 32 - integer.length; + raw[i * 32 + offset .. i * 32 + offset + integer.length] = integer[]; + } + + return true; + } +} diff --git a/src/urt/dbg.d b/src/urt/dbg.d index 8cc13d1..d5458db 100644 --- a/src/urt/dbg.d +++ b/src/urt/dbg.d @@ -55,48 +55,38 @@ else version (ARM) } } } -else - static assert(0, "TODO: Unsupported architecture"); - - -private: - -package(urt) void setupAssertHandler() +else version (RISCV64) { - import core.exception : assertHandler; - assertHandler = &urt_assert; + pragma(inline, true) + extern(C) void breakpoint() pure nothrow @nogc + { + debug asm pure nothrow @nogc + { + "ebreak"; + } + } } - -void urt_assert(string file, size_t line, string msg) nothrow @nogc +else version (RISCV32) { - import core.stdc.stdlib : exit; - - debug + pragma(inline, true) + extern(C) void breakpoint() pure nothrow @nogc { - import core.stdc.stdio; - - if (msg.length == 0) - msg = "Assertion failed"; - - version (Windows) + debug asm pure nothrow @nogc { - import core.sys.windows.winbase; - char[1024] buffer; - _snprintf(buffer.ptr, buffer.length, "%.*s(%d): %.*s\n", cast(int)file.length, file.ptr, cast(int)line, cast(int)msg.length, msg.ptr); - OutputDebugStringA(buffer.ptr); - - // Windows can have it at stdout aswell? - printf("%.*s(%d): %.*s\n", cast(int)file.length, file.ptr, cast(int)line, cast(int)msg.length, msg.ptr); + "ebreak"; } - else + } +} +else version (Xtensa) +{ + pragma(inline, true) + extern(C) void breakpoint() pure nothrow @nogc + { + debug asm pure nothrow @nogc { - // TODO: write to stderr would be better... - printf("%.*s(%d): %.*s\n", cast(int)file.length, file.ptr, cast(int)line, cast(int)msg.length, msg.ptr); + "break 1, 15"; } - - breakpoint(); -// exit(-1); // TODO: what if some systems don't support a software breakpoint? } - else - exit(-1); } +else + static assert(0, "TODO: Unsupported architecture"); diff --git a/src/urt/digest/md5.d b/src/urt/digest/md5.d index ef75d98..8771adc 100644 --- a/src/urt/digest/md5.d +++ b/src/urt/digest/md5.d @@ -5,178 +5,239 @@ import urt.endian; nothrow @nogc: // pure -struct MD5Context +version (Espressif) { - ulong size; // size of input in bytes - uint[4] buffer; // current accumulation of hash - ubyte[64] input; // input to be used in the next step + struct MD5Context + { + uint[4] buf; + uint[2] bits; + ubyte[64] input; + } - enum uint[4] initState = [ 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 ]; -} + private extern(C) nothrow @nogc + { + void MD5Init(MD5Context* ctx); + void MD5Update(MD5Context* ctx, const ubyte* buf, uint len); + void MD5Final(ubyte* digest, MD5Context* ctx); + } -void md5Init(ref MD5Context ctx) -{ - ctx.size = 0; - ctx.buffer = MD5Context.initState; -} + void md5_init(ref MD5Context ctx) + { + MD5Init(&ctx); + } -void md5Update(ref MD5Context ctx, const void[] input) -{ - size_t offset = ctx.size % 64; - ctx.size += input.length; + void md5_update(ref MD5Context ctx, const void[] data) + { + MD5Update(&ctx, cast(const ubyte*)data.ptr, cast(uint)data.length); + } - size_t i = 64 - offset; - if (input.length < i) + ubyte[16] md5_finalise(ref MD5Context ctx) + { + ubyte[16] digest; + MD5Final(digest.ptr, &ctx); + return digest; + } +} +else +{ + struct MD5Context { - ctx.input[offset .. offset + input.length] = cast(ubyte[])input[]; - return; + ulong size; // size of input in bytes + uint[4] buffer; // current accumulation of hash + ubyte[64] input; // input to be used in the next step + + enum uint[4] initState = [ 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 ]; } - ctx.input[offset .. 64] = cast(ubyte[])input[0 .. i]; + void md5_init(ref MD5Context ctx) + { + ctx.size = 0; + ctx.buffer = MD5Context.initState; + } - while (true) + void md5_update(ref MD5Context ctx, const void[] input) { - // The local variable `tmp` our 512-bit chunk separated into 32-bit words - // we can use in calculations - uint[16] tmp = void; - foreach (uint j; 0 .. 16) - tmp[j] = loadLittleEndian(cast(uint*)ctx.input.ptr + j); - md5Step(ctx.buffer, tmp); + size_t offset = ctx.size % 64; + ctx.size += input.length; - size_t tail = input.length - i; - if (tail < 64) + size_t i = 64 - offset; + if (input.length < i) { - ctx.input[0 .. tail] = (cast(ubyte*)input.ptr)[i .. input.length]; - break; + ctx.input[offset .. offset + input.length] = cast(ubyte[])input[]; + return; + } + + ctx.input[offset .. 64] = cast(ubyte[])input[0 .. i]; + + while (true) + { + uint[16] tmp = void; + foreach (uint j; 0 .. 16) + tmp[j] = loadLittleEndian(cast(uint*)ctx.input.ptr + j); + md5_step(ctx.buffer, tmp); + + size_t tail = input.length - i; + if (tail < 64) + { + ctx.input[0 .. tail] = (cast(ubyte*)input.ptr)[i .. input.length]; + break; + } + ctx.input[] = (cast(ubyte*)input.ptr)[i .. i + 64]; + i += 64; } - ctx.input[] = (cast(ubyte*)input.ptr)[i .. i + 64]; - i += 64; } -} -ubyte[16] md5Finalise(ref MD5Context ctx) -{ - uint[16] tmp = void; - uint offset = ctx.size % 64; - uint padding_length = offset < 56 ? 56 - offset : (56 + 64) - offset; - - // padding used to make the size (in bits) of the input congruent to 448 mod 512 - // TODO: on a large memory system, let's make this a global immutable? - ubyte[64] PADDING = void; - PADDING[0] = 0x80; - PADDING[1 .. padding_length] = 0; - - // Fill in the padding and undo the changes to size that resulted from the update - md5Update(ctx, PADDING[0 .. padding_length]); - ctx.size -= cast(ulong)padding_length; - - // Do a final update (internal to this function) - // Last two 32-bit words are the two halves of the size (converted from bytes to bits) - foreach (uint j; 0 .. 14) - tmp[j] = loadLittleEndian(cast(uint*)ctx.input.ptr + j); - - tmp[14] = cast(uint)(ctx.size*8); - tmp[15] = (ctx.size*8) >> 32; - - md5Step(ctx.buffer, tmp); - - uint[4] digest = void; - foreach (uint k; 0 .. 4) - storeLittleEndian(digest.ptr + k, ctx.buffer.ptr[k]); - return cast(ubyte[16])digest; -} + ubyte[16] md5_finalise(ref MD5Context ctx) + { + uint[16] tmp = void; + uint offset = ctx.size % 64; + uint padding_length = offset < 56 ? 56 - offset : (56 + 64) - offset; -unittest -{ - import urt.encoding; + ubyte[64] PADDING = void; + PADDING[0] = 0x80; + PADDING[1 .. padding_length] = 0; - MD5Context ctx; - md5Init(ctx); - auto digest = md5Finalise(ctx); - assert(digest == Hex!"d41d8cd98f00b204e9800998ecf8427e"); - - md5Init(ctx); - md5Update(ctx, "Hello, World!"); - digest = md5Finalise(ctx); - assert(digest == Hex!"65a8e27d8879283831b664bd8b7f0ad4"); -} + md5_update(ctx, PADDING[0 .. padding_length]); + ctx.size -= cast(ulong)padding_length; + foreach (uint j; 0 .. 14) + tmp[j] = loadLittleEndian(cast(uint*)ctx.input.ptr + j); -private: + tmp[14] = cast(uint)(ctx.size*8); + tmp[15] = (ctx.size*8) >> 32; -__gshared immutable ubyte[] S = [ - 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, - 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, - 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, - 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 -]; - -__gshared immutable uint[] K = [ - 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, - 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, - 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, - 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, - 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, - 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, - 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, - 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, - 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, - 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, - 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, - 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, - 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, - 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, - 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, - 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 -]; - -// rotates a 32-bit word left by n bits -uint rotateLeft(uint x, uint n) - => (x << n) | (x >> (32 - n)); - -// step on 512 bits of input with the main MD5 algorithm -void md5Step(ref uint[4] buffer, ref const uint[16] input) -{ - uint AA = buffer[0]; - uint BB = buffer[1]; - uint CC = buffer[2]; - uint DD = buffer[3]; + md5_step(ctx.buffer, tmp); + + uint[4] digest = void; + foreach (uint k; 0 .. 4) + storeLittleEndian(digest.ptr + k, ctx.buffer.ptr[k]); + return cast(ubyte[16])digest; + } - uint E; - uint j; +private: - foreach (uint i; 0 .. 64) + __gshared immutable ubyte[] S = [ + 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 + ]; + + __gshared immutable uint[] K = [ + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, + 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 + ]; + + // rotates a 32-bit word left by n bits + uint rotate_left(uint x, uint n) + => (x << n) | (x >> (32 - n)); + + // step on 512 bits of input with the main MD5 algorithm + void md5_step(ref uint[4] buffer, ref const uint[16] input) { - final switch(i / 16) + uint AA = buffer[0]; + uint BB = buffer[1]; + uint CC = buffer[2]; + uint DD = buffer[3]; + + uint E; + + uint j; + + foreach (uint i; 0 .. 64) { - case 0: - E = ((BB & CC) | (~BB & DD)); // F(BB, CC, DD); uint F(uint X, uint Y, uint Z) => ((X & Y) | (~X & Z)); - j = i; - break; - case 1: - E = ((BB & DD) | (CC & ~DD)); // G(BB, CC, DD); uint G(uint X, uint Y, uint Z) => ((X & Z) | (Y & ~Z)); - j = ((i*5) + 1) % 16; - break; - case 2: - E = (BB ^ CC ^ DD); // H(BB, CC, DD); uint H(uint X, uint Y, uint Z) => (X ^ Y ^ Z); - j = ((i*3) + 5) % 16; - break; - case 3: - E = (CC ^ (BB | ~DD)); // I(BB, CC, DD); uint I(uint X, uint Y, uint Z) => (Y ^ (X | ~Z)); - j = (i*7) % 16; - break; + final switch(i / 16) + { + case 0: + E = ((BB & CC) | (~BB & DD)); // F(BB, CC, DD); uint F(uint X, uint Y, uint Z) => ((X & Y) | (~X & Z)); + j = i; + break; + case 1: + E = ((BB & DD) | (CC & ~DD)); // G(BB, CC, DD); uint G(uint X, uint Y, uint Z) => ((X & Z) | (Y & ~Z)); + j = ((i*5) + 1) % 16; + break; + case 2: + E = (BB ^ CC ^ DD); // H(BB, CC, DD); uint H(uint X, uint Y, uint Z) => (X ^ Y ^ Z); + j = ((i*3) + 5) % 16; + break; + case 3: + E = (CC ^ (BB | ~DD)); // I(BB, CC, DD); uint I(uint X, uint Y, uint Z) => (Y ^ (X | ~Z)); + j = (i*7) % 16; + break; + } + + uint temp = DD; + DD = CC; + CC = BB; + BB = BB + rotate_left(AA + E + K[i] + input[j], S[i]); + AA = temp; } - uint temp = DD; - DD = CC; - CC = BB; - BB = BB + rotateLeft(AA + E + K[i] + input[j], S[i]); - AA = temp; + buffer[0] += AA; + buffer[1] += BB; + buffer[2] += CC; + buffer[3] += DD; } +} + +unittest +{ + import urt.encoding; + + MD5Context ctx; - buffer[0] += AA; - buffer[1] += BB; - buffer[2] += CC; - buffer[3] += DD; + // empty + md5_init(ctx); + auto digest = md5_finalise(ctx); + assert(digest == HexDecode!"d41d8cd98f00b204e9800998ecf8427e"); + + // single string + md5_init(ctx); + md5_update(ctx, "Hello, World!"); + digest = md5_finalise(ctx); + assert(digest == HexDecode!"65a8e27d8879283831b664bd8b7f0ad4"); + + // RFC 1321 test vectors + md5_init(ctx); + md5_update(ctx, "a"); + digest = md5_finalise(ctx); + assert(digest == HexDecode!"0cc175b9c0f1b6a831c399e269772661"); + + md5_init(ctx); + md5_update(ctx, "abc"); + digest = md5_finalise(ctx); + assert(digest == HexDecode!"900150983cd24fb0d6963f7d28e17f72"); + + md5_init(ctx); + md5_update(ctx, "message digest"); + digest = md5_finalise(ctx); + assert(digest == HexDecode!"f96b697d7cb7938d525a2f31aaf161d0"); + + // progressive: split across multiple updates + md5_init(ctx); + md5_update(ctx, "Hello, "); + md5_update(ctx, "World!"); + digest = md5_finalise(ctx); + assert(digest == HexDecode!"65a8e27d8879283831b664bd8b7f0ad4"); + + // data spanning multiple 64-byte blocks + md5_init(ctx); + md5_update(ctx, "12345678901234567890123456789012345678901234567890123456789012345678901234567890"); + digest = md5_finalise(ctx); + assert(digest == HexDecode!"57edf4a22be3c955ac49da2e2107b67a"); } diff --git a/src/urt/digest/sha.d b/src/urt/digest/sha.d index 196c835..db8c033 100644 --- a/src/urt/digest/sha.d +++ b/src/urt/digest/sha.d @@ -2,147 +2,336 @@ module urt.digest.sha; import urt.endian; +version (Espressif) +{ + version (ESP32) {} else + version = Espressif_Modern; +} + nothrow @nogc: // pure struct SHA1Context { enum DigestBits = 160; + enum DigestLen = DigestBits / 8; + + version (Espressif) + private SHA_CTX ctx; + else + { + enum DigestElements = DigestBits / 32; + + ubyte[64] data; + ulong bitlen; + uint datalen; + uint[DigestElements] state; + + alias transform = sha1_transform; + + enum uint[DigestElements] initState = [ 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 ]; + + __gshared immutable uint[4] K = [ 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6 ]; + } +} +struct SHA224Context +{ + enum DigestBits = 224; enum DigestLen = DigestBits / 8; - enum DigestElements = DigestBits / 32; - ubyte[64] data; - ulong bitlen; - uint datalen; - uint[DigestElements] state; + // TODO: ESP32 hardware SHA-224 should be possible by writing the SHA-224 IV to SHA_TEXT_BASE, + // triggering SHA_256_LOAD_REG, then running as SHA2_256 with truncated output. + version (Espressif_Modern) + private SHA_CTX ctx; + else + { + enum DigestElements = DigestBits / 32; + enum StateElements = 256 / 32; - alias transform = sha1Transform; + ubyte[64] data; + ulong bitlen; + uint datalen; + uint[StateElements] state; - enum uint[DigestElements] initState = [ 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 ]; + alias transform = sha256_transform; - __gshared immutable uint[4] K = [ 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6 ]; + enum uint[StateElements] initState = [ 0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, + 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4 ]; + } } struct SHA256Context { enum DigestBits = 256; - enum DigestLen = DigestBits / 8; - enum DigestElements = DigestBits / 32; - - ubyte[64] data; - ulong bitlen; - uint datalen; - uint[DigestElements] state; - - alias transform = sha256Transform; - - enum uint[DigestElements] initState = [ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, - 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 ]; - - __gshared immutable uint[64] K = [ - 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, - 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, - 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, - 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, - 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, - 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, - 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, - 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 - ]; -} + version (Espressif) + private SHA_CTX ctx; + else + { + enum DigestElements = DigestBits / 32; + enum StateElements = DigestBits / 32; -void shaInit(Context)(ref Context ctx) -{ - ctx.datalen = 0; - ctx.bitlen = 0; - ctx.state = Context.initState; + ubyte[64] data; + ulong bitlen; + uint datalen; + uint[StateElements] state; + + alias transform = sha256_transform; + + enum uint[StateElements] initState = [ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 ]; + } + version (Espressif_Modern) {} else { + __gshared immutable uint[64] K = [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 + ]; + } } -void shaUpdate(Context)(ref Context ctx, const void[] input) +void sha_init(Context)(ref Context ctx) { - const(ubyte)[] data = cast(ubyte[])input; - - while (data.length > 0) + static if (esp_hardware!Context) { - if (ctx.datalen + data.length < 64) + version (ESP32) { - ctx.data[ctx.datalen .. ctx.datalen + data.length] = data[]; - ctx.datalen += cast(uint)data.length; - break; + ets_sha_enable(); + ets_sha_init(&ctx.ctx); } - - ctx.data[ctx.datalen .. 64] = data[0 .. 64 - ctx.datalen]; - data = data[64 - ctx.datalen .. $]; - ctx.bitlen += 512; + else + { + ets_sha_enable(); + static if (is(Context == SHA1Context)) + ets_sha_init(&ctx.ctx, SHA_TYPE.SHA1); + else static if (is(Context == SHA224Context)) + ets_sha_init(&ctx.ctx, SHA_TYPE.SHA2_224); + else + ets_sha_init(&ctx.ctx, SHA_TYPE.SHA2_256); + ets_sha_starts(&ctx.ctx, 0); + } + } + else + { ctx.datalen = 0; - Context.transform(ctx, ctx.data); + ctx.bitlen = 0; + ctx.state = Context.initState; } } -ubyte[Context.DigestLen] shaFinalise(Context)(ref Context ctx) +void sha_update(Context)(ref Context ctx, const void[] input) { - uint i = ctx.datalen; + static if (esp_hardware!Context) + { + version (ESP32) + { + static if (is(Context == SHA1Context)) + enum sha_type = SHA_TYPE.SHA1; + else + enum sha_type = SHA_TYPE.SHA2_256; + ets_sha_update(&ctx.ctx, sha_type, cast(const ubyte*)input.ptr, input.length * 8); + } + else + ets_sha_update(&ctx.ctx, cast(const ubyte*)input.ptr, cast(uint)input.length, true); + } + else + { + const(ubyte)[] data = cast(ubyte[])input; + + while (data.length > 0) + { + if (ctx.datalen + data.length < 64) + { + ctx.data[ctx.datalen .. ctx.datalen + data.length] = data[]; + ctx.datalen += cast(uint)data.length; + break; + } + + ctx.data[ctx.datalen .. 64] = data[0 .. 64 - ctx.datalen]; + data = data[64 - ctx.datalen .. $]; + ctx.bitlen += 512; + ctx.datalen = 0; + Context.transform(ctx, ctx.data); + } + } +} - // Pad whatever data is left in the buffer. - ctx.data[i++] = 0x80; - if (ctx.datalen > 56) +ubyte[Context.DigestLen] sha_finalise(Context)(ref Context ctx) +{ + static if (esp_hardware!Context) { - ctx.data[i .. 64] = 0x00; - Context.transform(ctx, ctx.data); - i = 0; + version (ESP32) + { + static if (is(Context == SHA1Context)) + enum sha_type = SHA_TYPE.SHA1; + else + enum sha_type = SHA_TYPE.SHA2_256; + ubyte[Context.DigestLen] digest; + ets_sha_finish(&ctx.ctx, sha_type, digest.ptr); + ets_sha_disable(); + return digest; + } + else + { + ubyte[Context.DigestLen] digest; + ets_sha_finish(&ctx.ctx, digest.ptr); + ets_sha_disable(); + return digest; + } } - ctx.data[i .. 56] = 0x00; + else + { + uint i = ctx.datalen; + + ctx.data[i++] = 0x80; + if (ctx.datalen >= 56) + { + ctx.data[i .. 64] = 0x00; + Context.transform(ctx, ctx.data); + i = 0; + } + ctx.data[i .. 56] = 0x00; - // Append to the padding the total message's length in bits and transform. - ctx.bitlen += ctx.datalen * 8; - ctx.data[56..64] = ctx.bitlen.nativeToBigEndian; - Context.transform(ctx, ctx.data); + ctx.bitlen += ctx.datalen * 8; + ctx.data[56..64] = ctx.bitlen.nativeToBigEndian; + Context.transform(ctx, ctx.data); - // Since this implementation uses little endian byte ordering and SHA uses big endian, - // reverse all the bytes when copying the final state to the output hash. - uint[Context.DigestElements] digest = void; - foreach (uint j; 0 .. Context.DigestElements) - digest[j] = byteReverse(ctx.state[j]); + uint[Context.DigestElements] digest = void; + foreach (uint j; 0 .. Context.DigestElements) + digest[j] = byte_reverse(ctx.state[j]); - return cast(ubyte[Context.DigestLen])digest; + return cast(ubyte[Context.DigestLen])digest; + } } unittest { import urt.encoding; + // SHA-1 SHA1Context ctx; - shaInit(ctx); - auto digest = shaFinalise(ctx); - assert(digest == Hex!"da39a3ee5e6b4b0d3255bfef95601890afd80709"); - - shaInit(ctx); - shaUpdate(ctx, "Hello, World!"); - digest = shaFinalise(ctx); - assert(digest == Hex!"0a0a9f2a6772942557ab5355d76af442f8f65e01"); + // empty + sha_init(ctx); + auto digest = sha_finalise(ctx); + assert(digest == HexDecode!"da39a3ee5e6b4b0d3255bfef95601890afd80709"); + + // single string + sha_init(ctx); + sha_update(ctx, "Hello, World!"); + digest = sha_finalise(ctx); + assert(digest == HexDecode!"0a0a9f2a6772942557ab5355d76af442f8f65e01"); + + // FIPS 180 test vectors + sha_init(ctx); + sha_update(ctx, "abc"); + digest = sha_finalise(ctx); + assert(digest == HexDecode!"a9993e364706816aba3e25717850c26c9cd0d89d"); + + // progressive + sha_init(ctx); + sha_update(ctx, "Hello, "); + sha_update(ctx, "World!"); + digest = sha_finalise(ctx); + assert(digest == HexDecode!"0a0a9f2a6772942557ab5355d76af442f8f65e01"); + + // multi-block (>64 bytes) + sha_init(ctx); + sha_update(ctx, "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); + digest = sha_finalise(ctx); + assert(digest == HexDecode!"84983e441c3bd26ebaae4aa1f95129e5e54670f1"); + + // SHA-224 + SHA224Context ctx3; + + // empty + sha_init(ctx3); + auto digest3 = sha_finalise(ctx3); + assert(digest3 == HexDecode!"d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f"); + + // FIPS 180 test vector + sha_init(ctx3); + sha_update(ctx3, "abc"); + digest3 = sha_finalise(ctx3); + assert(digest3 == HexDecode!"23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7"); + + // progressive + sha_init(ctx3); + sha_update(ctx3, "Hello, "); + sha_update(ctx3, "World!"); + auto digest3b = sha_finalise(ctx3); + sha_init(ctx3); + sha_update(ctx3, "Hello, World!"); + digest3 = sha_finalise(ctx3); + assert(digest3 == digest3b); + + // multi-block + sha_init(ctx3); + sha_update(ctx3, "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); + digest3 = sha_finalise(ctx3); + assert(digest3 == HexDecode!"75388b16512776cc5dba5da1fd890150b0c6455cb4f58b1952522525"); + + // SHA-256 SHA256Context ctx2; - shaInit(ctx2); - auto digest2 = shaFinalise(ctx2); - assert(digest2 == Hex!"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); - - shaInit(ctx2); - shaUpdate(ctx2, "Hello, World!"); - digest2 = shaFinalise(ctx2); - assert(digest2 == Hex!"dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"); + + // empty + sha_init(ctx2); + auto digest2 = sha_finalise(ctx2); + assert(digest2 == HexDecode!"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); + + // single string + sha_init(ctx2); + sha_update(ctx2, "Hello, World!"); + digest2 = sha_finalise(ctx2); + assert(digest2 == HexDecode!"dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"); + + // FIPS 180 test vectors + sha_init(ctx2); + sha_update(ctx2, "abc"); + digest2 = sha_finalise(ctx2); + assert(digest2 == HexDecode!"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"); + + // progressive + sha_init(ctx2); + sha_update(ctx2, "Hello, "); + sha_update(ctx2, "World!"); + digest2 = sha_finalise(ctx2); + assert(digest2 == HexDecode!"dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"); + + // multi-block + sha_init(ctx2); + sha_update(ctx2, "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); + digest2 = sha_finalise(ctx2); + assert(digest2 == HexDecode!"248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"); } private: -uint ROTLEFT(uint a, uint b) => (a << b) | (a >> (32 - b)); -uint ROTRIGHT(uint a, uint b) => (a >> b) | (a << (32 - b)); +template esp_hardware(Context) +{ + version (Espressif_Modern) + enum esp_hardware = true; + else version (Espressif) + // SHA-224 hardware on ESP32 Classic would need SHA-256 with custom IV + // (see TODO on SHA224Context); fall through to software for now. + enum esp_hardware = !is(Context == SHA224Context); + else + enum esp_hardware = false; +} -void sha1Transform(ref SHA1Context ctx, const ubyte[] data) +version (Espressif) {} else +void sha1_transform(Context)(ref Context ctx, const ubyte[] data) { + static uint ROTLEFT(uint a, uint b) => (a << b) | (a >> (32 - b)); + uint a, b, c, d, e, i, j, t; uint[80] m = void; @@ -204,8 +393,11 @@ void sha1Transform(ref SHA1Context ctx, const ubyte[] data) ctx.state[4] += e; } -void sha256Transform(ref SHA256Context ctx, const ubyte[] data) +version (Espressif_Modern) {} else +void sha256_transform(Context)(ref Context ctx, const ubyte[] data) { + static uint ROTRIGHT(uint a, uint b) => (a >> b) | (a << (32 - b)); + static uint CH(uint x, uint y, uint z) => (x & y) ^ (~x & z); static uint MAJ(uint x, uint y, uint z) => (x & y) ^ (x & z) ^ (y & z); static uint EP0(uint x) => ROTRIGHT(x, 2) ^ ROTRIGHT(x, 13) ^ ROTRIGHT(x, 22); @@ -253,3 +445,56 @@ void sha256Transform(ref SHA256Context ctx, const ubyte[] data) ctx.state[6] += g; ctx.state[7] += h; } + + +// ESP32 (original) has a different ROM API from all later chips: +// - ets_sha_init takes no type; type passed to update/finish instead +// - ets_sha_update size is in bits, not bytes; no ets_sha_starts +// - smaller SHA_CTX struct +// - no SHA224 in ROM (only SHA1=0, SHA2_256=1) +version (Espressif): + +private extern(C) nothrow @nogc +{ + void ets_sha_enable(); + void ets_sha_disable(); + + version (Espressif_Modern) + { + int ets_sha_init(SHA_CTX* ctx, SHA_TYPE type); + int ets_sha_starts(SHA_CTX* ctx, ushort sha512_t); + void ets_sha_update(SHA_CTX* ctx, const ubyte* input, uint input_bytes, bool update_ctx); + int ets_sha_finish(SHA_CTX* ctx, ubyte* output); + } + else + { + void ets_sha_init(SHA_CTX* ctx); + void ets_sha_update(SHA_CTX* ctx, SHA_TYPE type, const ubyte* input, size_t input_bits); + void ets_sha_finish(SHA_CTX* ctx, SHA_TYPE type, ubyte* output); + } +} + +version (Espressif_Modern) +{ + private enum SHA_TYPE : int { SHA1 = 0, SHA2_224, SHA2_256 } + + private struct SHA_CTX + { + bool start; + bool in_hardware; + SHA_TYPE type; + uint[16] state; + ubyte[128] buffer; + uint[4] total_bits; + } +} +else +{ + private enum SHA_TYPE : int { SHA1 = 0, SHA2_256 } + + private struct SHA_CTX + { + bool start; + uint[4] total_input_bits; + } +} diff --git a/src/urt/driver/baremetal/exception.d b/src/urt/driver/baremetal/exception.d new file mode 100644 index 0000000..40bb9d4 --- /dev/null +++ b/src/urt/driver/baremetal/exception.d @@ -0,0 +1,149 @@ +/// Bare-metal exception/crash driver. +/// +/// Provides the stack-trace driver interface for bare-metal targets +/// (BK7231, BL618, BL808, RP2350, STM32, ...). No symbol resolution is +/// available on-device - addresses are printed raw; decode offline with +/// `addr2line -e `. +/// +/// Requires the compiler to keep the frame pointer live +/// (`-frame-pointer=all` on LDC). Without it the fp chain is garbage. +module urt.driver.baremetal.exception; + +version (BareMetal): + +import urt.internal.exception : Resolved; + +nothrow @nogc: + +// Linker symbols bounding the valid stack range. Used by the fp-chain +// walker to stop dereferencing junk. Every bare-metal linker script in +// this tree defines both. +extern(C) extern __gshared { + pragma(mangle, "_stack_top") void* _stack_top_ptr; + pragma(mangle, "_bss_end") void* _bss_end_ptr; +} + + +// --- Shared fp-chain walker ------------------------------------------- +// +// RV64 / AArch64 / ARM-AAPCS all share the same prologue layout when +// -frame-pointer=all is in effect: +// fp[-1] = saved return address +// fp[-2] = saved previous frame pointer +// (indices in machine-word units; RV64 uses fp-8 / fp-16 in bytes). +// +// On 32-bit ARM AAPCS the layout is fp[0] = prev fp, fp[+1] = saved lr +// for Thumb prologues - we don't currently target that combo, so stick +// to the standard layout for now. +// +/// Walk the frame-pointer chain starting at `fp`, writing return addresses +/// into `out_addrs`. Returns the number of frames captured. +size_t walk_fp_chain(size_t fp, void*[] out_addrs) @trusted +{ + if (out_addrs.length == 0) + return 0; + + const stack_hi = cast(size_t) _stack_top_ptr; + const stack_lo = cast(size_t) _bss_end_ptr; + + size_t n = 0; + while (n < out_addrs.length) + { + if (fp < stack_lo || fp >= stack_hi || (fp & (size_t.sizeof - 1)) != 0) + break; + + const ret_addr = *cast(size_t*)(fp - size_t.sizeof); + const prev_fp = *cast(size_t*)(fp - 2 * size_t.sizeof); + + if (ret_addr == 0) + break; + + out_addrs[n++] = cast(void*) ret_addr; + + if (prev_fp == 0 || prev_fp <= fp) + break; + fp = prev_fp; + } + return n; +} + + +// --- Read own frame pointer ------------------------------------------- + +private size_t read_fp() @trusted +{ + size_t fp; + version (RISCV64) + asm nothrow @nogc @trusted { "mv %0, s0" : "=r"(fp); } + else version (RISCV32) + asm nothrow @nogc @trusted { "mv %0, s0" : "=r"(fp); } + else version (AArch64) + asm nothrow @nogc @trusted { "mov %0, x29" : "=r"(fp); } + else version (ARM) + asm nothrow @nogc @trusted { "mov %0, r11" : "=r"(fp); } + else version (Xtensa) + asm nothrow @nogc @trusted { "mov %0, a7" : "=r"(fp); } // Xtensa: a7 with -mno-serialize-volatile + else + fp = 0; // unknown arch - capture returns empty + return fp; +} + + +// --- Driver interface ------------------------------------------------- +// +// All three primitives assume they are called through a one-level public +// wrapper in urt.internal.exception (kept non-inlined via pragma(inline, +// false)). The wrapper's frame is accounted for in the skip counts. + +/// Capture the caller's call stack. First entry = return address of +/// the function that called the public `capture_trace` wrapper. +size_t _capture_trace(void*[] addrs) @trusted +{ + auto fp = read_fp(); + if (fp == 0) + return 0; + // fp is _capture_trace's own frame. Step one up to the wrapper's + // frame, then walk_fp_chain reads the wrapper's saved ra (= return + // address in the caller). That becomes the first captured entry. + const prev_fp = *cast(size_t*)(fp - 2 * size_t.sizeof); + if (prev_fp == 0) + return 0; + return walk_fp_chain(prev_fp, addrs); +} + +/// Return the return address of the `skip`-th frame above the public +/// `caller_address` wrapper's caller. +void* _caller_address(uint skip) @trusted +{ + auto fp = read_fp(); + if (fp == 0) + return null; + + // walk_fp_chain starting from _caller_address's own fp produces: + // buf[0] = PC inside the public wrapper (where _caller_address returns) + // buf[1] = PC inside USER (where wrapper returns) + // buf[2] = PC inside USER's caller ← skip=0 wants this + void*[32] buf = void; + const need = skip + 3; + if (need > buf.length) + return null; + const got = walk_fp_chain(fp, buf[0 .. need]); + if (got < need) + return null; + return buf[need - 1]; +} + +/// On bare-metal we have no on-device symbol table. Always returns +/// false; decode the address offline with `addr2line`. +bool _resolve_address(void* addr, out Resolved r) @trusted +{ + return false; +} + +/// No on-device symbols - caller should treat `results[]` as empty. +bool _resolve_batch(const(void*)[] addrs, Resolved[] results) @trusted +{ + cast(void) addrs; + cast(void) results; + return false; +} diff --git a/src/urt/driver/bk7231/alloc.d b/src/urt/driver/bk7231/alloc.d new file mode 100644 index 0000000..af693c4 --- /dev/null +++ b/src/urt/driver/bk7231/alloc.d @@ -0,0 +1,38 @@ +module urt.driver.bk7231.alloc; + +import urt.mem.alloc : MemFlags; + +nothrow @nogc: + +enum has_realloc = false; +enum has_expand = false; +enum has_memsize = false; +enum has_exec = false; +enum has_retain = false; +enum has_memflags = false; + +void[] _alloc(size_t size, size_t alignment, MemFlags) pure +{ + import urt.util : align_down; + + size_t header_size = (void*).sizeof + alignment; + void* p = malloc(header_size + size); + if (p is null) + return null; + + size_t allocptr = align_down(cast(size_t)p + header_size, alignment); + (cast(void**)allocptr)[-1] = p; + return (cast(void*)allocptr)[0 .. size]; +} + +void _free(void* ptr) pure +{ + free((cast(void**)ptr)[-1]); +} + + +private: + +extern(C) void* malloc(size_t size) pure; +extern(C) void free(void* ptr) pure; + diff --git a/src/urt/driver/bk7231/gpio.d b/src/urt/driver/bk7231/gpio.d new file mode 100644 index 0000000..4a53a05 --- /dev/null +++ b/src/urt/driver/bk7231/gpio.d @@ -0,0 +1,112 @@ +// BK7231 GPIO. Per-pin config registers at GPIO_BASE + pin*4 (function/ +// input/output/pull). A 2-bit perial-mode field per pin in GPIO_FUNC_CFG +// selects the peripheral when GMODE_FUNC_EN is set; only pins 0..15 have +// a perial-mode field. +// +// Pin numbering: linear 0..31. +// +// Software GPIO primitives are unimplemented (need full per-pin config +// bit definitions from the BK7231 reference manual). gpio_set_function +// works and is sufficient for UART pin mux today. +module urt.driver.bk7231.gpio; + +import core.volatile : volatileLoad, volatileStore; + +import urt.driver.gpio : Pull, DriveMode; + +@nogc nothrow: + + +enum uint num_gpio = 32; +enum bool has_pull_up = true; +enum bool has_pull_down = true; +enum bool has_open_drain = false; +enum bool has_pin_function_muxing = true; + + +uint gpio_count() => num_gpio; + + +void gpio_output_init(uint pin, bool initial = false, DriveMode mode = DriveMode.push_pull) +{ + cast(void) pin; cast(void) initial; cast(void) mode; + assert(false, "bk7231 gpio: gpio_output_init not yet implemented"); +} + +void gpio_input_init(uint pin, Pull pull = Pull.none) +{ + cast(void) pin; cast(void) pull; + assert(false, "bk7231 gpio: gpio_input_init not yet implemented"); +} + +void gpio_output_set(uint pin, bool value) +{ + cast(void) pin; cast(void) value; + assert(false, "bk7231 gpio: gpio_output_set not yet implemented"); +} + +void gpio_output_toggle(uint pin) +{ + cast(void) pin; + assert(false, "bk7231 gpio: gpio_output_toggle not yet implemented"); +} + +bool gpio_input_read(uint pin) +{ + cast(void) pin; + assert(false, "bk7231 gpio: gpio_input_read not yet implemented"); + return false; +} + +void gpio_set_pull(uint pin, Pull pull) +{ + cast(void) pin; cast(void) pull; + assert(false, "bk7231 gpio: gpio_set_pull not yet implemented"); +} + +void gpio_release(uint pin) +{ + cast(void) pin; + assert(false, "bk7231 gpio: gpio_release not yet implemented"); +} + +void gpio_set_function(uint pin, uint function_id, Pull pull = Pull.none, DriveMode mode = DriveMode.push_pull) +{ + assert(pin < 16, "bk7231 gpio: perial-mode field only exists for pins 0..15"); + assert(function_id < 4, "bk7231 gpio: perial mode is 2-bit (0..3)"); + assert(mode == DriveMode.push_pull, "bk7231 gpio: open-drain not supported"); + + uint cfg = GMODE_FUNC_EN | GMODE_OUTPUT_EN; + if (pull == Pull.up) + cfg |= GMODE_PULL_EN | GMODE_PULL_UP; + else if (pull == Pull.down) + cfg |= GMODE_PULL_EN; + reg_write(GPIO_BASE + pin * 4, cfg); + + uint func_cfg = reg_read(GPIO_FUNC_CFG); + func_cfg &= ~(0x3u << (pin * 2)); + func_cfg |= (function_id & 0x3u) << (pin * 2); + reg_write(GPIO_FUNC_CFG, func_cfg); +} + + +private: + +enum uint GPIO_BASE = 0x0080_2800; +enum uint GPIO_FUNC_CFG = GPIO_BASE + 32 * 4; + +// Bit positions inferred from SDK gpio_enable_second_function value 0x78. +enum uint GMODE_FUNC_EN = 1u << 3; +enum uint GMODE_OUTPUT_EN = 1u << 4; +enum uint GMODE_PULL_EN = 1u << 5; +enum uint GMODE_PULL_UP = 1u << 6; + +uint reg_read(uint addr) +{ + return volatileLoad(cast(uint*)(cast(size_t)addr)); +} + +void reg_write(uint addr, uint val) +{ + volatileStore(cast(uint*)(cast(size_t)addr), val); +} diff --git a/src/urt/driver/bk7231/irq.d b/src/urt/driver/bk7231/irq.d new file mode 100644 index 0000000..21e1a70 --- /dev/null +++ b/src/urt/driver/bk7231/irq.d @@ -0,0 +1,85 @@ +// BK7231 interrupt controller (shared by BK7231N and BK7231T) +// +// ARM968E-S uses a custom Beken interrupt controller (not ARM GIC/NVIC). +// The BK7231N ICU (Interrupt Control Unit) is at 0x0080_2000. +module urt.driver.bk7231.irq; + +import core.volatile; + +@nogc nothrow: + +enum bool has_plic = false; +enum bool has_nvic = false; +enum bool has_per_irq_control = true; +enum bool has_irq_priority = false; +enum bool has_wait_for_interrupt = false; +enum bool has_irq_diagnostics = false; +enum uint irq_max = 32; + +// Disable all IRQs (set CPSR I+F bits) +void irq_disable() +{ + uint cpsr; + asm @nogc nothrow { "mrs %0, cpsr" : "=r" (cpsr); } + cpsr |= 0xC0; + asm @nogc nothrow { "msr cpsr_c, %0" :: "r" (cpsr); } +} + +// Enable IRQs (clear CPSR I bit) +void irq_enable() +{ + uint cpsr; + asm @nogc nothrow { "mrs %0, cpsr" : "=r" (cpsr); } + cpsr &= ~0x80; + asm @nogc nothrow { "msr cpsr_c, %0" :: "r" (cpsr); } +} + +void irq_set_enable(uint irq_num) +{ + uint mask = volatileLoad(cast(uint*)(cast(size_t)(icu_base + icu_int_enable))); + mask |= (1u << irq_num); + volatileStore(cast(uint*)(cast(size_t)(icu_base + icu_int_enable)), mask); +} + +void irq_clear_enable(uint irq_num) +{ + uint mask = volatileLoad(cast(uint*)(cast(size_t)(icu_base + icu_int_enable))); + mask &= ~(1u << irq_num); + volatileStore(cast(uint*)(cast(size_t)(icu_base + icu_int_enable)), mask); +} + + +private: + +enum uint icu_base = 0x0080_2000; + +// ICU register offsets (from SDK icu.h) +enum +{ + icu_peri_clk_pwd = 2 * 4, // 0x08: peripheral clock power-down (1=off) + icu_clk_gating = 3 * 4, // 0x0C: peripheral clock gating + icu_int_enable = 16 * 4, // 0x40: interrupt enable mask (FIQ [31:16] | IRQ [15:0]) + icu_global_int = 17 * 4, // 0x44: global IRQ/FIQ enable + icu_int_raw = 18 * 4, // 0x48: raw interrupt status + icu_int_status = 19 * 4, // 0x4C: masked interrupt status + icu_arm_wakeup = 20 * 4, // 0x50: ARM wakeup enable +} + +// IRQ bit positions in icu_int_enable / icu_int_status +enum : uint +{ + IRQ_UART1 = 1 << 0, + IRQ_UART2 = 1 << 1, + IRQ_I2C1 = 1 << 2, + IRQ_IRDA = 1 << 3, + IRQ_I2C2 = 1 << 5, + IRQ_SPI = 1 << 6, + IRQ_GPIO = 1 << 7, + IRQ_TIMER = 1 << 8, + IRQ_PWM = 1 << 9, + IRQ_ADC = 1 << 11, + IRQ_SDIO = 1 << 12, + IRQ_SEC = 1 << 13, + IRQ_LA = 1 << 14, + IRQ_DMA = 1 << 15, +} diff --git a/src/urt/driver/bk7231/package.d b/src/urt/driver/bk7231/package.d new file mode 100644 index 0000000..0e35501 --- /dev/null +++ b/src/urt/driver/bk7231/package.d @@ -0,0 +1,32 @@ +// BK7231 platform package (ARM968E-S, ARMv5TE) +// +// Provides sys_init() as the single entry point for all +// hardware initialization. Called from start.S before main(). +module urt.driver.bk7231; + +public import urt.driver.bk7231.uart; +public import urt.driver.bk7231.irq; +public import urt.driver.bk7231.timer; + +import urt.driver.uart : UartConfig; + +@nogc nothrow: + +private extern(C) void __register_frame_info(const void*, void*); +private extern(C) extern const ubyte __eh_frame_start; +private ubyte[48] __eh_frame_object; + +extern(C) void sys_init() +{ + __register_frame_info(&__eh_frame_start, &__eh_frame_object); + + // Init UART1 (console) at default baud for early output + uart_hw_init(0, UartConfig.init); + + uart0_hw_puts("BK7231: sys_init\r\n"); + + // Init freerunning timer for monotonic clock + timer_hw_init(); + + uart0_hw_puts("BK7231: ready\r\n"); +} diff --git a/src/urt/driver/bk7231/start.S b/src/urt/driver/bk7231/start.S new file mode 100644 index 0000000..a22f40b --- /dev/null +++ b/src/urt/driver/bk7231/start.S @@ -0,0 +1,119 @@ +/* BK7231N (ARM968E-S, ARMv5TE) startup + * + * Boot flow: + * 1. Beken bootloader validates image, jumps to Reset_Handler + * 2. Reset_Handler runs: + * a. Set up stack pointer + * b. Copy .got from flash to SRAM + * c. Copy .data from flash to SRAM + * d. Copy .tdata from flash to SRAM + * e. Zero .bss + * f. Call sys_init (platform init) + * g. Run .init_array (D module constructors) + * h. Call main + * + * ARM968E-S has no vector table relocation (no VTOR). Exception vectors live + * at 0x00000000 (or 0xFFFF0000 if high-vectors enabled). The Beken bootloader + * owns the vector table; we install handlers via the SDK's IRQ registration API. + */ + + .arm + .cpu arm968e-s + .fpu softvfp + + .section .text, "ax" + .global Reset_Handler + .type Reset_Handler, %function +Reset_Handler: + + /* -- Disable IRQs during init -- */ + mrs r0, cpsr + orr r0, r0, #0xC0 /* set I+F bits */ + msr cpsr_c, r0 + + /* -- Set up stack -- */ + ldr sp, =_stack_top + + /* -- Copy .got from flash (LMA) to SRAM (VMA) -- */ + ldr r0, =_got_start + ldr r1, =_got_load + ldr r2, =_got_end + cmp r0, r1 + beq .Lgot_done + b .Lgot_check +.Lgot_copy: + ldr r3, [r1], #4 + str r3, [r0], #4 +.Lgot_check: + cmp r0, r2 + blo .Lgot_copy +.Lgot_done: + + /* -- Copy .data from flash to SRAM -- */ + ldr r0, =_data_start + ldr r1, =_data_load + ldr r2, =_data_end + b .Ldata_check +.Ldata_copy: + ldr r3, [r1], #4 + str r3, [r0], #4 +.Ldata_check: + cmp r0, r2 + blo .Ldata_copy + + /* -- Copy .tdata from flash to SRAM -- */ + ldr r0, =_tdata_start + ldr r1, =_tdata_load + ldr r2, =_tdata_end + b .Ltdata_check +.Ltdata_copy: + ldr r3, [r1], #4 + str r3, [r0], #4 +.Ltdata_check: + cmp r0, r2 + blo .Ltdata_copy + + /* -- Zero .bss -- */ + ldr r0, =_bss_start + ldr r2, =_bss_end + mov r3, #0 + b .Lbss_check +.Lbss_zero: + str r3, [r0], #4 +.Lbss_check: + cmp r0, r2 + blo .Lbss_zero + + /* -- Platform init -- */ + bl sys_init + + /* -- Run .init_array (D module constructors) -- */ + ldr r4, =__init_array_start + ldr r5, =__init_array_end + b .Linit_check +.Linit_loop: + ldr r0, [r4], #4 + mov lr, pc + bx r0 +.Linit_check: + cmp r4, r5 + blo .Linit_loop + + /* -- Enter main (should never return) -- */ + bl main + +.Lhalt: + b .Lhalt + + .size Reset_Handler, . - Reset_Handler + +/* ================================================================ + * __aeabi_read_tp -- ARM EABI thread pointer for TLS access + * Single-threaded bare-metal: return fixed pointer to .tdata + * ================================================================ */ + .global __aeabi_read_tp + .type __aeabi_read_tp, %function +__aeabi_read_tp: + ldr r0, =_tdata_start + bx lr + .size __aeabi_read_tp, . - __aeabi_read_tp diff --git a/src/urt/driver/bk7231/syscalls.d b/src/urt/driver/bk7231/syscalls.d new file mode 100644 index 0000000..e89078f --- /dev/null +++ b/src/urt/driver/bk7231/syscalls.d @@ -0,0 +1,55 @@ +// BK7231 newlib/picolibc syscall stubs +// +// Minimal stubs to satisfy picolibc's syscall requirements. +// Same pattern as RP2350 -- most are no-ops for baremetal. +module urt.driver.bk7231.syscalls; + +@nogc nothrow: + +private extern(C) extern const void* __heap_start; +private extern(C) extern const void* __heap_end; + +private __gshared void* _heap_ptr; + +extern(C) void* _sbrk(ptrdiff_t incr) +{ + if (_heap_ptr is null) + _heap_ptr = cast(void*)&__heap_start; + + void* prev = _heap_ptr; + void* next = _heap_ptr + incr; + + if (next > cast(void*)&__heap_end) + return cast(void*)-1; + + _heap_ptr = next; + return prev; +} + +extern(C) int _write(int fd, const void* buf, size_t count) +{ + import urt.driver.bk7231.uart : uart0_hw_puts; + if (fd == 1 || fd == 2) + uart0_hw_puts((cast(const(char)*)buf)[0 .. count]); + return cast(int)count; +} + +extern(C) int _read(int, void*, size_t) { return 0; } +extern(C) int _close(int) { return -1; } +extern(C) int _lseek(int, int, int) { return 0; } +extern(C) int _fstat(int, void*) { return 0; } +extern(C) int _isatty(int) { return 1; } +extern(C) void _exit(int) { while (true) {} } +extern(C) int _kill(int, int) { return -1; } +extern(C) int _getpid() { return 1; } +extern(C) int usleep(uint) { return 0; } + +// DWARF unwinder stubs -- ARM uses EHABI, not DWARF. +// These satisfy link-time references from the exception personality code. +extern(C) void __register_frame_info(const void*, void*) {} +extern(C) size_t _Unwind_GetIPInfo(void*, int*) { return 0; } +extern(C) void _Unwind_SetGR(void*, int, size_t) {} +extern(C) void _Unwind_SetIP(void*, size_t) {} + +// ARM EHABI resume unwind -- called by LDC's exception propagation +extern(C) void _d_eh_resume_unwind(void*) {} diff --git a/src/urt/driver/bk7231/timer.d b/src/urt/driver/bk7231/timer.d new file mode 100644 index 0000000..13f627c --- /dev/null +++ b/src/urt/driver/bk7231/timer.d @@ -0,0 +1,143 @@ +// BK7231 timer driver (shared by BK7231N and BK7231T) +// +// The BK7231 timer/PWM block lives at PWM_NEW_BASE = 0x0080_2A00. +// Timers 0-2 run from 26MHz, timers 3-5 from 32kHz. +// +// We use Timer0 as a freerunning monotonic clock source at 26MHz. +// The counter counts up from 0 to the period register value, then wraps. +// With period = 0xFFFF_FFFF, it wraps every ~165s at 26MHz. +// +// Reading the current count is a two-step process: +// 1. Write (timer_index << 2) | 1 to READ_CTL +// 2. Poll READ_CTL until bit 0 clears +// 3. Read current count from READ_VALUE +// +// Register layout verified against Beken SDK: +// sdk/OpenBK7231N/platforms/bk7231n/bk7231n_os/beken378/driver/pwm/bk_timer.h +// sdk/OpenBK7231N/platforms/bk7231n/bk7231n_os/beken378/driver/pwm/bk_timer.c +module urt.driver.bk7231.timer; + +import core.volatile; + +@nogc nothrow: + + +// ─── Timer/PWM base (from SDK pwm.h) ──────────────────────────────── + +private enum uint PWM_NEW_BASE = 0x0080_2A00; + + +// ─── Timer0-2 registers (26MHz group) ──────────────────────────────── + +private enum : uint +{ + // Period/end-value registers (one per timer, writable) + TIMER0_PERIOD = PWM_NEW_BASE + 0 * 4, // 0x0080_2A00 + TIMER1_PERIOD = PWM_NEW_BASE + 1 * 4, // 0x0080_2A04 + TIMER2_PERIOD = PWM_NEW_BASE + 2 * 4, // 0x0080_2A08 + + // Shared control register for Timer0-2 + TIMER0_2_CTL = PWM_NEW_BASE + 3 * 4, // 0x0080_2A0C + + // Counter read-back registers (BK7231N/U; BK7231T has them in practice) + TIMER0_2_READ_CTL = PWM_NEW_BASE + 4 * 4, // 0x0080_2A10 + TIMER0_2_READ_VALUE = PWM_NEW_BASE + 5 * 4, // 0x0080_2A14 +} + + +// ─── Timer0-2 control register bits ────────────────────────────────── + +private enum : uint +{ + TIMER0_EN = 1 << 0, + TIMER1_EN = 1 << 1, + TIMER2_EN = 1 << 2, + CLK_DIV_POS = 3, // 3-bit divider: actual_div = reg_val + 1 + CLK_DIV_MASK = 0x7 << 3, + TIMER0_INT = 1 << 7, // Timer0 interrupt flag (write 1 to clear) + TIMER1_INT = 1 << 8, + TIMER2_INT = 1 << 9, + INT_FLAG_MASK = 0x7 << 7, +} + + +// ─── ICU clock power for timers ────────────────────────────────────── +// From SDK icu.h: ICU_PERI_CLK_PWD = ICU_BASE + 2*4 = 0x0080_2008 + +private enum uint ICU_PERI_CLK_PWD = 0x0080_2008; +private enum uint PWD_TIMER_26M_CLK = 1 << 20; + + +// ─── Public interface ──────────────────────────────────────────────── + +enum uint mtime_freq_hz = 26_000_000; +enum bool has_mtime = true; +enum bool has_rtc = false; +enum bool has_mcycle = false; +enum bool has_timer_stop = false; +enum bool has_wfi_sleep = false; + +private enum uint timer_freq_hz = mtime_freq_hz; + +private __gshared uint timer_high; +private __gshared uint timer_last; + +void timer_hw_init() +{ + // Enable 26MHz timer clock (clear power-down bit) + uint pwd = volatileLoad(cast(uint*)(cast(size_t)ICU_PERI_CLK_PWD)); + pwd &= ~PWD_TIMER_26M_CLK; + volatileStore(cast(uint*)(cast(size_t)ICU_PERI_CLK_PWD), pwd); + + // Set Timer0 period to maximum (freerunning) + volatileStore(cast(uint*)(cast(size_t)TIMER0_PERIOD), 0xFFFF_FFFF); + + // Configure control: enable Timer0, div=1 (CLK_DIV=0), clear interrupt flags + uint ctl = volatileLoad(cast(uint*)(cast(size_t)TIMER0_2_CTL)); + ctl &= ~(CLK_DIV_MASK | INT_FLAG_MASK); // div=0 means /1, clear int flags + ctl |= TIMER0_EN; + volatileStore(cast(uint*)(cast(size_t)TIMER0_2_CTL), ctl); + + timer_high = 0; + timer_last = 0; +} + +// Read the current Timer0 count via the READ_CTL/READ_VALUE mechanism. +// Hardware clears READ_CTL bit 0 when the snapshot is ready. +private uint timer0_read_count() +{ + enum uint read_ctl_addr = TIMER0_2_READ_CTL; + enum uint read_val_addr = TIMER0_2_READ_VALUE; + + // Trigger read for timer index 0: (index << 2) | 1 + volatileStore(cast(uint*)(cast(size_t)read_ctl_addr), (0 << 2) | 1); + + // Poll until hardware clears bit 0 (should be near-instant at 26MHz) + while (volatileLoad(cast(uint*)(cast(size_t)read_ctl_addr)) & 1) + {} + + return volatileLoad(cast(uint*)(cast(size_t)read_val_addr)); +} + +// Read monotonic 64-bit tick count. +// Must be called at least once per ~165 seconds (2^32 / 26MHz) to catch +// rollovers. The main loop at 20Hz guarantees this. +ulong mtime_read() +{ + uint now = timer0_read_count(); + if (now < timer_last) + ++timer_high; + timer_last = now; + return (cast(ulong)timer_high << 32) | now; +} + +alias TimerCallback = void function() @nogc nothrow; + +private __gshared TimerCallback tick_callback; + +void timer_set_periodic(uint period_us, TimerCallback cb) +{ + tick_callback = cb; + // TODO: configure Timer1 for periodic interrupt at period_us + // Requires IRQ vector dispatch in start.S (not yet implemented) +} diff --git a/src/urt/driver/bk7231/uart.d b/src/urt/driver/bk7231/uart.d new file mode 100644 index 0000000..edfb1f7 --- /dev/null +++ b/src/urt/driver/bk7231/uart.d @@ -0,0 +1,371 @@ +// BK7231 UART driver (shared by BK7231N and BK7231T) +// +// BK7231 has 2x UARTs: +// UART1 0x0080_2100 Log/console (TX=GPIO11, RX=GPIO10) +// UART2 0x0080_2200 Data/general purpose (TX=GPIO0, RX=GPIO1) +// +// Both have 128-byte TX/RX FIFOs. +// +// Register layout and init sequence verified against Beken SDK: +// sdk/OpenBK7231N/platforms/bk7231n/bk7231n_os/beken378/driver/uart/uart.h +// sdk/OpenBK7231N/platforms/bk7231n/bk7231n_os/beken378/driver/uart/uart_bk.c +module urt.driver.bk7231.uart; + +import core.volatile; + +import urt.driver.uart : FlowControl, Parity, StopBits, UartConfig; +import urt.driver.gpio : Pull, gpio_set_function; + +nothrow @nogc: + +enum num_uarts = 2; +enum uint uart_clock_hz = 26_000_000; +enum bool has_irq_driven_uart = false; +enum bool has_dma_driven_uart = false; + + +// ─── Register offsets (from SDK uart.h) ────────────────────────────── + +private enum uint[2] uart_bases = [0x0080_2100, 0x0080_2200]; + +private enum : uint +{ + REG_CONFIG = 0x00, // Baud rate, data bits, parity, stop bits, TX/RX enable + REG_FIFO_CONFIG = 0x04, // FIFO thresholds, RX stop detect time + REG_FIFO_STATUS = 0x08, // FIFO counts and flags (read-only) + REG_FIFO_PORT = 0x0C, // Data read/write port + REG_INT_ENABLE = 0x10, // Interrupt enable + REG_INT_STATUS = 0x14, // Interrupt status (write 1 to clear) + REG_FLOW_CONFIG = 0x18, // Flow control + REG_WAKE_CONFIG = 0x1C, // Wakeup configuration +} + + +// ─── CONFIG register (0x00) ────────────────────────────────────────── + +private enum : uint +{ + CFG_TX_ENABLE = 1 << 0, + CFG_RX_ENABLE = 1 << 1, + CFG_IRDA = 1 << 2, + CFG_DATA_LEN_POS = 3, // 2 bits: 0=5, 1=6, 2=7, 3=8 + CFG_DATA_LEN_MASK = 0x3 << 3, + CFG_PARITY_EN = 1 << 5, + CFG_PARITY_ODD = 1 << 6, // 0=even, 1=odd + CFG_STOP_LEN_2 = 1 << 7, // 0=1 stop, 1=2 stop + CFG_CLK_DIV_POS = 8, // 13-bit baud divisor + CFG_CLK_DIV_MASK = 0x1FFF << 8, +} + + +// ─── FIFO_CONFIG register (0x04) ───────────────────────────────────── + +private enum : uint +{ + FIFO_TX_THRESHOLD_POS = 0, // 8 bits + FIFO_TX_THRESHOLD_MASK = 0xFF, + FIFO_RX_THRESHOLD_POS = 8, // 8 bits + FIFO_RX_THRESHOLD_MASK = 0xFF << 8, + FIFO_RX_STOP_TIME_POS = 16, // 3 bits: 0=32, 1=64, 2=128, 3=256 clks + FIFO_RX_STOP_TIME_MASK = 0x7 << 16, +} + + +// ─── FIFO_STATUS register (0x08, read-only) ────────────────────────── + +private enum : uint +{ + STAT_TX_FIFO_COUNT_MASK = 0xFF, // bits [7:0] + STAT_RX_FIFO_COUNT_POS = 8, + STAT_RX_FIFO_COUNT_MASK = 0xFF << 8, // bits [15:8] + STAT_TX_FIFO_FULL = 1 << 16, + STAT_TX_FIFO_EMPTY = 1 << 17, + STAT_RX_FIFO_FULL = 1 << 18, + STAT_RX_FIFO_EMPTY = 1 << 19, + STAT_FIFO_WR_READY = 1 << 20, + STAT_FIFO_RD_READY = 1 << 21, +} + + +// ─── INT_ENABLE (0x10) / INT_STATUS (0x14) ─────────────────────────── +// Same bit layout for both registers. + +private enum : uint +{ + INT_TX_FIFO_NEED_WRITE = 1 << 0, + INT_RX_FIFO_NEED_READ = 1 << 1, + INT_RX_FIFO_OVERFLOW = 1 << 2, + INT_RX_PARITY_ERR = 1 << 3, + INT_RX_STOP_ERR = 1 << 4, + INT_TX_STOP_END = 1 << 5, + INT_RX_STOP_END = 1 << 6, + INT_RXD_WAKEUP = 1 << 7, + + INT_ALL_ERRORS = INT_RX_FIFO_OVERFLOW | INT_RX_PARITY_ERR | INT_RX_STOP_ERR, +} + + +// ─── FLOW_CONFIG register (0x18) ───────────────────────────────────── + +private enum : uint +{ + FLOW_LOW_CNT_POS = 0, // 8 bits: assert RTS below this count + FLOW_LOW_CNT_MASK = 0xFF, + FLOW_HIGH_CNT_POS = 8, // 8 bits: de-assert RTS above this count + FLOW_HIGH_CNT_MASK = 0xFF << 8, + FLOW_CTRL_EN = 1 << 16, + FLOW_RTS_POLARITY = 1 << 17, + FLOW_CTS_POLARITY = 1 << 18, +} + + +// ─── Hardware constants ────────────────────────────────────────────── + +private enum uint FIFO_DEPTH = 128; + +// SDK defaults (uart.h): TX_FIFO_THRD=0x40, RX_FIFO_THRD=0x30 +private enum uint SDK_TX_FIFO_THRESHOLD = 0x40; +private enum uint SDK_RX_FIFO_THRESHOLD = 0x30; +private enum uint SDK_RX_STOP_DETECT = 0; // 32 clock cycles + + +// ─── ICU registers for UART clock control ──────────────────────────── +// From SDK icu.h: ICU_PERI_CLK_PWD = ICU_BASE + 2*4 + +private enum uint ICU_BASE = 0x0080_2000; +private enum uint ICU_PERI_CLK_PWD = ICU_BASE + 2 * 4; // 0x0080_2008 + +// Power-down bits (1=powered down, 0=running) +private enum uint PWD_UART1_CLK = 1 << 0; +private enum uint PWD_UART2_CLK = 1 << 1; + + +// UART pins are fixed by hardware; perial mode 0 is the only valid value. +private enum ubyte[2] default_tx_pins = [11, 0]; // UART1=GPIO11, UART2=GPIO0 +private enum ubyte[2] default_rx_pins = [10, 1]; // UART1=GPIO10, UART2=GPIO1 +private enum uint UART_PERIAL_MODE = 0; + + +// ─── Register access helpers ───────────────────────────────────────── + +private uint reg_read(uint addr) +{ + return volatileLoad(cast(uint*)(cast(size_t)addr)); +} + +private void reg_write(uint addr, uint val) +{ + volatileStore(cast(uint*)(cast(size_t)addr), val); +} + + +private bool gpio_setup_uart_pins(uint id, ref const UartConfig cfg) +{ + ubyte tx_pin = cfg.tx_gpio == ubyte.max ? default_tx_pins[id] : cfg.tx_gpio; + ubyte rx_pin = cfg.rx_gpio == ubyte.max ? default_rx_pins[id] : cfg.rx_gpio; + + if (tx_pin != default_tx_pins[id] || rx_pin != default_rx_pins[id]) + return false; + + gpio_set_function(tx_pin, UART_PERIAL_MODE, Pull.up); + gpio_set_function(rx_pin, UART_PERIAL_MODE, Pull.up); + return true; +} + + +// ─── ICU clock control ─────────────────────────────────────────────── + +private void icu_uart_clock_enable(uint id) +{ + // Clear power-down bit to enable clock + uint pwd = reg_read(ICU_PERI_CLK_PWD); + pwd &= ~((id == 0) ? PWD_UART1_CLK : PWD_UART2_CLK); + reg_write(ICU_PERI_CLK_PWD, pwd); +} + +private void icu_uart_clock_disable(uint id) +{ + uint pwd = reg_read(ICU_PERI_CLK_PWD); + pwd |= (id == 0) ? PWD_UART1_CLK : PWD_UART2_CLK; + reg_write(ICU_PERI_CLK_PWD, pwd); +} + + +// ─── Public API ────────────────────────────────────────────────────── + +bool uart_hw_init(uint id, UartConfig cfg) +{ + if (id >= num_uarts) + return false; + + immutable uint base = uart_bases[id]; + + // Step 1: Enable peripheral clock (SDK: CMD_CLK_PWR_UP) + icu_uart_clock_enable(id); + + // Step 2: Configure GPIO pins for UART function (SDK: CMD_GPIO_ENABLE_SECOND) + const pins_ok = gpio_setup_uart_pins(id, cfg); + if (!pins_ok) + return false; + + // Step 3: Disable TX/RX during configuration + reg_write(base + REG_CONFIG, 0); + + // Step 4: Configure FIFO thresholds and RX stop detect time + // SDK defaults: TX_FIFO_THRD=0x40, RX_FIFO_THRD=0x30, RX_STOP_DETECT=0 (32 clks) + reg_write(base + REG_FIFO_CONFIG, + (SDK_TX_FIFO_THRESHOLD << FIFO_TX_THRESHOLD_POS) + | (SDK_RX_FIFO_THRESHOLD << FIFO_RX_THRESHOLD_POS) + | (SDK_RX_STOP_DETECT << FIFO_RX_STOP_TIME_POS)); + + // Step 5: Disable flow control (enable only if requested) + uint flow = 0; + if (cfg.flow_control == FlowControl.hardware) + { + // Assert RTS when RX FIFO < 32, de-assert when > 96 + flow = FLOW_CTRL_EN + | (0x20 << FLOW_LOW_CNT_POS) + | (0x60 << FLOW_HIGH_CNT_POS); + } + reg_write(base + REG_FLOW_CONFIG, flow); + + // Step 6: Disable wakeup + reg_write(base + REG_WAKE_CONFIG, 0); + + // Step 7: Disable all interrupts (polled mode) + reg_write(base + REG_INT_ENABLE, 0); + + // Step 8: Clear any pending interrupt status + uint pending = reg_read(base + REG_INT_STATUS); + reg_write(base + REG_INT_STATUS, pending); + + // Step 9: Build CONFIG register and enable + // Baud divisor: clock / baud - 1 (SDK: uart_hw_init) + uint divisor = uart_clock_hz / cfg.baud_rate - 1; + + uint config = CFG_TX_ENABLE | CFG_RX_ENABLE; + + // Data length: 5=0, 6=1, 7=2, 8=3 + config |= ((cfg.data_bits - 5) & 0x3) << CFG_DATA_LEN_POS; + + if (cfg.parity != Parity.none) + { + config |= CFG_PARITY_EN; + if (cfg.parity == Parity.odd) + config |= CFG_PARITY_ODD; + } + + if (cfg.stop_bits == StopBits.two) + config |= CFG_STOP_LEN_2; + + config |= (divisor & 0x1FFF) << CFG_CLK_DIV_POS; + + reg_write(base + REG_CONFIG, config); + + return true; +} + +bool uart_hw_open(uint id, UartConfig cfg) +{ + return uart_hw_init(id, cfg); +} + +void uart_hw_close(uint id) +{ + if (id >= num_uarts) + return; + + immutable uint base = uart_bases[id]; + + // Disable all interrupts + reg_write(base + REG_INT_ENABLE, 0); + + // Disable TX and RX + uint config = reg_read(base + REG_CONFIG); + config &= ~(CFG_TX_ENABLE | CFG_RX_ENABLE); + reg_write(base + REG_CONFIG, config); +} + +ptrdiff_t uart_hw_read(uint id, void[] buffer) +{ + immutable uint base = uart_bases[id]; + auto buf = cast(ubyte[])buffer; + ptrdiff_t n = 0; + + while (n < buf.length) + { + if (reg_read(base + REG_FIFO_STATUS) & STAT_RX_FIFO_EMPTY) + break; + buf[n] = cast(ubyte)(reg_read(base + REG_FIFO_PORT) & 0xFF); + ++n; + } + return n; +} + +ptrdiff_t uart_hw_write(uint id, const(void)[] data) +{ + immutable uint base = uart_bases[id]; + auto buf = cast(const(ubyte)[])data; + ptrdiff_t n = 0; + + while (n < buf.length) + { + if (reg_read(base + REG_FIFO_STATUS) & STAT_TX_FIFO_FULL) + break; + reg_write(base + REG_FIFO_PORT, buf[n]); + ++n; + } + return n; +} + +void uart_hw_poll(uint id) +{ + // Polled FIFO mode: clear any accumulated error/status interrupts + // so they don't pile up even though we're not using interrupt mode. + immutable uint base = uart_bases[id]; + uint status = reg_read(base + REG_INT_STATUS); + if (status) + reg_write(base + REG_INT_STATUS, status); +} + +bool uart_hw_check_errors(uint id) +{ + immutable uint base = uart_bases[id]; + uint status = reg_read(base + REG_INT_STATUS); + uint errors = status & INT_ALL_ERRORS; + + if (errors) + { + // Clear the error flags by writing 1 + reg_write(base + REG_INT_STATUS, errors); + return true; + } + return false; +} + +ptrdiff_t uart_hw_rx_pending(uint id) +{ + uint status = reg_read(uart_bases[id] + REG_FIFO_STATUS); + return (status & STAT_RX_FIFO_COUNT_MASK) >> STAT_RX_FIFO_COUNT_POS; +} + +ptrdiff_t uart_hw_flush(uint id) +{ + immutable uint base = uart_bases[id]; + while (!(reg_read(base + REG_FIFO_STATUS) & STAT_TX_FIFO_EMPTY)) + {} + return 0; +} + +// Blocking puts for early boot (before the serial stream is up). +// Uses UART1 (the log/console UART) at 0x0080_2100. +void uart0_hw_puts(const(char)[] s) +{ + enum uint base = 0x0080_2100; + foreach (c; s) + { + while (reg_read(base + REG_FIFO_STATUS) & STAT_TX_FIFO_FULL) + {} + reg_write(base + REG_FIFO_PORT, cast(uint)c); + } +} diff --git a/src/urt/driver/bl618/alloc.d b/src/urt/driver/bl618/alloc.d new file mode 100644 index 0000000..4fb991e --- /dev/null +++ b/src/urt/driver/bl618/alloc.d @@ -0,0 +1,37 @@ +module urt.driver.bl618.alloc; + +import urt.mem.alloc : MemFlags; + +nothrow @nogc: + +enum has_realloc = false; +enum has_expand = false; +enum has_memsize = false; +enum has_exec = false; +enum has_retain = false; +enum has_memflags = false; + +void[] _alloc(size_t size, size_t alignment, MemFlags) pure +{ + import urt.util : align_down; + + size_t header_size = (void*).sizeof + alignment; + void* p = malloc(header_size + size); + if (p is null) + return null; + + size_t allocptr = align_down(cast(size_t)p + header_size, alignment); + (cast(void**)allocptr)[-1] = p; + return (cast(void*)allocptr)[0 .. size]; +} + +void _free(void* ptr) pure +{ + free((cast(void**)ptr)[-1]); +} + + +private: + +extern(C) void* malloc(size_t size) pure; +extern(C) void free(void* ptr) pure; diff --git a/src/urt/driver/bl618/gpio.d b/src/urt/driver/bl618/gpio.d new file mode 100644 index 0000000..c7edbd7 --- /dev/null +++ b/src/urt/driver/bl618/gpio.d @@ -0,0 +1,120 @@ +// BL616/BL618 GPIO (also used by BL808 M0 core, which shares the +// bl618 peripheral set). +// +// GPIO_CFG register at GLB_BASE + 0x8C4 + pin*4: +// bits[4:0] = function (11 = SWGPIO) +// bit[6] = input enable +// bit[11] = output enable +// bit[17] = output value +// bit[18] = input value (read-only) +// bit[24] = pull-up enable +// bit[25] = pull-down enable +// +// Pin numbering: linear 0..34. +module urt.driver.bl618.gpio; + +import core.volatile : volatileLoad, volatileStore; + +import urt.driver.gpio : Pull, DriveMode; + +@nogc nothrow: + + +enum uint num_gpio = 35; +enum bool has_pull_up = true; +enum bool has_pull_down = true; +enum bool has_open_drain = false; +enum bool has_pin_function_muxing = true; + + +uint gpio_count() => num_gpio; + + +void gpio_output_init(uint pin, bool initial = false, DriveMode mode = DriveMode.push_pull) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + assert(mode == DriveMode.push_pull, "bl618 gpio: open-drain not supported"); + uint cfg = GPIO_FUN_SWGPIO | GPIO_OUTPUT_EN; + if (initial) + cfg |= GPIO_OUTPUT_HIGH; + gpio_cfg_write(pin, cfg); +} + +void gpio_input_init(uint pin, Pull pull = Pull.none) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + gpio_cfg_write(pin, GPIO_FUN_SWGPIO | GPIO_INPUT_EN | (uint(pull) << 24)); +} + +void gpio_output_set(uint pin, bool value) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + uint cfg = gpio_cfg_read(pin) & ~GPIO_OUTPUT_HIGH; + if (value) + cfg |= GPIO_OUTPUT_HIGH; + gpio_cfg_write(pin, cfg); +} + +void gpio_output_toggle(uint pin) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + gpio_cfg_write(pin, gpio_cfg_read(pin) ^ GPIO_OUTPUT_HIGH); +} + +bool gpio_input_read(uint pin) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + return (gpio_cfg_read(pin) & GPIO_INPUT_VALUE) != 0; +} + +void gpio_set_pull(uint pin, Pull pull) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + uint cfg = gpio_cfg_read(pin) & ~(GPIO_PULL_UP | GPIO_PULL_DOWN); + gpio_cfg_write(pin, cfg | (uint(pull) << 24)); +} + +void gpio_release(uint pin) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + gpio_cfg_write(pin, GPIO_FUN_SWGPIO); +} + +void gpio_set_function(uint pin, uint function_id, Pull pull = Pull.none, DriveMode mode = DriveMode.push_pull) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + assert(function_id <= GPIO_FUN_MASK, "gpio: function_id out of range (5-bit field)"); + assert(mode == DriveMode.push_pull, "bl618 gpio: open-drain not supported"); + uint cfg = (function_id & GPIO_FUN_MASK) | GPIO_INPUT_EN | (uint(pull) << 24); + gpio_cfg_write(pin, cfg); +} + + +private: + +enum uint GLB_BASE = 0x2000_0000; +enum uint GPIO_CFG_BASE = GLB_BASE + 0x8C4; + +enum uint GPIO_FUN_SWGPIO = 11; +enum uint GPIO_FUN_MASK = 0x1Fu; +enum uint GPIO_INPUT_EN = 1u << 6; +enum uint GPIO_OUTPUT_EN = 1u << 11; +enum uint GPIO_OUTPUT_HIGH = 1u << 17; +enum uint GPIO_INPUT_VALUE = 1u << 18; +enum uint GPIO_PULL_UP = 1u << 24; +enum uint GPIO_PULL_DOWN = 1u << 25; + +// Pull values map directly to GPIO_CFG bits[25:24]: none=0, up=bit24, down=bit25. +static assert(Pull.none == 0 && Pull.up == 1 && Pull.down == 2); + +pragma(inline, true) +uint gpio_cfg_read(uint pin) +{ + return volatileLoad(cast(uint*)(GPIO_CFG_BASE + pin * 4)); +} + +pragma(inline, true) +void gpio_cfg_write(uint pin, uint value) +{ + volatileStore(cast(uint*)(GPIO_CFG_BASE + pin * 4), value); +} diff --git a/src/urt/driver/bl618/irq.d b/src/urt/driver/bl618/irq.d new file mode 100644 index 0000000..20d8406 --- /dev/null +++ b/src/urt/driver/bl618/irq.d @@ -0,0 +1,30 @@ +// BL618 interrupt controller driver (CLIC-style) +module urt.driver.bl618.irq; + +@nogc nothrow: + +enum bool has_plic = false; +enum bool has_nvic = false; +enum bool has_per_irq_control = false; +enum bool has_irq_priority = false; +enum bool has_wait_for_interrupt = false; +enum bool has_irq_diagnostics = false; +enum uint irq_max = 0; + +// Globally disable interrupts +void irq_disable() +{ + asm @nogc nothrow + { + "csrc mstatus, 0x0008"; // Clear MIE + } +} + +/// Globally enable interrupts +void irq_enable() +{ + asm @nogc nothrow + { + "csrs mstatus, 0x0008"; // Set MIE + } +} diff --git a/src/urt/driver/bl618/package.d b/src/urt/driver/bl618/package.d new file mode 100644 index 0000000..5770a0a --- /dev/null +++ b/src/urt/driver/bl618/package.d @@ -0,0 +1,41 @@ +// BL618 platform package (T-Head E907 RV32IMAFC) +// +// Provides sys_init() as the single entry point for all +// hardware initialization. Call from main() before anything else. +module urt.driver.bl618; + +public import urt.driver.bl618.uart; +public import urt.driver.bl618.irq; +public import urt.driver.bl618.timer; + +@nogc nothrow: + +private extern(C) void __register_frame_info(const void*, void*); +private extern(C) extern const ubyte __eh_frame_start; +private ubyte[48] __eh_frame_object; // pre-allocated storage for libgcc + +// Initialize BL618 hardware. +// Call once at the top of main() before any other OpenWatt code. +// +// Order matters: +// 1. UART - so we have debug output for everything after +// 2. IRQ table - already done by start.S (_init_interrupts) +// 3. Timer - periodic tick for main loop +extern(C) void sys_init() +{ + // Register .eh_frame with libgcc's unwinder so that DWARF exception + // handling works (required for fibre abort, etc.). + __register_frame_info(&__eh_frame_start, &__eh_frame_object); + + uart0_hw_puts("BL618: sys_init\n"); + + // Timer: set up 20Hz tick (50ms) for the main loop + timer_set_periodic(50_000, &tick_stub); + + uart0_hw_puts("BL618: ready\n"); +} + +private void tick_stub() @nogc nothrow +{ + // placeholder - will drive urt.time / Application frame tick +} diff --git a/src/urt/driver/bl618/start.S b/src/urt/driver/bl618/start.S new file mode 100644 index 0000000..bf40de5 --- /dev/null +++ b/src/urt/driver/bl618/start.S @@ -0,0 +1,206 @@ +/* BL618 (T-Head E907 RV32IMAFC) startup + * + * Single-core startup — no M0 handshake needed (unlike BL808). + * + * Boot flow: + * 1. CPU setup — disable IRQ, enable extensions, FPU, trap vectors + * 2. Core regs — gp, tp, sp + * 3. Section init — .got, .tdata, .tbss, .bss, .data + * 4. Caches — I-cache enable + * 5. Interrupts — dispatch table, enable MIE+MEIE + * 6. D runtime — sys_init, .init_array, main + * + * T-Head custom CSR numbers: + * mxstatus = 0x7C0 (T-Head ISA extension enable) + * mhcr = 0x7C1 (hardware cache control) + * mhint = 0x7C5 (performance hints) + * + * TODO: Verify CSR values and peripheral base addresses against + * actual BL618 hardware. Based on BL616 reference manual. + */ +#define CSR_MXSTATUS 0x7C0 +#define CSR_MHCR 0x7C1 +#define CSR_MHINT 0x7C5 + + .section .text.entry, "ax" + .global _start + .type _start, @function + .align 2 + +_start: + /* ── 1. CPU setup ─────────────────────────────────────────────── */ + + /* Disable interrupts */ + csrc mstatus, 0x0008 /* MIE */ + + /* Enable T-Head ISA extensions (THEADISAEE + MM) */ + li t0, (1 << 22) | (1 << 15) + csrs CSR_MXSTATUS, t0 + + /* Enable FPU (FS = Initial) — E907 has single-precision FPU */ + li t0, (1 << 13) + csrs mstatus, t0 + + /* Set trap vector table (vectored mode) */ + la t0, __vectors + ori t0, t0, 1 + csrw mtvec, t0 + + /* ── 2. Core registers ────────────────────────────────────────── */ + + .option push + .option norelax + la gp, __global_pointer$ + .option pop + la tp, _tdata_start + la sp, _stack_top + + /* ── 3. Section init ──────────────────────────────────────────── */ + + /* Copy .got from flash (LMA) to DTCM (VMA) */ + la a0, _got_start + la a1, _got_load + la a2, _got_end + bgeu a0, a2, .Lgot_done +.Lgot_copy: + lw t0, 0(a1) + sw t0, 0(a0) + addi a0, a0, 4 + addi a1, a1, 4 + bltu a0, a2, .Lgot_copy +.Lgot_done: + + /* Copy .tdata from flash to DTCM */ + la a0, _tdata_start + la a1, _tdata_load + la a2, _tdata_end + bgeu a0, a2, .Ltdata_done +.Ltdata_copy: + lw t0, 0(a1) + sw t0, 0(a0) + addi a0, a0, 4 + addi a1, a1, 4 + bltu a0, a2, .Ltdata_copy +.Ltdata_done: + + /* Zero .tbss */ + la a0, _tbss_start + la a2, _tbss_end + bgeu a0, a2, .Ltbss_done +.Ltbss_zero: + sw zero, 0(a0) + addi a0, a0, 4 + bltu a0, a2, .Ltbss_zero +.Ltbss_done: + + /* Copy .data from flash to OCRAM */ + la a0, _data_start + la a1, _data_load + la a2, _data_end + bgeu a0, a2, .Ldata_done +.Ldata_copy: + lw t0, 0(a1) + sw t0, 0(a0) + addi a0, a0, 4 + addi a1, a1, 4 + bltu a0, a2, .Ldata_copy +.Ldata_done: + + /* Zero .bss */ + la a0, _bss_start + la a2, _bss_end + bgeu a0, a2, .Lbss_done +.Lbss_zero: + sw zero, 0(a0) + addi a0, a0, 4 + bltu a0, a2, .Lbss_zero +.Lbss_done: + + /* ── 4. Caches ────────────────────────────────────────────────── */ + + /* Enable I-cache */ + li t0, (1 << 0) /* IE: I-cache enable */ + csrs CSR_MHCR, t0 + + /* ── 5. Interrupts ────────────────────────────────────────────── */ + + /* Clear CLIC/PLIC pending — TODO: verify PLIC base for BL618 */ + + /* Enable machine external interrupts */ + li t0, (1 << 11) /* MEIE */ + csrs mie, t0 + /* Enable global interrupts */ + csrs mstatus, 0x0008 /* MIE */ + + /* ── 6. D runtime ─────────────────────────────────────────────── */ + + call sys_init + + /* Run .init_array (D module constructors, etc.) */ + la s0, __init_array_start + la s1, __init_array_end + bgeu s0, s1, .Linit_done +.Linit_loop: + lw t0, 0(s0) + jalr t0 + addi s0, s0, 4 + bltu s0, s1, .Linit_loop +.Linit_done: + + /* Enter main — should never return */ + call main + + /* If main returns, spin */ +.Lhalt: + wfi + j .Lhalt + + .size _start, . - _start + +/* ── Interrupt vector table ───────────────────────────────────────── */ + .section .text, "ax" + .global __vectors + .type __vectors, @function + .align 8 + +__vectors: + j _trap_handler /* 0: exception / sync trap */ + .align 2 + j _trap_handler /* 1: supervisor SW */ + .align 2 + j _trap_handler /* 2: reserved */ + .align 2 + j _trap_handler /* 3: machine SW */ + .align 2 + j _trap_handler /* 4: reserved */ + .align 2 + j _trap_handler /* 5: supervisor timer */ + .align 2 + j _trap_handler /* 6: reserved */ + .align 2 + j _trap_handler /* 7: machine timer */ + .align 2 + j _trap_handler /* 8: reserved */ + .align 2 + j _trap_handler /* 9: supervisor external */ + .align 2 + j _trap_handler /* 10: reserved */ + .align 2 + j _irq_handler /* 11: machine external */ + .align 2 + +/* ── Default trap handler ─────────────────────────────────────────── */ + .global _trap_handler + .weak _trap_handler + .type _trap_handler, @function +_trap_handler: + /* TODO: save context, call crash handler */ + j _trap_handler + +/* ── External IRQ handler ─────────────────────────────────────────── */ + .global _irq_handler + .weak _irq_handler + .type _irq_handler, @function +_irq_handler: + /* TODO: PLIC claim/complete dispatch — wire to driver.bl618.irq */ + j _irq_handler diff --git a/src/urt/driver/bl618/syscalls.d b/src/urt/driver/bl618/syscalls.d new file mode 100644 index 0000000..5212670 --- /dev/null +++ b/src/urt/driver/bl618/syscalls.d @@ -0,0 +1,46 @@ +/// BL618 newlib/picolibc syscall stubs +/// +/// Minimal stubs to satisfy picolibc's syscall requirements. +/// Same pattern as BL808 - most are no-ops for baremetal. +module urt.driver.bl618.syscalls; + +@nogc nothrow: + +private extern(C) extern __gshared { + pragma(mangle, "__heap_start") void* _heap_start_ptr; + pragma(mangle, "__heap_end") void* _heap_end_ptr; +} + +private __gshared void* _heap_ptr; + +extern(C) void* _sbrk(ptrdiff_t incr) +{ + if (_heap_ptr is null) + _heap_ptr = cast(void*)&_heap_start_ptr; + + void* prev = _heap_ptr; + void* next = _heap_ptr + incr; + + if (next > cast(void*)&_heap_end_ptr) + return cast(void*)-1; + + _heap_ptr = next; + return prev; +} + +extern(C) int _write(int fd, const void* buf, size_t count) +{ + import urt.driver.bl618.uart : uart0_hw_puts; + if (fd == 1 || fd == 2) + uart0_hw_puts((cast(const(char)*) buf)[0 .. count]); + return cast(int) count; +} + +extern(C) int _read(int, void*, size_t) { return 0; } +extern(C) int _close(int) { return -1; } +extern(C) int _lseek(int, int, int) { return 0; } +extern(C) int _fstat(int, void*) { return 0; } +extern(C) int _isatty(int) { return 1; } +extern(C) void _exit(int) { while (true) {} } +extern(C) int _kill(int, int) { return -1; } +extern(C) int _getpid() { return 1; } diff --git a/src/urt/driver/bl618/timer.d b/src/urt/driver/bl618/timer.d new file mode 100644 index 0000000..d5cba73 --- /dev/null +++ b/src/urt/driver/bl618/timer.d @@ -0,0 +1,41 @@ +// BL618 timer driver +// +// T-Head E907 (RV32IMAFC) has standard RISC-V mtime/mtimecmp. +// mtime runs at 1MHz (same as BL808's C906). +module urt.driver.bl618.timer; + +@nogc nothrow: + +enum uint mtime_freq_hz = 1_000_000; +enum bool has_mtime = true; +enum bool has_rtc = false; +enum bool has_mcycle = false; +enum bool has_timer_stop = false; +enum bool has_wfi_sleep = false; + +// Read the monotonic mtime counter via rdtime. +// RV32: reads high/low halves with retry on rollover. +ulong mtime_read() +{ + uint hi1, lo, hi2; + do + { + asm @nogc nothrow { "rdtimeh %0" : "=r" (hi1); } + asm @nogc nothrow { "rdtime %0" : "=r" (lo); } + asm @nogc nothrow { "rdtimeh %0" : "=r" (hi2); } + } + while (hi1 != hi2); + return (ulong(hi1) << 32) | lo; +} + +alias TimerCallback = void function() @nogc nothrow; + +private __gshared TimerCallback tick_callback; +private __gshared uint tick_interval; + +void timer_set_periodic(uint period_us, TimerCallback cb) +{ + tick_interval = period_us; + tick_callback = cb; + assert(false, "TODO: write mtimecmp and enable timer interrupt"); +} diff --git a/src/urt/driver/bl618/uart.d b/src/urt/driver/bl618/uart.d new file mode 100644 index 0000000..58c4a23 --- /dev/null +++ b/src/urt/driver/bl618/uart.d @@ -0,0 +1,77 @@ +/// BL618 UART driver +/// +/// BL616/BL618 has 2 UARTs sharing the same register-level IP block as BL808: +/// +/// UART0 0x2000_A000 Default console +/// UART1 0x2000_A100 General purpose +/// +/// Register layout is identical to BL808 (BL6xx/BL8xx common UART IP). +/// Single-core E907 has direct PLIC access to all UART interrupts. +/// +/// TODO: Implement full driver from BL616 register map. +/// Stubbed to provide the API surface needed by serial.d. +module urt.driver.bl618.uart; + +import urt.driver.uart : Parity, StopBits, UartConfig; + +nothrow @nogc: + +enum num_uarts = 2; +enum uint uart_clock_hz = 40_000_000; +enum bool has_irq_driven_uart = false; +enum bool has_dma_driven_uart = false; + +bool uart_hw_open(uint id, UartConfig cfg) +{ + // TODO: configure UART registers + return true; +} + +void uart_hw_close(uint id) +{ + // TODO: disable UART +} + +ptrdiff_t uart_hw_read(uint id, void[] buffer) +{ + // TODO: read from RX ring buffer + return 0; +} + +ptrdiff_t uart_hw_write(uint id, const(void)[] data) +{ + // TODO: write to TX FIFO/ring buffer + return 0; +} + +void uart_hw_poll(uint id) +{ + // TODO: poll RX/TX FIFOs +} + +bool uart_hw_check_errors(uint id) +{ + // TODO: check error status register + return false; +} + +ptrdiff_t uart_hw_rx_pending(uint id) +{ + // TODO: return ring buffer count + return 0; +} + +ptrdiff_t uart_hw_flush(uint id) +{ + // TODO: drain TX ring buffer + return 0; +} + +/// Transmit a string on UART0 (blocking, polled). +/// Used for early boot messages before the full console is up. +void uart0_hw_puts(const(char)[] s) +{ + // TODO: direct register write to UART0 TX FIFO + // UART0 base: 0x2000_A000, FIFO_WDATA offset: 0x88 + // For now, no-op until registers are wired up. +} diff --git a/src/urt/driver/bl808/alloc.d b/src/urt/driver/bl808/alloc.d new file mode 100644 index 0000000..9cbc158 --- /dev/null +++ b/src/urt/driver/bl808/alloc.d @@ -0,0 +1,37 @@ +module urt.driver.bl808.alloc; + +import urt.mem.alloc : MemFlags; + +nothrow @nogc: + +enum has_realloc = false; +enum has_expand = false; +enum has_memsize = false; +enum has_exec = false; +enum has_retain = false; // TODO: HBN RAM +enum has_memflags = false; // TODO: SRAM vs PSRAM + +void[] _alloc(size_t size, size_t alignment, MemFlags) pure +{ + import urt.util : align_down; + + size_t header_size = (void*).sizeof + alignment; + void* p = malloc(header_size + size); + if (p is null) + return null; + + size_t allocptr = align_down(cast(size_t)p + header_size, alignment); + (cast(void**)allocptr)[-1] = p; + return (cast(void*)allocptr)[0 .. size]; +} + +void _free(void* ptr) pure +{ + free((cast(void**)ptr)[-1]); +} + + +private: + +extern(C) void* malloc(size_t size) pure; +extern(C) void free(void* ptr) pure; diff --git a/src/urt/driver/bl808/exception.d b/src/urt/driver/bl808/exception.d new file mode 100644 index 0000000..a982397 --- /dev/null +++ b/src/urt/driver/bl808/exception.d @@ -0,0 +1,102 @@ +/// BL808 crash handler. +/// +/// Called from _trap_exception in start.S. Prints exception info, +/// register dump, and stack backtrace to UART0. +/// Requires -frame-pointer=all for reliable backtrace. +module urt.driver.bl808.exception; + +import urt.driver.baremetal.exception : walk_fp_chain; +import urt.driver.bl808.uart; + +private: + +immutable const(char)*[16] exception_names = [ + "Instruction address misaligned", + "Instruction access fault", + "Illegal instruction", + "Breakpoint", + "Load address misaligned", + "Load access fault", + "Store address misaligned", + "Store access fault", + "Environment call from U-mode", + "Environment call from S-mode", + "Reserved", + "Environment call from M-mode", + "Instruction page fault", + "Load page fault", + "Reserved", + "Store page fault", +]; + +immutable const(char)*[31] rnames = [ + "ra ", "sp ", "gp ", "tp ", + "t0 ", "t1 ", "t2 ", + "s0 ", "s1 ", + "a0 ", "a1 ", "a2 ", "a3 ", + "a4 ", "a5 ", "a6 ", "a7 ", + "s2 ", "s3 ", "s4 ", "s5 ", + "s6 ", "s7 ", "s8 ", "s9 ", + "s10", "s11", + "t3 ", "t4 ", "t5 ", "t6 ", +]; + +public: + +/// regs[] layout matches the save order in _trap_exception: +/// [0]=ra [1]=sp [2]=gp [3]=tp [4]=t0 [5]=t1 [6]=t2 +/// [7]=s0/fp [8]=s1 [9]=a0...[16]=a7 [17]=s2...[26]=s11 [27]=t3...[30]=t6 +extern(C) void _crash_handler(ulong* regs, ulong mcause, ulong mepc, ulong mtval) @nogc nothrow +{ + uart0_print("\n\n*** CRASH ***\nException: "); + + ulong cause = mcause & 0x7FFF_FFFF_FFFF_FFFF; + if (cause < 16) + uart0_print(exception_names[cast(uint) cause]); + else + uart0_print("Unknown exception"); + + uart0_print(" (cause="); + uart0_hex(mcause); + uart0_print(")\n mepc = "); + uart0_hex(mepc); + uart0_print("\n mtval = "); + uart0_hex(mtval); + uart0_print("\n\nRegisters:\n"); + + foreach (i; 0 .. 31) + { + uart0_print(" "); + uart0_print(rnames[i]); + uart0_print(" = "); + uart0_hex(regs[i]); + if ((i % 4) == 3 || i == 30) + uart0_putc('\n'); + } + + // Stack backtrace via the shared fp-chain walker. + uart0_print("\nBacktrace:\n [0] "); + uart0_hex(mepc); + uart0_print(" (faulting PC)\n"); + + void*[32] addrs = void; + const n = walk_fp_chain(cast(size_t) regs[7], addrs[]); // s0 = frame pointer + + foreach (depth, addr; addrs[0 .. n]) + { + uart0_print(" ["); + const d = depth + 1; + if (d < 10) + uart0_putc(cast(char)('0' + d)); + else + { + uart0_putc(cast(char)('0' + d / 10)); + uart0_putc(cast(char)('0' + d % 10)); + } + uart0_print("] "); + uart0_hex(cast(ulong) addr); + uart0_putc('\n'); + } + + uart0_print("\n*** HALTED ***\n"); +} diff --git a/src/urt/driver/bl808/gpio.d b/src/urt/driver/bl808/gpio.d new file mode 100644 index 0000000..35bfde9 --- /dev/null +++ b/src/urt/driver/bl808/gpio.d @@ -0,0 +1,195 @@ +// GPIO_CFG register at GLB_BASE + 0x8C4 + pin*4: +// bits[4:0] = function (11 = SWGPIO) +// bit[6] = input enable +// bit[11] = output enable +// bit[17] = output value +// bit[18] = input value (read-only) +// bit[24] = pull-up enable +// bit[25] = pull-down enable +// +// Pin numbering: linear 0..46. +// +// Also contains M1s Dock WS2812 LED helpers; will move to a portable +// baremetal/ws2812 driver once the GPIO API is on other SoCs. +module urt.driver.bl808.gpio; + +import core.volatile : volatileLoad, volatileStore; + +import urt.driver.gpio : Pull, DriveMode; + +@nogc nothrow: + + +enum uint num_gpio = 47; +enum bool has_pull_up = true; +enum bool has_pull_down = true; +enum bool has_open_drain = false; +enum bool has_pin_function_muxing = true; + + +uint gpio_count() => num_gpio; + + +void gpio_output_init(uint pin, bool initial = false, DriveMode mode = DriveMode.push_pull) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + assert(mode == DriveMode.push_pull, "bl808 gpio: open-drain not supported"); + uint cfg = GPIO_FUN_SWGPIO | GPIO_OUTPUT_EN; + if (initial) + cfg |= GPIO_OUTPUT_HIGH; + gpio_cfg_write(pin, cfg); +} + +void gpio_input_init(uint pin, Pull pull = Pull.none) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + gpio_cfg_write(pin, GPIO_FUN_SWGPIO | GPIO_INPUT_EN | (uint(pull) << 24)); +} + +void gpio_output_set(uint pin, bool value) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + uint cfg = gpio_cfg_read(pin) & ~GPIO_OUTPUT_HIGH; + if (value) + cfg |= GPIO_OUTPUT_HIGH; + gpio_cfg_write(pin, cfg); +} + +void gpio_output_toggle(uint pin) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + gpio_cfg_write(pin, gpio_cfg_read(pin) ^ GPIO_OUTPUT_HIGH); +} + +bool gpio_input_read(uint pin) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + return (gpio_cfg_read(pin) & GPIO_INPUT_VALUE) != 0; +} + +void gpio_set_pull(uint pin, Pull pull) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + uint cfg = gpio_cfg_read(pin) & ~(GPIO_PULL_UP | GPIO_PULL_DOWN); + gpio_cfg_write(pin, cfg | (uint(pull) << 24)); +} + +void gpio_release(uint pin) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + gpio_cfg_write(pin, GPIO_FUN_SWGPIO); +} + +void gpio_set_function(uint pin, uint function_id, Pull pull = Pull.none, DriveMode mode = DriveMode.push_pull) +{ + assert(pin < num_gpio, "gpio: pin out of range"); + assert(function_id <= GPIO_FUN_MASK, "gpio: function_id out of range (5-bit field)"); + assert(mode == DriveMode.push_pull, "bl808 gpio: open-drain not supported"); + uint cfg = (function_id & GPIO_FUN_MASK) | GPIO_INPUT_EN | (uint(pull) << 24); + gpio_cfg_write(pin, cfg); +} + + +void ws2812_send(uint pin, ubyte r, ubyte g, ubyte b) +{ + gpio_output_init(pin); + ws2812_byte(pin, g); // GRB order + ws2812_byte(pin, r); + ws2812_byte(pin, b); + ws2812_raw_set(pin, false); + delay_loops(WS_RESET_US * 200); +} + +void led_set(ubyte r, ubyte g, ubyte b) +{ + ws2812_send(WS2812_PIN, r, g, b); +} + +void led_red() { led_set(32, 0, 0); } +void led_green() { led_set(0, 32, 0); } +void led_blue() { led_set(0, 0, 32); } +void led_white() { led_set(16, 16, 16); } +void led_off() { led_set(0, 0, 0); } + + +private: + +enum uint GLB_BASE = 0x2000_0000; +enum uint GPIO_CFG_BASE = GLB_BASE + 0x8C4; + +enum uint GPIO_FUN_SWGPIO = 11; +enum uint GPIO_FUN_MASK = 0x1Fu; +enum uint GPIO_INPUT_EN = 1u << 6; +enum uint GPIO_OUTPUT_EN = 1u << 11; +enum uint GPIO_OUTPUT_HIGH = 1u << 17; +enum uint GPIO_INPUT_VALUE = 1u << 18; +enum uint GPIO_PULL_UP = 1u << 24; +enum uint GPIO_PULL_DOWN = 1u << 25; + +// Pull values map directly to GPIO_CFG bits[25:24]: none=0, up=bit24, down=bit25. +static assert(Pull.none == 0 && Pull.up == 1 && Pull.down == 2); + +pragma(inline, true) +uint gpio_cfg_read(uint pin) +{ + return volatileLoad(cast(uint*)(GPIO_CFG_BASE + pin * 4)); +} + +pragma(inline, true) +void gpio_cfg_write(uint pin, uint value) +{ + volatileStore(cast(uint*)(GPIO_CFG_BASE + pin * 4), value); +} + + +// WS2812B spec: T0H=400ns, T0L=850ns, T1H=800ns, T1L=450ns (+/- 150ns). +// At 480MHz: 1 cycle ~= 2.08ns. Loop body (addi + bnez compressed) ~= 2 +// cycles, so divide by 2. +enum uint WS2812_PIN = 8; // M1s Dock board +enum uint WS_T0H_LOOPS = 80; +enum uint WS_T0L_LOOPS = 170; +enum uint WS_T1H_LOOPS = 160; +enum uint WS_T1L_LOOPS = 90; +enum uint WS_RESET_US = 60; // >50us required by spec + +pragma(inline, true) +void delay_loops(ulong n) +{ + import ldc.llvmasm; + cast(void) __asm!ulong(` + 1: addi $0, $0, -1 + bnez $0, 1b + `, "=r,0", n); +} + +void ws2812_raw_set(uint pin, bool high) +{ + uint cfg = GPIO_FUN_SWGPIO | GPIO_OUTPUT_EN; + if (high) + cfg |= GPIO_OUTPUT_HIGH; + gpio_cfg_write(pin, cfg); +} + +void ws2812_bit(uint pin, bool one) +{ + if (one) + { + ws2812_raw_set(pin, true); + delay_loops(WS_T1H_LOOPS); + ws2812_raw_set(pin, false); + delay_loops(WS_T1L_LOOPS); + } + else + { + ws2812_raw_set(pin, true); + delay_loops(WS_T0H_LOOPS); + ws2812_raw_set(pin, false); + delay_loops(WS_T0L_LOOPS); + } +} + +void ws2812_byte(uint pin, ubyte b) +{ + foreach (i; 0 .. 8) + ws2812_bit(pin, (b & (0x80 >> i)) != 0); +} diff --git a/src/urt/driver/bl808/hbn_ram.c b/src/urt/driver/bl808/hbn_ram.c new file mode 100644 index 0000000..b0a679a --- /dev/null +++ b/src/urt/driver/bl808/hbn_ram.c @@ -0,0 +1,12 @@ +/* HBN RAM persistent storage — placed in .hbn_ram section by linker. + * Survives hibernate if VBAT is maintained. */ + +#include + +struct hbn_persist { + uint32_t magic; + int64_t utc_offset; /* HBN ticks from RTC epoch to Unix epoch */ +}; + +__attribute__((section(".hbn_ram"), used)) +struct hbn_persist _hbn_persist; diff --git a/src/urt/driver/bl808/i2c.d b/src/urt/driver/bl808/i2c.d new file mode 100644 index 0000000..06ce7c1 --- /dev/null +++ b/src/urt/driver/bl808/i2c.d @@ -0,0 +1,5 @@ +/// BL808 I2C peripheral +/// +/// Direct register access to BL808 I2C controller. +/// TODO: implement when needed for hardware I/O. +module urt.driver.bl808.i2c; diff --git a/src/urt/driver/bl808/ipc.d b/src/urt/driver/bl808/ipc.d new file mode 100644 index 0000000..83ef373 --- /dev/null +++ b/src/urt/driver/bl808/ipc.d @@ -0,0 +1,49 @@ +/// BL808 inter-processor communication +/// +/// Initializes XRAM ring buffers and provides typed send/receive +/// for peripheral commands and network frames. +module urt.driver.bl808.ipc; + +import urt.driver.bl808.xram; + +@nogc nothrow: + +/// Global ring buffer handles (initialized by ipc_init) +__gshared XramRing[RingId.max] rings; + +/// Initialize all XRAM ring buffers from the shared memory layout. +/// Call once at startup after M0 has initialized the XRAM region. +void ipc_init() +{ + // TODO: read ring layout from XRAM_BASE + // The M0 firmware sets up ring_pos structures at fixed offsets. + // For now this is a placeholder - actual offsets need to be + // determined by examining the running M0 firmware's XRAM layout. +} + +/// Send a peripheral command (GPIO/SPI/PWM/Flash) +bool peri_send(PeriType type, const(ubyte)[] payload) +{ + PeriHeader hdr; + hdr.type = type; + hdr.err = 0; + hdr.len = cast(ushort) payload.length; + + auto written = rings[RingId.peripheral].write((cast(ubyte*)&hdr)[0 .. 4]); + if (written != PeriHeader.sizeof) + return false; + + if (payload.length > 0) + { + written = rings[RingId.peripheral].write(payload); + if (written != payload.length) + return false; + } + return true; +} + +/// Receive a peripheral response header. Returns false if ring is empty. +bool peri_recv(ref PeriHeader hdr) +{ + return PeriHeader.sizeof == rings[RingId.peripheral].read((cast(ubyte*)&hdr)[0 .. 4]); +} diff --git a/src/urt/driver/bl808/irq.d b/src/urt/driver/bl808/irq.d new file mode 100644 index 0000000..6d5dce6 --- /dev/null +++ b/src/urt/driver/bl808/irq.d @@ -0,0 +1,138 @@ +module urt.driver.bl808.irq; + +import core.volatile; + +nothrow @nogc: + +enum bool has_plic = true; +enum bool has_nvic = false; +enum bool has_per_irq_control = true; +enum bool has_irq_priority = false; +enum bool has_wait_for_interrupt = true; +enum bool has_irq_diagnostics = true; + + +// ================================================================ +// CPU interrupt control +// ================================================================ + +// Enable interrupt delivery. Returns previous state. +bool enable_interrupts() +{ + ulong prev; + asm nothrow @nogc { "csrrsi %0, mstatus, 0x8" : "=r" (prev); } + return (prev & 0x8) != 0; +} + +// Disable interrupt delivery. Returns previous state. +bool disable_interrupts() +{ + ulong prev; + asm nothrow @nogc { "csrrci %0, mstatus, 0x8" : "=r" (prev); } + return (prev & 0x8) != 0; +} + +// Set interrupt delivery state. Returns previous state. +bool set_interrupts(bool state) +{ + return state ? enable_interrupts() : disable_interrupts(); +} + +// Halt CPU until an interrupt is pending. Near-zero power. +void wait_for_interrupt() +{ + asm nothrow @nogc { "wfi"; } +} + +// ================================================================ +// Interrupt source control +// ================================================================ + +// Interrupt classes (mie register bits) +enum IrqClass : uint { software = 3, timer = 7, external = 11 } + +// Enable an interrupt class. Returns previous state. +bool enable_irq(IrqClass c) +{ + ulong prev; + asm nothrow @nogc { "csrrs %0, mie, %1" : "=r" (prev) : "r" (1UL << c); } + return (prev & (1UL << c)) != 0; +} + +// Disable an interrupt class. Returns previous state. +bool disable_irq(IrqClass c) +{ + ulong prev; + asm nothrow @nogc { "csrrc %0, mie, %1" : "=r" (prev) : "r" (1UL << c); } + return (prev & (1UL << c)) != 0; +} + +enum uint irq_max = 80; + +alias IrqHandler = void function(uint irq) nothrow @nogc; + +// Set the external interrupt handler. Receives the PLIC IRQ number. +// Returns the previous handler for chaining. +IrqHandler irq_set_handler(IrqHandler handler) +{ + auto prev = irq_handler; + irq_handler = handler; + return prev; +} + +// Enable an individual PLIC IRQ (set priority > 0 and enable bit) +bool enable_irq(uint irq) +{ + if (irq >= irq_max) + return false; + auto prio = cast(uint*)(plic_base + irq * 4); + volatileStore(prio, 1); + auto en = cast(uint*)(plic_enable + (irq / 32) * 4); + uint mask = 1U << (irq % 32); + uint prev = volatileLoad(en); + volatileStore(en, prev | mask); + return (prev & mask) != 0; +} + +// Disable an individual PLIC IRQ. Returns previous state. +bool disable_irq(uint irq) +{ + if (irq >= irq_max) + return false; + auto en = cast(uint*)(plic_enable + (irq / 32) * 4); + uint mask = 1U << (irq % 32); + uint prev = volatileLoad(en); + volatileStore(en, prev & ~mask); + return (prev & mask) != 0; +} + +private: + +enum ulong plic_base = 0xE000_0000; +enum ulong plic_enable = 0xE000_2000; + +__gshared IrqHandler irq_handler; + +public: + +// Diagnostic counters (temporary) +__gshared uint irq_count = 0; +__gshared uint[irq_max] irq_histogram; + +package: + +// Called from start.S _trap_mext +extern(C) void _irq_dispatch(uint irq) +{ + ++irq_count; + if (irq < irq_max) + ++irq_histogram[irq]; + if (irq_handler !is null) + irq_handler(irq); +} + +// Called from start.S _trap_mtimer +extern(C) void _timer_irq_handler() +{ + // TODO: hook up to urt.time tick +} diff --git a/src/urt/driver/bl808/package.d b/src/urt/driver/bl808/package.d new file mode 100644 index 0000000..d5ef31d --- /dev/null +++ b/src/urt/driver/bl808/package.d @@ -0,0 +1,50 @@ +/// BL808 D0 core platform package +/// +/// Provides sys_init() as the single entry point for all +/// hardware initialization. Call from main() before anything else. +module urt.driver.bl808; + +public import urt.driver.bl808.uart; +public import urt.driver.bl808.irq; +public import urt.driver.bl808.timer; +public import urt.driver.bl808.xram; +public import urt.driver.bl808.ipc; + +@nogc nothrow: + +private extern(C) void __register_frame_info(const void*, void*); +private extern(C) extern const ubyte __eh_frame_start; +private ubyte[48] __eh_frame_object; // pre-allocated storage for libgcc + +/// Initialize all D0 core hardware. +/// Call once at the top of main() before any other OpenWatt code. +/// +/// Order matters: +/// 1. UART - so we have debug output for everything after +/// 2. IRQ table - already done by start.S (_init_interrupts) +/// 3. Timer - periodic tick for main loop +/// 4. IPC - XRAM ring buffers to M0 +extern(C) void sys_init() +{ + // Register .eh_frame with libgcc's unwinder so that DWARF exception + // handling works (required for fibre abort, etc.). + __register_frame_info(&__eh_frame_start, &__eh_frame_object); + + // UART0 is already initialized by M0 before D0 boots. + // Just confirm we're alive. + uart0_hw_puts("BL808 D0: sys_init\n"); + + // Timer: set up 20Hz tick (50ms) for the main loop + // TODO: wire this to Application.run() instead of a stub + timer_set_periodic(50_000, &tick_stub); + + // IPC: initialize XRAM ring buffers + ipc_init(); + + uart0_hw_puts("BL808 D0: ready\n"); +} + +private void tick_stub() @nogc nothrow +{ + // placeholder - will drive urt.time / Application frame tick +} diff --git a/src/urt/driver/bl808/spi.d b/src/urt/driver/bl808/spi.d new file mode 100644 index 0000000..3393b81 --- /dev/null +++ b/src/urt/driver/bl808/spi.d @@ -0,0 +1,5 @@ +/// BL808 SPI peripheral +/// +/// Direct register access to BL808 SPI controller. +/// TODO: implement when needed for hardware I/O. +module urt.driver.bl808.spi; diff --git a/src/urt/driver/bl808/start.S b/src/urt/driver/bl808/start.S new file mode 100644 index 0000000..5f59bdf --- /dev/null +++ b/src/urt/driver/bl808/start.S @@ -0,0 +1,407 @@ +/* BL808 D0 core (T-Head C906 RV64GC) startup + * + * Based on Bouffalo SDK startup.S + vectors.S, consolidated into + * a single file with no SDK header dependencies. + * + * Boot flow: + * 1. CPU setup — wait for M0, disable IRQ, extensions, FPU, trap vectors + * 2. Core regs — gp, tp, sp + * 3. Memory access — TZC release, clear PLIC + * 4. Section init — .got, .tdata, .tbss, .bss, .data + * 5. Caches — I-cache, D-cache, prefetch + * 6. Interrupts — dispatch table, enable MIE+MEIE + * 7. D runtime — sys_init, .init_array, main + * + * T-Head custom CSR numbers (not in standard riscv asm): + * mxstatus = 0x7C0 (T-Head ISA extension enable) + * mhcr = 0x7C1 (hardware cache control) + * mhint = 0x7C5 (performance hints — prefetch, AMR) + */ +#define CSR_MXSTATUS 0x7C0 +#define CSR_MHCR 0x7C1 +#define CSR_MHINT 0x7C5 + + .section .text.entry, "ax" + .global _start + .type _start, @function + .align 2 + +_start: + /* ── 1. CPU setup ─────────────────────────────────────────────── */ + + /* Wait for M0 to finish clock/peripheral init. + * M0 switches D0's CPU clock (XTAL→PLL) AFTER starting D0, which + * glitches the pipeline if we're executing. */ + li t0, 1000000 /* ~80ms at 24MHz, ~5ms at 400MHz */ +.Lwait_m0: + addi t0, t0, -1 + bnez t0, .Lwait_m0 + + /* Disable interrupts */ + csrc mstatus, 0x0008 /* MIE */ + csrc mstatus, 0x0002 /* SIE */ + + /* Enable T-Head ISA extensions (THEADISAEE + MM) */ + li t0, (1 << 22) | (1 << 15) + csrs CSR_MXSTATUS, t0 + + /* Enable FPU (FS = Initial) and vector unit (VS = Initial) */ + li t0, (1 << 13) | (1 << 23) + csrs mstatus, t0 + + /* Set trap vector table (vectored mode) */ + la t0, __vectors + ori t0, t0, 1 + csrw mtvec, t0 + + /* ── 2. Core registers ────────────────────────────────────────── */ + + .option push + .option norelax + la gp, __global_pointer$ + .option pop + la tp, _tdata_start + la sp, _stack_top + + /* ── 3. Memory access ─────────────────────────────────────────── */ + + /* Release TZC security so D0 can access full PSRAM */ + li t0, 0x20005380 /* TZC_SEC_TZC_PSRAMA_TZSRG_CTRL */ + lw t1, 0(t0) + li t2, -65537 /* ~(1 << 16) sign-extended */ + and t1, t1, t2 + sw t1, 0(t0) + + /* Clear PLIC interrupt enables and pending bits */ + li t0, 0xE0002000 /* PLIC enable base */ + li t1, 0xE0001000 /* PLIC pending base */ + li t2, 0 + li t3, 4 /* 4 words = 128 bits */ +.Lclear_plic: + sw t2, 0(t0) + sw t2, 0(t1) + addi t0, t0, 4 + addi t1, t1, 4 + addi t3, t3, -1 + bnez t3, .Lclear_plic + + /* ── 4. Section init ──────────────────────────────────────────── */ + + /* Copy .got from flash to SRAM (must come first — gp-relative access) */ + la t0, _got_load + la t1, _got_start + la t2, _got_end +.Lcopy_got: + bgeu t1, t2, .Lgot_done + ld t3, 0(t0) + sd t3, 0(t1) + addi t0, t0, 8 + addi t1, t1, 8 + j .Lcopy_got +.Lgot_done: + + /* Copy .tdata from flash to SRAM (TLS initialized data) */ + la t0, _tdata_load + la t1, _tdata_start + la t2, _tdata_end +.Lcopy_tdata: + bgeu t1, t2, .Ltdata_done + ld t3, 0(t0) + sd t3, 0(t1) + addi t0, t0, 8 + addi t1, t1, 8 + j .Lcopy_tdata +.Ltdata_done: + + /* Zero .tbss in SRAM (TLS zero-initialized data) */ + la t0, _tbss_start + la t1, _tbss_end +.Lzero_tbss: + bgeu t0, t1, .Ltbss_done + sd zero, 0(t0) + addi t0, t0, 8 + j .Lzero_tbss +.Ltbss_done: + + /* Zero BSS in PSRAM */ + la t0, _bss_start + la t1, _bss_end +.Lzero_bss: + bgeu t0, t1, .Lbss_done + sd zero, 0(t0) + addi t0, t0, 8 + j .Lzero_bss +.Lbss_done: + + /* Copy .data from flash to PSRAM */ + la t0, _data_load + la t1, _data_start + la t2, _data_end +.Lcopy_data: + bgeu t1, t2, .Ldata_done + ld t3, 0(t0) + sd t3, 0(t1) + addi t0, t0, 8 + addi t1, t1, 8 + j .Lcopy_data +.Ldata_done: + + /* ── 5. Caches ────────────────────────────────────────────────── */ + + li t0, 0x3 /* IE + DE */ + csrs CSR_MHCR, t0 + li t0, (1 << 2) | (1 << 8) | (1 << 12) + csrs CSR_MHINT, t0 + + /* ── 6. Interrupts ────────────────────────────────────────────── */ + + csrs mstatus, 0x0008 /* MIE */ + li t0, (1 << 11) /* MEIE */ + csrs mie, t0 + + /* ── 7. D runtime ─────────────────────────────────────────────── */ + + call sys_init + + /* .init_array — LDC module info registration */ + la t0, __init_array_start + la t1, __init_array_end +.Linit_array_loop: + bgeu t0, t1, .Linit_array_done + ld t2, 0(t0) + jalr ra, t2 + addi t0, t0, 8 + j .Linit_array_loop +.Linit_array_done: + + /* Jump to uRT main(int argc, char** argv) */ + li a0, 0 /* argc = 0 */ + li a1, 0 /* argv = NULL */ + call main + + /* If main returns, spin */ +.Lhalt: + wfi + j .Lhalt + + +/* ================================================================ + * Machine-mode trap vector table (vectored mode) + * + * mtvec[1:0] = 01 means: on interrupt with cause N, jump to + * mtvec_base + N * 4. Each entry is a single jump instruction. + * ================================================================ */ + + .section .text, "ax" + .align 6 + .global __vectors + .type __vectors, @object +__vectors: + .option push + .option norvc + j _trap_exception /* 0: exception (sync trap) */ + j _trap_default /* 1: S-mode software interrupt */ + j _trap_default /* 2: reserved */ + j _trap_msoft /* 3: M-mode software interrupt */ + j _trap_default /* 4: reserved */ + j _trap_default /* 5: S-mode timer */ + j _trap_default /* 6: reserved */ + j _trap_mtimer /* 7: M-mode timer */ + j _trap_default /* 8: reserved */ + j _trap_default /* 9: S-mode external */ + j _trap_default /* 10: reserved */ + j _trap_mext /* 11: M-mode external interrupt */ + .option pop + + +/* ================================================================ + * Trap handlers + * ================================================================ */ + + .align 2 +_trap_default: + j _trap_default + + .align 2 +_trap_msoft: + mret + +/* + * ISR save/restore macros. + * + * Integer caller-saved: ra, t0-t6, a0-a7 (16 regs × 8 = 128 bytes) + * FP caller-saved: ft0-ft11, fa0-fa7 (20 regs × 8 = 160 bytes) + * Total frame: 288 bytes + */ + +.macro ISR_SAVE + addi sp, sp, -288 + sd ra, 0(sp) + sd t0, 8(sp) + sd t1, 16(sp) + sd t2, 24(sp) + sd t3, 32(sp) + sd t4, 40(sp) + sd t5, 48(sp) + sd t6, 56(sp) + sd a0, 64(sp) + sd a1, 72(sp) + sd a2, 80(sp) + sd a3, 88(sp) + sd a4, 96(sp) + sd a5, 104(sp) + sd a6, 112(sp) + sd a7, 120(sp) + fsd ft0, 128(sp) + fsd ft1, 136(sp) + fsd ft2, 144(sp) + fsd ft3, 152(sp) + fsd ft4, 160(sp) + fsd ft5, 168(sp) + fsd ft6, 176(sp) + fsd ft7, 184(sp) + fsd ft8, 192(sp) + fsd ft9, 200(sp) + fsd ft10, 208(sp) + fsd ft11, 216(sp) + fsd fa0, 224(sp) + fsd fa1, 232(sp) + fsd fa2, 240(sp) + fsd fa3, 248(sp) + fsd fa4, 256(sp) + fsd fa5, 264(sp) + fsd fa6, 272(sp) + fsd fa7, 280(sp) +.endm + +.macro ISR_RESTORE + fld ft0, 128(sp) + fld ft1, 136(sp) + fld ft2, 144(sp) + fld ft3, 152(sp) + fld ft4, 160(sp) + fld ft5, 168(sp) + fld ft6, 176(sp) + fld ft7, 184(sp) + fld ft8, 192(sp) + fld ft9, 200(sp) + fld ft10, 208(sp) + fld ft11, 216(sp) + fld fa0, 224(sp) + fld fa1, 232(sp) + fld fa2, 240(sp) + fld fa3, 248(sp) + fld fa4, 256(sp) + fld fa5, 264(sp) + fld fa6, 272(sp) + fld fa7, 280(sp) + ld ra, 0(sp) + ld t0, 8(sp) + ld t1, 16(sp) + ld t2, 24(sp) + ld t3, 32(sp) + ld t4, 40(sp) + ld t5, 48(sp) + ld t6, 56(sp) + ld a0, 64(sp) + ld a1, 72(sp) + ld a2, 80(sp) + ld a3, 88(sp) + ld a4, 96(sp) + ld a5, 104(sp) + ld a6, 112(sp) + ld a7, 120(sp) + addi sp, sp, 288 +.endm + + .align 2 +_trap_mtimer: + ISR_SAVE + call _timer_irq_handler + ISR_RESTORE + mret + + .align 2 +_trap_mext: + ISR_SAVE + + /* Read PLIC claim register to get IRQ number */ + li t0, 0xE0200004 + lw a0, 0(t0) + beqz a0, .Lmext_done + + call _irq_dispatch + + /* Write IRQ number back to PLIC claim to complete */ + li t0, 0xE0200004 + sw a0, 0(t0) + +.Lmext_done: + ISR_RESTORE + mret + + .align 2 +_trap_exception: + /* Redirect mtvec to a simple spin so double-faults don't recurse */ + la t0, .Lcrash_spin2 + csrw mtvec, t0 + + /* Save ALL registers to stack for the crash handler */ + addi sp, sp, -256 + sd ra, 0(sp) + sd sp, 8(sp) /* saved sp (pre-exception value + 256) */ + sd gp, 16(sp) + sd tp, 24(sp) + sd t0, 32(sp) + sd t1, 40(sp) + sd t2, 48(sp) + sd s0, 56(sp) /* fp */ + sd s1, 64(sp) + sd a0, 72(sp) + sd a1, 80(sp) + sd a2, 88(sp) + sd a3, 96(sp) + sd a4, 104(sp) + sd a5, 112(sp) + sd a6, 120(sp) + sd a7, 128(sp) + sd s2, 136(sp) + sd s3, 144(sp) + sd s4, 152(sp) + sd s5, 160(sp) + sd s6, 168(sp) + sd s7, 176(sp) + sd s8, 184(sp) + sd s9, 192(sp) + sd s10, 200(sp) + sd s11, 208(sp) + sd t3, 216(sp) + sd t4, 224(sp) + sd t5, 232(sp) + sd t6, 240(sp) + + /* Fix saved sp to the value before our addi */ + addi t0, sp, 256 + sd t0, 8(sp) + + /* a0 = pointer to saved register block on stack */ + mv a0, sp + /* a1 = mcause */ + csrr a1, mcause + /* a2 = mepc (faulting PC) */ + csrr a2, mepc + /* a3 = mtval (faulting address/instruction) */ + csrr a3, mtval + + call _crash_handler + + /* If crash handler returns or double-faults, spin here */ +.Lcrash_spin: + wfi + j .Lcrash_spin + + /* Non-vectored mtvec target for double-fault: spin immediately */ + .align 2 +.Lcrash_spin2: + wfi + j .Lcrash_spin2 diff --git a/src/urt/driver/bl808/syscalls.d b/src/urt/driver/bl808/syscalls.d new file mode 100644 index 0000000..8795081 --- /dev/null +++ b/src/urt/driver/bl808/syscalls.d @@ -0,0 +1,72 @@ +/// BL808 newlib syscall stubs and POSIX networking stubs +/// +/// _write routes stdout/stderr to UART0. +/// Network stubs return -1 until replaced by XRAM WiFi IPC. +module urt.driver.bl808.syscalls; + +import urt.driver.bl808.uart; + +private: + +extern(C) extern __gshared { + pragma(mangle, "_bss_end") void* _bss_end_ptr; + pragma(mangle, "__heap_end") void* _heap_end_ptr; +} + +public: + +// ================================================================ +// newlib syscall stubs +// ================================================================ + +extern(C) int _close(int fd) @nogc nothrow { return -1; } +extern(C) int _read(int fd, void* buf, size_t n) @nogc nothrow { return 0; } + +extern(C) int _write(int fd, const(void)* buf, size_t n) @nogc nothrow +{ + if (fd == 1 || fd == 2) + uart0_hw_puts((cast(const(char)*) buf)[0 .. n]); + return cast(int) n; +} + +extern(C) int _lseek(int fd, int offset, int whence) @nogc nothrow { return 0; } +extern(C) int _fstat(int fd, void* st) @nogc nothrow { return 0; } +extern(C) int _isatty(int fd) @nogc nothrow { return 1; } + +extern(C) void* _sbrk(int incr) @nogc nothrow +{ + __gshared char* heap = null; + if (heap is null) + heap = cast(char*)&_bss_end_ptr; + char* new_heap = heap + incr; + if (new_heap > cast(char*)&_heap_end_ptr) + return cast(void*)-1; // ENOMEM + char* prev = heap; + heap = new_heap; + return prev; +} + +extern(C) void _exit(int code) @nogc nothrow { while (true) {} } +extern(C) int _kill(int pid, int sig) @nogc nothrow { return -1; } +extern(C) int _getpid() @nogc nothrow { return 1; } + +// ================================================================ +// POSIX networking stubs (replaced by XRAM WiFi IPC when ready) +// ================================================================ + +extern(C) int socket(int domain, int type, int protocol) @nogc nothrow { return -1; } +extern(C) int close(int fd) @nogc nothrow { return -1; } +extern(C) int poll(void* fds, uint nfds, int timeout) @nogc nothrow { return -1; } +extern(C) int _accept(int fd, void* addr, void* len) @nogc nothrow { return -1; } +extern(C) ptrdiff_t _recv(int fd, void* buf, size_t len, int flags) @nogc nothrow { return -1; } +extern(C) ptrdiff_t _recvfrom(int fd, void* buf, size_t len, int flags, void* src, void* al) @nogc nothrow { return -1; } +extern(C) ptrdiff_t _sendmsg(int fd, const(void)* msg, int flags) @nogc nothrow { return -1; } +extern(C) int _shutdown(int fd, int how) @nogc nothrow { return -1; } +extern(C) int _bind(int fd, const(void)* addr, uint len) @nogc nothrow { return -1; } +extern(C) int _listen(int fd, int backlog) @nogc nothrow { return -1; } +extern(C) int _connect(int fd, const(void)* addr, uint len) @nogc nothrow { return -1; } +extern(C) int setsockopt(int fd, int level, int name, const(void)* val, uint len) @nogc nothrow { return -1; } +extern(C) int getsockname(int fd, void* addr, uint* len) @nogc nothrow { return -1; } +extern(C) int getpeername(int fd, void* addr, uint* len) @nogc nothrow { return -1; } +extern(C) int getaddrinfo(const(char)* node, const(char)* service, const(void)* hints, void** res) @nogc nothrow { return -1; } +extern(C) void freeaddrinfo(void* res) @nogc nothrow {} diff --git a/src/urt/driver/bl808/timer.d b/src/urt/driver/bl808/timer.d new file mode 100644 index 0000000..8387af6 --- /dev/null +++ b/src/urt/driver/bl808/timer.d @@ -0,0 +1,246 @@ +/// BL808 C906 timer support +/// +/// Two time sources: +/// +/// 1. mtime (RISC-V standard) - 1 MHz monotonic counter. +/// Read via rdtime. Survives WFI/clock scaling, resets on system reset. +/// Used for monotonic timekeeping (getTime / Duration / Timer). +/// +/// 2. HBN RTC - 32,768 Hz counter in the Hibernate block. +/// 40-bit, survives deep sleep (HBN) if VBAT is maintained. +/// Resets on full power cycle. Used with a stored UTC offset +/// for wall-clock time across sleep/wake cycles. +/// +/// CLINT layout (T-Head C906): +/// CORET_BASE = 0xE400_0000 (PLIC_BASE + 0x400_0000) +/// MTIMECMPL0 @ CORET_BASE + 0x4000 +/// MTIMECMPH0 @ CORET_BASE + 0x4004 +/// +/// HBN layout: +/// HBN_BASE = 0x2000_F000 +/// HBN_CTL @ +0x00 - bit 0: RTC enable +/// HBN_TIME_L @ +0x04 - compare value low (alarm) +/// HBN_TIME_H @ +0x08 - compare value high (alarm) +/// HBN_RTC_TIME_L @ +0x0C - latched counter low (read-only) +/// HBN_RTC_TIME_H @ +0x10 - latched counter high [7:0] + latch trigger [31] +module urt.driver.bl808.timer; + +import core.volatile; + +@nogc nothrow: + +// ================================================================ +// Hardware addresses +// ================================================================ + +private enum ulong CORET_BASE = 0xE400_0000; +private enum ulong MTIMECMPL0 = CORET_BASE + 0x4000; +private enum ulong MTIMECMPH0 = CORET_BASE + 0x4004; + +private enum ulong HBN_BASE = 0x2000_F000; +private enum ulong HBN_CTL = HBN_BASE + 0x00; +private enum ulong HBN_RTC_TIME_L = HBN_BASE + 0x0C; +private enum ulong HBN_RTC_TIME_H = HBN_BASE + 0x10; + +// ================================================================ +// mtime frequency +// +// The BL808 mtime counter runs at 1MHz (from the XTAL/PLL +// divided down). Confirm on hardware by measuring against +// a known delay or reading the clock tree registers. +// ================================================================ + +enum uint mtime_freq_hz = 1_000_000; +enum bool has_mtime = true; +enum bool has_rtc = true; +enum bool has_mcycle = true; +enum bool has_timer_stop = true; +enum bool has_wfi_sleep = true; + +// ================================================================ +// Time reading +// ================================================================ + +/// Read the monotonic mtime counter via rdtime. +/// Does not stop during WFI, unaffected by clock scaling. +ulong mtime_read() +{ + ulong t; + asm @nogc nothrow { "rdtime %0" : "=r" (t); } + return t; +} + +/// Read CPU cycle counter (for profiling, NOT timekeeping). +/// Stops during WFI, rate changes with clock scaling. +ulong mcycle_read() +{ + ulong c; + asm @nogc nothrow { "rdcycle %0" : "=r" (c); } + return c; +} + +// ================================================================ +// Timer interrupt (periodic tick) +// ================================================================ + +private __gshared ulong tick_interval = 0; +private __gshared void function() @nogc nothrow tick_callback = null; + +/// Set up a periodic timer interrupt. +/// interval_us: microseconds between ticks +/// callback: called from _timer_irq_handler (keep it short!) +void timer_set_periodic(ulong interval_us, void function() @nogc nothrow callback) +{ + import urt.driver.bl808.irq : IrqClass, enable_irq; + + tick_interval = interval_us; + tick_callback = callback; + + ulong now = mtime_read(); + mtimecmp_write(now + tick_interval); + enable_irq(IrqClass.timer); +} + +/// Stop the periodic timer +void timer_stop() +{ + import urt.driver.bl808.irq : IrqClass, disable_irq; + + disable_irq(IrqClass.timer); + mtimecmp_write(ulong.max); + tick_callback = null; +} + +/// Called from start.S _trap_mtimer. +/// Sets next deadline and invokes the user callback. +extern(C) void _timer_irq_handler() +{ + if (tick_interval > 0) + { + // Advance deadline relative to current compare value + // (not current time - avoids drift) + ulong cmp = mtimecmp_read(); + mtimecmp_write(cmp + tick_interval); + } + + if (tick_callback !is null) + tick_callback(); +} + +// ================================================================ +// MTIMECMP register access +// +// Split into two 32-bit writes. Write high word to max first +// to prevent a spurious interrupt when the low word is updated. +// ================================================================ + +/// Set mtimecmp for a one-shot wakeup (used by sleep). +/// The periodic timer handler will re-arm on the next tick if active. +void mtimecmp_write_oneshot(ulong value) +{ + mtimecmp_write(value); +} + +private void mtimecmp_write(ulong value) +{ + auto lo = cast(uint*)MTIMECMPL0; + auto hi = cast(uint*)MTIMECMPH0; + + // Write 0xFFFFFFFF to high first to prevent spurious fire + volatileStore(hi, 0xFFFF_FFFF); + volatileStore(lo, cast(uint)(value & 0xFFFF_FFFF)); + volatileStore(hi, cast(uint)(value >> 32)); +} + +private ulong mtimecmp_read() +{ + auto lo = cast(uint*)MTIMECMPL0; + auto hi = cast(uint*)MTIMECMPH0; + + // Read high-low-high to handle rollover + uint h1 = volatileLoad(hi); + uint l = volatileLoad(lo); + uint h2 = volatileLoad(hi); + if (h1 != h2) + l = volatileLoad(lo); + return (cast(ulong)h2 << 32) | l; +} + +// ================================================================ +// HBN RTC (32,768 Hz, 40-bit, survives hibernate) +// ================================================================ + +enum uint rtc_freq_hz = 32_768; + +/// Enable the HBN RTC counter (bit 0 of HBN_CTL). +/// Does NOT reset the counter - call rtc_reset() first if needed. +/// Note: read-modify-write on HBN_CTL is not interrupt-safe. +/// If called after interrupts are enabled, wrap with mstatus.MIE guard. +void rtc_enable() +{ + auto ctl = cast(uint*)HBN_CTL; + volatileStore(ctl, volatileLoad(ctl) | 0x01); +} + +/// Disable and reset the HBN RTC counter to zero. +/// Note: same mstatus.MIE guard applies - see rtc_enable(). +void rtc_reset() +{ + auto ctl = cast(uint*)HBN_CTL; + volatileStore(ctl, volatileLoad(ctl) & ~uint(0x01)); +} + +/// Read the 40-bit HBN RTC counter. +/// Latches the value first (toggle bit 31 of RTC_TIME_H), then reads. +ulong rtc_read() +{ + auto lo = cast(uint*)HBN_RTC_TIME_L; + auto hi = cast(uint*)HBN_RTC_TIME_H; + + // Latch: set bit 31, then clear it + uint h = volatileLoad(hi); + volatileStore(hi, h | (1u << 31)); + volatileStore(hi, h & ~(1u << 31)); + + uint l = volatileLoad(lo); + h = volatileLoad(hi); + return (ulong(h & 0xFF) << 32) | l; +} + +/// Convert RTC ticks (32,768 Hz) to seconds. +ulong rtc_ticks_to_sec(ulong ticks) +{ + return ticks / rtc_freq_hz; +} + +/// Convert seconds to RTC ticks. +ulong rtc_sec_to_ticks(ulong sec) +{ + return sec * rtc_freq_hz; +} + +// ================================================================ +// HBN RAM (4KB, survives hibernate if VBAT maintained) +// +// The actual storage is in hbn_ram.c, placed in .hbn_ram section +// by the linker. This avoids hardcoding addresses and lets the +// linker manage the HBN RAM region. +// ================================================================ + +/// Persistent state across hibernate cycles. +/// Backed by .hbn_ram section (see hbn_ram.c / linker script). +struct HbnPersist +{ + enum uint HBN_MAGIC = 0x4F57_4254; // "OWBT" (OpenWatt Boot Time) + + uint magic; + long utc_offset; // HBN ticks from RTC epoch to Unix epoch +} + +/// Access the persistent state in HBN RAM. +HbnPersist* hbn_persist() +{ + return cast(HbnPersist*)&_hbn_persist; +} + +private extern extern(C) __gshared HbnPersist _hbn_persist; diff --git a/src/urt/driver/bl808/uart.d b/src/urt/driver/bl808/uart.d new file mode 100644 index 0000000..ddea138 --- /dev/null +++ b/src/urt/driver/bl808/uart.d @@ -0,0 +1,587 @@ +// BL808 UART driver +// +// Four UART peripherals sharing the same register-level IP block (BL6xx/BL8xx): +// +// UART0 0x2000_A000 MCU domain M0's console (COM7 on M1s Dock) +// UART1 0x2000_A100 MCU domain General purpose +// UART2 0x2000_AA00 MCU domain Shares address with ISO11898 CAN (mutually exclusive) +// UART3 0x3000_2000 MM domain D0's console (COM8 on M1s Dock) +// +// M0 initializes UART0 before releasing D0. M0 initializes UART3 clock +// ("UART CLK select MM XCLK") shortly after starting D0. +// +// GPIO pin muxing is handled by M0 at boot. UART0/3 are pre-configured. +// UART1/2 require M0 cooperation or future GPIO mux support from D0. +// +// Interrupt availability from D0: +// UART3 - PLIC IRQ 20 (IRQ_NUM_BASE + 4), interrupt-driven +// UART0/1/2 - IRQs are in M0's PLIC domain, must be polled from D0 +// +// All UARTs use software ring buffers (512 bytes RX, 512 bytes TX). +// UART3 fills/drains them via ISR. UART0/1/2 require explicit uart_poll(). +module urt.driver.bl808.uart; + +import core.volatile; +import urt.driver.bl808.irq; + +import urt.driver.uart : Parity, StopBits, UartConfig; + +nothrow @nogc: + + +// ══════════════════════════════════════════════════════════════════════════════ +// Register definitions +// ══════════════════════════════════════════════════════════════════════════════ + +enum num_uarts = 4; +enum uint uart_clock_hz = 40_000_000; +enum bool has_irq_driven_uart = true; +enum bool has_dma_driven_uart = false; + +private immutable uint[num_uarts] uart_base = [ + 0x2000_A000, // UART0 + 0x2000_A100, // UART1 + 0x2000_AA00, // UART2 (shared with ISO11898 CAN) + 0x3000_2000, // UART3 +]; + +// D0 PLIC IRQ numbers (IRQ_NUM_BASE = 16 for D0) +private enum uint UART3_PLIC_IRQ = 20; // IRQ_NUM_BASE + 4 + +// Register offsets +private enum : uint +{ + UTX_CONFIG = 0x00, + URX_CONFIG = 0x04, + BIT_PRD = 0x08, + DATA_CONFIG = 0x0C, + UTX_IR_POSITION = 0x10, + URX_IR_POSITION = 0x14, + URX_RTO_TIMER = 0x18, + SW_MODE = 0x1C, + INT_STS = 0x20, + INT_MASK = 0x24, + INT_CLEAR = 0x28, + INT_EN = 0x2C, + STATUS = 0x30, + FIFO_CONFIG_0 = 0x80, + FIFO_CONFIG_1 = 0x84, + FIFO_WDATA = 0x88, + FIFO_RDATA = 0x8C, +} + +// UTX_CONFIG (0x00) bits +private enum : uint +{ + CR_UTX_EN = 1 << 0, + CR_UTX_CTS_EN = 1 << 1, + CR_UTX_FRM_EN = 1 << 2, + CR_UTX_LIN_EN = 1 << 3, + CR_UTX_PRT_EN = 1 << 4, + CR_UTX_PRT_SEL = 1 << 5, // 0 = even, 1 = odd + CR_UTX_BIT_CNT_D_SHIFT = 8, // [10:8] data bits: value + 4 (so 4 = 8-bit) + CR_UTX_BIT_CNT_D_MASK = 0x7 << 8, + CR_UTX_BIT_CNT_P_SHIFT = 12, // [13:12] stop bits + CR_UTX_BIT_CNT_P_MASK = 0x3 << 12, +} + +// URX_CONFIG (0x04) bits +private enum : uint +{ + CR_URX_EN = 1 << 0, + CR_URX_PRT_EN = 1 << 4, + CR_URX_PRT_SEL = 1 << 5, + CR_URX_BIT_CNT_D_SHIFT = 8, + CR_URX_BIT_CNT_D_MASK = 0x7 << 8, +} + +// FIFO_CONFIG_0 (0x80) bits +private enum : uint +{ + DMA_TX_EN = 1 << 0, + DMA_RX_EN = 1 << 1, + TX_FIFO_CLR = 1 << 2, + RX_FIFO_CLR = 1 << 3, + TX_FIFO_OVERFLOW = 1 << 4, + TX_FIFO_UNDERFLOW = 1 << 5, + RX_FIFO_OVERFLOW = 1 << 6, + RX_FIFO_UNDERFLOW = 1 << 7, +} + +// FIFO_CONFIG_1 (0x84) bits +private enum : uint +{ + TX_FIFO_CNT_MASK = 0x3F, // [5:0] available TX slots + RX_FIFO_CNT_SHIFT = 8, + RX_FIFO_CNT_MASK = 0x3F << 8, // [13:8] available RX bytes + TX_FIFO_TH_SHIFT = 16, + TX_FIFO_TH_MASK = 0x1F << 16, // [20:16] + RX_FIFO_TH_SHIFT = 24, + RX_FIFO_TH_MASK = 0x1F << 24, // [28:24] +} + +// INT_STS / INT_MASK / INT_CLEAR / INT_EN bit positions +private enum : uint +{ + INT_UTX_END = 1 << 0, + INT_URX_END = 1 << 1, + INT_UTX_FIFO = 1 << 2, + INT_URX_FIFO = 1 << 3, + INT_URX_RTO = 1 << 4, + INT_URX_PCE = 1 << 5, + INT_UTX_FER = 1 << 6, + INT_URX_FER = 1 << 7, + INT_MASK_ALL = 0xFF, +} + +// FIFO depth +private enum UART_FIFO_MAX = 32; + +// RX FIFO threshold - interrupt fires when RX FIFO count >= this value. +// Set to 16 so we drain before the 32-byte FIFO overflows. +private enum RX_FIFO_THRESHOLD = 16; + +// TX FIFO threshold - interrupt fires when TX FIFO count >= this value +// (i.e., space is available). Set low so we refill aggressively. +private enum TX_FIFO_THRESHOLD = 8; + +// RX timeout - number of bit periods of silence before RX timeout interrupt. +// Catches partial frames shorter than RX_FIFO_THRESHOLD. +private enum RX_TIMEOUT_BITS = 80; // ~10 byte times at any baud rate + +// UART clock frequency - M0 sets all UARTs to XCLK = 40 MHz. +// TODO: read GLB_UART_CFG0 (GLB_BASE + 0x150) to determine actual clock +// source. Bits: [2:0] = divider, [4] = clock enable, [7] = clk_sel, +// [22] = clk_sel2. The 2-bit selector chooses between MCU PBCLK (0), +// 160 MHz PLL mux (1), or XCLK (2). Actual uart_clk = source / (div + 1). +private enum uint UART_CLK_HZ = 40_000_000; + + +// Software ring buffer - reuse urt.mem.ring with fixed 512-byte capacity. +import urt.mem.ring : RingBuffer; +private alias Ring = RingBuffer!512; + + +// ══════════════════════════════════════════════════════════════════════════════ +// Driver API +// ══════════════════════════════════════════════════════════════════════════════ + +// Per-UART state +private __gshared Ring[num_uarts] rx_ring; +private __gshared Ring[num_uarts] tx_ring; +private __gshared bool[num_uarts] uart_open_flag; +private __gshared IrqHandler prev_irq_handler; +private __gshared bool irq_handler_installed; + +// Open a UART: configure baud rate, frame format, clear FIFOs, enable TX+RX. +// UART3 gets interrupt-driven I/O. UART0/1/2 require uart_poll(). +// Returns false if id is out of range. +bool uart_hw_open(uint id, UartConfig cfg) +{ + if (id >= num_uarts) + return false; + + immutable base = uart_base[id]; + + // Disable TX and RX while reconfiguring + auto tx_cfg = reg_read(base, UTX_CONFIG); + auto rx_cfg = reg_read(base, URX_CONFIG); + tx_cfg &= ~CR_UTX_EN; + rx_cfg &= ~CR_URX_EN; + reg_write(base, UTX_CONFIG, tx_cfg); + reg_write(base, URX_CONFIG, rx_cfg); + + // Baud rate divisor + immutable uint div = cast(uint)((cast(ulong)UART_CLK_HZ * 10 / cfg.baud_rate + 5) / 10); + reg_write(base, BIT_PRD, ((div - 1) << 16) | (div - 1)); + + // TX config: data bits, stop bits, parity + tx_cfg &= ~(CR_UTX_BIT_CNT_D_MASK | CR_UTX_BIT_CNT_P_MASK | CR_UTX_PRT_EN | CR_UTX_PRT_SEL | CR_UTX_FRM_EN); + tx_cfg |= cast(uint)(cfg.data_bits - 4) << CR_UTX_BIT_CNT_D_SHIFT; + tx_cfg |= cast(uint)cfg.stop_bits << CR_UTX_BIT_CNT_P_SHIFT; + tx_cfg |= CR_UTX_FRM_EN; + if (cfg.parity != Parity.none) + { + tx_cfg |= CR_UTX_PRT_EN; + if (cfg.parity == Parity.odd) + tx_cfg |= CR_UTX_PRT_SEL; + } + + // RX config: data bits, parity (stop bits are TX-only in hardware) + rx_cfg &= ~(CR_URX_BIT_CNT_D_MASK | CR_URX_PRT_EN | CR_URX_PRT_SEL); + rx_cfg |= cast(uint)(cfg.data_bits - 4) << CR_URX_BIT_CNT_D_SHIFT; + if (cfg.parity != Parity.none) + { + rx_cfg |= CR_URX_PRT_EN; + if (cfg.parity == Parity.odd) + rx_cfg |= CR_URX_PRT_SEL; + } + + // Clear software ring buffers + rx_ring[id].purge(); + tx_ring[id].purge(); + + // Mask all interrupts initially + reg_write(base, INT_MASK, INT_MASK_ALL); + + // Clear FIFOs + auto fifo0 = reg_read(base, FIFO_CONFIG_0); + reg_write(base, FIFO_CONFIG_0, fifo0 | TX_FIFO_CLR | RX_FIFO_CLR); + + // Set FIFO thresholds + auto fifo1 = reg_read(base, FIFO_CONFIG_1); + fifo1 &= ~(TX_FIFO_TH_MASK | RX_FIFO_TH_MASK); + fifo1 |= TX_FIFO_THRESHOLD << TX_FIFO_TH_SHIFT; + fifo1 |= RX_FIFO_THRESHOLD << RX_FIFO_TH_SHIFT; + reg_write(base, FIFO_CONFIG_1, fifo1); + + // Set RX timeout + reg_write(base, URX_RTO_TIMER, RX_TIMEOUT_BITS); + + // Enable TX + RX + tx_cfg |= CR_UTX_EN; + rx_cfg |= CR_URX_EN; + reg_write(base, UTX_CONFIG, tx_cfg); + reg_write(base, URX_CONFIG, rx_cfg); + + uart_open_flag[id] = true; + + // UART3: set up interrupt-driven I/O + if (id == 3) + { + // Install our IRQ handler (chain with previous) + if (!irq_handler_installed) + { + prev_irq_handler = irq_set_handler(&uart_irq_handler); + irq_handler_installed = true; + } + + // Enable UART3 PLIC IRQ + enable_irq(UART3_PLIC_IRQ); + + // Unmask RX FIFO threshold + RX timeout interrupts + // TX FIFO interrupt is unmasked on demand when tx_ring has data + auto mask = reg_read(base, INT_MASK); + mask &= ~(INT_URX_FIFO | INT_URX_RTO); + reg_write(base, INT_MASK, mask); + + // Enable interrupt generation + reg_write(base, INT_EN, INT_URX_FIFO | INT_URX_RTO | INT_UTX_FIFO); + } + + return true; +} + +// Disable TX and RX, mask interrupts. +void uart_hw_close(uint id) +{ + assert(id < num_uarts); + + immutable base = uart_base[id]; + + // Mask all interrupts + reg_write(base, INT_MASK, INT_MASK_ALL); + + if (id == 3) + disable_irq(UART3_PLIC_IRQ); + + auto tx_cfg = reg_read(base, UTX_CONFIG); + auto rx_cfg = reg_read(base, URX_CONFIG); + tx_cfg &= ~CR_UTX_EN; + rx_cfg &= ~CR_URX_EN; + reg_write(base, UTX_CONFIG, tx_cfg); + reg_write(base, URX_CONFIG, rx_cfg); + + uart_open_flag[id] = false; +} + +// Poll hardware FIFOs and transfer to/from ring buffers. +// Required for UART0/1/2 (no D0 interrupt). Harmless for UART3. +void uart_hw_poll(uint id) +{ + if (id >= num_uarts || !uart_open_flag[id]) + return; + drain_rx_fifo(id); + fill_tx_fifo(id); +} + +// Non-blocking read: pull from RX ring buffer, return bytes read. +ptrdiff_t uart_hw_read(uint id, void[] buffer) +{ + if (id >= num_uarts) + return -1; + + immutable prev = disable_interrupts(); + auto n = rx_ring[id].read(buffer); + set_interrupts(prev); + return cast(ptrdiff_t)n; +} + +// Non-blocking write: push into TX ring buffer, kick TX if needed. +// Returns bytes accepted (may be less than data.length if ring is full). +ptrdiff_t uart_hw_write(uint id, const(void)[] data) +{ + if (id >= num_uarts) + return -1; + + immutable prev = disable_interrupts(); + + auto n = tx_ring[id].write(data); + + // Kick: fill hardware FIFO from ring + fill_tx_fifo(id); + + // For UART3, unmask TX FIFO interrupt so ISR continues draining + if (id == 3 && !tx_ring[id].empty) + { + auto mask = reg_read(uart_base[id], INT_MASK); + mask &= ~INT_UTX_FIFO; + reg_write(uart_base[id], INT_MASK, mask); + } + + set_interrupts(prev); + return cast(ptrdiff_t)n; +} + +// Return number of bytes available to read from RX ring. +ptrdiff_t uart_hw_rx_pending(uint id) +{ + if (id >= num_uarts) + return -1; + + immutable prev = disable_interrupts(); + auto p = rx_ring[id].pending; + set_interrupts(prev); + return cast(ptrdiff_t)p; +} + +// Clear RX ring buffer and hardware FIFO. Returns bytes discarded. +ptrdiff_t uart_hw_flush(uint id) +{ + if (id >= num_uarts) + return -1; + + immutable prev = disable_interrupts(); + + immutable p = rx_ring[id].pending; + rx_ring[id].purge(); + + // Also clear hardware RX FIFO + immutable base = uart_base[id]; + auto fifo0 = reg_read(base, FIFO_CONFIG_0); + reg_write(base, FIFO_CONFIG_0, fifo0 | RX_FIFO_CLR); + + set_interrupts(prev); + return cast(ptrdiff_t)p; +} + +// Check and clear FIFO error flags (overflow/underflow). +// Returns true if any error was detected. +bool uart_hw_check_errors(uint id) +{ + if (id >= num_uarts) + return true; + + immutable base = uart_base[id]; + immutable fifo0 = reg_read(base, FIFO_CONFIG_0); + immutable errors = fifo0 & (TX_FIFO_OVERFLOW | TX_FIFO_UNDERFLOW | RX_FIFO_OVERFLOW | RX_FIFO_UNDERFLOW); + + if (errors != 0) + { + // Clear error flags by writing them back + reg_write(base, FIFO_CONFIG_0, fifo0); + return true; + } + + return false; +} + + +// ══════════════════════════════════════════════════════════════════════════════ +// Interrupt handler and FIFO transfer +// ══════════════════════════════════════════════════════════════════════════════ + +private: + +// Drain hardware RX FIFO into software ring buffer. +void drain_rx_fifo(uint id) +{ + immutable base = uart_base[id]; + ubyte[1] b = void; + + while (rx_ring[id].available > 0) + { + immutable avail = (reg_read(base, FIFO_CONFIG_1) & RX_FIFO_CNT_MASK) >> RX_FIFO_CNT_SHIFT; + if (avail == 0) + break; + b[0] = cast(ubyte)reg_read(base, FIFO_RDATA); + rx_ring[id].write(b); + } +} + +// Fill hardware TX FIFO from software ring buffer. +void fill_tx_fifo(uint id) +{ + immutable base = uart_base[id]; + ubyte[1] b = void; + + while (!tx_ring[id].empty) + { + immutable space = reg_read(base, FIFO_CONFIG_1) & TX_FIFO_CNT_MASK; + if (space == 0) + break; + tx_ring[id].read(b); + reg_write(base, FIFO_WDATA, cast(uint)b[0]); + } +} + +// PLIC IRQ handler - services UART3 interrupts. +// Chains to previous handler for non-UART IRQs. +void uart_irq_handler(uint irq) +{ + if (irq == UART3_PLIC_IRQ) + { + immutable base = uart_base[3]; + immutable sts = reg_read(base, INT_STS); + immutable mask = reg_read(base, INT_MASK); + immutable active = sts & ~mask; + + // RX FIFO threshold or RX timeout - drain into ring + if (active & (INT_URX_FIFO | INT_URX_RTO)) + { + drain_rx_fifo(3); + if (active & INT_URX_RTO) + reg_write(base, INT_CLEAR, INT_URX_RTO); + } + + // TX FIFO has space - refill from ring + if (active & INT_UTX_FIFO) + { + fill_tx_fifo(3); + // If ring is drained, mask TX interrupt until more data arrives + if (tx_ring[3].empty) + { + auto m = reg_read(base, INT_MASK); + m |= INT_UTX_FIFO; + reg_write(base, INT_MASK, m); + } + } + } + else if (prev_irq_handler !is null) + prev_irq_handler(irq); +} + + +// ══════════════════════════════════════════════════════════════════════════════ +// Early-boot debug output (used before driver is initialized) +// ══════════════════════════════════════════════════════════════════════════════ + +public: + +private auto u0_cfg() { return cast(uint*)cast(ulong)(uart_base[0] + FIFO_CONFIG_1); } +private auto u0_wr() { return cast(uint*)cast(ulong)(uart_base[0] + FIFO_WDATA); } +private auto u3_cfg() { return cast(uint*)cast(ulong)(uart_base[3] + FIFO_CONFIG_1); } +private auto u3_wr() { return cast(uint*)cast(ulong)(uart_base[3] + FIFO_WDATA); } + +// ── UART0 (COM7, MCU domain) ──────────────────────────────────────────────── + +void uart0_putc(char c) +{ + while ((volatileLoad(u0_cfg) & 0x3F) == 0) {} + volatileStore(u0_wr, cast(uint)c); +} + +void uart0_hw_puts(const(char)[] s) +{ + foreach (c; s) + { + if (c == '\n') + uart0_putc('\r'); + uart0_putc(c); + } +} + +void uart0_print(const(char)* s) +{ + while (*s != 0) + { + if (*s == '\n') + uart0_putc('\r'); + uart0_putc(*s); + ++s; + } +} + +void uart0_hex(ulong val) +{ + uart0_putc('0'); + uart0_putc('x'); + int start = 60; + while (start > 0 && ((val >> start) & 0xF) == 0) + start -= 4; + for (int i = start; i >= 0; i -= 4) + { + uint nibble = cast(uint)((val >> i) & 0xF); + uart0_putc(nibble < 10 ? cast(char)('0' + nibble) : cast(char)('a' + nibble - 10)); + } +} + +// ── UART3 (COM8, MM domain - D0's console) ────────────────────────────────── + +void uart3_putc(char c) +{ + while ((volatileLoad(u3_cfg) & 0x3F) == 0) {} + volatileStore(u3_wr, cast(uint)c); +} + +void uart3_puts(const(char)[] s) +{ + foreach (c; s) + { + if (c == '\n') + uart3_putc('\r'); + uart3_putc(c); + } +} + +void uart3_print(const(char)* s) +{ + while (*s != 0) + { + if (*s == '\n') + uart3_putc('\r'); + uart3_putc(*s); + ++s; + } +} + +void uart3_hex(ulong val) +{ + uart3_putc('0'); + uart3_putc('x'); + int start = 60; + while (start > 0 && ((val >> start) & 0xF) == 0) + start -= 4; + for (int i = start; i >= 0; i -= 4) + { + uint nibble = cast(uint)((val >> i) & 0xF); + uart3_putc(nibble < 10 ? cast(char)('0' + nibble) : cast(char)('a' + nibble - 10)); + } +} + + +// ══════════════════════════════════════════════════════════════════════════════ +// Register access helpers +// ══════════════════════════════════════════════════════════════════════════════ + +private: + +uint reg_read(uint base, uint offset) +{ + return volatileLoad(cast(uint*)cast(ulong)(base + offset)); +} + +void reg_write(uint base, uint offset, uint value) +{ + volatileStore(cast(uint*)cast(ulong)(base + offset), value); +} diff --git a/src/urt/driver/bl808/wifi.d b/src/urt/driver/bl808/wifi.d new file mode 100644 index 0000000..c97a5ae --- /dev/null +++ b/src/urt/driver/bl808/wifi.d @@ -0,0 +1,6 @@ +/// BL808 WiFi interface via XRAM IPC to M0 +/// +/// M0 runs the WiFi/BT stack. D0 sends commands (connect, disconnect) +/// and exchanges Ethernet frames via the XRAM NET ring buffer. +/// TODO: implement using ipc module. +module urt.driver.bl808.wifi; diff --git a/src/urt/driver/bl808/xram.d b/src/urt/driver/bl808/xram.d new file mode 100644 index 0000000..6af9386 --- /dev/null +++ b/src/urt/driver/bl808/xram.d @@ -0,0 +1,225 @@ +/// BL808 XRAM inter-processor communication +/// +/// D0 ↔ M0 shared memory ring buffers at 0x2202_0000 (16KB). +/// Each ring has 16-bit head/tail cursors in shared memory, +/// requiring volatile access and memory barriers. +/// +/// Ring IDs (fixed by M0 firmware): +/// 0 = LOG_C906 D0 log output +/// 1 = LOG_E902 LP core log +/// 2 = NET Ethernet frames (WiFi bridge) +/// 3 = PERIPHERAL GPIO/SPI/PWM/Flash control +/// 4 = RPC Remote procedure calls +module urt.driver.bl808.xram; + +import core.volatile; + +@nogc nothrow: + +// ================================================================ +// Constants +// ================================================================ + +enum ulong XRAM_BASE = 0x2202_0000; +enum uint XRAM_SIZE = 16 * 1024; + +enum RingId : uint +{ + log_c906 = 0, + log_e902 = 1, + net = 2, + peripheral = 3, + rpc = 4, + max = 5, +} + +// ================================================================ +// Peripheral message header (4 bytes) +// Used on PERIPHERAL ring for GPIO/SPI/PWM/Flash ops +// ================================================================ + +struct PeriHeader +{ + ubyte type; + ubyte err; + ushort len; +} + +enum PeriType : ubyte +{ + flash = 0x31, + pwm = 0x32, + spi = 0x33, +} + +// ================================================================ +// Net message header (12 bytes) +// Used on NET ring for Ethernet frames and WiFi commands +// ================================================================ + +struct NetHeader +{ + ubyte[4] magic; // "ring" = [0x72, 0x69, 0x6e, 0x67] + ushort len; + ubyte type; // high nibble: msg type, low nibble: dev type + ubyte flag; + ushort crc16; + ushort reserved; +} + +enum NetMsgType : ubyte +{ + command = 0, + frame = 1, + sniffer_pkt = 2, +} + +// ================================================================ +// WiFi operation commands +// ================================================================ + +enum WifiOp : uint +{ + init_ = 0, + deinit = 1, + connect = 2, + disconnect = 3, + upload_stream = 4, +} + +struct WifiConnect +{ + char[32] ssid; + char[63] passwd; +} + +// ================================================================ +// Shared-memory ring buffer +// +// Layout in XRAM (set by M0 firmware): +// struct { uint16_t head, tail; uint32_t buffer_size; uint8_t buffer[]; } +// +// Head is advanced by the reader, tail by the writer. +// Both cores access these via volatile loads/stores. +// ================================================================ + +struct XramRing +{ + @nogc nothrow @trusted: + + ushort* head_ptr; + ushort* tail_ptr; + ubyte* buffer_ptr; + uint buffer_size; + + /// Bytes available to read + uint pending() + { + uint h = volatileLoad(head_ptr); + uint t = volatileLoad(tail_ptr); + if (t >= h) + return t - h; + else + return buffer_size - h + t; + } + + /// Bytes available to write + uint available() + { + uint h = volatileLoad(head_ptr); + uint t = volatileLoad(tail_ptr); + if (t >= h) + return buffer_size - t + h - 1; + else + return h - t - 1; + } + + bool empty() + { + return volatileLoad(head_ptr) == volatileLoad(tail_ptr); + } + + void reset() + { + volatileStore(head_ptr, cast(ushort) 0); + volatileStore(tail_ptr, cast(ushort) 0); + fence(); + } + + /// Read up to dst.length bytes from the ring. Returns bytes read. + uint read(ubyte[] dst) + { + fence(); // ensure we see latest writes from other core + + uint h = volatileLoad(head_ptr); + uint t = volatileLoad(tail_ptr); + uint avail = (t >= h) ? (t - h) : (buffer_size - h + t); + uint len = (dst.length < avail) ? cast(uint) dst.length : avail; + + if (len == 0) + return 0; + + uint first = buffer_size - h; + if (len <= first) + { + dst[0 .. len] = buffer_ptr[h .. h + len]; + h += len; + if (h == buffer_size) + h = 0; + } + else + { + dst[0 .. first] = buffer_ptr[h .. buffer_size]; + uint second = len - first; + dst[first .. len] = buffer_ptr[0 .. second]; + h = second; + } + + fence(); // ensure our reads complete before advancing head + volatileStore(head_ptr, cast(ushort) h); + return len; + } + + /// Write data to the ring. Returns bytes written. + uint write(const(ubyte)[] src) + { + uint h = volatileLoad(head_ptr); + uint t = volatileLoad(tail_ptr); + uint space = (t >= h) ? (buffer_size - t + h - 1) : (h - t - 1); + uint len = (src.length < space) ? cast(uint) src.length : space; + + if (len == 0) + return 0; + + uint tail_room = buffer_size - t; + if (len <= tail_room) + { + buffer_ptr[t .. t + len] = src[0 .. len]; + t += len; + if (t == buffer_size) + t = 0; + } + else + { + buffer_ptr[t .. buffer_size] = src[0 .. tail_room]; + uint second = len - tail_room; + buffer_ptr[0 .. second] = src[tail_room .. len]; + t = second; + } + + fence(); // ensure writes visible before advancing tail + volatileStore(tail_ptr, cast(ushort) t); + return len; + } +} + +/// Memory barrier - RISC-V fence instruction +private void fence() @nogc nothrow +{ + version (RISCV64) + asm @nogc nothrow { "fence rw, rw"; } + else version (RISCV32) + asm @nogc nothrow { "fence rw, rw"; } + else + asm @nogc nothrow { ""; } +} diff --git a/src/urt/driver/ble.d b/src/urt/driver/ble.d new file mode 100644 index 0000000..84bf670 --- /dev/null +++ b/src/urt/driver/ble.d @@ -0,0 +1,537 @@ +module urt.driver.ble; + +import urt.result : Result, InternalResult; +import urt.uuid : GUID; + +version (Windows) + public import urt.driver.windows.ble; +else version (Espressif) + public import urt.driver.esp32.ble; +else + enum uint num_ble = 0; + +nothrow @nogc: + + +// ════════════════════════════════════════════════════════════════════ +// Types +// ════════════════════════════════════════════════════════════════════ + +enum BLEError : ubyte +{ + none, + not_found, // device not found / unreachable + auth_failed, // pairing or encryption failure + timeout, // operation timed out + protocol, // ATT/GATT protocol error + access_denied, // insufficient permissions + internal, // platform-specific internal error +} + +enum BLERole : ubyte +{ + central, // scan + connect to peripherals + peripheral, // advertise + accept connections + observer, // scan only (no connections) + broadcaster, // advertise only (no connections) +} + +enum BLEAdvType : ubyte +{ + connectable, // ADV_IND: connectable, scannable, undirected + connectable_direct, // ADV_DIRECT_IND: connectable, directed + scannable, // ADV_SCAN_IND: scannable, not connectable + nonconnectable, // ADV_NONCONN_IND: not connectable, not scannable +} + +enum BLEAddrType : ubyte +{ + public_ = 0x00, + random_static = 0x01, + rpa_public = 0x02, // resolvable private, public identity + rpa_random = 0x03, // resolvable private, random identity +} + +enum BLEPhy : ubyte +{ + phy_1m = 0x01, // mandatory, 1 Mbit/s + phy_2m = 0x02, // optional, 2 Mbit/s + phy_coded = 0x03, // long range +} + +enum GattCharProps : ushort +{ + none = 0x0000, + broadcast = 0x0001, + read = 0x0002, + write_without_response = 0x0004, + write = 0x0008, + notify = 0x0010, + indicate = 0x0020, + authenticated_writes = 0x0040, + extended_properties = 0x0080, + reliable_write = 0x0100, +} + + +// --- Configuration structs --- + +struct BLEConfig +{ + byte tx_power; // dBm (0 = platform default) + BLEPhy preferred_phy; // preferred PHY (0 = platform default) +} + +struct BLEScanConfig +{ + bool active = true; // active scan (send SCAN_REQ for extra data) + ushort interval_ms = 100; // scan interval + ushort window_ms = 50; // scan window, <= interval + bool filter_duplicates; // suppress repeated advertisements +} + +struct BLEAdvConfig +{ + BLEAdvType adv_type; + ushort interval_ms = 100; // advertising interval + byte tx_power; // dBm (0 = default) + const(ubyte)[] adv_data; // raw AD structures (max 31 bytes) + const(ubyte)[] scan_rsp; // scan response data (max 31 bytes) +} + +struct BLEConnConfig +{ + ushort interval_min_ms = 7; // connection interval range + ushort interval_max_ms = 30; + ushort latency; // slave latency (number of skippable events) + ushort timeout_ms = 5000; // supervision timeout +} + +// Discovered advertisement report from scanning. +struct BLEAdvReport +{ + ubyte[6] addr; + BLEAddrType addr_type; + BLEAdvType adv_type; + byte rssi; // dBm (-128 = unknown) + byte tx_power; // dBm (-128 = not present) + ubyte data_len; + ubyte[62] data_buf; // adv_data + scan_rsp combined (max 31+31) + + const(ubyte)[] data() const pure nothrow @nogc + => data_buf[0 .. data_len]; +} + +// A discovered GATT characteristic on a connected device. +struct BLEGattChar +{ + ushort handle; // ATT handle for read/write + ushort cccd_handle; // Client Characteristic Config descriptor (0 = none) + GUID service_uuid; // owning service UUID + GUID char_uuid; // characteristic UUID + GattCharProps properties; +} + + +// --- Handles --- + +struct BLE +{ + ubyte port = ubyte.max; +} + +bool is_open(ref const BLE ble) +{ + return ble.port != ubyte.max; +} + +// Opaque connection handle. Lifetime: from connect callback (success) +// until disconnect callback fires. Do not use after disconnect. +struct BLEConn +{ + ubyte id = ubyte.max; +} + +bool is_valid(ref const BLEConn conn) +{ + return conn.id != ubyte.max; +} + +// Opaque advertising handle. Returned by ble_adv_start, used to stop +// a specific advertisement. Invalid after ble_adv_stop. +struct BLEAdv +{ + ubyte id = ubyte.max; +} + +bool is_valid(ref const BLEAdv adv) +{ + return adv.id != ubyte.max; +} + + +// --- Callbacks --- + +// Scan result received. Called once per advertisement/scan response. +// The report is only valid for the duration of the callback. +alias BLEScanCallback = void function(BLE ble, ref const BLEAdvReport report) nothrow @nogc; + +// Connection state change. On success: conn is valid, error is .none. +// On failure or disconnect: conn becomes invalid after callback returns. +alias BLEConnCallback = void function(BLE ble, BLEConn conn, bool connected, BLEError error) nothrow @nogc; + +// GATT service discovery complete. chars is only valid during callback. +// On error: chars.length == 0 and error != .none. +alias BLEDiscoverCallback = void function(BLE ble, BLEConn conn, const(BLEGattChar)[] chars, BLEError error) nothrow @nogc; + +// GATT read complete. data is only valid during callback. +alias BLEReadCallback = void function(BLE ble, BLEConn conn, ushort handle, const(ubyte)[] data, BLEError error) nothrow @nogc; + +// GATT write complete. +alias BLEWriteCallback = void function(BLE ble, BLEConn conn, ushort handle, BLEError error) nothrow @nogc; + +// Notification or indication received from a connected peripheral. +// data is only valid during callback. +alias BLENotifyCallback = void function(BLE ble, BLEConn conn, ushort handle, const(ubyte)[] data) nothrow @nogc; + + +// ════════════════════════════════════════════════════════════════════ +// Error type +// ════════════════════════════════════════════════════════════════════ + +BLEError ble_result(Result result) +{ + return cast(BLEError)result.system_code; +} + + +// ════════════════════════════════════════════════════════════════════ +// Implementation +// ════════════════════════════════════════════════════════════════════ + +// --- Lifecycle --- + +void ble_init() +{ + if (_init_refcount++ == 0) + { + // TODO: enable clocks/power for BLE peripheral block + } +} + +void ble_deinit() +{ + assert(_init_refcount > 0); + if (--_init_refcount == 0) + { + // TODO: disable clocks/power for BLE peripheral block + } +} + +Result ble_open(ref BLE ble, ubyte port, ref const BLEConfig cfg) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + { + if (port >= num_ble) + return InternalResult.invalid_parameter; + + if (!ble_hw_open(port, cfg)) + return InternalResult.failed; + + ble.port = port; + return Result.success; + } +} + +void ble_close(ref BLE ble) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + ble_hw_close(ble.port); + ble.port = ubyte.max; +} + +// --- Scanning (central/observer role) --- + +// Start scanning for advertisements. Reports are delivered via the +// scan callback set with ble_set_scan_callback(). Scanning continues +// until ble_scan_stop() is called or the radio is closed. +Result ble_scan_start(ref BLE ble, ref const BLEScanConfig cfg) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + { + if (!ble_hw_scan_start(ble.port, cfg)) + return InternalResult.failed; + return Result.success; + } +} + +void ble_scan_stop(ref BLE ble) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + ble_hw_scan_stop(ble.port); +} + +// --- Advertising (peripheral/broadcaster role) --- + +// Start advertising. Returns an opaque handle on success that can be +// passed to ble_adv_stop. Multiple advertisements may be active +// concurrently (platform permitting). For connectable advertisements, +// incoming connections are delivered via the connection callback. +BLEAdv ble_adv_start(ref BLE ble, ref const BLEAdvConfig cfg) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + return ble_hw_adv_start(ble.port, cfg); +} + +// Stop a specific advertisement. Pass the handle returned by +// ble_adv_start. The handle is invalid after this call. +void ble_adv_stop(ref BLE ble, BLEAdv adv) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + ble_hw_adv_stop(ble.port, adv); +} + +// --- Connection (central role) --- + +// Initiate a connection to a peripheral. Completion (success or failure) +// is delivered via the connection callback. Only one connect may be +// pending at a time per radio. +Result ble_connect(ref BLE ble, ref const ubyte[6] peer_addr, BLEAddrType addr_type, ref const BLEConnConfig cfg) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + { + if (!ble_hw_connect(ble.port, peer_addr, addr_type, cfg)) + return InternalResult.failed; + return Result.success; + } +} + +// Cancel a pending connection attempt. +void ble_connect_cancel(ref BLE ble) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + ble_hw_connect_cancel(ble.port); +} + +// Disconnect an established connection. The disconnect callback fires +// when teardown is complete. +Result ble_disconnect(ref BLE ble, BLEConn conn) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + { + if (!ble_hw_disconnect(ble.port, conn)) + return InternalResult.failed; + return Result.success; + } +} + +// --- GATT discovery --- + +// Discover all services and characteristics on a connected device. +// Results are delivered via the discover callback. Only one discovery +// may be active per connection at a time. +Result ble_gatt_discover(ref BLE ble, BLEConn conn) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + { + if (!ble_hw_gatt_discover(ble.port, conn)) + return InternalResult.failed; + return Result.success; + } +} + +// --- GATT read/write --- + +// Read a characteristic value by handle. Result is delivered via the +// read callback. Multiple reads may be in flight concurrently (up to +// a platform-defined limit). +Result ble_gatt_read(ref BLE ble, BLEConn conn, ushort handle) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + { + if (!ble_hw_gatt_read(ble.port, conn, handle)) + return InternalResult.failed; + return Result.success; + } +} + +// Write a characteristic value. If with_response is true, the write +// callback fires on completion; otherwise the write is fire-and-forget +// (Write Without Response / Write Command). +Result ble_gatt_write(ref BLE ble, BLEConn conn, ushort handle, const(ubyte)[] data, bool with_response = true) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + { + if (!ble_hw_gatt_write(ble.port, conn, handle, data, with_response)) + return InternalResult.failed; + return Result.success; + } +} + +// --- Notifications / Indications --- + +// Enable or disable notifications/indications on a characteristic. +// Writes the CCCD descriptor on the remote device. Incoming notifications +// are delivered via the notify callback. +Result ble_gatt_subscribe(ref BLE ble, BLEConn conn, ushort handle, bool enable) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + { + if (!ble_hw_gatt_subscribe(ble.port, conn, handle, enable)) + return InternalResult.failed; + return Result.success; + } +} + +// --- Queries --- + +Result ble_get_mac(ref BLE ble, ref ubyte[6] mac) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + { + if (!ble_hw_get_mac(ble.port, mac)) + return InternalResult.failed; + return Result.success; + } +} + +byte ble_get_rssi(ref BLE ble, BLEConn conn) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + return ble_hw_get_rssi(ble.port, conn); +} + +// --- Callbacks --- + +void ble_set_scan_callback(ref BLE ble, BLEScanCallback cb) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + ble_hw_set_scan_callback(ble.port, cb); +} + +void ble_set_conn_callback(ref BLE ble, BLEConnCallback cb) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + ble_hw_set_conn_callback(ble.port, cb); +} + +void ble_set_discover_callback(ref BLE ble, BLEDiscoverCallback cb) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + ble_hw_set_discover_callback(ble.port, cb); +} + +void ble_set_read_callback(ref BLE ble, BLEReadCallback cb) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + ble_hw_set_read_callback(ble.port, cb); +} + +void ble_set_write_callback(ref BLE ble, BLEWriteCallback cb) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + ble_hw_set_write_callback(ble.port, cb); +} + +void ble_set_notify_callback(ref BLE ble, BLENotifyCallback cb) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + ble_hw_set_notify_callback(ble.port, cb); +} + +// --- Poll --- + +// Drive completions for platforms that don't deliver events natively. +// Must be called periodically from the main loop. +void ble_poll(ref BLE ble) +{ + static if (num_ble == 0) + assert(false, "no BLE on this platform"); + else + ble_hw_poll(ble.port); +} + + +// ════════════════════════════════════════════════════════════════════ +// Tests +// ════════════════════════════════════════════════════════════════════ + +unittest +{ + static if (num_ble > 0) + { + BLE b; + BLEConfig cfg; + + ble_init(); + + // Out-of-range port + auto r = ble_open(b, cast(ubyte)num_ble, cfg); + assert(!r); + assert(!b.is_open); + + // Open/close each valid port + foreach (p; 0 .. num_ble) + { + BLE port; + auto r2 = ble_open(port, cast(ubyte)p, cfg); + assert(r2, "ble_open failed"); + assert(port.is_open); + assert(port.port == p); + + ble_poll(port); + + ble_close(port); + assert(!port.is_open); + } + + ble_deinit(); + } +} + + +private: + +__gshared ubyte _init_refcount; diff --git a/src/urt/driver/can.d b/src/urt/driver/can.d new file mode 100644 index 0000000..b03ce07 --- /dev/null +++ b/src/urt/driver/can.d @@ -0,0 +1,376 @@ +module urt.driver.can; + +import urt.result : Result, InternalResult; + +version (Espressif) + public import urt.driver.esp32.can; +else +{ + enum uint num_can = 0; + enum bool has_can_fd = false; +} + +nothrow @nogc: + + +enum CanError : ubyte +{ + none = 0, + bit = 1 << 0, // bit error (dominant/recessive mismatch) + stuff = 1 << 1, // bit stuffing violation + crc = 1 << 2, // CRC mismatch + form = 1 << 3, // fixed-form bit field violation + ack = 1 << 4, // no ACK from any receiver + overrun = 1 << 5, // RX FIFO overflow +} + +enum CanBusState : ubyte +{ + error_active, // TEC/REC < 128, normal operation + error_warning, // TEC or REC >= 96 + error_passive, // TEC or REC >= 128, can't send active error frames + bus_off, // TEC >= 256, node disconnected from bus +} + +struct CanConfig +{ + uint bitrate = 500_000; // nominal bitrate (bits/s) + uint data_bitrate; // FD data phase bitrate (0 = classic CAN only) + ubyte tx_gpio = ubyte.max; // GPIO pin for TX (max = platform default) + ubyte rx_gpio = ubyte.max; // GPIO pin for RX (max = platform default) + + // Bit timing (0 = auto-calculate from bitrate) + ubyte sjw; // synchronization jump width (1-4 TQ) + ubyte tseg1; // time segment 1 / prop + phase1 (1-16 TQ) + ubyte tseg2; // time segment 2 / phase2 (1-8 TQ) + ushort brp; // baud rate prescaler (1-1024) +} + +struct CanFrame +{ + uint id; // 11-bit standard or 29-bit extended + bool extended; // true = 29-bit ID, false = 11-bit + bool rtr; // remote transmission request + bool fd; // CAN FD frame (up to 64 bytes, DLC 9-15 = 12..64) + bool brs; // FD bit rate switch (data phase at data_bitrate) + ubyte dlc; // data length code (0-8 classic, 0-15 FD) + ubyte[8] data; // classic CAN payload (FD >8 bytes needs external buffer) +} + +struct CanFilter +{ + uint id; // ID to match + uint mask; // 1-bits = must match, 0-bits = don't care + bool extended; // match extended frames only + bool rtr; // match RTR frames only + bool match_all; // ignore id/mask, accept everything (default) +} + +// Called from ISR when a frame has been received. +alias CanRxCallback = void function(Can can, ref const CanFrame frame) nothrow @nogc; + +// Called from ISR when a TX mailbox becomes free. +alias CanTxCallback = void function(Can can) nothrow @nogc; + +// Called when bus state changes (error active/passive/bus-off). +alias CanBusStateCallback = void function(Can can, CanBusState state) nothrow @nogc; + +// Caller-owned transmit operation token. +struct CanTxOp +{ + enum Status : ubyte + { + idle, + pending, + complete, + cancelled, + error, + arbitration_lost, + } + + alias Callback = void function(Can* can, ref CanTxOp op) nothrow @nogc; + + Status status; + void* user_data; + Callback cb; + + bool is_pending() const => status == Status.pending; + bool is_done() const => status >= Status.complete; +} + +struct Can +{ + ubyte port = ubyte.max; +} + +bool is_open(ref const Can can) +{ + return can.port != ubyte.max; +} + + +// ════════════════════════════════════════════════════════════════════ +// Error type +// ════════════════════════════════════════════════════════════════════ + +CanError can_result(Result result) +{ + return cast(CanError)result.system_code; +} + + +// ════════════════════════════════════════════════════════════════════ +// Implementation +// ════════════════════════════════════════════════════════════════════ + +// Lifecycle + +void can_init() +{ + if (_init_refcount++ == 0) + { + // TODO: enable clocks/power for CAN peripheral block + } +} + +void can_deinit() +{ + assert(_init_refcount > 0); + if (--_init_refcount == 0) + { + // TODO: disable clocks/power for CAN peripheral block + } +} + +// Port operations + +Result can_open(ref Can can, ubyte port, ref const CanConfig cfg, CanRxCallback rx_cb = null) +{ + static if (num_can == 0) + assert(false, "no CAN on this platform"); + else + { + if (port >= num_can) + return InternalResult.invalid_parameter; + + if (!can_hw_open(port, cfg)) + return InternalResult.failed; + + can.port = port; + return Result.success; + } +} + +Result can_reconfigure(ref Can can, ref const CanConfig cfg) +{ + assert(false, "TODO: can_reconfigure (hot reconfigure)"); +} + +void can_close(ref Can can) +{ + static if (num_can == 0) + assert(false, "no CAN on this platform"); + else + can_hw_close(can.port); + can.port = ubyte.max; +} + +// Transmit + +Result can_transmit(ref Can can, ref const CanFrame frame, CanTxOp* op = null) +{ + static if (num_can == 0) + assert(false, "no CAN on this platform"); + else + { + bool ok = can_hw_transmit(can.port, frame); + if (op !is null) + { + op.status = ok ? CanTxOp.Status.complete : CanTxOp.Status.error; + if (op.cb !is null) + op.cb(&can, *op); + } + if (!ok) + return InternalResult.failed; + return Result.success; + } +} + +void can_tx_abort(ref Can can, CanTxOp* op) +{ + static if (num_can == 0) + assert(false, "no CAN on this platform"); + else + { + can_hw_tx_abort(can.port); + if (op !is null) + { + op.status = CanTxOp.Status.cancelled; + if (op.cb !is null) + op.cb(&can, *op); + } + } +} + +// Receive + +bool can_receive(ref Can can, out CanFrame frame) +{ + static if (num_can == 0) + assert(false, "no CAN on this platform"); + else + return can_hw_receive(can.port, frame); +} + +size_t can_rx_available(ref const Can can) +{ + static if (num_can == 0) + assert(false, "no CAN on this platform"); + else + return can_hw_rx_available(can.port); +} + +void can_rx_flush(ref Can can) +{ + static if (num_can == 0) + assert(false, "no CAN on this platform"); + else + can_hw_rx_flush(can.port); +} + +// Filters + +Result can_filter_set(ref Can can, ubyte slot, ref const CanFilter filter) +{ + assert(false, "TODO: can_filter_set"); +} + +Result can_filter_clear(ref Can can, ubyte slot) +{ + assert(false, "TODO: can_filter_clear"); +} + +void can_filter_clear_all(ref Can can) +{ + assert(false, "TODO: can_filter_clear_all"); +} + +// Bus status + +CanBusState can_bus_state(ref const Can can) +{ + static if (num_can == 0) + assert(false, "no CAN on this platform"); + else + return can_hw_bus_state(can.port); +} + +ubyte can_tx_error_count(ref const Can can) +{ + static if (num_can == 0) + assert(false, "no CAN on this platform"); + else + return can_hw_tx_error_count(can.port); +} + +ubyte can_rx_error_count(ref const Can can) +{ + static if (num_can == 0) + assert(false, "no CAN on this platform"); + else + return can_hw_rx_error_count(can.port); +} + +CanError can_check_errors(ref Can can) +{ + static if (num_can == 0) + assert(false, "no CAN on this platform"); + else + return can_hw_check_errors(can.port); +} + +// Bus recovery + +Result can_bus_recover(ref Can can) +{ + static if (num_can == 0) + assert(false, "no CAN on this platform"); + else + { + if (!can_hw_bus_recover(can.port)) + return InternalResult.failed; + return Result.success; + } +} + +// Callbacks + +void can_set_rx_callback(ref Can can, CanRxCallback cb) +{ + assert(false, "TODO: can_set_rx_callback"); +} + +void can_set_tx_callback(ref Can can, CanTxCallback cb) +{ + assert(false, "TODO: can_set_tx_callback"); +} + +void can_set_bus_state_callback(ref Can can, CanBusStateCallback cb) +{ + assert(false, "TODO: can_set_bus_state_callback"); +} + +// Poll (for polled mode - check HW FIFOs and invoke callbacks) + +void can_poll(ref Can can) +{ + static if (num_can == 0) + assert(false, "no CAN on this platform"); + else + can_hw_poll(can.port); +} + + +// ════════════════════════════════════════════════════════════════════ +// Tests +// ════════════════════════════════════════════════════════════════════ + +unittest +{ + static if (num_can > 0) + { + Can c; + CanConfig cfg; + + can_init(); + + // Out-of-range port + auto r = can_open(c, cast(ubyte)num_can, cfg); + assert(!r); + assert(!c.is_open); + + // Open/close each valid port + foreach (p; 0 .. num_can) + { + Can port; + auto r2 = can_open(port, cast(ubyte)p, cfg); + assert(r2, "can_open failed"); + assert(port.is_open); + assert(port.port == p); + + can_poll(port); + + assert(can_check_errors(port) == CanError.none); + + can_close(port); + assert(!port.is_open); + } + + can_deinit(); + } +} + + +private: + +__gshared ubyte _init_refcount; diff --git a/src/urt/driver/esp32/alloc.d b/src/urt/driver/esp32/alloc.d new file mode 100644 index 0000000..e465e91 --- /dev/null +++ b/src/urt/driver/esp32/alloc.d @@ -0,0 +1,77 @@ +module urt.driver.esp32.alloc; + +import urt.mem.alloc : MemFlags; + +nothrow @nogc: + +enum has_realloc = false; +enum has_expand = false; +enum has_memsize = true; +enum has_exec = true; +enum has_retain = true; +enum has_memflags = true; + +void[] _alloc(size_t size, size_t alignment, MemFlags flags) pure +{ + void* p = heap_caps_aligned_alloc(alignment, size, _esp_caps[flags]); + return p ? p[0 .. size] : null; +} + +void _free(void* ptr) pure +{ + heap_caps_aligned_free(ptr); +} + +size_t _memsize(void* ptr) pure +{ + return heap_caps_get_allocated_size(ptr); +} + +void[] _alloc_exec(size_t size) pure +{ + void* p = heap_caps_aligned_alloc(8, size, CAP_INTERNAL); + return p ? p[0 .. size] : null; +} + +void _free_exec(void[] mem) pure +{ + heap_caps_aligned_free(mem.ptr); +} + +void[] _alloc_retain(size_t size) pure +{ + void* p = heap_caps_aligned_alloc(8, size, CAP_RTCRAM); + return p ? p[0 .. size] : null; +} + +void _free_retain(void[] mem) pure +{ + heap_caps_aligned_free(mem.ptr); +} + + +private: + +enum CAP_DMA = 1 << 2; +enum CAP_INTERNAL = 1 << 11; +enum CAP_DEFAULT = 1 << 12; +enum CAP_SPIRAM = 1 << 17; +enum CAP_RTCRAM = 1 << 10; + +// MemFlags [2:0] → ESP-IDF heap_caps +// [1:0] speed: 0=default, 1=fast, 2=slow, 3=fastest +// [2] dma +immutable uint[8] _esp_caps = [ + CAP_DEFAULT, // 0: default + CAP_DEFAULT | CAP_INTERNAL, // 1: fast + CAP_DEFAULT | CAP_SPIRAM, // 2: slow + CAP_DEFAULT | CAP_INTERNAL, // 3: fastest (no TCM on ESP32) + CAP_DEFAULT | CAP_DMA, // 4: dma + CAP_DEFAULT | CAP_DMA | CAP_INTERNAL, // 5: dma+fast + CAP_DEFAULT | CAP_DMA | CAP_SPIRAM, // 6: dma+slow + CAP_DEFAULT | CAP_DMA | CAP_INTERNAL, // 7: dma+fastest +]; + +extern(C) void* heap_caps_aligned_alloc(size_t alignment, size_t size, uint caps) pure; +extern(C) void heap_caps_aligned_free(void* ptr) pure; +extern(C) size_t heap_caps_get_allocated_size(void* ptr) pure; diff --git a/src/urt/driver/esp32/ble.d b/src/urt/driver/esp32/ble.d new file mode 100644 index 0000000..372f661 --- /dev/null +++ b/src/urt/driver/esp32/ble.d @@ -0,0 +1,980 @@ +// ESP32 BLE driver -- D wrapper over NimBLE via C shim + direct calls +// +// The C shim (ow_shim.c) handles: +// - ow_ble_init/deinit: NimBLE host config struct, port init, host task +// - ow_ble_set_gap_callback: GAP event dispatch trampoline to D +// +// Everything else (scan, connect, GATT) calls NimBLE C API directly. +// +// BLE controller count per chip (ESP-IDF v6.0): +// ESP32, S3, C2, C3, C5, C6, H2: 1 S2, P4: 0 +module urt.driver.esp32.ble; + +import urt.driver.ble; + +import urt.uuid : GUID; + +nothrow @nogc: + + +version (ESP32) enum uint num_ble = 1; +else version (ESP32_S3) enum uint num_ble = 1; +else version (ESP32_C2) enum uint num_ble = 1; +else version (ESP32_C3) enum uint num_ble = 1; +else version (ESP32_C5) enum uint num_ble = 1; +else version (ESP32_C6) enum uint num_ble = 1; +else version (ESP32_H2) enum uint num_ble = 1; +else enum uint num_ble = 0; // S2, P4 + + +static if (num_ble > 0): + + +bool ble_hw_open(uint port, ref const BLEConfig cfg) +{ + if (port >= num_ble) + return false; + if (_opened) + return true; + + if (ow_ble_init() != 0) + return false; + + ow_ble_set_gap_callback(&gap_event_trampoline); + + _opened = true; + return true; +} + +void ble_hw_close(uint port) +{ + if (!_opened) + return; + + ble_hw_scan_stop(port); + ble_hw_adv_stop(port, BLEAdv.init); + + // disconnect all active connections + foreach (ref s; _sessions) + { + if (s.active) + ble_gap_terminate(s.nimble_handle, 0x13); // Remote User Terminated + } + + ow_ble_set_gap_callback(null); + ow_ble_deinit(); + + _opened = false; + _scan_cb = null; + _conn_cb = null; + _discover_cb = null; + _read_cb = null; + _write_cb = null; + _notify_cb = null; + _num_sessions = 0; +} + +// --- Scanning --- + +bool ble_hw_scan_start(uint port, ref const BLEScanConfig cfg) +{ + ble_gap_disc_params params; + params.itvl = cast(ushort)(cfg.interval_ms * 1000 / 625); // BLE units of 0.625ms + params.window = cast(ushort)(cfg.window_ms * 1000 / 625); + params.filter_duplicates = cfg.filter_duplicates ? 1 : 0; + params.passive = cfg.active ? 0 : 1; + + if (ble_gap_disc(0, 0, ¶ms, &gap_event_trampoline, null) != 0) + return false; + return true; +} + +void ble_hw_scan_stop(uint port) +{ + ble_gap_disc_cancel(); +} + +// --- Advertising --- + +BLEAdv ble_hw_adv_start(uint port, ref const BLEAdvConfig cfg) +{ + if (cfg.adv_data.length > 0 && cfg.adv_data.length <= 31) + { + if (ble_gap_adv_set_data(cfg.adv_data.ptr, cast(int)cfg.adv_data.length) != 0) + return BLEAdv.init; + } + + if (cfg.scan_rsp.length > 0 && cfg.scan_rsp.length <= 31) + { + if (ble_gap_adv_rsp_set_data(cfg.scan_rsp.ptr, cast(int)cfg.scan_rsp.length) != 0) + return BLEAdv.init; + } + + ble_gap_adv_params params; + params.conn_mode = cfg.adv_type == BLEAdvType.connectable ? 2 : 0; // BLE_GAP_CONN_MODE_UND : NON + params.disc_mode = 2; // BLE_GAP_DISC_MODE_GEN + params.itvl_min = cast(ushort)(cfg.interval_ms * 1000 / 625); + params.itvl_max = params.itvl_min; + + if (ble_gap_adv_start(0, null, 0, ¶ms, &gap_event_trampoline, null) != 0) + return BLEAdv.init; + return BLEAdv(0); +} + +void ble_hw_adv_stop(uint port, BLEAdv) +{ + ble_gap_adv_stop(); +} + +// --- Connection --- + +bool ble_hw_connect(uint port, ref const ubyte[6] peer_addr, BLEAddrType addr_type, ref const BLEConnConfig cfg) +{ + if (_pending_connect) + return false; + + ble_addr_t addr; + addr.type = cast(ubyte)addr_type; + // NimBLE uses LSB-first byte order + foreach (i; 0 .. 6) + addr.val[i] = peer_addr[5 - i]; + + ble_gap_conn_params params; + params.itvl_min = cast(ushort)(cfg.interval_min_ms * 1000 / 1250); // units of 1.25ms + params.itvl_max = cast(ushort)(cfg.interval_max_ms * 1000 / 1250); + params.latency = cfg.latency; + params.supervision_timeout = cast(ushort)(cfg.timeout_ms / 10); // units of 10ms + params.min_ce_len = 0; + params.max_ce_len = 0; + + _pending_connect = true; + if (ble_gap_connect(0, &addr, 30_000, ¶ms, &gap_event_trampoline, null) != 0) + { + _pending_connect = false; + return false; + } + return true; +} + +void ble_hw_connect_cancel(uint port) +{ + ble_gap_conn_cancel(); + _pending_connect = false; +} + +bool ble_hw_disconnect(uint port, BLEConn conn) +{ + auto s = find_session(conn.id); + if (s is null) + return false; + + if (ble_gap_terminate(s.nimble_handle, 0x13) != 0) + return false; + return true; +} + +// --- GATT discovery --- + +bool ble_hw_gatt_discover(uint port, BLEConn conn) +{ + auto s = find_session(conn.id); + if (s is null) + return false; + + _discovering_conn = conn.id; + _discover_phase = DiscoverPhase.services; + s.num_chars = 0; + + if (ble_gattc_disc_all_svcs(s.nimble_handle, &svc_discover_cb, null) != 0) + return false; + return true; +} + +// --- GATT read/write --- + +bool ble_hw_gatt_read(uint port, BLEConn conn, ushort handle) +{ + auto s = find_session(conn.id); + if (s is null) + return false; + + if (ble_gattc_read(s.nimble_handle, handle, &gatt_read_cb, cast(void*)cast(size_t)conn.id) != 0) + return false; + return true; +} + +bool ble_hw_gatt_write(uint port, BLEConn conn, ushort handle, const(ubyte)[] data, bool with_response) +{ + auto s = find_session(conn.id); + if (s is null) + return false; + + if (with_response) + { + if (ble_gattc_write_flat(s.nimble_handle, handle, data.ptr, + cast(ushort)data.length, &gatt_write_cb, cast(void*)cast(size_t)conn.id) != 0) + return false; + } + else + { + if (ble_gattc_write_no_rsp_flat(s.nimble_handle, handle, + data.ptr, cast(ushort)data.length) != 0) + return false; + } + return true; +} + +// --- Notifications --- + +bool ble_hw_gatt_subscribe(uint port, BLEConn conn, ushort handle, bool enable) +{ + auto s = find_session(conn.id); + if (s is null) + return false; + + // find CCCD handle (handle + 1 by convention for standard GATT) + ushort cccd_handle = cast(ushort)(handle + 1); + + ubyte[2] cccd_value; + if (enable) + cccd_value[0] = 0x01; // enable notifications + + if (ble_gattc_write_flat(s.nimble_handle, cccd_handle, + cccd_value.ptr, 2, null, null) != 0) + return false; + return true; +} + +// --- Queries --- + +bool ble_hw_get_mac(uint port, ref ubyte[6] mac) +{ + ubyte addr_type; + if (ble_hs_id_infer_auto(0, &addr_type) != 0) + return false; + if (ble_hs_id_copy_addr(addr_type, mac.ptr, null) != 0) + return false; + return true; +} + +byte ble_hw_get_rssi(uint port, BLEConn conn) +{ + auto s = find_session(conn.id); + if (s is null) + return -128; + + byte rssi; + if (ble_gap_conn_rssi(s.nimble_handle, &rssi) != 0) + return -128; + return rssi; +} + +// --- Callbacks --- + +void ble_hw_set_scan_callback(uint port, BLEScanCallback cb) { _scan_cb = cb; } +void ble_hw_set_conn_callback(uint port, BLEConnCallback cb) { _conn_cb = cb; } +void ble_hw_set_discover_callback(uint port, BLEDiscoverCallback cb) { _discover_cb = cb; } +void ble_hw_set_read_callback(uint port, BLEReadCallback cb) { _read_cb = cb; } +void ble_hw_set_write_callback(uint port, BLEWriteCallback cb) { _write_cb = cb; } +void ble_hw_set_notify_callback(uint port, BLENotifyCallback cb) { _notify_cb = cb; } + +// --- Poll --- + +void ble_hw_poll(uint port) +{ + // Drain buffered events from NimBLE host task. + // Events are queued by GAP/GATT callbacks which run on the NimBLE task. + + auto ble = BLE(0); + + // scan results + while (_scan_queue.count > 0) + { + auto report = &_scan_queue.buf[_scan_queue.tail]; + if (_scan_cb !is null) + _scan_cb(ble, *report); + _scan_queue.tail = (_scan_queue.tail + 1) % _scan_queue.buf.length; + _scan_queue.count--; + } + + // connection events + if (_evt_connected) + { + _evt_connected = false; + if (_conn_cb !is null) + _conn_cb(ble, BLEConn(_evt_conn_id), true, BLEError.none); + } + if (_evt_connect_failed) + { + _evt_connect_failed = false; + if (_conn_cb !is null) + _conn_cb(ble, BLEConn(_evt_conn_id), false, BLEError.timeout); + } + if (_evt_disconnected) + { + _evt_disconnected = false; + if (_conn_cb !is null) + _conn_cb(ble, BLEConn(_evt_disconn_id), false, BLEError.none); + remove_session(_evt_disconn_id); + } + + // discovery complete + if (_evt_discover_done) + { + _evt_discover_done = false; + if (_discover_cb !is null) + { + auto s = find_session(_evt_discover_conn); + if (s !is null) + { + BLEGattChar[max_chars_per_session] chars = void; + foreach (i; 0 .. s.num_chars) + { + chars[i].handle = s.chars[i].handle; + chars[i].cccd_handle = s.chars[i].cccd_handle; + chars[i].service_uuid = s.chars[i].service_uuid; + chars[i].char_uuid = s.chars[i].char_uuid; + chars[i].properties = cast(GattCharProps)s.chars[i].properties; + } + _discover_cb(ble, BLEConn(_evt_discover_conn), chars[0 .. s.num_chars], BLEError.none); + } + } + } + if (_evt_discover_failed) + { + _evt_discover_failed = false; + if (_discover_cb !is null) + _discover_cb(ble, BLEConn(_evt_discover_conn), null, BLEError.protocol); + } + + // GATT read/write completions + while (_gatt_queue.count > 0) + { + auto evt = &_gatt_queue.buf[_gatt_queue.tail]; + if (evt.is_read && _read_cb !is null) + _read_cb(ble, BLEConn(evt.conn_id), evt.handle, evt.data[0 .. evt.data_len], evt.error); + else if (!evt.is_read && _write_cb !is null) + _write_cb(ble, BLEConn(evt.conn_id), evt.handle, evt.error); + _gatt_queue.tail = (_gatt_queue.tail + 1) % _gatt_queue.buf.length; + _gatt_queue.count--; + } + + // notifications + while (_notify_queue.count > 0) + { + auto evt = &_notify_queue.buf[_notify_queue.tail]; + if (_notify_cb !is null) + _notify_cb(ble, BLEConn(evt.conn_id), evt.handle, evt.data[0 .. evt.data_len]); + _notify_queue.tail = (_notify_queue.tail + 1) % _notify_queue.buf.length; + _notify_queue.count--; + } +} + + +private: + +enum int ESP_OK = 0; +enum max_sessions = 4; +enum max_chars_per_session = 32; + +// --- Session table --- + +struct SessionCharInfo +{ + ushort handle; + ushort cccd_handle; + GUID service_uuid; + GUID char_uuid; + ushort properties; +} + +struct Session +{ + bool active; + ubyte id; // our BLEConn.id + ushort nimble_handle; // NimBLE connection handle + SessionCharInfo[max_chars_per_session] chars; + ubyte num_chars; +} + +Session* find_session(ubyte id) +{ + foreach (ref s; _sessions[0 .. _num_sessions]) + { + if (s.active && s.id == id) + return &s; + } + return null; +} + +Session* find_session_by_nimble(ushort nimble_handle) +{ + foreach (ref s; _sessions[0 .. _num_sessions]) + { + if (s.active && s.nimble_handle == nimble_handle) + return &s; + } + return null; +} + +Session* alloc_session(ushort nimble_handle) +{ + if (_num_sessions >= max_sessions) + return null; + auto s = &_sessions[_num_sessions++]; + s.active = true; + s.id = _next_conn_id++; + s.nimble_handle = nimble_handle; + s.num_chars = 0; + return s; +} + +void remove_session(ubyte id) +{ + foreach (i, ref s; _sessions[0 .. _num_sessions]) + { + if (s.id == id) + { + _sessions[i] = _sessions[_num_sessions - 1]; + _num_sessions--; + return; + } + } +} + +// --- Event ring buffers --- + +struct RingBuffer(T, uint N) +{ + T[N] buf; + uint head; + uint tail; + uint count; + + T* push() nothrow @nogc + { + if (count >= N) + return null; // drop oldest would be: tail = (tail + 1) % N; count--; + auto p = &buf[head]; + head = (head + 1) % N; + count++; + return p; + } +} + +struct GattCompletionEvent +{ + ubyte conn_id; + ushort handle; + bool is_read; + BLEError error; + ubyte data_len; + ubyte[247] data; +} + +struct NotifyEvent +{ + ubyte conn_id; + ushort handle; + ubyte data_len; + ubyte[247] data; +} + +// --- Module state --- + +__gshared bool _opened; +__gshared bool _pending_connect; +__gshared ubyte _next_conn_id; + +__gshared Session[max_sessions] _sessions; +__gshared ubyte _num_sessions; + +__gshared BLEScanCallback _scan_cb; +__gshared BLEConnCallback _conn_cb; +__gshared BLEDiscoverCallback _discover_cb; +__gshared BLEReadCallback _read_cb; +__gshared BLEWriteCallback _write_cb; +__gshared BLENotifyCallback _notify_cb; + +// scan result ring buffer (set from NimBLE task, drained from main loop) +__gshared RingBuffer!(BLEAdvReport, 16) _scan_queue; + +// GATT completion ring buffer +__gshared RingBuffer!(GattCompletionEvent, 16) _gatt_queue; + +// notification ring buffer +__gshared RingBuffer!(NotifyEvent, 16) _notify_queue; + +// connection event flags (set from NimBLE task) +__gshared bool _evt_connected; +__gshared bool _evt_connect_failed; +__gshared bool _evt_disconnected; +__gshared ubyte _evt_conn_id; +__gshared ubyte _evt_disconn_id; + +// discovery state +enum DiscoverPhase : ubyte { idle, services, chars } +__gshared DiscoverPhase _discover_phase; +__gshared ubyte _discovering_conn; +__gshared bool _evt_discover_done; +__gshared bool _evt_discover_failed; +__gshared ubyte _evt_discover_conn; + +// service discovery iteration state (used from NimBLE task callbacks) +__gshared ble_gatt_svc[16] _discovered_svcs; +__gshared ubyte _num_discovered_svcs; +__gshared ubyte _current_svc_idx; +__gshared GUID _current_svc_uuid; + + +// --- GAP event trampoline (called from NimBLE host task) --- + +extern(C) int gap_event_trampoline(ble_gap_event* event, void*) nothrow @nogc +{ + if (event is null) + return 0; + + switch (event.type) + { + case BLE_GAP_EVENT_DISC: + // scan result + auto report = _scan_queue.push(); + if (report !is null) + { + auto disc = &event.disc; + // NimBLE addr is LSB-first, we want MSB-first + foreach (i; 0 .. 6) + report.addr[i] = disc.addr.val[5 - i]; + report.addr_type = cast(BLEAddrType)disc.addr.type; + report.rssi = disc.rssi; + report.tx_power = -128; // not in base event + + ubyte len = disc.length_data > 62 ? 62 : disc.length_data; + report.data_len = len; + if (len > 0) + report.data_buf[0 .. len] = disc.data[0 .. len]; + + report.adv_type = disc.event_type == 0 ? BLEAdvType.connectable : BLEAdvType.nonconnectable; + } + return 0; + + case BLE_GAP_EVENT_CONNECT: + _pending_connect = false; + if (event.connect.status == 0) + { + auto s = alloc_session(event.connect.conn_handle); + if (s !is null) + { + _evt_conn_id = s.id; + _evt_connected = true; + } + } + else + { + _evt_conn_id = ubyte.max; + _evt_connect_failed = true; + } + return 0; + + case BLE_GAP_EVENT_DISCONNECT: + auto s = find_session_by_nimble(event.disconnect.conn.conn_handle); + if (s !is null) + { + _evt_disconn_id = s.id; + _evt_disconnected = true; + s.active = false; + } + return 0; + + case BLE_GAP_EVENT_NOTIFY_RX: + auto s = find_session_by_nimble(event.notify_rx.conn_handle); + if (s !is null) + { + auto evt = _notify_queue.push(); + if (evt !is null) + { + evt.conn_id = s.id; + evt.handle = event.notify_rx.attr_handle; + auto om = event.notify_rx.om; + // copy mbuf chain to flat buffer + evt.data_len = 0; + while (om !is null && evt.data_len < evt.data.length) + { + ushort copy = om.om_len; + if (evt.data_len + copy > evt.data.length) + copy = cast(ushort)(evt.data.length - evt.data_len); + evt.data[evt.data_len .. evt.data_len + copy] = om.om_data[0 .. copy]; + evt.data_len += copy; + om = om.om_next; + } + } + } + return 0; + + default: + return 0; + } +} + +// --- GATT service discovery callback (NimBLE task) --- + +extern(C) int svc_discover_cb(ushort conn_handle, const(ble_gatt_error)* error, + const(ble_gatt_svc)* service, void*) nothrow @nogc +{ + if (error !is null && error.status == 0 && service !is null) + { + // accumulate services + if (_num_discovered_svcs < _discovered_svcs.length) + _discovered_svcs[_num_discovered_svcs++] = *service; + return 0; + } + + // discovery complete (error.status != 0 means end of list or actual error) + if (_num_discovered_svcs == 0) + { + _evt_discover_conn = _discovering_conn; + _evt_discover_done = true; + _discover_phase = DiscoverPhase.idle; + return 0; + } + + // start characteristic discovery for first service + _current_svc_idx = 0; + return discover_next_svc_chars(conn_handle); +} + +int discover_next_svc_chars(ushort conn_handle) nothrow @nogc +{ + while (_current_svc_idx < _num_discovered_svcs) + { + auto svc = &_discovered_svcs[_current_svc_idx]; + _current_svc_uuid = nimble_uuid_to_guid(&svc.uuid); + _discover_phase = DiscoverPhase.chars; + + if (ble_gattc_disc_all_chrs(conn_handle, svc.start_handle, svc.end_handle, + &chr_discover_cb, null) == 0) + return 0; + + _current_svc_idx++; + } + + // all services done + _evt_discover_conn = _discovering_conn; + _evt_discover_done = true; + _discover_phase = DiscoverPhase.idle; + _num_discovered_svcs = 0; + return 0; +} + +// --- GATT characteristic discovery callback (NimBLE task) --- + +extern(C) int chr_discover_cb(ushort conn_handle, const(ble_gatt_error)* error, + const(ble_gatt_chr)* chr, void*) nothrow @nogc +{ + if (error !is null && error.status == 0 && chr !is null) + { + auto s = find_session_by_nimble(conn_handle); + if (s !is null && s.num_chars < max_chars_per_session) + { + auto ci = &s.chars[s.num_chars++]; + ci.handle = chr.val_handle; + ci.cccd_handle = 0; // TODO: discover descriptors for CCCD + ci.service_uuid = _current_svc_uuid; + ci.char_uuid = nimble_uuid_to_guid(&chr.uuid); + ci.properties = chr.properties; + } + return 0; + } + + // this service's chars done, move to next + _current_svc_idx++; + return discover_next_svc_chars(conn_handle); +} + +// --- GATT read callback (NimBLE task) --- + +extern(C) int gatt_read_cb(ushort conn_handle, const(ble_gatt_error)* error, + ble_gatt_attr* attr, void* cb_arg) nothrow @nogc +{ + ubyte conn_id = cast(ubyte)cast(size_t)cb_arg; + auto evt = _gatt_queue.push(); + if (evt is null) + return 0; + + evt.conn_id = conn_id; + evt.is_read = true; + + if (error !is null && error.status == 0 && attr !is null) + { + evt.handle = attr.handle; + evt.error = BLEError.none; + // copy mbuf to flat buffer + evt.data_len = 0; + auto om = attr.om; + while (om !is null && evt.data_len < evt.data.length) + { + ushort copy = om.om_len; + if (evt.data_len + copy > evt.data.length) + copy = cast(ushort)(evt.data.length - evt.data_len); + evt.data[evt.data_len .. evt.data_len + copy] = om.om_data[0 .. copy]; + evt.data_len += copy; + om = om.om_next; + } + } + else + { + evt.handle = attr !is null ? attr.handle : 0; + evt.error = BLEError.protocol; + evt.data_len = 0; + } + return 0; +} + +// --- GATT write callback (NimBLE task) --- + +extern(C) int gatt_write_cb(ushort conn_handle, const(ble_gatt_error)* error, + ble_gatt_attr* attr, void* cb_arg) nothrow @nogc +{ + ubyte conn_id = cast(ubyte)cast(size_t)cb_arg; + auto evt = _gatt_queue.push(); + if (evt is null) + return 0; + + evt.conn_id = conn_id; + evt.handle = attr !is null ? attr.handle : 0; + evt.is_read = false; + evt.data_len = 0; + evt.error = (error !is null && error.status == 0) ? BLEError.none : BLEError.protocol; + return 0; +} + +// --- UUID conversion --- + +GUID nimble_uuid_to_guid(const(ble_uuid_any)* uuid) nothrow @nogc +{ + GUID g; + if (uuid.u.type == 16) // BLE_UUID_TYPE_16 + { + // BT SIG base: 0000xxxx-0000-1000-8000-00805F9B34FB + g.data1 = uuid.u16.value; + g.data3 = 0x1000; + g.data4 = [0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB]; + } + else if (uuid.u.type == 32) // BLE_UUID_TYPE_32 + { + g.data1 = uuid.u32.value; + g.data3 = 0x1000; + g.data4 = [0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB]; + } + else if (uuid.u.type == 128) // BLE_UUID_TYPE_128 + { + // NimBLE stores 128-bit UUIDs in little-endian byte order + auto v = uuid.u128.value; + g.data1 = v[12] | (cast(uint)v[13] << 8) | (cast(uint)v[14] << 16) | (cast(uint)v[15] << 24); + g.data2 = cast(ushort)(v[10] | (cast(ushort)v[11] << 8)); + g.data3 = cast(ushort)(v[8] | (cast(ushort)v[9] << 8)); + g.data4[0 .. 2] = v[6 .. 8]; // big-endian in GUID + g.data4[2 .. 8] = v[0 .. 6]; + } + return g; +} + + +// ════════════════════════════════════════════════════════════════════ +// NimBLE C API declarations +// ════════════════════════════════════════════════════════════════════ + +struct ble_addr_t +{ + ubyte type; + ubyte[6] val; +} + +struct ble_gap_disc_params +{ + ushort itvl; + ushort window; + ubyte filter_policy; + ubyte limited; + ubyte passive; + ubyte filter_duplicates; +} + +struct ble_gap_conn_params +{ + ushort scan_itvl; + ushort scan_window; + ushort itvl_min; + ushort itvl_max; + ushort latency; + ushort supervision_timeout; + ushort min_ce_len; + ushort max_ce_len; +} + +struct ble_gap_adv_params +{ + ubyte conn_mode; + ubyte disc_mode; + ushort itvl_min; + ushort itvl_max; + ubyte channel_map; + ubyte filter_policy; + ubyte high_duty_cycle; +} + +// NimBLE GAP event structure (simplified) +struct ble_gap_event +{ + ubyte type; + ubyte[3] _pad; + + struct ConnectData { int status; ushort conn_handle; } + struct DisconnectData { int reason; ble_gap_conn_desc conn; } + struct DiscData { ubyte event_type; ubyte length_data; const(ubyte)* data; byte rssi; + ble_addr_t addr; } + struct NotifyRxData { ushort conn_handle; ushort attr_handle; ubyte indication; + os_mbuf* om; } + + union + { + ConnectData connect; + DisconnectData disconnect; + DiscData disc; + NotifyRxData notify_rx; + } +} + +struct ble_gap_conn_desc +{ + ble_addr_t our_id_addr; + ble_addr_t peer_id_addr; + ble_addr_t our_ota_addr; + ble_addr_t peer_ota_addr; + ushort conn_handle; + ushort conn_itvl; + ushort conn_latency; + ushort supervision_timeout; + ubyte role; + ubyte encrypted; + ubyte authenticated; + ubyte bonded; +} + +struct ble_gatt_error +{ + ushort status; + ushort att_handle; +} + +struct ble_uuid +{ + ubyte type; // 16, 32, or 128 +} + +struct ble_uuid16 +{ + ble_uuid u; + ushort value; +} + +struct ble_uuid32 +{ + ble_uuid u; + uint value; +} + +struct ble_uuid128 +{ + ble_uuid u; + ubyte[16] value; +} + +union ble_uuid_any +{ + ble_uuid u; + ble_uuid16 u16; + ble_uuid32 u32; + ble_uuid128 u128; +} + +struct ble_gatt_svc +{ + ushort start_handle; + ushort end_handle; + ble_uuid_any uuid; +} + +struct ble_gatt_chr +{ + ushort def_handle; + ushort val_handle; + ushort properties; + ble_uuid_any uuid; +} + +struct ble_gatt_attr +{ + ushort handle; + ushort offset; + os_mbuf* om; +} + +// NimBLE mbuf +struct os_mbuf +{ + os_mbuf* om_next; + ubyte* om_data; + ushort om_len; + ushort om_flags; + // ... more fields follow but we only need these +} + +// GAP event types +enum : ubyte +{ + BLE_GAP_EVENT_CONNECT = 0, + BLE_GAP_EVENT_DISCONNECT = 1, + BLE_GAP_EVENT_DISC = 7, + BLE_GAP_EVENT_NOTIFY_RX = 12, +} + +// C shim functions +extern(C) nothrow @nogc +{ + int ow_ble_init(); + void ow_ble_deinit(); + void ow_ble_set_gap_callback(int function(ble_gap_event*, void*) nothrow @nogc cb); +} + +// Direct NimBLE calls +extern(C) nothrow @nogc +{ + int ble_gap_disc(ubyte own_addr_type, int duration_ms, const(ble_gap_disc_params)* params, + int function(ble_gap_event*, void*) cb, void* cb_arg); + int ble_gap_disc_cancel(); + int ble_gap_connect(ubyte own_addr_type, const(ble_addr_t)* peer_addr, int duration_ms, + const(ble_gap_conn_params)* params, int function(ble_gap_event*, void*) cb, void* cb_arg); + int ble_gap_conn_cancel(); + int ble_gap_terminate(ushort conn_handle, ubyte hci_reason); + int ble_gap_conn_rssi(ushort conn_handle, byte* rssi); + + int ble_gap_adv_set_data(const(ubyte)* data, int data_len); + int ble_gap_adv_rsp_set_data(const(ubyte)* data, int data_len); + int ble_gap_adv_start(ubyte own_addr_type, const(ble_addr_t)* direct_addr, int duration_ms, + const(ble_gap_adv_params)* params, int function(ble_gap_event*, void*) cb, void* cb_arg); + int ble_gap_adv_stop(); + + int ble_gattc_disc_all_svcs(ushort conn_handle, + int function(ushort, const(ble_gatt_error)*, const(ble_gatt_svc)*, void*) cb, void* cb_arg); + int ble_gattc_disc_all_chrs(ushort conn_handle, ushort start_handle, ushort end_handle, + int function(ushort, const(ble_gatt_error)*, const(ble_gatt_chr)*, void*) cb, void* cb_arg); + int ble_gattc_read(ushort conn_handle, ushort attr_handle, + int function(ushort, const(ble_gatt_error)*, ble_gatt_attr*, void*) cb, void* cb_arg); + int ble_gattc_write_flat(ushort conn_handle, ushort attr_handle, const(void)* data, ushort data_len, + int function(ushort, const(ble_gatt_error)*, ble_gatt_attr*, void*) cb, void* cb_arg); + int ble_gattc_write_no_rsp_flat(ushort conn_handle, ushort attr_handle, const(void)* data, ushort data_len); + + int ble_hs_id_infer_auto(int privacy, ubyte* out_addr_type); + int ble_hs_id_copy_addr(ubyte addr_type, ubyte* out_addr, int* out_is_nrpa); +} diff --git a/src/urt/driver/esp32/can.d b/src/urt/driver/esp32/can.d new file mode 100644 index 0000000..088ecda --- /dev/null +++ b/src/urt/driver/esp32/can.d @@ -0,0 +1,222 @@ +// ESP32 CAN (TWAI) driver -- thin D layer over ESP-IDF _v2 handle API +// +// Only ow_can_open is a C shim (bitrate table + config construction). +// All other calls go directly to the ESP-IDF twai_*_v2 functions. +// +// Controller count per chip (ESP-IDF v6.0): +// ESP32, S3, C3, H2: 1 C5, C6: 2 P4: 3 S2, C2, C61: 0 +module urt.driver.esp32.can; + +import urt.driver.can : CanConfig, CanFrame, CanError, CanBusState; + +nothrow @nogc: + + +// SOC_TWAI_CONTROLLER_NUM per chip variant (ESP-IDF v6.0 soc_caps.h) +version (ESP32) enum uint num_can = 1; +else version (ESP32_S3) enum uint num_can = 1; +else version (ESP32_P4) enum uint num_can = 3; +else version (ESP32_C3) enum uint num_can = 1; +else version (ESP32_C5) enum uint num_can = 2; +else version (ESP32_C6) enum uint num_can = 2; +else version (ESP32_H2) enum uint num_can = 1; +else enum uint num_can = 0; // S2, C2, C61 + +// SOC_TWAI_FD_SUPPORTED (ESP-IDF v6.0 soc_caps.h) +version (ESP32_C5) enum bool has_can_fd = true; +else enum bool has_can_fd = false; + + +static if (num_can > 0): + + +bool can_hw_open(uint port, ref const CanConfig cfg) +{ + if (port >= num_can) + return false; + if (_handles[port] !is null) + return true; + int tx = cfg.tx_gpio == ubyte.max ? -1 : cast(int)cfg.tx_gpio; + int rx = cfg.rx_gpio == ubyte.max ? -1 : cast(int)cfg.rx_gpio; + _handles[port] = ow_can_open(port, cfg.bitrate, tx, rx, + cfg.sjw, cfg.tseg1, cfg.tseg2, cfg.brp); + return _handles[port] !is null; +} + +void can_hw_close(uint port) +{ + if (port >= num_can || _handles[port] is null) + return; + twai_stop_v2(_handles[port]); + twai_driver_uninstall_v2(_handles[port]); + _handles[port] = null; +} + +bool can_hw_transmit(uint port, ref const CanFrame frame) +{ + if (port >= num_can || _handles[port] is null) + return false; + twai_message_t msg; + msg.flags = cast(uint)frame.extended | (cast(uint)frame.rtr << 1); + msg.identifier = frame.id; + msg.data_length_code = frame.dlc; + if (frame.dlc > 0) + msg.data[0 .. frame.dlc] = frame.data[0 .. frame.dlc]; + return twai_transmit_v2(_handles[port], &msg, 0) == ESP_OK; +} + +bool can_hw_receive(uint port, out CanFrame frame) +{ + if (port >= num_can || _handles[port] is null) + return false; + twai_message_t msg; + if (twai_receive_v2(_handles[port], &msg, 0) != ESP_OK) + return false; + frame.id = msg.identifier; + frame.extended = (msg.flags & 1) != 0; + frame.rtr = (msg.flags & 2) != 0; + frame.dlc = msg.data_length_code; + if (msg.data_length_code > 0) + frame.data[0 .. msg.data_length_code] = msg.data[0 .. msg.data_length_code]; + return true; +} + +CanError can_hw_check_errors(uint port) +{ + if (port >= num_can || _handles[port] is null) + return CanError.none; + uint alerts; + twai_read_alerts_v2(_handles[port], &alerts, 0); + CanError err = CanError.none; + if (alerts & 0x0200) // TWAI_ALERT_BUS_ERROR + err |= CanError.bit; + if (alerts & 0x4800) // TWAI_ALERT_RX_FIFO_OVERRUN | RX_QUEUE_FULL + err |= CanError.overrun; + if (alerts & 0x0400) // TWAI_ALERT_TX_FAILED + err |= CanError.ack; + return err; +} + +CanBusState can_hw_bus_state(uint port) +{ + if (port >= num_can || _handles[port] is null) + return CanBusState.bus_off; + twai_status_info_t info; + if (twai_get_status_info_v2(_handles[port], &info) != ESP_OK) + return CanBusState.bus_off; + if (info.state >= 2) // BUS_OFF or RECOVERING + return CanBusState.bus_off; + if (info.tx_error_counter >= 128 || info.rx_error_counter >= 128) + return CanBusState.error_passive; + if (info.tx_error_counter >= 96 || info.rx_error_counter >= 96) + return CanBusState.error_warning; + return CanBusState.error_active; +} + +ubyte can_hw_tx_error_count(uint port) +{ + if (port >= num_can || _handles[port] is null) + return 0; + twai_status_info_t info; + if (twai_get_status_info_v2(_handles[port], &info) != ESP_OK) + return 0; + return info.tx_error_counter > 255 ? 255 : cast(ubyte)info.tx_error_counter; +} + +ubyte can_hw_rx_error_count(uint port) +{ + if (port >= num_can || _handles[port] is null) + return 0; + twai_status_info_t info; + if (twai_get_status_info_v2(_handles[port], &info) != ESP_OK) + return 0; + return info.rx_error_counter > 255 ? 255 : cast(ubyte)info.rx_error_counter; +} + +size_t can_hw_rx_available(uint port) +{ + if (port >= num_can || _handles[port] is null) + return 0; + twai_status_info_t info; + if (twai_get_status_info_v2(_handles[port], &info) != ESP_OK) + return 0; + return cast(size_t)info.msgs_to_rx; +} + +void can_hw_rx_flush(uint port) +{ + if (port >= num_can || _handles[port] is null) + return; + twai_clear_receive_queue_v2(_handles[port]); +} + +void can_hw_tx_abort(uint port) +{ + if (port >= num_can || _handles[port] is null) + return; + twai_clear_transmit_queue_v2(_handles[port]); +} + +bool can_hw_bus_recover(uint port) +{ + if (port >= num_can || _handles[port] is null) + return false; + return twai_initiate_recovery_v2(_handles[port]) == ESP_OK; +} + +void can_hw_poll(uint port) +{ + // TWAI driver buffers RX internally +} + + +private: + +enum int ESP_OK = 0; + +// Opaque ESP-IDF TWAI handle +struct twai_obj_t {} +alias twai_handle_t = twai_obj_t*; + +// ESP-IDF twai_message_t (flags union flattened to uint) +struct twai_message_t +{ + uint flags; // bit 0: extd, bit 1: rtr, bit 2: ss, bit 3: self + uint identifier; + ubyte data_length_code; + ubyte[8] data; +} + +// ESP-IDF twai_status_info_t +struct twai_status_info_t +{ + int state; // 0=STOPPED, 1=RUNNING, 2=BUS_OFF, 3=RECOVERING + uint msgs_to_tx; + uint msgs_to_rx; + uint tx_error_counter; + uint rx_error_counter; + uint tx_failed_count; + uint rx_missed_count; + uint rx_overrun_count; + uint arb_lost_count; + uint bus_error_count; +} + +__gshared twai_handle_t[num_can] _handles; + +extern(C) nothrow @nogc +{ + // Shim -- bitrate table + config construction + install + start + twai_handle_t ow_can_open(uint port, uint bitrate, int tx_gpio, int rx_gpio, ubyte sjw, ubyte tseg1, ubyte tseg2, ushort brp); + + // Direct ESP-IDF v2 calls + int twai_stop_v2(twai_handle_t handle); + int twai_driver_uninstall_v2(twai_handle_t handle); + int twai_transmit_v2(twai_handle_t handle, const(twai_message_t)* message, uint ticks_to_wait); + int twai_receive_v2(twai_handle_t handle, twai_message_t* message, uint ticks_to_wait); + int twai_read_alerts_v2(twai_handle_t handle, uint* alerts, uint ticks_to_wait); + int twai_get_status_info_v2(twai_handle_t handle, twai_status_info_t* status_info); + int twai_initiate_recovery_v2(twai_handle_t handle); + int twai_clear_receive_queue_v2(twai_handle_t handle); + int twai_clear_transmit_queue_v2(twai_handle_t handle); +} diff --git a/src/urt/driver/esp32/exception.d b/src/urt/driver/esp32/exception.d new file mode 100644 index 0000000..b639efc --- /dev/null +++ b/src/urt/driver/esp32/exception.d @@ -0,0 +1,85 @@ +/// Espressif (ESP-IDF + FreeRTOS) exception driver. +/// +/// Stack-trace capture uses libgcc's `_Unwind_Backtrace` (pulled in +/// by the ESP-IDF toolchain). No on-device symbol resolution - +/// decode addresses offline with `xtensa-esp32-elf-addr2line` / +/// `riscv32-esp-elf-addr2line`. +module urt.driver.esp32.exception; + +version (Espressif): + +import urt.internal.exception : Resolved; + +nothrow @nogc: + + +// --- libgcc unwind bindings ------------------------------------------ + +private alias _Unwind_Trace_Fn = extern(C) int function(void* ctx, void* data) nothrow @nogc; +extern(C) private int _Unwind_Backtrace(_Unwind_Trace_Fn, void*) nothrow @nogc; +extern(C) private size_t _Unwind_GetIP(void*) nothrow @nogc; + + +// --- Driver interface ------------------------------------------------ + +// Capture the caller's call stack. First entry = return address of +// the function that called the public `capture_trace` wrapper. +size_t _capture_trace(void*[] addrs) @trusted +{ + if (addrs.length == 0) + return 0; + + struct State + { + void*[] out_addrs; + size_t count; + ubyte skip; + } + + extern(C) static int callback(void* ctx, void* data) nothrow @nogc + { + auto s = cast(State*) data; + if (s.skip > 0) { --s.skip; return 0; } + if (s.count >= s.out_addrs.length) return 1; + auto ip = _Unwind_GetIP(ctx); + if (!ip) return 1; + s.out_addrs[s.count++] = cast(void*) ip; + return 0; + } + + // Skip 2 frames: _Unwind_Backtrace's direct caller (_capture_trace + // itself) + the public wrapper in urt.internal.exception. + State state = State(addrs, 0, 2); + _Unwind_Backtrace(&callback, &state); + return state.count; +} + +// Return the return address of the `skip`-th frame above the public +// `caller_address` wrapper's caller. +void* _caller_address(uint skip) @trusted +{ + void*[32] buf = void; + const n = _capture_trace(buf[]); + // When _capture_trace is reached via _caller_address, _Unwind already + // skipped 2 (itself + wrapper) - but those skips were sized for the + // direct-call path. From _caller_address the buffer layout is: + // buf[0] = PC inside _caller_address (an extra intermediate) + // buf[1] = PC inside USER + // buf[2] = PC inside USER's caller ← skip=0 wants this + const want = skip + 2; + if (n <= want) + return null; + return buf[want]; +} + +// No on-device symbol table. Decode offline with addr2line. +bool _resolve_address(void* addr, out Resolved r) @trusted +{ + return false; +} + +// No on-device symbols - caller should treat `results[]` as empty. +bool _resolve_batch(const(void*)[], Resolved[]) @trusted +{ + return false; +} diff --git a/src/urt/driver/esp32/gpio.d b/src/urt/driver/esp32/gpio.d new file mode 100644 index 0000000..2869195 --- /dev/null +++ b/src/urt/driver/esp32/gpio.d @@ -0,0 +1,75 @@ +// ESP32 software GPIO via ESP-IDF (driver/gpio.h) through ow_shim.c. +// +// Peripheral function routing on ESP32 uses the GPIO matrix per-signal, +// not per-pin function selection, so has_pin_function_muxing = false +// and gpio_set_function is not provided. Peripheral drivers (UART, SPI, +// I2C, etc.) route their own signals via the IDF. +// +// Pin numbering: linear 0..SOC_GPIO_PIN_COUNT-1. The compile-time +// num_gpio is a conservative upper bound; gpio_count() returns the +// actual SOC_GPIO_PIN_COUNT for the active chip variant. +module urt.driver.esp32.gpio; + +import urt.driver.gpio : Pull, DriveMode; + +nothrow @nogc: + + +enum uint num_gpio = 64; +enum bool has_pull_up = true; +enum bool has_pull_down = true; +enum bool has_open_drain = false; +enum bool has_pin_function_muxing = false; + + +uint gpio_count() => ow_gpio_count(); + +void gpio_output_init(uint pin, bool initial = false, DriveMode mode = DriveMode.push_pull) +{ + assert(mode == DriveMode.push_pull, "esp32 gpio: open-drain not exposed via this API"); + ow_gpio_output_init(int(pin), initial ? 1 : 0); +} + +void gpio_input_init(uint pin, Pull pull = Pull.none) +{ + ow_gpio_input_init(int(pin), int(pull)); +} + +void gpio_output_set(uint pin, bool value) +{ + ow_gpio_output_set(int(pin), value ? 1 : 0); +} + +void gpio_output_toggle(uint pin) +{ + ow_gpio_output_set(int(pin), ow_gpio_input_read(int(pin)) ? 0 : 1); +} + +bool gpio_input_read(uint pin) +{ + return ow_gpio_input_read(int(pin)) != 0; +} + +void gpio_set_pull(uint pin, Pull pull) +{ + ow_gpio_set_pull(int(pin), int(pull)); +} + +void gpio_release(uint pin) +{ + ow_gpio_release(int(pin)); +} + + +private: + +extern(C) nothrow @nogc +{ + void ow_gpio_output_init(int pin, int initial); + void ow_gpio_input_init(int pin, int pull); + void ow_gpio_output_set(int pin, int value); + int ow_gpio_input_read(int pin); + void ow_gpio_set_pull(int pin, int pull); + void ow_gpio_release(int pin); + uint ow_gpio_count(); +} diff --git a/src/urt/driver/esp32/irq.d b/src/urt/driver/esp32/irq.d new file mode 100644 index 0000000..36438cd --- /dev/null +++ b/src/urt/driver/esp32/irq.d @@ -0,0 +1,59 @@ +// ESP32 interrupt controller driver +// +// ESP-IDF manages interrupts via esp_intr_alloc(). Direct vector table +// access is discouraged since FreeRTOS owns it. Global disable/enable +// uses FreeRTOS critical section primitives. +module urt.driver.esp32.irq; + +nothrow @nogc: + + +enum bool has_plic = false; +enum bool has_nvic = false; +enum bool has_per_irq_control = false; // TODO: wire up esp_intr_alloc +enum bool has_irq_priority = false; // TODO: wire up esp_intr_alloc priority flags +enum bool has_wait_for_interrupt = true; +enum bool has_irq_diagnostics = false; +enum uint irq_max = 32; + +void irq_disable() +{ + _saved_state = ow_irq_disable(); +} + +void irq_enable() +{ + ow_irq_enable(_saved_state); +} + +void irq_set_enable(uint irq) +{ + assert(false, "TODO: use esp_intr_alloc"); +} + +void irq_clear_enable(uint irq) +{ + assert(false, "TODO: use esp_intr_free"); +} + +void irq_set_priority(uint irq, ubyte priority) +{ + assert(false, "TODO: use esp_intr_set_in_iram / priority flags"); +} + +void wait_for_interrupt() +{ + ow_irq_wait(); +} + + +private: + +__gshared uint _saved_state; + +extern(C) nothrow @nogc +{ + uint ow_irq_disable(); + void ow_irq_enable(uint prev); + void ow_irq_wait(); +} diff --git a/src/urt/driver/esp32/main.c b/src/urt/driver/esp32/main.c new file mode 100644 index 0000000..06b8b74 --- /dev/null +++ b/src/urt/driver/esp32/main.c @@ -0,0 +1,61 @@ +// OpenWatt ESP-IDF entry point. +// ESP-IDF calls app_main() after FreeRTOS scheduler starts. +// .init_array (D module constructors) are called automatically by +// ESP-IDF startup before app_main, so we just call D's main(). + +#include "esp_netif.h" +#include "esp_event.h" +#include "nvs_flash.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +extern int ets_printf(const char *fmt, ...); +extern int main(int argc, char **argv); + +// Software watchdog: reboots if main task stops feeding. +// Call ow_watchdog_feed() from the main loop each frame. +static volatile uint32_t watchdog_counter; + +void ow_watchdog_feed(void) +{ + watchdog_counter++; +} + +static void watchdog_task(void *arg) +{ + const int timeout_ms = 5000; + for (;;) + { + uint32_t before = watchdog_counter; + vTaskDelay(pdMS_TO_TICKS(timeout_ms)); + if (watchdog_counter == before) + { + ets_printf("WATCHDOG: main loop stalled for %ds, aborting\n", + timeout_ms / 1000); + abort(); + } + } +} + +void app_main(void) +{ + // Initialize NVS -- required by WiFi for calibration data storage. + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) + { + nvs_flash_erase(); + nvs_flash_init(); + } + + // Initialize lwIP and event loop early -- D code may open sockets + // during startup before any WiFi/Ethernet interface is created. + esp_netif_init(); + esp_event_loop_create_default(); + + xTaskCreate(watchdog_task, "ow_wdt", 2048, NULL, 1, NULL); + + // Delay so early boot logs are visible on USB Serial/JTAG + vTaskDelay(pdMS_TO_TICKS(3000)); + + main(0, (char **)0); +} diff --git a/src/urt/driver/esp32/ow_shim.c b/src/urt/driver/esp32/ow_shim.c new file mode 100644 index 0000000..fe901fb --- /dev/null +++ b/src/urt/driver/esp32/ow_shim.c @@ -0,0 +1,624 @@ +// OpenWatt ESP-IDF shim -- the single C bridge between D and ESP-IDF. +// Wraps FreeRTOS macros, UART HAL inlines, and anything else that +// needs C headers or static-inline access. + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "hal/uart_hal.h" +#include "hal/uart_types.h" +#include "hal/uart_periph.h" +#include "esp_rom_gpio.h" +#include "esp_rom_serial_output.h" +#include "lwip/netdb.h" + +// ESP32-C3 ROM exports uart_tx_one_char but not esp_rom_uart_putc. +// Provide the missing symbol so the D object links. +#if defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(esp_rom_uart_putc) +void esp_rom_uart_putc(char c) { esp_rom_output_tx_one_char(c); } +#endif + +// -- errno accessor (picolibc uses _Thread_local errno, incompatible with emulated-TLS) -- + +#include + +int *ow_errno_location(void) +{ + return &errno; +} + +// -- IRQ wrappers -- + +static portMUX_TYPE ow_irq_mux = portMUX_INITIALIZER_UNLOCKED; + +uint32_t ow_irq_disable(void) +{ + portENTER_CRITICAL(&ow_irq_mux); + return 1; +} + +void ow_irq_enable(uint32_t prev) +{ + (void)prev; + portEXIT_CRITICAL(&ow_irq_mux); +} + +void ow_irq_wait(void) +{ +#if CONFIG_IDF_TARGET_ARCH_XTENSA + __asm__ volatile("waiti 0"); +#elif CONFIG_IDF_TARGET_ARCH_RISCV + __asm__ volatile("wfi"); +#endif +} + +// -- FreeRTOS task wrappers -- + +typedef void (*ow_task_func_t)(void *); + +int ow_task_create(ow_task_func_t func, const char *name, uint32_t stack_bytes, + void *param, uint32_t priority, void **out_handle) +{ + return xTaskCreate(func, name, stack_bytes, param, priority, + (TaskHandle_t *)out_handle) == pdPASS ? 1 : 0; +} + +void ow_task_delete(void *handle) +{ + vTaskDelete((TaskHandle_t)handle); +} + +void *ow_task_current(void) +{ + return (void *)xTaskGetCurrentTaskHandle(); +} + +void ow_task_notify_give(void *handle) +{ + xTaskNotifyGive((TaskHandle_t)handle); +} + +uint32_t ow_task_notify_take(uint32_t ticks_to_wait) +{ + return ulTaskNotifyTake(pdTRUE, ticks_to_wait); +} + +uint32_t ow_task_priority_get(void *handle) +{ + return uxTaskPriorityGet((TaskHandle_t)handle); +} + +// -- GPIO wrappers (software GPIO; peripheral function routing goes +// through ESP-IDF's signal matrix on a per-peripheral basis, not +// through this module). + +#include "driver/gpio.h" +#include "soc/gpio_num.h" + +// pull: 0=none, 1=up, 2=down (matches D Pull enum encoding) +static gpio_pull_mode_t ow_pull_to_idf(int pull) +{ + return (pull == 1) ? GPIO_PULLUP_ONLY : + (pull == 2) ? GPIO_PULLDOWN_ONLY : GPIO_FLOATING; +} + +void ow_gpio_output_init(int pin, int initial) +{ + gpio_reset_pin((gpio_num_t)pin); + gpio_set_direction((gpio_num_t)pin, GPIO_MODE_OUTPUT); + gpio_set_level((gpio_num_t)pin, initial); +} + +void ow_gpio_input_init(int pin, int pull) +{ + gpio_reset_pin((gpio_num_t)pin); + gpio_set_direction((gpio_num_t)pin, GPIO_MODE_INPUT); + gpio_set_pull_mode((gpio_num_t)pin, ow_pull_to_idf(pull)); +} + +void ow_gpio_output_set(int pin, int value) +{ + gpio_set_level((gpio_num_t)pin, value); +} + +int ow_gpio_input_read(int pin) +{ + return gpio_get_level((gpio_num_t)pin); +} + +void ow_gpio_set_pull(int pin, int pull) +{ + gpio_set_pull_mode((gpio_num_t)pin, ow_pull_to_idf(pull)); +} + +void ow_gpio_release(int pin) +{ + gpio_reset_pin((gpio_num_t)pin); +} + +uint32_t ow_gpio_count(void) +{ + return SOC_GPIO_PIN_COUNT; +} + + +// -- UART HAL wrappers -- + +#include "soc/soc_caps.h" +#include "hal/uart_ll.h" +#include "esp_private/periph_ctrl.h" +#define NUM_UARTS SOC_UART_NUM + +static uart_hal_context_t uart_ctx[NUM_UARTS]; +static bool uart_initialized[NUM_UARTS]; + +// D enums: StopBits { half=0, one=1, one_point_five=2, two=3 } +// Parity { none=0, even=1, odd=2, mark=3, space=4 } +// HAL enums: UART_STOP_BITS_1=1, _1_5=2, _2=3 +// UART_PARITY_DISABLE=0, _EVEN=2, _ODD=3 +static const uart_stop_bits_t stop_bits_map[] = { + UART_STOP_BITS_1, UART_STOP_BITS_1, UART_STOP_BITS_1_5, UART_STOP_BITS_2 +}; +static const uart_parity_t parity_map[] = { + UART_PARITY_DISABLE, UART_PARITY_EVEN, UART_PARITY_ODD, + UART_PARITY_DISABLE, UART_PARITY_DISABLE +}; + +int ow_uart_open(unsigned port, uint32_t baud_rate, uint8_t data_bits, + uint8_t stop_bits, uint8_t parity, + int8_t tx_gpio, int8_t rx_gpio) +{ + if (port >= NUM_UARTS) + return 0; + + // Enable peripheral clock before touching any registers + PERIPH_RCC_ATOMIC() + { + uart_ll_enable_bus_clock(port, true); + uart_ll_reset_register(port); + } + + // Set device pointer before calling hal_init (v6 API expects it pre-set) + uart_ctx[port].dev = UART_LL_GET_HW(port); + uart_hal_init(&uart_ctx[port], port); + + int __DECLARE_RCC_ATOMIC_ENV __attribute__((unused)); + uart_ll_set_sclk(uart_ctx[port].dev, UART_SCLK_DEFAULT); + uart_ll_set_baudrate(uart_ctx[port].dev, baud_rate, 80000000); + uart_ll_set_data_bit_num(uart_ctx[port].dev, data_bits - 5); + uart_ll_set_stop_bits(uart_ctx[port].dev, stop_bits < sizeof(stop_bits_map) ? stop_bits_map[stop_bits] : 1); + uart_ll_set_parity(uart_ctx[port].dev, parity < sizeof(parity_map)/sizeof(parity_map[0]) ? parity_map[parity] : UART_PARITY_DISABLE); + + uart_hal_rxfifo_rst(&uart_ctx[port]); + uart_hal_txfifo_rst(&uart_ctx[port]); + + // GPIO pin routing -- UART0 defaults (TX=43, RX=44) set by bootloader. + // For non-default pins or UART1/2, route via GPIO matrix. + if (tx_gpio >= 0) + { + esp_rom_gpio_pad_select_gpio(tx_gpio); + esp_rom_gpio_connect_out_signal(tx_gpio, + uart_periph_signal[port].pins[SOC_UART_PERIPH_SIGNAL_TX].signal, false, false); + } + if (rx_gpio >= 0) + { + esp_rom_gpio_pad_select_gpio(rx_gpio); + esp_rom_gpio_pad_pullup_only(rx_gpio); + esp_rom_gpio_connect_in_signal(rx_gpio, + uart_periph_signal[port].pins[SOC_UART_PERIPH_SIGNAL_RX].signal, false); + } + + uart_initialized[port] = true; + return 1; +} + +void ow_uart_close(unsigned port) +{ + if (port >= NUM_UARTS || !uart_initialized[port]) + return; + uart_hal_txfifo_rst(&uart_ctx[port]); + uart_hal_rxfifo_rst(&uart_ctx[port]); + PERIPH_RCC_ATOMIC() + { + uart_ll_enable_bus_clock(port, false); + } + uart_initialized[port] = false; +} + +int32_t ow_uart_read(unsigned port, uint8_t *buf, int32_t len) +{ + if (port >= NUM_UARTS || !uart_initialized[port]) + return 0; + int rd_len = (int)len; + uart_hal_read_rxfifo(&uart_ctx[port], buf, &rd_len); + return rd_len; +} + +int32_t ow_uart_write(unsigned port, const uint8_t *buf, int32_t len) +{ + if (port >= NUM_UARTS || !uart_initialized[port]) + return 0; + uint32_t written = 0; + uart_hal_write_txfifo(&uart_ctx[port], buf, (uint32_t)len, &written); + return (int32_t)written; +} + +int32_t ow_uart_rx_pending(unsigned port) +{ + if (port >= NUM_UARTS || !uart_initialized[port]) + return 0; + return (int32_t)uart_ll_get_rxfifo_len(uart_ctx[port].dev); +} + +int ow_uart_tx_idle(unsigned port) +{ + if (port >= NUM_UARTS || !uart_initialized[port]) + return 1; + return uart_ll_is_tx_idle(uart_ctx[port].dev) ? 1 : 0; +} + +int32_t ow_uart_flush(unsigned port) +{ + if (port >= NUM_UARTS || !uart_initialized[port]) + return 0; + while (!uart_ll_is_tx_idle(uart_ctx[port].dev)) + {} + return 0; +} + +// -- WiFi wrappers -- + +#if CONFIG_ESP_WIFI_ENABLED +#include "esp_wifi.h" +#include "esp_private/wifi.h" +#include "esp_netif.h" +#include "esp_event.h" +#include "esp_mac.h" + +static esp_netif_t *ow_wifi_netif_sta; +static esp_netif_t *ow_wifi_netif_ap; +static int ow_wifi_refcount; + +typedef void (*ow_wifi_event_cb_t)(int event_id, void *data, int data_len); + +static ow_wifi_event_cb_t ow_wifi_sta_cb; +static ow_wifi_event_cb_t ow_wifi_ap_cb; + +static void ow_wifi_event_handler(void *arg, esp_event_base_t base, + int32_t event_id, void *event_data) +{ + if (base == WIFI_EVENT) + { + switch (event_id) + { + case WIFI_EVENT_STA_CONNECTED: + case WIFI_EVENT_STA_DISCONNECTED: + case WIFI_EVENT_STA_START: + case WIFI_EVENT_STA_STOP: + if (ow_wifi_sta_cb) + ow_wifi_sta_cb(event_id, event_data, 0); + break; + + case WIFI_EVENT_AP_START: + case WIFI_EVENT_AP_STOP: + case WIFI_EVENT_AP_STACONNECTED: + case WIFI_EVENT_AP_STADISCONNECTED: + if (ow_wifi_ap_cb) + ow_wifi_ap_cb(event_id, event_data, 0); + break; + } + } + else if (base == IP_EVENT) + { + if (event_id == IP_EVENT_STA_GOT_IP && ow_wifi_sta_cb) + ow_wifi_sta_cb(event_id, event_data, 0); + } +} + +int ow_wifi_init(void) +{ + if (ow_wifi_refcount++ > 0) + return 0; + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + esp_err_t err = esp_wifi_init(&cfg); + if (err != ESP_OK) + { + ow_wifi_refcount--; + return (int)err; + } + + // Create netifs before registering RX callbacks or starting WiFi, + // so esp_netif_receive() always has valid targets. + if (!ow_wifi_netif_sta) + ow_wifi_netif_sta = esp_netif_create_default_wifi_sta(); + if (!ow_wifi_netif_ap) + ow_wifi_netif_ap = esp_netif_create_default_wifi_ap(); + + esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, + &ow_wifi_event_handler, NULL); + esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, + &ow_wifi_event_handler, NULL); + + return 0; +} + +void ow_wifi_deinit(void) +{ + if (--ow_wifi_refcount > 0) + return; + + esp_wifi_stop(); + esp_wifi_deinit(); + + if (ow_wifi_netif_sta) + { + esp_netif_destroy(ow_wifi_netif_sta); + ow_wifi_netif_sta = NULL; + } + if (ow_wifi_netif_ap) + { + esp_netif_destroy(ow_wifi_netif_ap); + ow_wifi_netif_ap = NULL; + } +} + +int ow_wifi_sta_config(const char *ssid, const char *password, const uint8_t *bssid) +{ + wifi_config_t cfg = {0}; + if (ssid) + { + size_t len = strlen(ssid); + if (len > sizeof(cfg.sta.ssid) - 1) len = sizeof(cfg.sta.ssid) - 1; + memcpy(cfg.sta.ssid, ssid, len); + } + if (password) + { + size_t len = strlen(password); + if (len > sizeof(cfg.sta.password) - 1) len = sizeof(cfg.sta.password) - 1; + memcpy(cfg.sta.password, password, len); + } + if (bssid) + { + memcpy(cfg.sta.bssid, bssid, 6); + cfg.sta.bssid_set = true; + } + + return esp_wifi_set_config(WIFI_IF_STA, &cfg) == ESP_OK ? 1 : 0; +} + +int ow_wifi_ap_config(const char *ssid, const char *password, + uint8_t channel, uint8_t max_conn, uint8_t hidden) +{ + wifi_config_t cfg = {0}; + if (ssid) + { + size_t len = strlen(ssid); + if (len > sizeof(cfg.ap.ssid) - 1) len = sizeof(cfg.ap.ssid) - 1; + memcpy(cfg.ap.ssid, ssid, len); + cfg.ap.ssid_len = (uint8_t)len; + } + if (password) + { + size_t len = strlen(password); + if (len > sizeof(cfg.ap.password) - 1) len = sizeof(cfg.ap.password) - 1; + memcpy(cfg.ap.password, password, len); + cfg.ap.authmode = (len > 0) ? WIFI_AUTH_WPA2_PSK : WIFI_AUTH_OPEN; + } + else + cfg.ap.authmode = WIFI_AUTH_OPEN; + + cfg.ap.channel = channel; + cfg.ap.max_connection = max_conn > 0 ? max_conn : 4; + cfg.ap.ssid_hidden = hidden; + + return esp_wifi_set_config(WIFI_IF_AP, &cfg) == ESP_OK ? 1 : 0; +} + +// Ethernet frame RX callbacks -- one per netif (STA=0, AP=1). +// esp_wifi_internal_reg_rxcb gives us raw Ethernet frames before lwIP, +// so the bridge/routing layer sees all traffic. + +typedef void (*ow_wifi_rx_cb_t)(const uint8_t *data, int len, int iface); + +static ow_wifi_rx_cb_t ow_wifi_rx_callback; + +static esp_err_t ow_wifi_sta_rx(void *buffer, uint16_t len, void *eb) +{ + if (ow_wifi_rx_callback) + ow_wifi_rx_callback((const uint8_t *)buffer, len, 0); + return esp_netif_receive(ow_wifi_netif_sta, buffer, len, eb); +} + +static esp_err_t ow_wifi_ap_rx(void *buffer, uint16_t len, void *eb) +{ + if (ow_wifi_rx_callback) + ow_wifi_rx_callback((const uint8_t *)buffer, len, 1); + return esp_netif_receive(ow_wifi_netif_ap, buffer, len, eb); +} + +int ow_wifi_set_rx_callback(ow_wifi_rx_cb_t cb) +{ + ow_wifi_rx_callback = cb; + esp_err_t err; + err = esp_wifi_internal_reg_rxcb(WIFI_IF_STA, cb ? &ow_wifi_sta_rx : NULL); + if (err != ESP_OK) + return 0; + err = esp_wifi_internal_reg_rxcb(WIFI_IF_AP, cb ? &ow_wifi_ap_rx : NULL); + return err == ESP_OK ? 1 : 0; +} + +void ow_wifi_set_sta_callback(ow_wifi_event_cb_t cb) +{ + ow_wifi_sta_cb = cb; +} + +void ow_wifi_set_ap_callback(ow_wifi_event_cb_t cb) +{ + ow_wifi_ap_cb = cb; +} +#else // !CONFIG_ESP_WIFI_ENABLED + +typedef void (*ow_wifi_event_cb_t)(int, void *, int); +typedef void (*ow_wifi_rx_cb_t)(const uint8_t *, int, int); + +int ow_wifi_init(void) { return -1; } +void ow_wifi_deinit(void) {} +int ow_wifi_sta_config(const char *s, const char *p, const uint8_t *b) { (void)s;(void)p;(void)b; return 0; } +int ow_wifi_ap_config(const char *s, const char *p, uint8_t c, uint8_t m, uint8_t h) { (void)s;(void)p;(void)c;(void)m;(void)h; return 0; } +int ow_wifi_set_rx_callback(ow_wifi_rx_cb_t cb) { (void)cb; return 0; } +void ow_wifi_set_sta_callback(ow_wifi_event_cb_t cb) { (void)cb; } +void ow_wifi_set_ap_callback(ow_wifi_event_cb_t cb) { (void)cb; } + +#endif // CONFIG_ESP_WIFI_ENABLED + +// -- BLE (NimBLE) wrappers -- + +#if CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ENABLED +#include "nimble/nimble_port.h" +#include "nimble/nimble_port_freertos.h" +#include "host/ble_hs.h" +#include "host/ble_gap.h" + +typedef int (*ow_gap_event_cb_t)(struct ble_gap_event *, void *); +static ow_gap_event_cb_t ow_gap_callback; + +static int ow_gap_event_dispatch(struct ble_gap_event *event, void *arg) +{ + if (ow_gap_callback) + return ow_gap_callback(event, arg); + return 0; +} + +static void ow_nimble_host_task(void *param) +{ + (void)param; + nimble_port_run(); + nimble_port_freertos_deinit(); +} + +int ow_ble_init(void) +{ + int rc = nimble_port_init(); + if (rc != 0) + return rc; + nimble_port_freertos_init(ow_nimble_host_task); + return 0; +} + +void ow_ble_deinit(void) +{ + nimble_port_stop(); + nimble_port_deinit(); +} + +void ow_ble_set_gap_callback(ow_gap_event_cb_t cb) +{ + ow_gap_callback = cb; +} + +#else // !BT_NIMBLE + +typedef int (*ow_gap_event_cb_t)(void *, void *); + +int ow_ble_init(void) { return -1; } +void ow_ble_deinit(void) {} +void ow_ble_set_gap_callback(ow_gap_event_cb_t cb) { (void)cb; } + +#endif // CONFIG_BT_NIMBLE_ENABLED + +// -- CAN (TWAI) driver -- +// +// Only ow_can_open lives here -- it builds the timing/general/filter config +// structs and does install+start. Everything else is called directly from D +// via the ESP-IDF _v2 handle API. + +#include "soc/soc_caps.h" +#if SOC_TWAI_SUPPORTED +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcpp" +#include "driver/twai.h" +#pragma GCC diagnostic pop + +twai_handle_t ow_can_open(unsigned port, uint32_t bitrate, int tx_gpio, int rx_gpio, uint8_t sjw, uint8_t tseg1, uint8_t tseg2, uint16_t brp) +{ + if (port >= SOC_TWAI_CONTROLLER_NUM) + return NULL; + + twai_timing_config_t timing; + if (brp > 0) + { + memset(&timing, 0, sizeof(timing)); + timing.clk_src = TWAI_CLK_SRC_DEFAULT; + timing.brp = brp; + timing.tseg_1 = tseg1; + timing.tseg_2 = tseg2; + timing.sjw = sjw; + } + else + { + switch (bitrate) + { + case 1000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_1KBITS(); break; + case 5000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_5KBITS(); break; + case 10000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_10KBITS(); break; + case 12500: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_12_5KBITS(); break; + case 16000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_16KBITS(); break; + case 20000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_20KBITS(); break; + case 25000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_25KBITS(); break; + case 50000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_50KBITS(); break; + case 100000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_100KBITS(); break; + case 125000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_125KBITS(); break; + case 250000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_250KBITS(); break; + case 500000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_500KBITS(); break; + case 800000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_800KBITS(); break; + case 1000000: timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_1MBITS(); break; + default: return NULL; + } + } + + twai_general_config_t general = TWAI_GENERAL_CONFIG_DEFAULT_V2( + port, tx_gpio, rx_gpio, TWAI_MODE_NORMAL); + general.alerts_enabled = TWAI_ALERT_BUS_ERROR | TWAI_ALERT_ERR_PASS + | TWAI_ALERT_ERR_ACTIVE | TWAI_ALERT_BUS_OFF | TWAI_ALERT_ABOVE_ERR_WARN + | TWAI_ALERT_BELOW_ERR_WARN | TWAI_ALERT_RX_FIFO_OVERRUN + | TWAI_ALERT_RX_QUEUE_FULL | TWAI_ALERT_ARB_LOST | TWAI_ALERT_TX_FAILED; + twai_filter_config_t filter = TWAI_FILTER_CONFIG_ACCEPT_ALL(); + + twai_handle_t handle = NULL; + if (twai_driver_install_v2(&general, &timing, &filter, &handle) != ESP_OK) + return NULL; + + if (twai_start_v2(handle) != ESP_OK) + { + twai_driver_uninstall_v2(handle); + return NULL; + } + + return handle; +} + +#else // !SOC_TWAI_SUPPORTED + +typedef struct twai_obj_t *twai_handle_t; + +twai_handle_t ow_can_open(unsigned, uint32_t, int, int, uint8_t, uint8_t, uint8_t, uint16_t) +{ + return (twai_handle_t)0; +} + +#endif // SOC_TWAI_SUPPORTED + +// -- lwIP netdb wrappers (link-order fix) -- +// D object references lwip_getaddrinfo/lwip_freeaddrinfo but the D object +// appears after liblwip.a in the link. These wrappers are in libmain.a +// which is linked with --whole-archive, ensuring they're always present. + +int ow_lwip_getaddrinfo(const char *nodename, const char *servname, const struct addrinfo *hints, struct addrinfo **res) +{ + return lwip_getaddrinfo(nodename, servname, hints, res); +} + +void ow_lwip_freeaddrinfo(struct addrinfo *ai) +{ + lwip_freeaddrinfo(ai); +} diff --git a/src/urt/driver/esp32/timer.d b/src/urt/driver/esp32/timer.d new file mode 100644 index 0000000..d1a2f2c --- /dev/null +++ b/src/urt/driver/esp32/timer.d @@ -0,0 +1,38 @@ +// ESP32 timer driver -- D wrapper over ESP-IDF timer APIs +// +// Uses esp_timer_get_time() for monotonic microsecond clock. +// Periodic tick uses FreeRTOS tick or esp_timer under the hood. +module urt.driver.esp32.timer; + +nothrow @nogc: + + +enum uint mtime_freq_hz = 1_000_000; // esp_timer_get_time returns microseconds +enum bool has_mtime = true; +enum bool has_rtc = false; +enum bool has_mcycle = false; +enum bool has_timer_stop = false; +enum bool has_wfi_sleep = false; + +ulong mtime_read() +{ + return cast(ulong)esp_timer_get_time(); +} + +alias TimerCallback = void function() nothrow @nogc; + +void timer_set_periodic(uint period_us, TimerCallback cb) +{ + tick_callback = cb; + // TODO: configure esp_timer or FreeRTOS tick for periodic callback +} + + +private: + +private __gshared TimerCallback tick_callback; + +extern(C) nothrow @nogc +{ + long esp_timer_get_time(); +} diff --git a/src/urt/driver/esp32/uart.d b/src/urt/driver/esp32/uart.d new file mode 100644 index 0000000..a752be7 --- /dev/null +++ b/src/urt/driver/esp32/uart.d @@ -0,0 +1,100 @@ +// ESP32 UART driver -- D wrapper over the C shim (ow_shim.c) +// +// The C shim calls ESP-IDF UART HAL directly (no FreeRTOS UART driver). +// GPIO pin routing is done via the ROM GPIO matrix. +// +// 3 UART ports: UART0 (console), UART1, UART2. +// UART0 TX/RX defaults set by bootloader (typically GPIO43/44 on S3). +module urt.driver.esp32.uart; + +import urt.driver.uart : Parity, StopBits, UartConfig; + +nothrow @nogc: + + +// SOC_UART_NUM per chip variant +version (ESP32) enum num_uarts = 3; +else version (ESP32_S3) enum num_uarts = 3; +else version (ESP32_P4) enum num_uarts = 6; +else version (ESP32_S2) enum num_uarts = 2; +else version (ESP32_C2) enum num_uarts = 2; +else version (ESP32_C3) enum num_uarts = 2; +else version (ESP32_C5) enum num_uarts = 2; +else version (ESP32_C6) enum num_uarts = 3; +else version (ESP32_H2) enum num_uarts = 2; +else static assert(false, "unknown Espressif chip -- add num_uarts"); + +enum uint uart_clock_hz = 80_000_000; +enum bool has_irq_driven_uart = false; +enum bool has_dma_driven_uart = false; + +bool uart_hw_open(uint id, ref const UartConfig cfg) +{ + if (id >= num_uarts) + return false; + byte tx = cfg.tx_gpio == ubyte.max ? -1 : cast(byte)cfg.tx_gpio; + byte rx = cfg.rx_gpio == ubyte.max ? -1 : cast(byte)cfg.rx_gpio; + return ow_uart_open(id, cfg.baud_rate, cfg.data_bits, cast(ubyte)cfg.stop_bits, cast(ubyte)cfg.parity, tx, rx) != 0; +} + +void uart_hw_close(uint id) +{ + ow_uart_close(id); +} + +ptrdiff_t uart_hw_read(uint id, void[] buffer) +{ + return ow_uart_read(id, cast(ubyte*)buffer.ptr, cast(int)buffer.length); +} + +ptrdiff_t uart_hw_write(uint id, const(void)[] data) +{ + return ow_uart_write(id, cast(const(ubyte)*)data.ptr, cast(int)data.length); +} + +void uart_hw_poll(uint id) +{ + // ESP32 UART HAL is polled via read/rx_pending -- no separate poll needed +} + +bool uart_hw_check_errors(uint id) +{ + // TODO: check UART error status register via HAL + return false; +} + +ptrdiff_t uart_hw_rx_pending(uint id) +{ + return ow_uart_rx_pending(id); +} + +ptrdiff_t uart_hw_flush(uint id) +{ + return ow_uart_flush(id); +} + +bool uart_tx_idle(uint id) +{ + return ow_uart_tx_idle(id) != 0; +} + +void uart0_hw_puts(const(char)[] s) +{ + foreach (ch; s) + esp_rom_uart_putc(ch); +} + +private: + +extern(C) nothrow @nogc +{ + void esp_rom_uart_putc(char c) nothrow @nogc; + + int ow_uart_open(uint port, uint baud_rate, ubyte data_bits, ubyte stop_bits, ubyte parity, byte tx_gpio, byte rx_gpio); + void ow_uart_close(uint port); + int ow_uart_read(uint port, ubyte* buf, int len); + int ow_uart_write(uint port, const(ubyte)* buf, int len); + int ow_uart_rx_pending(uint port); + int ow_uart_tx_idle(uint port); + int ow_uart_flush(uint port); +} diff --git a/src/urt/driver/esp32/wifi.d b/src/urt/driver/esp32/wifi.d new file mode 100644 index 0000000..22d7a1e --- /dev/null +++ b/src/urt/driver/esp32/wifi.d @@ -0,0 +1,322 @@ +// ESP32 WiFi driver -- D wrapper over C shim + direct ESP-IDF calls +// +// The C shim (ow_shim.c) handles: +// - ow_wifi_init/deinit: WIFI_INIT_CONFIG_DEFAULT macro, netif creation, +// event handler registration +// - ow_wifi_sta_config/ap_config: wifi_config_t struct construction +// - ow_wifi_set_rx_callback: RX trampolines that forward to D AND to +// esp_netif_receive (so lwIP still works) +// +// Everything else (mode, connect, disconnect, tx, mac, channel, power) +// calls ESP-IDF directly. +// +// ESP32 has one WiFi port (port 0). +module urt.driver.esp32.wifi; + +import urt.driver.wifi; + +nothrow @nogc: + + +enum uint num_wifi = 1; + + +bool wifi_hw_open(uint port, ref const WifiConfig cfg) +{ + if (_opened) + return false; + + if (ow_wifi_init() != 0) + return false; + + ow_wifi_set_sta_callback(&sta_event_trampoline); + ow_wifi_set_ap_callback(&ap_event_trampoline); + + if (cfg.tx_power != 0) + esp_wifi_set_max_tx_power(cfg.tx_power); + + if (esp_wifi_start() != ESP_OK) + { + ow_wifi_deinit(); + return false; + } + + _opened = true; + return true; +} + +void wifi_hw_close(uint port) +{ + if (!_opened) + return; + ow_wifi_set_rx_callback(null); + ow_wifi_set_sta_callback(null); + ow_wifi_set_ap_callback(null); + esp_wifi_stop(); + ow_wifi_deinit(); + _opened = false; + _event_cb = null; + _rx_cb = null; + _raw_rx_cb = null; + _evt_sta_connected = false; + _evt_sta_disconnected = false; + _evt_ap_started = false; + _evt_ap_stopped = false; +} + +bool wifi_hw_set_mode(uint port, WifiMode mode) +{ + if (mode == WifiMode.monitor) + return false; + + // Map to ESP-IDF wifi_mode_t: 0=none,1=sta,2=ap,3=apsta + static immutable ubyte[5] mode_map = [0, 0, 1, 2, 3]; + return esp_wifi_set_mode(mode_map[mode]) == ESP_OK; +} + +bool wifi_hw_sta_configure(uint port, ref const WifiStaConfig cfg) +{ + // Stack buffers for null-termination (SSID max 32, password max 64) + char[33] ssid_z = 0; + char[65] pw_z = 0; + + if (cfg.ssid.length > 0 && cfg.ssid.length <= 32) + ssid_z[0 .. cfg.ssid.length] = cfg.ssid[]; + if (cfg.password.length > 0 && cfg.password.length <= 64) + pw_z[0 .. cfg.password.length] = cfg.password[]; + + bool has_bssid = cfg.bssid != typeof(cfg.bssid).init; + + return ow_wifi_sta_config( + cfg.ssid.length > 0 ? ssid_z.ptr : null, + cfg.password.length > 0 ? pw_z.ptr : null, + has_bssid ? cfg.bssid.ptr : null) != 0; +} + +bool wifi_hw_sta_connect(uint port) +{ + _evt_sta_connected = false; + _evt_sta_disconnected = false; + return esp_wifi_connect() == ESP_OK; +} + +bool wifi_hw_sta_disconnect(uint port) +{ + return esp_wifi_disconnect() == ESP_OK; +} + +bool wifi_hw_ap_configure(uint port, ref const WifiApConfig cfg) +{ + char[33] ssid_z = 0; + char[65] pw_z = 0; + + if (cfg.ssid.length > 0 && cfg.ssid.length <= 32) + ssid_z[0 .. cfg.ssid.length] = cfg.ssid[]; + if (cfg.password.length > 0 && cfg.password.length <= 64) + pw_z[0 .. cfg.password.length] = cfg.password[]; + + return ow_wifi_ap_config( + cfg.ssid.length > 0 ? ssid_z.ptr : null, + cfg.password.length > 0 ? pw_z.ptr : null, + cfg.channel, cfg.max_clients, cfg.hidden ? 1 : 0) != 0; +} + +size_t wifi_hw_ap_get_clients(uint port, WifiStaInfo[] buf) +{ + // TODO: esp_wifi_ap_get_sta_list + return 0; +} + +// Scanning + +bool wifi_hw_scan_start(uint port, ref const WifiScanConfig cfg) +{ + // TODO: esp_wifi_scan_start + return false; +} + +void wifi_hw_scan_stop(uint port) +{ + // TODO: esp_wifi_scan_stop +} + +size_t wifi_hw_scan_get_results(uint port, WifiScanResult[] buf) +{ + // TODO: esp_wifi_scan_get_ap_records + return 0; +} + +// Frame TX/RX + +bool wifi_hw_tx(uint port, WifiVif vif, const(ubyte)[] data) +{ + if (data.length == 0) + return false; + return esp_wifi_internal_tx(cast(int)vif, cast(void*)data.ptr, cast(ushort)data.length) == ESP_OK; +} + +void wifi_hw_set_rx_callback(uint port, WifiRxCallback cb) +{ + _rx_cb = cb; + ow_wifi_set_rx_callback(cb !is null ? &rx_trampoline : null); +} + +// Raw 802.11 TX/RX + +bool wifi_hw_raw_tx(uint port, const(ubyte)[] frame) +{ + // TODO: esp_wifi_80211_tx + return false; +} + +void wifi_hw_set_raw_rx_callback(uint port, WifiRawRxCallback cb) +{ + // TODO: esp_wifi_set_promiscuous + esp_wifi_set_promiscuous_rx_cb + _raw_rx_cb = cb; +} + +// Queries + +bool wifi_hw_get_mac(uint port, WifiVif vif, ref ubyte[6] mac) +{ + // ESP_MAC_WIFI_STA=0, ESP_MAC_WIFI_SOFTAP=1 + return esp_read_mac(mac.ptr, cast(int)vif) == ESP_OK; +} + +ubyte wifi_hw_get_channel(uint port) +{ + ubyte primary = void; + int second = void; + if (esp_wifi_get_channel(&primary, &second) != ESP_OK) + return 0; + return primary; +} + +byte wifi_hw_get_rssi(uint port) +{ + // TODO: esp_wifi_sta_get_ap_info -> rssi + return -127; +} + +bool wifi_hw_set_tx_power(uint port, byte power_dbm) +{ + return esp_wifi_set_max_tx_power(power_dbm) == ESP_OK; +} + +// Events + +void wifi_hw_set_event_callback(uint port, WifiEventCallback cb) +{ + _event_cb = cb; +} + +// Poll -- check ISR-set flags and deliver events to D callback. +// Called from main loop since ESP events arrive on the event task. +void wifi_hw_poll(uint port) +{ + if (_event_cb is null) + return; + + Wifi w = Wifi(0); + + if (_evt_sta_connected) + { + _evt_sta_connected = false; + _event_cb(w, WifiEvent.sta_connected, null); + } + if (_evt_sta_disconnected) + { + _evt_sta_disconnected = false; + _event_cb(w, WifiEvent.sta_disconnected, null); + } + if (_evt_ap_started) + { + _evt_ap_started = false; + _event_cb(w, WifiEvent.ap_started, null); + } + if (_evt_ap_stopped) + { + _evt_ap_stopped = false; + _event_cb(w, WifiEvent.ap_stopped, null); + } +} + + +private: + +enum int ESP_OK = 0; + +// ESP-IDF event IDs (from esp_wifi_types.h) +enum : int +{ + WIFI_EVENT_STA_START = 2, + WIFI_EVENT_STA_STOP = 3, + WIFI_EVENT_STA_CONNECTED = 4, + WIFI_EVENT_STA_DISCONNECTED = 5, + WIFI_EVENT_AP_START = 12, + WIFI_EVENT_AP_STOP = 13, + WIFI_EVENT_AP_STACONNECTED = 14, + WIFI_EVENT_AP_STADISCONNECTED = 15, +} + +__gshared bool _opened; +__gshared WifiEventCallback _event_cb; +__gshared WifiRxCallback _rx_cb; +__gshared WifiRawRxCallback _raw_rx_cb; + +// Flags set from ESP event task, polled from main loop +__gshared bool _evt_sta_connected; +__gshared bool _evt_sta_disconnected; +__gshared bool _evt_ap_started; +__gshared bool _evt_ap_stopped; + +// Event trampolines -- called from ESP event task via C shim +extern(C) void sta_event_trampoline(int event_id, void*, int) nothrow @nogc +{ + if (event_id == WIFI_EVENT_STA_CONNECTED) + _evt_sta_connected = true; + else if (event_id == WIFI_EVENT_STA_DISCONNECTED) + _evt_sta_disconnected = true; +} + +extern(C) void ap_event_trampoline(int event_id, void*, int) nothrow @nogc +{ + if (event_id == WIFI_EVENT_AP_START) + _evt_ap_started = true; + else if (event_id == WIFI_EVENT_AP_STOP) + _evt_ap_stopped = true; +} + +// RX trampoline -- called from C shim's esp_wifi_internal_reg_rxcb handler. +// The C shim also forwards to esp_netif_receive so lwIP still gets frames. +extern(C) void rx_trampoline(const(ubyte)* data, int len, int iface) nothrow @nogc +{ + if (_rx_cb !is null && len > 0) + _rx_cb(Wifi(0), cast(WifiVif)iface, data[0 .. len]); +} + +// C shim functions (ow_shim.c) -- needed for macros, complex structs, netif +extern(C) nothrow @nogc +{ + int ow_wifi_init(); + void ow_wifi_deinit(); + int ow_wifi_sta_config(const(char)* ssid, const(char)* password, const(ubyte)* bssid); + int ow_wifi_ap_config(const(char)* ssid, const(char)* password, ubyte channel, ubyte max_conn, ubyte hidden); + int ow_wifi_set_rx_callback(void function(const(ubyte)*, int, int) nothrow @nogc cb); + void ow_wifi_set_sta_callback(void function(int, void*, int) nothrow @nogc); + void ow_wifi_set_ap_callback(void function(int, void*, int) nothrow @nogc); +} + +// Direct ESP-IDF calls +extern(C) nothrow @nogc +{ + int esp_wifi_set_mode(int mode); + int esp_wifi_start(); + int esp_wifi_stop(); + int esp_wifi_connect(); + int esp_wifi_disconnect(); + int esp_wifi_set_max_tx_power(byte power); + int esp_wifi_get_channel(ubyte* primary, int* second); + int esp_read_mac(ubyte* mac, int type); + int esp_wifi_internal_tx(int ifx, void* buffer, ushort len); +} diff --git a/src/urt/driver/freertos/fibre.d b/src/urt/driver/freertos/fibre.d new file mode 100644 index 0000000..e19606e --- /dev/null +++ b/src/urt/driver/freertos/fibre.d @@ -0,0 +1,101 @@ +// FreeRTOS task-based fibre implementation. +// Each fibre is a FreeRTOS task that blocks on a task notification. +// co_switch does a notify-target + wait-for-notification handoff, +// giving us cooperative coroutine semantics on top of FreeRTOS. +// +// C-side ow_task_* shims live in driver/esp32/ow_shim.c (and equivalent +// per-platform shim modules); they translate to FreeRTOS task-notify APIs. +module urt.driver.freertos.fibre; + +import urt.fibre : cothread_t, coentry_t; +import urt.mem; + +nothrow @nogc: + + +extern(C) int ow_task_create(void function(void*), const(char)*, uint, void*, uint, void**) nothrow @nogc; +extern(C) void ow_task_delete(void*) nothrow @nogc; +extern(C) void* ow_task_current() nothrow @nogc; +extern(C) void ow_task_notify_give(void*) nothrow @nogc; +extern(C) uint ow_task_notify_take(uint) nothrow @nogc; +extern(C) uint ow_task_priority_get(void*) nothrow @nogc; + +struct co_fibre_data +{ + void* user_data; + uint stack_size; + void* task_handle; + coentry_t coentry; +} + +co_fibre_data main_fibre_data; +cothread_t co_active_handle = null; + +inout(co_fibre_data)* co_get_fibre_data(inout cothread_t fibre) pure + => cast(co_fibre_data*)fibre; + +cothread_t co_active() +{ + if (!co_active_handle) + { + main_fibre_data.task_handle = ow_task_current(); + co_active_handle = &main_fibre_data; + } + return co_active_handle; +} + +void* co_data() + => (cast(co_fibre_data*)co_active_handle).user_data; + +cothread_t co_derive(void[] memory, coentry_t entry, void* data) +{ + return null; // not supported with FreeRTOS tasks +} + +extern(C) static void co_freertos_entry(void* param) nothrow @nogc +{ + co_active_handle = cast(cothread_t)param; // set TLS for this task + ow_task_notify_take(0xFFFFFFFF); // block until first co_switch + (cast(co_fibre_data*)param).coentry(); +} + +cothread_t co_create(size_t stack_size, coentry_t entry, void* data) +{ + assert(stack_size <= uint.max, "Stack size too large"); + co_active(); // ensure main fibre initialized + + auto fdata = defaultAllocator().allocT!co_fibre_data(); + if (!fdata) return null; + + fdata.user_data = data; + fdata.stack_size = cast(uint)stack_size; + fdata.coentry = entry; + + auto priority = ow_task_priority_get(null); + + if (!ow_task_create(&co_freertos_entry, "fibre", cast(uint)stack_size, + fdata, priority, &fdata.task_handle)) + { + defaultAllocator().freeT(fdata); + return null; + } + + return fdata; +} + +void co_delete(cothread_t handle) +{ + auto fdata = cast(co_fibre_data*)handle; + if (fdata && fdata != &main_fibre_data) + { + if (fdata.task_handle) + ow_task_delete(fdata.task_handle); + defaultAllocator().freeT(fdata); + } +} + +void co_switch(cothread_t handle) +{ + ow_task_notify_give((cast(co_fibre_data*)handle).task_handle); + ow_task_notify_take(0xFFFFFFFF); +} diff --git a/src/urt/driver/gpio.d b/src/urt/driver/gpio.d new file mode 100644 index 0000000..4aee2a0 --- /dev/null +++ b/src/urt/driver/gpio.d @@ -0,0 +1,89 @@ +// GPIO driver. Software-GPIO primitives plus per-pin function muxing +// for SoCs that support it (BL6xx, BL8xx, BK7231, RP2350, STM32). On +// targets that route via signal matrix (ESP32) or have no pin concept +// (Windows/POSIX without sysfs), has_pin_function_muxing is false and +// gpio_set_function is not declared. +// +// Pin numbering is a flat uint per SoC. STM32 packs port and pin as +// (port * 16 + pin_in_port) so PA9 = 9, PB3 = 19, PE15 = 79. Other +// SoCs use linear pin numbers from 0. +// +// num_gpio is a compile-time upper bound (exact on SoCs with fixed +// pin counts; on hosted targets like Linux SBC it is a static cap and +// gpio_count() returns the actual runtime number). +// +// Function bodies live in /gpio.d, pulled in by the version +// dispatch below. Each backend exports: +// uint gpio_count(); +// void gpio_output_init(uint pin, bool initial = false, DriveMode = push_pull); +// void gpio_input_init(uint pin, Pull = none); +// void gpio_output_set(uint pin, bool value); +// void gpio_output_toggle(uint pin); +// bool gpio_input_read(uint pin); +// void gpio_set_pull(uint pin, Pull); +// void gpio_release(uint pin); +// void gpio_set_function(uint pin, uint function_id, Pull = none, DriveMode = push_pull); +// +// function_id is opaque per chip; peripheral drivers know the right +// value. The peripheral owns I/O direction once muxed. +module urt.driver.gpio; + +version (BL808_M0) + public import urt.driver.bl618.gpio; +else version (BL808) + public import urt.driver.bl808.gpio; +else version (BL618) + public import urt.driver.bl618.gpio; +else version (Beken) + public import urt.driver.bk7231.gpio; +else version (Espressif) + public import urt.driver.esp32.gpio; +else version (linux) + public import urt.driver.posix.gpio; +else +{ + enum uint num_gpio = 0; + enum bool has_pull_up = false; + enum bool has_pull_down = false; + enum bool has_open_drain = false; + enum bool has_pin_function_muxing = false; + + uint gpio_count() nothrow @nogc => 0; +} + +nothrow @nogc: + +enum Pull : ubyte +{ + none, + up, + down, +} + +enum DriveMode : ubyte +{ + push_pull, + open_drain, +} + + +unittest +{ + static assert(is(typeof(num_gpio) == uint)); + static assert(is(typeof(has_pull_up) == bool)); + static assert(is(typeof(has_pull_down) == bool)); + static assert(is(typeof(has_open_drain) == bool)); + static assert(is(typeof(has_pin_function_muxing) == bool)); + + // Pull encoding is load-bearing: bl808/bl618 backends shift the + // ordinal directly into GPIO_CFG bits[25:24]. + static assert(Pull.none == 0); + static assert(Pull.up == 1); + static assert(Pull.down == 2); + + static assert(DriveMode.push_pull == 0); + static assert(DriveMode.open_drain == 1); + + // gpio_count() is callable on every backend (returns 0 on fallback). + gpio_count(); +} diff --git a/src/urt/driver/irq.d b/src/urt/driver/irq.d new file mode 100644 index 0000000..1e330a8 --- /dev/null +++ b/src/urt/driver/irq.d @@ -0,0 +1,215 @@ +// Unified baremetal interrupt controller driver +// +// Normalizes the IRQ API across RISC-V PLIC, ARM NVIC, Beken ICU, etc. +// Platform drivers export capabilities; the baremetal layer re-exports +// them and provides a uniform function surface. +module urt.driver.irq; + +version (BL808_M0) + public import urt.driver.bl618.irq; +else version (BL808) + public import urt.driver.bl808.irq; +else version (BL618) + public import urt.driver.bl618.irq; +else version (Beken) + public import urt.driver.bk7231.irq; +else version (RP2350) + public import urt.driver.rp2350.irq; +else version (STM32) + public import urt.driver.stm32.irq; +else version (Espressif) + public import urt.driver.esp32.irq; +else +{ + enum bool has_plic = false; + enum bool has_nvic = false; + enum bool has_per_irq_control = false; + enum bool has_irq_priority = false; + enum bool has_wait_for_interrupt = false; + enum bool has_irq_diagnostics = false; + enum uint irq_max = 0; +} + +nothrow @nogc: + +alias IrqHandler = void function(uint irq) nothrow @nogc; + + +// ════════════════════════════════════════════════════════════════════ +// Driver API +// ════════════════════════════════════════════════════════════════════ + +// Global interrupt control + +// Disable all interrupt delivery. Returns previous state. +bool irq_global_disable() +{ + static if (has_plic) + return disable_interrupts(); + else static if (irq_max > 0) + { + irq_disable(); + return true; + } + else + assert(false, "no IRQ controller"); +} + +// Enable all interrupt delivery. Returns previous state. +bool irq_global_enable() +{ + static if (has_plic) + return enable_interrupts(); + else static if (irq_max > 0) + { + irq_enable(); + return true; + } + else + assert(false, "no IRQ controller"); +} + +// Set global interrupt state. Returns previous state. +bool irq_global_set(bool enabled) +{ + return enabled ? irq_global_enable() : irq_global_disable(); +} + +// RAII-style critical section guard. +// Usage: auto guard = irq_critical(); +struct IrqGuard +{ + private bool _prev; + @disable this(); + @disable this(this); + + ~this() nothrow @nogc + { + irq_global_set(_prev); + } +} + +IrqGuard irq_critical() +{ + IrqGuard g = void; + g._prev = irq_global_disable(); + return g; +} + +// Per-IRQ control + +// Enable a specific peripheral interrupt. Returns previous state. +bool irq_line_enable(uint irq) +{ + static if (has_per_irq_control) + { + static if (has_plic) + return enable_irq(irq); + else + { + irq_set_enable(irq); + return false; + } + } + else + assert(false, "no per-IRQ control"); +} + +// Disable a specific peripheral interrupt. Returns previous state. +bool irq_line_disable(uint irq) +{ + static if (has_per_irq_control) + { + static if (has_plic) + return disable_irq(irq); + else + { + irq_clear_enable(irq); + return false; + } + } + else + assert(false, "no per-IRQ control"); +} + +// Set priority for a peripheral interrupt (0 = highest). +void irq_line_set_priority(uint irq, ubyte priority) +{ + static if (has_irq_priority) + irq_set_priority(irq, priority); + else + assert(false, "no IRQ priority support"); +} + +// Handler registration + +// Install an interrupt handler. Returns the previous handler for chaining. +IrqHandler irq_handler_set(IrqHandler handler) +{ + static if (has_plic) + return irq_set_handler(handler); + else + assert(false, "TODO: irq_handler_set for this platform"); +} + +// Power management + +// Halt CPU until an interrupt fires. Near-zero power draw. +void irq_wait() +{ + static if (has_wait_for_interrupt) + wait_for_interrupt(); + else + assert(false, "WFI not available"); +} + +// Diagnostics + +static if (has_irq_diagnostics) +{ + ref uint irq_total_count() + { + return irq_count; + } + + uint[] irq_hit_histogram() + { + return irq_histogram[]; + } +} + + +// ════════════════════════════════════════════════════════════════════ +// Tests +// ════════════════════════════════════════════════════════════════════ + +unittest +{ + static if (has_plic) static assert(has_per_irq_control); + static if (has_nvic) static assert(has_per_irq_control); + + static if (irq_max > 0) + { + // Global disable/enable round-trip + bool prev = irq_global_disable(); + irq_global_enable(); + + // Critical section guard + { + auto guard = irq_critical(); + } + + static if (has_per_irq_control) + { + irq_line_disable(0); + irq_line_enable(0); + } + + static if (has_irq_diagnostics) + { + uint total = irq_total_count(); + uint[] hist = irq_hit_histogram(); + assert(hist.length == irq_max); + } + } +} diff --git a/src/urt/driver/posix/alloc.d b/src/urt/driver/posix/alloc.d new file mode 100644 index 0000000..909edd8 --- /dev/null +++ b/src/urt/driver/posix/alloc.d @@ -0,0 +1,56 @@ +module urt.driver.posix.alloc; + +import urt.mem.alloc : MemFlags; + +nothrow @nogc: + +enum has_realloc = false; +enum has_expand = false; +enum has_memsize = true; +enum has_exec = true; +enum has_retain = false; +enum has_memflags = false; + +void[] _alloc(size_t size, size_t alignment, MemFlags) pure +{ + void* p; + return posix_memalign(&p, alignment <= 8 ? 8 : alignment, size) ? null : p[0 .. size]; +} + +void _free(void* ptr) pure +{ + free(ptr); +} + + +size_t _memsize(void* ptr) pure +{ + return malloc_usable_size(ptr); +} + +void[] _alloc_exec(size_t size) pure +{ + void* p = mmap(null, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + return (p is MAP_FAILED) ? null : p[0 .. size]; +} + +void _free_exec(void[] mem) pure +{ + cast(void)munmap(mem.ptr, mem.length); +} + + +private: + +extern(C) int posix_memalign(void** memptr, size_t alignment, size_t size) pure; +extern(C) size_t malloc_usable_size(void* ptr) pure; +extern(C) void* mmap(void* addr, size_t length, int prot, int flags, int fd, long offset) pure; +extern(C) int munmap(void* addr, size_t length) pure; +extern(C) void free(void* ptr) pure; + +enum PROT_READ = 0x1; +enum PROT_WRITE = 0x2; +enum PROT_EXEC = 0x4; +enum MAP_PRIVATE = 0x02; +enum MAP_ANONYMOUS = 0x20; +enum MAP_FAILED = cast(void*)-1; diff --git a/src/urt/driver/posix/exception.d b/src/urt/driver/posix/exception.d new file mode 100644 index 0000000..e8ac1f3 --- /dev/null +++ b/src/urt/driver/posix/exception.d @@ -0,0 +1,1077 @@ +/// POSIX exception driver - stack-trace capture/print via ELF + DWARF. +/// +/// Uses _Unwind_Backtrace (ARM/AArch64/RISC-V) or inline-asm RBP walking +/// (x86/x86_64) for capture. Symbol resolution via dladdr; file:line via +/// .debug_line (DWARF v3/4/5). Ported from druntime's +/// core.internal.backtrace.{dwarf,elf} and core.internal.elf.{io,dl}. +/// +/// The DWARF exception-handling runtime (_d_throw_exception, personality +/// function, etc.) lives in urt.internal.dwarfeh so it compiles on bare- +/// metal builds too where urt.driver.posix.exception is not in the source list. +module urt.driver.posix.exception; + +// Windows has its own driver; BareMetal has its own. Everything else +// (Linux, macOS, BSD) lands here. Compiled always (not just `debug`) +// so allocation-site tracking works in release builds. +version (Windows) {} else version (BareMetal) {} else: + +import urt.internal.exception : Resolved, StackTraceData, _d_createTrace, _d_isbaseof, terminate; + +import urt.mem : strlen, memcpy; + +import urt.internal.sys.posix; + +// ╔═══════════════════════════════════════════════════════════════════╗ +// ║ !!! TODO !!! THREADING IS NOT SUPPORTED. ║ +// ║ ║ +// ║ dladdr is MT-safe but the static scratch buffers used by the ║ +// ║ DWARF .debug_line decoder (_dir_scratch, _file_scratch) and the ║ +// ║ ELF self-mapping state are not. Add a mutex or per-thread state ║ +// ║ before this program can use threads. ║ +// ╚═══════════════════════════════════════════════════════════════════╝ + +private enum SEEK_END = 2; + +// --- _Unwind_Backtrace capture (ARM, AArch64, RISC-V) ------------ + +version (D_InlineAsm_X86_64) {} else version (D_InlineAsm_X86) {} else +{ + private alias _Unwind_Trace_Fn = extern(C) int function(void* ctx, void* data) nothrow @nogc; + + extern(C) private int _Unwind_Backtrace(_Unwind_Trace_Fn, void*) nothrow @nogc; + extern(C) private size_t _Unwind_GetIP(void*) nothrow @nogc; + + private struct UnwindState { StackTraceData* trace; ubyte skip; } + + extern(C) private int unwind_trace_callback(void* ctx, void* data) nothrow @nogc + { + auto s = cast(UnwindState*) data; + if (s.skip > 0) { s.skip--; return 0; } + if (s.trace.length >= 32) return 1; + auto ip = _Unwind_GetIP(ctx); + if (!ip) return 1; + s.trace.addrs[s.trace.length++] = cast(void*) ip; + return 0; + } + + private void unwind_backtrace(ref StackTraceData trace) nothrow @nogc @trusted + { + UnwindState state = UnwindState(&trace, 2); + _Unwind_Backtrace(&unwind_trace_callback, &state); + } +} + +// --- Minimal ELF types -------------------------------------------- + +version (D_LP64) +{ + private struct Elf_Ehdr + { + ubyte[16] e_ident; + ushort e_type; + ushort e_machine; + uint e_version; + ulong e_entry; + ulong e_phoff; + ulong e_shoff; + uint e_flags; + ushort e_ehsize; + ushort e_phentsize; + ushort e_phnum; + ushort e_shentsize; + ushort e_shnum; + ushort e_shstrndx; + } + + private struct Elf_Shdr + { + uint sh_name; + uint sh_type; + ulong sh_flags; + ulong sh_addr; + ulong sh_offset; + ulong sh_size; + uint sh_link; + uint sh_info; + ulong sh_addralign; + ulong sh_entsize; + } + + private struct Elf_Phdr + { + uint p_type; + uint p_flags; + ulong p_offset; + ulong p_vaddr; + ulong p_paddr; + ulong p_filesz; + ulong p_memsz; + ulong p_align; + } +} +else +{ + private struct Elf_Ehdr + { + ubyte[16] e_ident; + ushort e_type; + ushort e_machine; + uint e_version; + uint e_entry; + uint e_phoff; + uint e_shoff; + uint e_flags; + ushort e_ehsize; + ushort e_phentsize; + ushort e_phnum; + ushort e_shentsize; + ushort e_shnum; + ushort e_shstrndx; + } + + private struct Elf_Shdr + { + uint sh_name; + uint sh_type; + uint sh_flags; + uint sh_addr; + uint sh_offset; + uint sh_size; + uint sh_link; + uint sh_info; + uint sh_addralign; + uint sh_entsize; + } + + private struct Elf_Phdr + { + uint p_type; + uint p_offset; + uint p_vaddr; + uint p_paddr; + uint p_filesz; + uint p_memsz; + uint p_flags; + uint p_align; + } +} + +private enum EI_MAG0 = 0; +private enum EI_CLASS = 4; +private enum EI_DATA = 5; +private enum ELFMAG = "\x7fELF"; +private enum ET_DYN = 3; +private enum SHF_COMPRESSED = 0x800; + +version (D_LP64) + private enum ELFCLASS_NATIVE = 2; // ELFCLASS64 +else + private enum ELFCLASS_NATIVE = 1; // ELFCLASS32 + +version (LittleEndian) + private enum ELFDATA_NATIVE = 1; // ELFDATA2LSB +else + private enum ELFDATA_NATIVE = 2; // ELFDATA2MSB + +// --- dl_iterate_phdr for base address ----------------------------- + +private struct dl_phdr_info +{ + size_t dlpi_addr; + const(char)* dlpi_name; + const(Elf_Phdr)* dlpi_phdr; + ushort dlpi_phnum; +} + +private alias dl_iterate_phdr_callback_t = extern(C) int function(dl_phdr_info*, size_t, void*) nothrow @nogc; + +extern(C) private int dl_iterate_phdr(dl_iterate_phdr_callback_t callback, void* data) nothrow @nogc; + +private size_t get_executable_base_address() nothrow @nogc @trusted +{ + size_t result = 0; + + extern(C) static int callback(dl_phdr_info* info, size_t, void* data) nothrow @nogc + { + // First entry is the executable itself + *cast(size_t*) data = info.dlpi_addr; + return 1; // stop iteration + } + + dl_iterate_phdr(&callback, &result); + return result; +} + +// --- Memory-mapped file region ------------------------------------ + +private struct MappedRegion +{ + const(ubyte)* data; + size_t mapped_size; + +nothrow @nogc @trusted: + + static MappedRegion map(int fd, size_t offset, size_t length) + { + if (fd == -1 || length == 0) + return MappedRegion.init; + + auto pgsz = cast(size_t) sysconf(_SC_PAGE_SIZE); + if (cast(int) pgsz <= 0) + pgsz = 4096; + + const page_off = offset / pgsz; + const diff = offset - page_off * pgsz; + const needed = length + diff; + const pages = (needed + pgsz - 1) / pgsz; + const msize = pages * pgsz; + + auto p = mmap(null, msize, PROT_READ, MAP_PRIVATE, fd, cast(off_t)(page_off * pgsz)); + if (p is MAP_FAILED) + return MappedRegion.init; + + return MappedRegion(cast(const(ubyte)*) p + diff, msize); + } + + void unmap() + { + if (data !is null) + { + auto pgsz = cast(size_t) sysconf(_SC_PAGE_SIZE); + if (cast(int) pgsz <= 0) + pgsz = 4096; + // Align back to page boundary + auto base = cast(void*)(cast(size_t) data & ~(pgsz - 1)); + munmap(base, mapped_size); + data = null; + } + } +} + +// --- ELF self-reader ---------------------------------------------- + +private struct ElfSelf +{ + int fd = -1; + MappedRegion ehdr_region; + const(Elf_Ehdr)* ehdr; + +nothrow @nogc @trusted: + + static ElfSelf open() + { + ElfSelf self; + + // Read /proc/self/exe path + char[512] pathbuf = void; + auto n = readlink("/proc/self/exe", pathbuf.ptr, pathbuf.length - 1); + if (n <= 0) + return self; + pathbuf[n] = 0; + + self.fd = .open(pathbuf.ptr, O_RDONLY); + if (self.fd == -1) + return self; + + // Map the ELF header + self.ehdr_region = MappedRegion.map(self.fd, 0, Elf_Ehdr.sizeof); + if (self.ehdr_region.data is null) + { + .close(self.fd); + self.fd = -1; + return self; + } + + self.ehdr = cast(const(Elf_Ehdr)*) self.ehdr_region.data; + + // Validate ELF magic, class, and byte order + if (self.ehdr.e_ident[0..4] != cast(const(ubyte)[4]) ELFMAG + || self.ehdr.e_ident[EI_CLASS] != ELFCLASS_NATIVE + || self.ehdr.e_ident[EI_DATA] != ELFDATA_NATIVE) + { + self.close(); + return ElfSelf.init; + } + + return self; + } + + void close() + { + ehdr_region.unmap(); + ehdr = null; + if (fd != -1) { .close(fd); fd = -1; } + } + + bool valid() const { return fd != -1 && ehdr !is null; } + + /// Find a section by name, return its offset and size. + bool find_section(const(char)[] name, out size_t offset, out size_t size) + { + if (!valid()) + return false; + + // Map section headers + auto shdr_total = cast(size_t) ehdr.e_shnum * Elf_Shdr.sizeof; + auto shdr_region = MappedRegion.map(fd, cast(size_t) ehdr.e_shoff, shdr_total); + if (shdr_region.data is null) + return false; + scope(exit) shdr_region.unmap(); + + auto shdrs = (cast(const(Elf_Shdr)*) shdr_region.data)[0 .. ehdr.e_shnum]; + + // Map string table section + if (ehdr.e_shstrndx >= ehdr.e_shnum) + return false; + auto strtab_shdr = &shdrs[ehdr.e_shstrndx]; + auto strtab_region = MappedRegion.map(fd, + cast(size_t) strtab_shdr.sh_offset, + cast(size_t) strtab_shdr.sh_size); + if (strtab_region.data is null) + return false; + scope(exit) strtab_region.unmap(); + + auto strtab = cast(const(char)*) strtab_region.data; + + // Search for the named section + foreach (ref shdr; shdrs) + { + if (shdr.sh_name >= strtab_shdr.sh_size) + continue; + auto sec_name = strtab + shdr.sh_name; + auto sec_name_len = strlen(sec_name); + if (sec_name_len == name.length && sec_name[0 .. sec_name_len] == name) + { + if (shdr.sh_flags & SHF_COMPRESSED) + return false; // compressed debug sections not supported + offset = cast(size_t) shdr.sh_offset; + size = cast(size_t) shdr.sh_size; + return true; + } + } + return false; + } +} + +// --- DWARF .debug_line types and constants ------------------------ + +private struct LocationInfo +{ + int file = -1; + int line = -1; +} + +private struct SourceFile +{ + const(char)[] file; + size_t dir_index; // 1-based +} + +private struct LineNumberProgram +{ + ulong unit_length; + ushort dwarf_version; + ubyte address_size; + ubyte segment_selector_size; + ulong header_length; + ubyte minimum_instruction_length; + ubyte maximum_operations_per_instruction; + bool default_is_statement; + byte line_base; + ubyte line_range; + ubyte opcode_base; + const(ubyte)[] standard_opcode_lengths; + // Directory and file tables stored as slices into scratch buffers. + // The caller owns the scratch; the LineNumberProgram just borrows. + const(char)[][] include_directories; + size_t num_dirs; + SourceFile[] source_files; + size_t num_files; + const(ubyte)[] program; +} + +private struct StateMachine +{ + const(void)* address; + uint operation_index = 0; + uint file_index = 1; + int line = 1; + uint column = 0; + bool is_statement; + bool is_end_sequence = false; +} + +private enum StandardOpcode : ubyte +{ + extended_op = 0, + copy = 1, + advance_pc = 2, + advance_line = 3, + set_file = 4, + set_column = 5, + negate_statement = 6, + set_basic_block = 7, + const_add_pc = 8, + fixed_advance_pc = 9, + set_prologue_end = 10, + set_epilogue_begin = 11, + set_isa = 12, +} + +private enum ExtendedOpcode : ubyte +{ + end_sequence = 1, + set_address = 2, + define_file = 3, + set_discriminator = 4, +} + +// --- LEB128 and DWARF helpers ------------------------------------- + +private T dw_read(T)(ref const(ubyte)[] buf) nothrow @nogc @trusted +{ + if (buf.length < T.sizeof) + return T.init; + version (X86_64) + T result = *cast(const(T)*) buf.ptr; + else version (X86) + T result = *cast(const(T)*) buf.ptr; + else + { + T result = void; + memcpy(&result, buf.ptr, T.sizeof); + } + buf = buf[T.sizeof .. $]; + return result; +} + +private const(char)[] dw_read_stringz(ref const(ubyte)[] buf) nothrow @nogc @trusted +{ + auto p = cast(const(char)*) buf.ptr; + auto len = strlen(p); + buf = buf[len + 1 .. $]; + return p[0 .. len]; +} + +private ulong dw_read_uleb128(ref const(ubyte)[] buf) nothrow @nogc +{ + ulong val = 0; + uint shift = 0; + while (buf.length > 0) + { + ubyte b = buf[0]; buf = buf[1 .. $]; + val |= cast(ulong)(b & 0x7f) << shift; + if ((b & 0x80) == 0) break; + shift += 7; + } + return val; +} + +private long dw_read_sleb128(ref const(ubyte)[] buf) nothrow @nogc +{ + long val = 0; + uint shift = 0; + ubyte b; + while (buf.length > 0) + { + b = buf[0]; buf = buf[1 .. $]; + val |= cast(long)(b & 0x7f) << shift; + shift += 7; + if ((b & 0x80) == 0) break; + } + if (shift < 64 && (b & 0x40) != 0) + val |= -(cast(long) 1 << shift); + return val; +} + +// --- DWARF v5 entry format ---------------------------------------- + +private enum DW_LNCT : ushort +{ + path = 1, + directory_index = 2, +} + +private enum DW_FORM : ubyte +{ + data1 = 11, + data2 = 5, + data4 = 6, + data8 = 7, + data16 = 30, + string_ = 8, + strp = 14, + line_strp = 31, + udata = 15, + block = 9, + strx = 26, + strx1 = 37, + strx2 = 38, + strx3 = 39, + strx4 = 40, + sec_offset = 23, + sdata = 13, + flag = 12, + flag_present = 25, +} + +private struct EntryFormatPair +{ + DW_LNCT type; + DW_FORM form; +} + +/// Skip a DWARF form value we don't care about. +private void dw_skip_form(ref const(ubyte)[] data, DW_FORM form, bool is64bit) nothrow @nogc +{ + with (DW_FORM) switch (form) + { + case strp, line_strp, sec_offset: + data = data[is64bit ? 8 : 4 .. $]; break; + case data1, strx1, flag, flag_present: + data = data[1 .. $]; break; + case data2, strx2: + data = data[2 .. $]; break; + case strx3: + data = data[3 .. $]; break; + case data4, strx4: + data = data[4 .. $]; break; + case data8: + data = data[8 .. $]; break; + case data16: + data = data[16 .. $]; break; + case udata, strx, sdata: + dw_read_uleb128(data); break; + case block: + auto length = cast(size_t) dw_read_uleb128(data); + data = data[length .. $]; break; + default: + break; + } +} + +// --- Read DWARF line number program header ------------------------ + +// Scratch buffers allocated on the stack for directory/file tables. +// 256 entries each should be more than enough for any compilation unit. +private enum MAX_DIRS = 256; +private enum MAX_FILES = 512; + +private LineNumberProgram dw_read_line_number_program(ref const(ubyte)[] data) nothrow @nogc @trusted +{ + const original_data = data; + LineNumberProgram lp; + + bool is_64bit_dwarf = false; + lp.unit_length = dw_read!uint(data); + if (lp.unit_length == uint.max) + { + is_64bit_dwarf = true; + lp.unit_length = dw_read!ulong(data); + } + + const version_field_offset = cast(size_t)(data.ptr - original_data.ptr); + lp.dwarf_version = dw_read!ushort(data); + + if (lp.dwarf_version >= 5) + { + lp.address_size = dw_read!ubyte(data); + lp.segment_selector_size = dw_read!ubyte(data); + } + + lp.header_length = is_64bit_dwarf ? dw_read!ulong(data) : dw_read!uint(data); + + const min_insn_field_offset = cast(size_t)(data.ptr - original_data.ptr); + lp.minimum_instruction_length = dw_read!ubyte(data); + lp.maximum_operations_per_instruction = (lp.dwarf_version >= 4) ? dw_read!ubyte(data) : 1; + lp.default_is_statement = (dw_read!ubyte(data) != 0); + lp.line_base = dw_read!byte(data); + lp.line_range = dw_read!ubyte(data); + lp.opcode_base = dw_read!ubyte(data); + + lp.standard_opcode_lengths = data[0 .. lp.opcode_base - 1]; + data = data[lp.opcode_base - 1 .. $]; + + if (lp.dwarf_version >= 5) + { + // DWARF v5: directory format + entries + auto num_pairs = dw_read!ubyte(data); + EntryFormatPair[8] dir_fmt = void; + foreach (i; 0 .. num_pairs) + { + if (i < 8) + { + dir_fmt[i].type = cast(DW_LNCT) dw_read_uleb128(data); + dir_fmt[i].form = cast(DW_FORM) dw_read_uleb128(data); + } + } + + lp.num_dirs = cast(size_t) dw_read_uleb128(data); + // Caller must provide scratch buffers; we use __gshared static for simplicity. + foreach (d; 0 .. lp.num_dirs) + { + foreach (p; 0 .. num_pairs) + { + if (p < 8 && dir_fmt[p].type == DW_LNCT.path && dir_fmt[p].form == DW_FORM.string_) + { + if (d < MAX_DIRS) + _dir_scratch[d] = dw_read_stringz(data); + else + dw_read_stringz(data); + } + else if (p < 8) + dw_skip_form(data, dir_fmt[p].form, is_64bit_dwarf); + } + } + if (lp.num_dirs > MAX_DIRS) lp.num_dirs = MAX_DIRS; + lp.include_directories = _dir_scratch[0 .. lp.num_dirs]; + + // File format + entries + num_pairs = dw_read!ubyte(data); + EntryFormatPair[8] file_fmt = void; + foreach (i; 0 .. num_pairs) + { + if (i < 8) + { + file_fmt[i].type = cast(DW_LNCT) dw_read_uleb128(data); + file_fmt[i].form = cast(DW_FORM) dw_read_uleb128(data); + } + } + + lp.num_files = cast(size_t) dw_read_uleb128(data); + foreach (f; 0 .. lp.num_files) + { + SourceFile sf; + sf.file = ""; + foreach (p; 0 .. num_pairs) + { + if (p < 8 && file_fmt[p].type == DW_LNCT.path && file_fmt[p].form == DW_FORM.string_) + sf.file = dw_read_stringz(data); + else if (p < 8 && file_fmt[p].type == DW_LNCT.directory_index) + { + if (file_fmt[p].form == DW_FORM.data1) + sf.dir_index = dw_read!ubyte(data); + else if (file_fmt[p].form == DW_FORM.data2) + sf.dir_index = dw_read!ushort(data); + else if (file_fmt[p].form == DW_FORM.udata) + sf.dir_index = cast(size_t) dw_read_uleb128(data); + else + dw_skip_form(data, file_fmt[p].form, is_64bit_dwarf); + sf.dir_index++; // DWARF v5 indices are 0-based, normalize to 1-based + } + else if (p < 8) + dw_skip_form(data, file_fmt[p].form, is_64bit_dwarf); + } + if (f < MAX_FILES) + _file_scratch[f] = sf; + } + if (lp.num_files > MAX_FILES) lp.num_files = MAX_FILES; + lp.source_files = _file_scratch[0 .. lp.num_files]; + } + else + { + // DWARF v3/v4: NUL-terminated sequences + lp.num_dirs = 0; + while (data.length > 0 && data[0] != 0) + { + auto dir = dw_read_stringz(data); + if (lp.num_dirs < MAX_DIRS) + _dir_scratch[lp.num_dirs++] = dir; + } + if (data.length > 0) data = data[1 .. $]; // skip NUL terminator + lp.include_directories = _dir_scratch[0 .. lp.num_dirs]; + + lp.num_files = 0; + while (data.length > 0 && data[0] != 0) + { + SourceFile sf; + sf.file = dw_read_stringz(data); + sf.dir_index = cast(size_t) dw_read_uleb128(data); + dw_read_uleb128(data); // last modification time + dw_read_uleb128(data); // file length + if (lp.num_files < MAX_FILES) + _file_scratch[lp.num_files++] = sf; + } + if (data.length > 0) data = data[1 .. $]; // skip NUL terminator + lp.source_files = _file_scratch[0 .. lp.num_files]; + } + + const program_start = cast(size_t)(min_insn_field_offset + lp.header_length); + const program_end = cast(size_t)(version_field_offset + lp.unit_length); + if (program_start <= original_data.length && program_end <= original_data.length) + lp.program = original_data[program_start .. program_end]; + + data = (program_end <= original_data.length) ? original_data[program_end .. $] : null; + + return lp; +} + +// Static scratch buffers for DWARF parsing (debug-only, no allocator needed). +private __gshared const(char)[][MAX_DIRS] _dir_scratch; +private __gshared SourceFile[MAX_FILES] _file_scratch; + +// --- DWARF state machine - resolve addresses to file:line --------- + +private struct ResolvedLocation +{ + const(char)[] file; + const(char)[] dir; + int line = -1; +} + +/// Resolve an array of addresses to file:line using .debug_line data. +private void dw_resolve_addresses( + const(ubyte)[] debug_line_data, + const(void*)[] addresses, + ResolvedLocation[] results, + size_t base_address) nothrow @nogc @trusted +{ + size_t found = 0; + const num_addrs = addresses.length; + + while (debug_line_data.length > 0 && found < num_addrs) + { + auto lp = dw_read_line_number_program(debug_line_data); + if (lp.program.length == 0) + break; + + StateMachine machine; + machine.is_statement = lp.default_is_statement; + + LocationInfo last_loc = LocationInfo(-1, -1); + const(void)* last_address; + + const(ubyte)[] prog = lp.program; + while (prog.length > 0) + { + size_t advance_addr(size_t op_advance) + { + const inc = lp.minimum_instruction_length * + ((machine.operation_index + op_advance) / lp.maximum_operations_per_instruction); + machine.address += inc; + machine.operation_index = + (machine.operation_index + op_advance) % lp.maximum_operations_per_instruction; + return inc; + } + + void emit_row(bool is_end) + { + auto addr = machine.address + base_address; + + foreach (idx; 0 .. num_addrs) + { + if (results[idx].line != -1) + continue; + auto target = addresses[idx]; + + void apply_loc(LocationInfo loc) + { + auto file_idx = loc.file - (lp.dwarf_version < 5 ? 1 : 0); + if (file_idx >= 0 && file_idx < lp.num_files) + { + results[idx].file = lp.source_files[file_idx].file; + auto di = lp.source_files[file_idx].dir_index; + if (di > 0 && di <= lp.num_dirs) + results[idx].dir = lp.include_directories[di - 1]; + } + results[idx].line = loc.line; + found++; + } + + if (target == addr) + apply_loc(LocationInfo(machine.file_index, machine.line)); + else if (last_address !is null && target > last_address && target < addr) + apply_loc(last_loc); + } + + if (is_end) + last_address = null; + else + { + last_address = addr; + last_loc = LocationInfo(machine.file_index, machine.line); + } + } + + ubyte opcode = prog[0]; prog = prog[1 .. $]; + + if (opcode >= lp.opcode_base) + { + // Special opcode + opcode -= lp.opcode_base; + advance_addr(opcode / lp.line_range); + machine.line += lp.line_base + (opcode % lp.line_range); + emit_row(false); + } + else if (opcode == 0) + { + // Extended opcode + auto len = cast(size_t) dw_read_uleb128(prog); + if (prog.length == 0) break; + ubyte eopcode = prog[0]; prog = prog[1 .. $]; + + switch (eopcode) + { + case ExtendedOpcode.end_sequence: + machine.is_end_sequence = true; + emit_row(true); + machine = StateMachine.init; + machine.is_statement = lp.default_is_statement; + break; + case ExtendedOpcode.set_address: + machine.address = dw_read!(const(void)*)(prog); + machine.operation_index = 0; + break; + case ExtendedOpcode.set_discriminator: + dw_read_uleb128(prog); + break; + default: + if (len > 1) + prog = prog[len - 1 .. $]; + break; + } + } + else switch (opcode) with (StandardOpcode) + { + case copy: + emit_row(false); + break; + case advance_pc: + advance_addr(cast(size_t) dw_read_uleb128(prog)); + break; + case advance_line: + machine.line += cast(int) dw_read_sleb128(prog); + break; + case set_file: + machine.file_index = cast(uint) dw_read_uleb128(prog); + break; + case set_column: + machine.column = cast(uint) dw_read_uleb128(prog); + break; + case negate_statement: + machine.is_statement = !machine.is_statement; + break; + case set_basic_block: + break; + case const_add_pc: + advance_addr((255 - lp.opcode_base) / lp.line_range); + break; + case fixed_advance_pc: + machine.address += dw_read!ushort(prog); + machine.operation_index = 0; + break; + case set_prologue_end: + case set_epilogue_begin: + break; + case set_isa: + dw_read_uleb128(prog); + break; + default: + // Unknown standard opcode: skip according to standard_opcode_lengths + if (opcode > 0 && opcode <= lp.standard_opcode_lengths.length) + { + foreach (_; 0 .. lp.standard_opcode_lengths[opcode - 1]) + dw_read_uleb128(prog); + } + break; + } + } + } +} + + + + +// --- Driver interface -------------------------------------------------- +// +// All three primitives assume they are called through a one-level public +// wrapper in urt.internal.exception (kept non-inlined via pragma(inline, +// false)). The wrapper's frame is accounted for in the skip counts +// below. Direct callers (like _d_createTrace) get the same semantics +// because their frame substitutes for the wrapper's. + +/// Capture the caller's call stack. First entry = return address of +/// the function that called the public `capture_trace` wrapper. +size_t _capture_trace(void*[] addrs) nothrow @nogc @trusted +{ + if (addrs.length == 0) + return 0; + + version (D_InlineAsm_X86_64) + { + size_t bp; + asm nothrow @nogc { mov bp, RBP; } + // Walk from _capture_trace's own frame up, storing caller frames. + size_t n = 0; + // Skip one: our own frame so first captured is wrapper/caller. + if (bp) + { + auto next = *cast(size_t*) bp; + if (next > bp) bp = next; + else bp = 0; + } + while (n < addrs.length) + { + if (!bp) break; + auto next_bp = *cast(size_t*) bp; + if (!next_bp || next_bp <= bp) break; + auto retaddr = *cast(void**)(bp + size_t.sizeof); + if (!retaddr) break; + addrs[n++] = retaddr; + bp = next_bp; + } + return n; + } + else version (D_InlineAsm_X86) + { + size_t bp; + asm nothrow @nogc { mov bp, EBP; } + size_t n = 0; + if (bp) + { + auto next = *cast(size_t*) bp; + if (next > bp) bp = next; + else bp = 0; + } + while (n < addrs.length) + { + if (!bp) break; + auto next_bp = *cast(size_t*) bp; + if (!next_bp || next_bp <= bp) break; + auto retaddr = *cast(void**)(bp + size_t.sizeof); + if (!retaddr) break; + addrs[n++] = retaddr; + bp = next_bp; + } + return n; + } + else + { + StackTraceData tmp; + unwind_backtrace(tmp); // skips its own frames internally + auto n = tmp.length < addrs.length ? tmp.length : addrs.length; + addrs[0 .. n] = tmp.addrs[0 .. n]; + return n; + } +} + +/// Return the return address of the `skip`-th frame above the public +/// `caller_address` wrapper's caller. +void* _caller_address(uint skip) nothrow @nogc @trusted +{ + void*[32] buf = void; + const n = _capture_trace(buf[]); + // _capture_trace, when invoked from _caller_address, lays out: + // buf[0] = PC inside the public wrapper + // buf[1] = PC inside USER (the caller_address caller) + // buf[2] = PC inside USER's caller ← skip=0 wants this + const want = skip + 2; + if (n <= want) + return null; + return buf[want]; +} + +/// Resolve via dladdr (symbol name + offset). File/line is omitted - +/// per-address DWARF scans would re-parse .debug_line on every call. +/// For full file:line info, use `_resolve_batch`, which amortises one +/// scan across all frames. +/// Returned `name` slice is owned by dladdr-internal storage - consume +/// before the next call. +bool _resolve_address(void* addr, out Resolved r) nothrow @nogc @trusted +{ + Dl_info info = void; + if (!dladdr(addr, &info) || info.dli_sname is null) + return false; + r.name = info.dli_sname[0 .. strlen(info.dli_sname)]; + r.offset = cast(size_t) addr - cast(size_t) info.dli_saddr; + return true; +} + +// Persistent .debug_line mapping. The first _resolve_batch call opens +// /proc/self/exe, finds .debug_line, mmap()s it, and closes the fd. +// We never unmap - the slice handed back to callers via Resolved.file +// and Resolved.dir points into this region, and the API contract +// promises stability until the next _resolve_batch call. Holding the +// mapping for process lifetime is the cheapest way to satisfy that +// (a few MB of read-only file pages the OS can demand-page). +private __gshared bool _elf_init_attempted; +private __gshared const(ubyte)[] _persistent_debug_line; +private __gshared size_t _persistent_base_addr; + +private void ensure_debug_line_mapped() nothrow @nogc @trusted +{ + if (_elf_init_attempted) + return; + _elf_init_attempted = true; + + auto elf = ElfSelf.open(); + scope(exit) elf.close(); + if (!elf.valid()) + return; + + size_t dbg_offset, dbg_size; + if (!elf.find_section(".debug_line", dbg_offset, dbg_size)) + return; + + auto region = MappedRegion.map(elf.fd, dbg_offset, dbg_size); + if (region.data is null) + return; + // Intentionally not unmapped - kept for process lifetime. + + _persistent_debug_line = region.data[0 .. dbg_size]; + _persistent_base_addr = (elf.ehdr.e_type == ET_DYN) + ? get_executable_base_address() : cast(size_t) 0; +} + +/// Resolve many addresses in one pass: dladdr per address for symbol +/// name + offset, then a single DWARF .debug_line scan to fill file / +/// dir / line for all of them at once. +/// +/// String slices point into dladdr-internal storage, the static DWARF +/// scratch buffers (`_dir_scratch`, `_file_scratch`), or the persistent +/// .debug_line mapping. All three remain valid until the next +/// `_resolve_batch` call overwrites the scratch buffers; copy if you +/// need fields to outlive that. +bool _resolve_batch(const(void*)[] addrs, Resolved[] results) nothrow @nogc @trusted +{ + // Symbol + offset via dladdr (per-address; dladdr is O(1)-ish via + // the dynamic linker's hash tables). + bool any_resolved = false; + foreach (i, a; addrs) + { + Dl_info info = void; + if (dladdr(cast(void*) a, &info) && info.dli_sname !is null) + { + results[i].name = info.dli_sname[0 .. strlen(info.dli_sname)]; + results[i].offset = cast(size_t) a - cast(size_t) info.dli_saddr; + any_resolved = true; + } + } + + // File / line via one DWARF .debug_line scan covering all addrs. + // Failures here still leave dladdr-populated name/offset intact. + ensure_debug_line_mapped(); + if (_persistent_debug_line.length == 0) + return any_resolved; + + ResolvedLocation[32] locations; + const n = addrs.length > locations.length ? locations.length : addrs.length; + + dw_resolve_addresses( + _persistent_debug_line, + addrs[0 .. n], + locations[0 .. n], + _persistent_base_addr); + + foreach (i; 0 .. n) + { + if (locations[i].line >= 0) + { + results[i].file = locations[i].file; + results[i].dir = locations[i].dir; + results[i].line = cast(uint) locations[i].line; + any_resolved = true; + } + } + return any_resolved; +} diff --git a/src/urt/driver/posix/gpio.d b/src/urt/driver/posix/gpio.d new file mode 100644 index 0000000..03fca09 --- /dev/null +++ b/src/urt/driver/posix/gpio.d @@ -0,0 +1,133 @@ +// Linux GPIO via legacy /sys/class/gpio sysfs. Targets SBCs (Pi, Orange +// Pi, BeagleBone) where the kernel exposes pins as files. Pin numbering +// is the kernel's flat global GPIO number; each board documents its +// mapping. +// +// Sysfs is deprecated since Linux 4.8 in favour of gpio-cdev +// (/dev/gpiochipN ioctl) but ships in every shipping kernel. cdev +// backend is a future TODO. +// +// Limitations: no pull config (sysfs doesn't expose it; pulls come from +// device tree or external resistors), no peripheral muxing (kernel +// owns it), and one open/close per op (~10us each, fine for management +// rates). +module urt.driver.posix.gpio; + +import urt.driver.gpio : Pull, DriveMode; +import urt.file : File, FileOpenMode, save_file, open, close, read; +import urt.mem.temp : tconcat; + +nothrow @nogc: + + +enum uint num_gpio = 256; +enum bool has_pull_up = false; +enum bool has_pull_down = false; +enum bool has_open_drain = false; +enum bool has_pin_function_muxing = false; + + +// Walks /sys/class/gpio/gpiochip/{base,ngpio} for N in 0..64 (covers +// real SBCs incl. Pi 5 where chips are at 0 and 4) and returns +// max(base+ngpio). 0 means no GPIO chips registered in sysfs. +uint gpio_count() +{ + uint max_pin = 0; + foreach (i; 0 .. 64) + { + uint base, ngpio; + if (!read_uint_file(tconcat("/sys/class/gpio/gpiochip", i, "/base"), base)) + continue; + if (!read_uint_file(tconcat("/sys/class/gpio/gpiochip", i, "/ngpio"), ngpio)) + continue; + uint top = base + ngpio; + if (top > max_pin) + max_pin = top; + } + return max_pin; +} + +void gpio_output_init(uint pin, bool initial = false, DriveMode mode = DriveMode.push_pull) +{ + assert(mode == DriveMode.push_pull, "posix gpio: open-drain not supported via sysfs"); + cast(void) save_file("/sys/class/gpio/export", tconcat(pin)); // EBUSY = already exported + // "low" / "high" atomically set direction=out plus initial value. + cast(void) save_file(tconcat("/sys/class/gpio/gpio", pin, "/direction"), initial ? "high" : "low"); +} + +void gpio_input_init(uint pin, Pull pull = Pull.none) +{ + cast(void) pull; + cast(void) save_file("/sys/class/gpio/export", tconcat(pin)); + cast(void) save_file(tconcat("/sys/class/gpio/gpio", pin, "/direction"), "in"); +} + +void gpio_output_set(uint pin, bool value) +{ + cast(void) save_file(tconcat("/sys/class/gpio/gpio", pin, "/value"), value ? "1" : "0"); +} + +void gpio_output_toggle(uint pin) +{ + auto path = tconcat("/sys/class/gpio/gpio", pin, "/value"); + bool current; + if (!read_bool_file(path, current)) + return; + cast(void) save_file(path, current ? "0" : "1"); +} + +bool gpio_input_read(uint pin) +{ + bool v; + return read_bool_file(tconcat("/sys/class/gpio/gpio", pin, "/value"), v) && v; +} + +void gpio_set_pull(uint pin, Pull pull) +{ + cast(void) pin; cast(void) pull; +} + +void gpio_release(uint pin) +{ + cast(void) save_file("/sys/class/gpio/unexport", tconcat(pin)); +} + + +private: + +bool read_bool_file(const(char)[] path, out bool value) +{ + File f; + if (!f.open(path, FileOpenMode.ReadExisting)) + return false; + ubyte[2] buf; + size_t n; + auto r = f.read(buf, n); + f.close(); + if (!r || n == 0) + return false; + value = buf[0] == '1'; + return true; +} + +bool read_uint_file(const(char)[] path, out uint value) +{ + File f; + if (!f.open(path, FileOpenMode.ReadExisting)) + return false; + ubyte[16] buf; + size_t n; + auto r = f.read(buf, n); + f.close(); + if (!r) + return false; + uint v = 0; + foreach (c; buf[0 .. n]) + { + if (c < '0' || c > '9') + break; + v = v * 10 + (c - '0'); + } + value = v; + return true; +} diff --git a/src/urt/driver/rp2350/alloc.d b/src/urt/driver/rp2350/alloc.d new file mode 100644 index 0000000..44d71e9 --- /dev/null +++ b/src/urt/driver/rp2350/alloc.d @@ -0,0 +1,37 @@ +module urt.driver.rp2350.alloc; + +import urt.mem.alloc : MemFlags; + +nothrow @nogc: + +enum has_realloc = false; +enum has_expand = false; +enum has_memsize = false; +enum has_exec = false; +enum has_retain = false; +enum has_memflags = false; + +void[] _alloc(size_t size, size_t alignment, MemFlags) pure +{ + import urt.util : align_down; + + size_t header_size = (void*).sizeof + alignment; + void* p = malloc(header_size + size); + if (p is null) + return null; + + size_t allocptr = align_down(cast(size_t)p + header_size, alignment); + (cast(void**)allocptr)[-1] = p; + return (cast(void*)allocptr)[0 .. size]; +} + +void _free(void* ptr) pure +{ + free((cast(void**)ptr)[-1]); +} + + +private: + +extern(C) void* malloc(size_t size) pure; +extern(C) void free(void* ptr) pure; diff --git a/src/urt/driver/rp2350/boot2.S b/src/urt/driver/rp2350/boot2.S new file mode 100644 index 0000000..4d9514b --- /dev/null +++ b/src/urt/driver/rp2350/boot2.S @@ -0,0 +1,69 @@ +/* RP2350 second-stage bootloader (boot2) + * + * The RP2350 ROM reads this 256-byte block from the start of QSPI flash, + * copies it to SRAM, and executes it. boot2's job is to configure the + * QSPI controller for XIP (execute-in-place), then return to the ROM + * which will then load the vector table from flash. + * + * This is a minimal stub that configures generic 03h SPI read mode. + * It works with any standard SPI flash but is slow (single-bit, no XIP cache). + * For production, replace with the Pico SDK's boot2_w25q080.S (or similar) + * which enables QSPI and continuous read mode for much better performance. + * + * The block must be exactly 256 bytes. The last 4 bytes are a CRC32 + * checksum of the first 252 bytes (verified by the ROM before execution). + */ + + .syntax unified + .cpu cortex-m33 + .thumb + + .section .boot2, "ax" + .global __boot2_entry + .type __boot2_entry, %function +__boot2_entry: + +/* SSI (Synopsys SSI / QSPI controller) registers */ +.equ SSI_BASE, 0x18000000 +.equ SSI_CTRLR0, 0x00 +.equ SSI_SSIENR, 0x08 /* SSI enable */ +.equ SSI_SPI_CTRLR0, 0xF4 /* SPI control register */ + +/* Configure SSI for standard SPI 03h read, 32-bit address+data frames */ + + /* Disable SSI while configuring */ + ldr r3, =SSI_BASE + movs r0, #0 + str r0, [r3, #SSI_SSIENR] + + /* CTRLR0: 8-bit data frames, SPI mode 0 (CPOL=0, CPHA=0) */ + movs r0, #0x07 /* DFS = 8-1 = 7 (8-bit frames) */ + movs r1, #0 + orr r0, r0, r1 /* TMOD=0 (TX+RX), FRF=0 (Motorola SPI) */ + str r0, [r3, #SSI_CTRLR0] + + /* SPI_CTRLR0: standard 03h read, 24-bit address, 8 wait cycles */ + movs r0, #0x03 /* INST_L=8bit (0x03), ADDR_L=24bit */ + lsls r1, r0, #0 + movs r0, #0x06 /* ADDR_L = 24 bits (6 * 4) */ + lsls r0, r0, #2 + orr r1, r1, r0 + /* TRANS_TYPE = 0 (instruction + address both in standard SPI) */ + str r1, [r3, #SSI_SPI_CTRLR0] + + /* Re-enable SSI */ + movs r0, #1 + str r0, [r3, #SSI_SSIENR] + + /* Return to ROM -- it will now use XIP to read the vector table */ + bx lr + + .size __boot2_entry, . - __boot2_entry + + /* Pad to 252 bytes (256 - 4 byte CRC32 at end) */ + .balign 256, 0x00 + /* CRC32 placeholder -- must be computed by the build tooling or + * flash programmer. The Pico SDK's pad_checksum tool does this. + * For now, set to zero -- will need post-processing before flashing. */ + + .size __boot2_entry, . - __boot2_entry diff --git a/src/urt/driver/rp2350/irq.d b/src/urt/driver/rp2350/irq.d new file mode 100644 index 0000000..d461f77 --- /dev/null +++ b/src/urt/driver/rp2350/irq.d @@ -0,0 +1,59 @@ +// RP2350 interrupt controller driver +// +// Cortex-M33 uses the standard ARM NVIC (Nested Vectored Interrupt Controller). +// RP2350 has 52 peripheral interrupts (IRQ 0-51). +module urt.driver.rp2350.irq; + +@nogc nothrow: + +enum bool has_plic = false; +enum bool has_nvic = true; +enum bool has_per_irq_control = true; +enum bool has_irq_priority = true; +enum bool has_wait_for_interrupt = false; +enum bool has_irq_diagnostics = false; +enum uint irq_max = 52; + +import core.volatile; + +// NVIC registers (ARM standard) +private enum ulong NVIC_ISER0 = 0xE000E100; // Interrupt Set Enable (2 words for 52 IRQs) +private enum ulong NVIC_ICER0 = 0xE000E180; // Interrupt Clear Enable +private enum ulong NVIC_ISPR0 = 0xE000E200; // Interrupt Set Pending +private enum ulong NVIC_ICPR0 = 0xE000E280; // Interrupt Clear Pending +private enum ulong NVIC_IPR0 = 0xE000E400; // Interrupt Priority (byte-accessible) + +// Globally disable interrupts (set PRIMASK) +void irq_disable() +{ + asm @nogc nothrow { "cpsid i"; } +} + +// Globally enable interrupts (clear PRIMASK) +void irq_enable() +{ + asm @nogc nothrow { "cpsie i"; } +} + +// Enable a specific peripheral IRQ (0-51) +void irq_set_enable(uint irq_num) +{ + immutable reg = irq_num / 32; + immutable bit = irq_num % 32; + volatileStore(cast(uint*)(NVIC_ISER0 + reg * 4), 1u << bit); +} + +// Disable a specific peripheral IRQ +void irq_clear_enable(uint irq_num) +{ + immutable reg = irq_num / 32; + immutable bit = irq_num % 32; + volatileStore(cast(uint*)(NVIC_ICER0 + reg * 4), 1u << bit); +} + +// Set priority for a peripheral IRQ (0 = highest, 255 = lowest) +// Cortex-M33 on RP2350 implements 4 priority bits (top 4 of 8) +void irq_set_priority(uint irq_num, ubyte priority) +{ + volatileStore(cast(ubyte*)(NVIC_IPR0 + irq_num), priority); +} diff --git a/src/urt/driver/rp2350/package.d b/src/urt/driver/rp2350/package.d new file mode 100644 index 0000000..b19fd8d --- /dev/null +++ b/src/urt/driver/rp2350/package.d @@ -0,0 +1,96 @@ +// RP2350 platform package (ARM Cortex-M33) +// +// Provides sys_init() as the single entry point for all +// hardware initialization. Called from start.S before main(). +module urt.driver.rp2350; + +public import urt.driver.rp2350.uart; +public import urt.driver.rp2350.irq; +public import urt.driver.rp2350.timer; + +import urt.driver.uart : UartConfig; +import core.volatile; + +@nogc nothrow: + +private extern(C) void __register_frame_info(const void*, void*); +private extern(C) extern const ubyte __eh_frame_start; +private ubyte[48] __eh_frame_object; // storage for libgcc (no-op on ARM EHABI) + +// RP2350 peripheral base addresses +enum ulong RESETS_BASE = 0x40020000; +enum ulong CLOCKS_BASE = 0x40010000; +enum ulong XOSC_BASE = 0x40048000; +enum ulong PLL_SYS_BASE = 0x40060000; +enum ulong SIO_BASE = 0xD0000000; +enum ulong IO_BANK0_BASE = 0x40028000; +enum ulong PADS_BANK0_BASE = 0x40038000; + +// Atomic set/clear/xor aliases (RP2350 address alias trick) +enum ulong REG_ALIAS_SET = 0x00002000; +enum ulong REG_ALIAS_CLR = 0x00003000; + +// RESETS register offsets +enum ulong RESETS_RESET = 0x00; +enum ulong RESETS_DONE = 0x08; + +// Reset bits for peripherals we need early +enum uint RESET_UART0 = 1 << 26; +enum uint RESET_UART1 = 1 << 27; +enum uint RESET_IO_BANK0 = 1 << 8; +enum uint RESET_PADS_BANK0 = 1 << 9; + +private void mmio_write(ulong addr, uint val) @nogc nothrow +{ + volatileStore(cast(uint*)addr, val); +} + +private uint mmio_read(ulong addr) @nogc nothrow +{ + return volatileLoad(cast(uint*)addr); +} + +// Take peripherals out of reset and wait for them to be ready +private void unreset_wait(uint bits) +{ + // Clear reset bits (take out of reset) + mmio_write(RESETS_BASE + RESETS_RESET + REG_ALIAS_CLR, bits); + // Wait for reset done + while ((mmio_read(RESETS_BASE + RESETS_DONE) & bits) != bits) + {} +} + +// Initialize all clocks, peripherals, and I/O needed at boot. +// Order matters: +// 1. Unreset GPIO and UART pads +// 2. Configure GPIO pins for UART0 +// 3. Initialize UART0 for console output +// 4. Set up SysTick timer +extern(C) void sys_init() +{ + __register_frame_info(&__eh_frame_start, &__eh_frame_object); + + // Bring up IO bank and pads, then UART0 + unreset_wait(RESET_IO_BANK0 | RESET_PADS_BANK0 | RESET_UART0); + + // GPIO0 = UART0 TX, GPIO1 = UART0 RX (function 2 on RP2350) + // IO_BANK0 GPIO_CTRL registers are at offset 0x04 + n*0x08 + enum ulong GPIO0_CTRL = IO_BANK0_BASE + 0x04; + enum ulong GPIO1_CTRL = IO_BANK0_BASE + 0x0C; + mmio_write(GPIO0_CTRL, 2); // FUNCSEL = UART + mmio_write(GPIO1_CTRL, 2); // FUNCSEL = UART + + // Init UART0 at default baud for early console + uart_hw_init(0, UartConfig.init); + + uart0_hw_puts("RP2350: sys_init\r\n"); + + // SysTick: 20Hz tick (50ms) for the main loop + // Default clock is ~150MHz after PLL init, but we're running on the + // ring oscillator (~6MHz) until clock init is implemented. + // SysTick reload = clock_hz / desired_hz - 1 + // At 6MHz ring osc: 6_000_000 / 20 - 1 = 299_999 + timer_init(299_999); + + uart0_hw_puts("RP2350: ready\r\n"); +} diff --git a/src/urt/driver/rp2350/start.S b/src/urt/driver/rp2350/start.S new file mode 100644 index 0000000..606aaa9 --- /dev/null +++ b/src/urt/driver/rp2350/start.S @@ -0,0 +1,244 @@ +/* RP2350 (ARM Cortex-M33) startup + * + * Boot flow: + * 1. ROM bootloader loads 256-byte boot2 from flash, runs it from SRAM + * 2. boot2 configures QSPI flash for XIP, then returns to ROM + * 3. ROM reads vector table from flash: initial SP + Reset_Handler + * 4. Reset_Handler runs: + * a. Copy .got from flash to SRAM + * b. Copy .data from flash to SRAM + * c. Copy .tdata from flash to SRAM + * d. Zero .tbss + .bss + * e. Enable FPU (Cortex-M33 has optional FPv5-SP) + * f. Call sys_init (platform init) + * g. Run .init_array (D module constructors) + * h. Call main + * + * RP2350B register addresses: + * RESETS: 0x40020000 (peripheral reset control) + * CLOCKS: 0x40010000 + * SIO: 0xD0000000 (single-cycle I/O for GPIO, spinlocks) + * UART0: 0x40070000 + * UART1: 0x40078000 + */ + + .syntax unified + .cpu cortex-m33 + .fpu fpv5-sp-d16 + .thumb + +/* ================================================================ + * Vector table -- placed at start of FLASH (after boot2) + * Cortex-M reads entry 0 as initial SP, entry 1 as Reset_Handler + * ================================================================ */ + .section .vector_table, "a" + .global __vector_table + .type __vector_table, %object +__vector_table: + .word _stack_top /* 0: Initial stack pointer */ + .word Reset_Handler /* 1: Reset */ + .word NMI_Handler /* 2: NMI */ + .word HardFault_Handler /* 3: Hard fault */ + .word MemManage_Handler /* 4: MPU fault */ + .word BusFault_Handler /* 5: Bus fault */ + .word UsageFault_Handler /* 6: Usage fault */ + .word SecureFault_Handler /* 7: Secure fault (ARMv8-M) */ + .word 0 /* 8: Reserved */ + .word 0 /* 9: Reserved */ + .word 0 /* 10: Reserved */ + .word SVCall_Handler /* 11: SVCall */ + .word DebugMon_Handler /* 12: Debug monitor */ + .word 0 /* 13: Reserved */ + .word PendSV_Handler /* 14: PendSV */ + .word SysTick_Handler /* 15: SysTick */ + + /* IRQ 0-51 (RP2350 has 52 peripheral interrupts) */ + .rept 52 + .word Default_Handler + .endr + + .size __vector_table, . - __vector_table + +/* ================================================================ + * Reset handler + * ================================================================ */ + .section .text, "ax" + .global Reset_Handler + .type Reset_Handler, %function + .thumb_func +Reset_Handler: + + /* -- Copy .got from flash (LMA) to SRAM (VMA) -- */ + ldr r0, =_got_start + ldr r1, =_got_load + ldr r2, =_got_end + cmp r0, r1 /* skip if VMA == LMA (not relocated) */ + beq .Lgot_done + b .Lgot_check +.Lgot_copy: + ldm r1!, {r3} + stm r0!, {r3} +.Lgot_check: + cmp r0, r2 + blo .Lgot_copy +.Lgot_done: + + /* -- Copy .data from flash to SRAM -- */ + ldr r0, =_data_start + ldr r1, =_data_load + ldr r2, =_data_end + b .Ldata_check +.Ldata_copy: + ldm r1!, {r3} + stm r0!, {r3} +.Ldata_check: + cmp r0, r2 + blo .Ldata_copy + + /* -- Copy .tdata from flash to SRAM -- */ + ldr r0, =_tdata_start + ldr r1, =_tdata_load + ldr r2, =_tdata_end + b .Ltdata_check +.Ltdata_copy: + ldm r1!, {r3} + stm r0!, {r3} +.Ltdata_check: + cmp r0, r2 + blo .Ltdata_copy + + /* -- Zero .tbss -- */ + ldr r0, =_tbss_start + ldr r2, =_tbss_end + movs r3, #0 + b .Ltbss_check +.Ltbss_zero: + stm r0!, {r3} +.Ltbss_check: + cmp r0, r2 + blo .Ltbss_zero + + /* -- Zero .bss -- */ + ldr r0, =_bss_start + ldr r2, =_bss_end + movs r3, #0 + b .Lbss_check +.Lbss_zero: + stm r0!, {r3} +.Lbss_check: + cmp r0, r2 + blo .Lbss_zero + + /* -- Enable FPU (CP10 + CP11 full access) -- */ + ldr r0, =0xE000ED88 /* CPACR */ + ldr r1, [r0] + orr r1, r1, #(0xF << 20) + str r1, [r0] + dsb + isb + + /* -- Platform init -- */ + bl sys_init + + /* -- Run .init_array (D module constructors) -- */ + ldr r4, =__init_array_start + ldr r5, =__init_array_end + b .Linit_check +.Linit_loop: + ldr r0, [r4] + blx r0 + adds r4, r4, #4 +.Linit_check: + cmp r4, r5 + blo .Linit_loop + + /* -- Enter main (should never return) -- */ + bl main + + /* If main returns, spin */ +.Lhalt: + wfi + b .Lhalt + + .size Reset_Handler, . - Reset_Handler + +/* ================================================================ + * Default exception/interrupt handlers (weak, overridable) + * ================================================================ */ + .thumb_func + .weak NMI_Handler + .type NMI_Handler, %function +NMI_Handler: + b . + + .thumb_func + .weak HardFault_Handler + .type HardFault_Handler, %function +HardFault_Handler: + b . + + .thumb_func + .weak MemManage_Handler + .type MemManage_Handler, %function +MemManage_Handler: + b . + + .thumb_func + .weak BusFault_Handler + .type BusFault_Handler, %function +BusFault_Handler: + b . + + .thumb_func + .weak UsageFault_Handler + .type UsageFault_Handler, %function +UsageFault_Handler: + b . + + .thumb_func + .weak SecureFault_Handler + .type SecureFault_Handler, %function +SecureFault_Handler: + b . + + .thumb_func + .weak SVCall_Handler + .type SVCall_Handler, %function +SVCall_Handler: + b . + + .thumb_func + .weak DebugMon_Handler + .type DebugMon_Handler, %function +DebugMon_Handler: + b . + + .thumb_func + .weak PendSV_Handler + .type PendSV_Handler, %function +PendSV_Handler: + b . + + .thumb_func + .weak SysTick_Handler + .type SysTick_Handler, %function +SysTick_Handler: + b . + + .thumb_func + .weak Default_Handler + .type Default_Handler, %function +Default_Handler: + b . + +/* ================================================================ + * __aeabi_read_tp -- ARM EABI thread pointer for TLS access + * Single-threaded bare-metal: return fixed pointer to .tdata + * ================================================================ */ + .global __aeabi_read_tp + .type __aeabi_read_tp, %function + .thumb_func +__aeabi_read_tp: + ldr r0, =_tdata_start + bx lr + .size __aeabi_read_tp, . - __aeabi_read_tp diff --git a/src/urt/driver/rp2350/syscalls.d b/src/urt/driver/rp2350/syscalls.d new file mode 100644 index 0000000..bb603b4 --- /dev/null +++ b/src/urt/driver/rp2350/syscalls.d @@ -0,0 +1,52 @@ +// RP2350 newlib/picolibc syscall stubs +// +// Minimal stubs to satisfy picolibc's syscall requirements. +// Same pattern as BL618 -- most are no-ops for baremetal. +module urt.driver.rp2350.syscalls; + +@nogc nothrow: + +private extern(C) extern const void* __heap_start; +private extern(C) extern const void* __heap_end; + +private __gshared void* _heap_ptr; + +extern(C) void* _sbrk(ptrdiff_t incr) +{ + if (_heap_ptr is null) + _heap_ptr = cast(void*)&__heap_start; + + void* prev = _heap_ptr; + void* next = _heap_ptr + incr; + + if (next > cast(void*)&__heap_end) + return cast(void*)-1; + + _heap_ptr = next; + return prev; +} + +extern(C) int _write(int fd, const void* buf, size_t count) +{ + import urt.driver.rp2350.uart : uart0_hw_puts; + if (fd == 1 || fd == 2) + uart0_hw_puts((cast(const(char)*)buf)[0 .. count]); + return cast(int)count; +} + +extern(C) int _read(int, void*, size_t) { return 0; } +extern(C) int _close(int) { return -1; } +extern(C) int _lseek(int, int, int) { return 0; } +extern(C) int _fstat(int, void*) { return 0; } +extern(C) int _isatty(int) { return 1; } +extern(C) void _exit(int) { while (true) {} } +extern(C) int _kill(int, int) { return -1; } +extern(C) int _getpid() { return 1; } + +// DWARF unwinder stubs -- ARM Cortex-M uses EHABI, not DWARF. +// These satisfy link-time references from the D runtime's exception +// personality code which assumes DWARF on all platforms. +extern(C) void __register_frame_info(const void*, void*) {} +extern(C) size_t _Unwind_GetIPInfo(void*, int*) { return 0; } +extern(C) void _Unwind_SetGR(void*, int, size_t) {} +extern(C) void _Unwind_SetIP(void*, size_t) {} diff --git a/src/urt/driver/rp2350/timer.d b/src/urt/driver/rp2350/timer.d new file mode 100644 index 0000000..dd7c64d --- /dev/null +++ b/src/urt/driver/rp2350/timer.d @@ -0,0 +1,80 @@ +// RP2350 timer driver +// +// Uses the ARM Cortex-M33 SysTick timer for the periodic main loop tick. +// SysTick is a 24-bit down-counter clocked from the processor clock. +// +// RP2350 also has a 64-bit microsecond timer at 0x400B_0000 (TIMER0) +// which can be used for wall-clock time -- not yet implemented here. +module urt.driver.rp2350.timer; + +import core.volatile; + +@nogc nothrow: + +enum uint mtime_freq_hz = 1_000_000; // TIMER0 runs at 1MHz (microsecond counter) +enum bool has_mtime = true; +enum bool has_rtc = false; +enum bool has_mcycle = false; +enum bool has_timer_stop = false; +enum bool has_wfi_sleep = false; + +// RP2350 TIMER0: 64-bit free-running microsecond counter at 0x400B_0000 +// Always enabled, always 1MHz. Read TIMELR first (latches TIMEHR). +private enum uint TIMER0_BASE = 0x400B_0000; +private enum uint TIMEHR = TIMER0_BASE + 0x08; // Time read high (latched on TIMELR read) +private enum uint TIMELR = TIMER0_BASE + 0x0C; // Time read low (triggers latch) + +// SysTick registers (ARM standard, part of the System Control Block) +private enum uint SYST_CSR = 0xE000_E010; +private enum uint SYST_RVR = 0xE000_E014; +private enum uint SYST_CVR = 0xE000_E018; + +private enum uint CSR_ENABLE = 1 << 0; +private enum uint CSR_TICKINT = 1 << 1; +private enum uint CSR_CLKSOURCE = 1 << 2; + +alias TimerCallback = void function() @nogc nothrow; + +private __gshared TimerCallback tick_callback; + +void timer_init(uint reload_value) +{ + volatileStore(cast(uint*)(cast(size_t)SYST_RVR), reload_value & 0x00FFFFFF); + volatileStore(cast(uint*)(cast(size_t)SYST_CVR), 0); + volatileStore(cast(uint*)(cast(size_t)SYST_CSR), CSR_ENABLE | CSR_TICKINT | CSR_CLKSOURCE); +} + +void timer_hw_init() +{ + // TIMER0 is always running at 1MHz on RP2350 -- nothing to init. +} + +// Read 64-bit monotonic microsecond counter. +// Must read TIMELR first -- this latches TIMEHR atomically. +ulong mtime_read() +{ + uint lo = volatileLoad(cast(uint*)(cast(size_t)TIMELR)); + uint hi = volatileLoad(cast(uint*)(cast(size_t)TIMEHR)); + return (cast(ulong)hi << 32) | lo; +} + +void timer_set_periodic(uint period_ticks, TimerCallback cb) +{ + tick_callback = cb; + // Use SysTick for periodic interrupts. + // period_ticks is in timer ticks (microseconds at 1MHz). + // SysTick runs from processor clock -- assume 150MHz after PLL init. + // Convert: systick_reload = period_us * 150 + uint reload = period_ticks * 150; + if (reload > 0x00FF_FFFF) + reload = 0x00FF_FFFF; // SysTick is 24-bit + volatileStore(cast(uint*)(cast(size_t)SYST_RVR), reload); + volatileStore(cast(uint*)(cast(size_t)SYST_CVR), 0); + volatileStore(cast(uint*)(cast(size_t)SYST_CSR), CSR_ENABLE | CSR_TICKINT | CSR_CLKSOURCE); +} + +extern(C) void SysTick_Handler() @nogc nothrow +{ + if (tick_callback !is null) + tick_callback(); +} diff --git a/src/urt/driver/rp2350/uart.d b/src/urt/driver/rp2350/uart.d new file mode 100644 index 0000000..ccee2a1 --- /dev/null +++ b/src/urt/driver/rp2350/uart.d @@ -0,0 +1,195 @@ +// RP2350 UART driver +// +// RP2350 has 2x PL011 UARTs: +// UART0 0x4007_0000 Default console (GPIO0=TX, GPIO1=RX) +// UART1 0x4007_8000 General purpose (GPIO4=TX, GPIO5=RX typical) +// +// PL011 register layout (ARM PrimeCell UART). +module urt.driver.rp2350.uart; + +import core.volatile; + +import urt.driver.uart : Parity, StopBits, UartConfig; + +nothrow @nogc: + +enum num_uarts = 2; +enum uint uart_clock_hz = 6_000_000; +enum bool has_irq_driven_uart = false; +enum bool has_dma_driven_uart = false; + +// PL011 register offsets +private enum +{ + UARTDR = 0x000, // Data register + UARTRSR = 0x004, // Receive status / error clear + UARTFR = 0x018, // Flag register + UARTILPR = 0x020, // IrDA low-power counter + UARTIBRD = 0x024, // Integer baud rate divisor + UARTFBRD = 0x028, // Fractional baud rate divisor + UARTLCR_H = 0x02C, // Line control + UARTCR = 0x030, // Control register + UARTIFLS = 0x034, // Interrupt FIFO level select + UARTIMSC = 0x038, // Interrupt mask set/clear + UARTRIS = 0x03C, // Raw interrupt status + UARTMIS = 0x040, // Masked interrupt status + UARTICR = 0x044, // Interrupt clear + UARTDMACR = 0x048, // DMA control +} + +// Flag register bits +private enum +{ + FR_TXFF = 1 << 5, // TX FIFO full + FR_RXFE = 1 << 4, // RX FIFO empty + FR_BUSY = 1 << 3, // UART busy transmitting +} + +// Control register bits +private enum +{ + CR_UARTEN = 1 << 0, // UART enable + CR_TXE = 1 << 8, // TX enable + CR_RXE = 1 << 9, // RX enable +} + +private ulong uart_base(uint id) +{ + return (id == 0) ? 0x40070000 : 0x40078000; +} + +private void uart_write_reg(ulong base, uint offset, uint val) +{ + volatileStore(cast(uint*)(base + offset), val); +} + +private uint uart_read_reg(ulong base, uint offset) +{ + return volatileLoad(cast(uint*)(base + offset)); +} + +// Assumed peripheral clock -- ring oscillator ~6MHz at boot. +// After PLL init this should be updated to the actual peri_clk frequency. +private __gshared uint peri_clk_hz = 6_000_000; + +bool uart_hw_init(uint id, UartConfig cfg) +{ + immutable base = uart_base(id); + + // Disable UART while configuring + uart_write_reg(base, UARTCR, 0); + + // Baud rate: BAUDDIV = peri_clk / (16 * baud) + // Integer part = BAUDDIV + // Fractional part = (frac * 64 + 0.5) + immutable uint bauddiv_x64 = (peri_clk_hz * 4) / cfg.baud_rate; + immutable uint ibrd = bauddiv_x64 / 64; + immutable uint fbrd = bauddiv_x64 % 64; + uart_write_reg(base, UARTIBRD, ibrd); + uart_write_reg(base, UARTFBRD, fbrd); + + // Line control: 8N1, FIFO enable + uint lcr = 0; + lcr |= (cfg.data_bits - 5) << 5; // WLEN: 5=0b00, 6=0b01, 7=0b10, 8=0b11 + lcr |= 1 << 4; // FEN: enable FIFOs + if (cfg.parity != Parity.none) + { + lcr |= 1 << 1; // PEN: parity enable + if (cfg.parity == Parity.even) + lcr |= 1 << 2; // EPS: even parity + } + if (cfg.stop_bits != StopBits.one) + lcr |= 1 << 3; // STP2: 2 stop bits + uart_write_reg(base, UARTLCR_H, lcr); + + // Enable UART, TX, RX + uart_write_reg(base, UARTCR, CR_UARTEN | CR_TXE | CR_RXE); + + return true; +} + +bool uart_hw_open(uint id, UartConfig cfg) +{ + return uart_hw_init(id, cfg); +} + +void uart_hw_close(uint id) +{ + uart_write_reg(uart_base(id), UARTCR, 0); +} + +ptrdiff_t uart_hw_read(uint id, void[] buffer) +{ + immutable base = uart_base(id); + auto buf = cast(ubyte[])buffer; + ptrdiff_t n = 0; + while (n < buf.length) + { + if (uart_read_reg(base, UARTFR) & FR_RXFE) + break; + buf[n] = cast(ubyte)(uart_read_reg(base, UARTDR) & 0xFF); + ++n; + } + return n; +} + +ptrdiff_t uart_hw_write(uint id, const(void)[] data) +{ + immutable base = uart_base(id); + auto buf = cast(const(ubyte)[])data; + ptrdiff_t n = 0; + while (n < buf.length) + { + if (uart_read_reg(base, UARTFR) & FR_TXFF) + break; + uart_write_reg(base, UARTDR, buf[n]); + ++n; + } + return n; +} + +void uart_hw_poll(uint id) +{ + // PL011 FIFOs handle buffering -- nothing to poll +} + +bool uart_hw_check_errors(uint id) +{ + immutable base = uart_base(id); + immutable rsr = uart_read_reg(base, UARTRSR); + if (rsr != 0) + { + uart_write_reg(base, UARTRSR, 0); // Clear errors + return true; + } + return false; +} + +ptrdiff_t uart_hw_rx_pending(uint id) +{ + // PL011 doesn't expose FIFO level directly in a simple way. + // Return 1 if data available, 0 otherwise. + if (uart_read_reg(uart_base(id), UARTFR) & FR_RXFE) + return 0; + return 1; +} + +ptrdiff_t uart_hw_flush(uint id) +{ + immutable base = uart_base(id); + while (uart_read_reg(base, UARTFR) & FR_BUSY) + {} + return 0; +} + +// Blocking puts for early boot (before the serial stream is up) +void uart0_hw_puts(const(char)[] s) +{ + enum ulong base = 0x40070000; + foreach (c; s) + { + while (volatileLoad(cast(uint*)(base + UARTFR)) & FR_TXFF) + {} + volatileStore(cast(uint*)(base + UARTDR), cast(uint)c); + } +} diff --git a/src/urt/driver/stm32/alloc.d b/src/urt/driver/stm32/alloc.d new file mode 100644 index 0000000..806bbd5 --- /dev/null +++ b/src/urt/driver/stm32/alloc.d @@ -0,0 +1,37 @@ +module urt.driver.stm32.alloc; + +import urt.mem.alloc : MemFlags; + +nothrow @nogc: + +enum has_realloc = false; +enum has_expand = false; +enum has_memsize = false; +enum has_exec = false; +enum has_retain = false; // TODO: backup SRAM +enum has_memflags = false; // TODO: TCM vs SRAM + +void[] _alloc(size_t size, size_t alignment, MemFlags) pure +{ + import urt.util : align_down; + + size_t header_size = (void*).sizeof + alignment; + void* p = malloc(header_size + size); + if (p is null) + return null; + + size_t allocptr = align_down(cast(size_t)p + header_size, alignment); + (cast(void**)allocptr)[-1] = p; + return (cast(void*)allocptr)[0 .. size]; +} + +void _free(void* ptr) pure +{ + free((cast(void**)ptr)[-1]); +} + + +private: + +extern(C) void* malloc(size_t size) pure; +extern(C) void free(void* ptr) pure; diff --git a/src/urt/driver/stm32/irq.d b/src/urt/driver/stm32/irq.d new file mode 100644 index 0000000..d99f6c6 --- /dev/null +++ b/src/urt/driver/stm32/irq.d @@ -0,0 +1,59 @@ +// STM32 interrupt controller driver +// +// Cortex-M4/M7 use the standard ARM NVIC (Nested Vectored Interrupt Controller). +// STM32F4xx has up to 82 peripheral interrupts, STM32F7xx up to 98. +module urt.driver.stm32.irq; + +@nogc nothrow: + +enum bool has_plic = false; +enum bool has_nvic = true; +enum bool has_per_irq_control = true; +enum bool has_irq_priority = true; +enum bool has_wait_for_interrupt = false; +enum bool has_irq_diagnostics = false; + +version (STM32F7) + enum uint irq_max = 98; +else + enum uint irq_max = 82; + +import core.volatile; + +// NVIC registers (ARM standard) +private enum ulong NVIC_ISER0 = 0xE000E100; +private enum ulong NVIC_ICER0 = 0xE000E180; +private enum ulong NVIC_ISPR0 = 0xE000E200; +private enum ulong NVIC_ICPR0 = 0xE000E280; +private enum ulong NVIC_IPR0 = 0xE000E400; + +void irq_disable() +{ + asm @nogc nothrow { "cpsid i"; } +} + +void irq_enable() +{ + asm @nogc nothrow { "cpsie i"; } +} + +void irq_set_enable(uint irq_num) +{ + immutable reg = irq_num / 32; + immutable bit = irq_num % 32; + volatileStore(cast(uint*)(NVIC_ISER0 + reg * 4), 1u << bit); +} + +void irq_clear_enable(uint irq_num) +{ + immutable reg = irq_num / 32; + immutable bit = irq_num % 32; + volatileStore(cast(uint*)(NVIC_ICER0 + reg * 4), 1u << bit); +} + +// Set priority for a peripheral IRQ (0 = highest, 255 = lowest) +// STM32F4/F7 implement 4 priority bits (top 4 of 8) +void irq_set_priority(uint irq_num, ubyte priority) +{ + volatileStore(cast(ubyte*)(NVIC_IPR0 + irq_num), priority); +} diff --git a/src/urt/driver/stm32/package.d b/src/urt/driver/stm32/package.d new file mode 100644 index 0000000..4137525 --- /dev/null +++ b/src/urt/driver/stm32/package.d @@ -0,0 +1,108 @@ +// STM32 platform package (ARM Cortex-M4/M7) +// +// Provides sys_init() as the single entry point for all +// hardware initialization. Called from start.S before main(). +// +// At reset, STM32 runs on HSI at 16 MHz (no PLL). +// sys_init brings up USART1 for console output and SysTick. +// PLL configuration for full-speed operation is not yet implemented. +module urt.driver.stm32; + +public import urt.driver.stm32.uart; +public import urt.driver.stm32.irq; +public import urt.driver.stm32.timer; + +import urt.driver.uart : UartConfig; +import core.volatile; + +@nogc nothrow: + +private extern(C) void __register_frame_info(const void*, void*); +private extern(C) extern const ubyte __eh_frame_start; +private ubyte[48] __eh_frame_object; + +// RCC base and clock enable registers (same for F4 and F7) +enum ulong RCC_BASE = 0x40023800; +enum ulong RCC_AHB1ENR = 0x30; +enum ulong RCC_APB2ENR = 0x44; + +// GPIO base addresses +enum ulong GPIOA_BASE = 0x40020000; + +// GPIO register offsets +enum ulong GPIO_MODER = 0x00; +enum ulong GPIO_OSPEEDR = 0x08; +enum ulong GPIO_AFRH = 0x24; + +// SCB registers for cache control (F7 only) +enum ulong SCB_CCR = 0xE000ED14; +enum ulong ICIALLU = 0xE000EF50; + +private void mmio_write(ulong addr, uint val) +{ + volatileStore(cast(uint*)addr, val); +} + +private uint mmio_read(ulong addr) +{ + return volatileLoad(cast(uint*)addr); +} + +private void mmio_set(ulong addr, uint bits) +{ + volatileStore(cast(uint*)addr, volatileLoad(cast(uint*)addr) | bits); +} + +private void mmio_rmw(ulong addr, uint clear_mask, uint set_bits) +{ + immutable val = volatileLoad(cast(uint*)addr); + volatileStore(cast(uint*)addr, (val & ~clear_mask) | set_bits); +} + +extern(C) void sys_init() +{ + __register_frame_info(&__eh_frame_start, &__eh_frame_object); + + // Enable GPIOA clock (AHB1ENR bit 0) + mmio_set(RCC_BASE + RCC_AHB1ENR, 1 << 0); + + // Enable USART1 clock (APB2ENR bit 4) + mmio_set(RCC_BASE + RCC_APB2ENR, 1 << 4); + + // Configure PA9 (USART1_TX) and PA10 (USART1_RX) as AF7 + // MODER: bits 19:18 = 0b10 (PA9 AF), bits 21:20 = 0b10 (PA10 AF) + mmio_rmw(GPIOA_BASE + GPIO_MODER, + (3u << 18) | (3u << 20), + (2u << 18) | (2u << 20)); + + // OSPEEDR: high speed for PA9/PA10 + mmio_set(GPIOA_BASE + GPIO_OSPEEDR, (3u << 18) | (3u << 20)); + + // AFRH: PA9 bits 7:4 = 7 (AF7), PA10 bits 11:8 = 7 (AF7) + mmio_rmw(GPIOA_BASE + GPIO_AFRH, + (0xFu << 4) | (0xFu << 8), + (7u << 4) | (7u << 8)); + + // Init UART0 (USART1) at default baud for early console + uart_hw_init(0, UartConfig.init); + + uart0_hw_puts("STM32: sys_init\r\n"); + + version (STM32F7) + { + // Enable instruction cache (D-cache needs DMA coherence handling) + asm @nogc nothrow { "dsb sy"; "isb"; } + mmio_write(ICIALLU, 0); + asm @nogc nothrow { "dsb sy"; "isb"; } + mmio_set(SCB_CCR, 1 << 17); + asm @nogc nothrow { "dsb sy"; "isb"; } + uart0_hw_puts("STM32F7: I-cache enabled\r\n"); + } + + // SysTick: 20 Hz tick (50ms) + // HSI = 16 MHz, AHB prescaler = 1 at reset + // Reload = 16_000_000 / 20 - 1 = 799_999 + timer_init(799_999); + + uart0_hw_puts("STM32: ready\r\n"); +} diff --git a/src/urt/driver/stm32/start.S b/src/urt/driver/stm32/start.S new file mode 100644 index 0000000..403806e --- /dev/null +++ b/src/urt/driver/stm32/start.S @@ -0,0 +1,207 @@ +/* STM32 (ARM Cortex-M4/M7) startup + * + * Shared between STM32F4xx and STM32F7xx. The assembler receives + * -mcpu=cortex-m4 or -mcpu=cortex-m7 from the Makefile, so no + * .cpu directive here. + * + * Boot flow: + * 1. Cortex-M reads vector table from flash: initial SP + Reset_Handler + * 2. Reset_Handler runs: + * a. Copy .got from flash to RAM + * b. Copy .data from flash to RAM + * c. Zero .bss + * d. Enable FPU (CP10 + CP11) + * e. Call sys_init (clock, GPIO, UART, cache) + * f. Run .init_array (D module constructors) + * g. Call main + */ + + .syntax unified + .thumb + +/* ================================================================ + * Vector table -- placed at start of FLASH + * Cortex-M reads entry 0 as initial SP, entry 1 as Reset_Handler + * ================================================================ */ + .section .vector_table, "a" + .global __vector_table + .type __vector_table, %object +__vector_table: + .word _stack_top /* 0: Initial stack pointer */ + .word Reset_Handler /* 1: Reset */ + .word NMI_Handler /* 2: NMI */ + .word HardFault_Handler /* 3: Hard fault */ + .word MemManage_Handler /* 4: MPU fault */ + .word BusFault_Handler /* 5: Bus fault */ + .word UsageFault_Handler /* 6: Usage fault */ + .word 0 /* 7: Reserved */ + .word 0 /* 8: Reserved */ + .word 0 /* 9: Reserved */ + .word 0 /* 10: Reserved */ + .word SVCall_Handler /* 11: SVCall */ + .word DebugMon_Handler /* 12: Debug monitor */ + .word 0 /* 13: Reserved */ + .word PendSV_Handler /* 14: PendSV */ + .word SysTick_Handler /* 15: SysTick */ + + /* IRQ 0-97 (98 entries covers both F4 82 and F7 98 IRQs) */ + .rept 98 + .word Default_Handler + .endr + + .size __vector_table, . - __vector_table + +/* ================================================================ + * Reset handler + * ================================================================ */ + .section .text, "ax" + .global Reset_Handler + .type Reset_Handler, %function + .thumb_func +Reset_Handler: + + /* -- Copy .got from flash (LMA) to RAM (VMA) -- */ + ldr r0, =_got_start + ldr r1, =_got_load + ldr r2, =_got_end + cmp r0, r1 + beq .Lgot_done + b .Lgot_check +.Lgot_copy: + ldm r1!, {r3} + stm r0!, {r3} +.Lgot_check: + cmp r0, r2 + blo .Lgot_copy +.Lgot_done: + + /* -- Copy .data from flash to RAM -- */ + ldr r0, =_data_start + ldr r1, =_data_load + ldr r2, =_data_end + b .Ldata_check +.Ldata_copy: + ldm r1!, {r3} + stm r0!, {r3} +.Ldata_check: + cmp r0, r2 + blo .Ldata_copy + + /* -- Zero .bss -- */ + ldr r0, =_bss_start + ldr r2, =_bss_end + movs r3, #0 + b .Lbss_check +.Lbss_zero: + stm r0!, {r3} +.Lbss_check: + cmp r0, r2 + blo .Lbss_zero + + /* -- Enable FPU (CP10 + CP11 full access) -- */ + ldr r0, =0xE000ED88 /* CPACR */ + ldr r1, [r0] + orr r1, r1, #(0xF << 20) + str r1, [r0] + dsb + isb + + /* -- Platform init (clock, GPIO, UART, cache) -- */ + bl sys_init + + /* -- Run .init_array (D module constructors) -- */ + ldr r4, =__init_array_start + ldr r5, =__init_array_end + b .Linit_check +.Linit_loop: + ldr r0, [r4] + blx r0 + adds r4, r4, #4 +.Linit_check: + cmp r4, r5 + blo .Linit_loop + + /* -- Enter main (should never return) -- */ + bl main + + /* If main returns, spin */ +.Lhalt: + wfi + b .Lhalt + + .size Reset_Handler, . - Reset_Handler + +/* ================================================================ + * Default exception/interrupt handlers (weak, overridable) + * ================================================================ */ + .thumb_func + .weak NMI_Handler + .type NMI_Handler, %function +NMI_Handler: + b . + + .thumb_func + .weak HardFault_Handler + .type HardFault_Handler, %function +HardFault_Handler: + b . + + .thumb_func + .weak MemManage_Handler + .type MemManage_Handler, %function +MemManage_Handler: + b . + + .thumb_func + .weak BusFault_Handler + .type BusFault_Handler, %function +BusFault_Handler: + b . + + .thumb_func + .weak UsageFault_Handler + .type UsageFault_Handler, %function +UsageFault_Handler: + b . + + .thumb_func + .weak SVCall_Handler + .type SVCall_Handler, %function +SVCall_Handler: + b . + + .thumb_func + .weak DebugMon_Handler + .type DebugMon_Handler, %function +DebugMon_Handler: + b . + + .thumb_func + .weak PendSV_Handler + .type PendSV_Handler, %function +PendSV_Handler: + b . + + .thumb_func + .weak SysTick_Handler + .type SysTick_Handler, %function +SysTick_Handler: + b . + + .thumb_func + .weak Default_Handler + .type Default_Handler, %function +Default_Handler: + b . + +/* ================================================================ + * __aeabi_read_tp -- ARM EABI thread pointer for TLS access + * Single-threaded bare-metal: return fixed pointer to .tdata + * ================================================================ */ + .global __aeabi_read_tp + .type __aeabi_read_tp, %function + .thumb_func +__aeabi_read_tp: + ldr r0, =_tdata_start + bx lr + .size __aeabi_read_tp, . - __aeabi_read_tp diff --git a/src/urt/driver/stm32/syscalls.d b/src/urt/driver/stm32/syscalls.d new file mode 100644 index 0000000..3c5e95c --- /dev/null +++ b/src/urt/driver/stm32/syscalls.d @@ -0,0 +1,50 @@ +// STM32 newlib/picolibc syscall stubs +// +// Minimal stubs to satisfy picolibc's syscall requirements. +// Same pattern as RP2350 -- most are no-ops for baremetal. +module urt.driver.stm32.syscalls; + +@nogc nothrow: + +private extern(C) extern const void* __heap_start; +private extern(C) extern const void* __heap_end; + +private __gshared void* _heap_ptr; + +extern(C) void* _sbrk(ptrdiff_t incr) +{ + if (_heap_ptr is null) + _heap_ptr = cast(void*)&__heap_start; + + void* prev = _heap_ptr; + void* next = _heap_ptr + incr; + + if (next > cast(void*)&__heap_end) + return cast(void*)-1; + + _heap_ptr = next; + return prev; +} + +extern(C) int _write(int fd, const void* buf, size_t count) +{ + import urt.driver.stm32.uart : uart0_hw_puts; + if (fd == 1 || fd == 2) + uart0_hw_puts((cast(const(char)*)buf)[0 .. count]); + return cast(int)count; +} + +extern(C) int _read(int, void*, size_t) { return 0; } +extern(C) int _close(int) { return -1; } +extern(C) int _lseek(int, int, int) { return 0; } +extern(C) int _fstat(int, void*) { return 0; } +extern(C) int _isatty(int) { return 1; } +extern(C) void _exit(int) { while (true) {} } +extern(C) int _kill(int, int) { return -1; } +extern(C) int _getpid() { return 1; } + +// DWARF unwinder stubs -- ARM Cortex-M uses EHABI, not DWARF. +extern(C) void __register_frame_info(const void*, void*) {} +extern(C) size_t _Unwind_GetIPInfo(void*, int*) { return 0; } +extern(C) void _Unwind_SetGR(void*, int, size_t) {} +extern(C) void _Unwind_SetIP(void*, size_t) {} diff --git a/src/urt/driver/stm32/timer.d b/src/urt/driver/stm32/timer.d new file mode 100644 index 0000000..7b546a8 --- /dev/null +++ b/src/urt/driver/stm32/timer.d @@ -0,0 +1,73 @@ +// STM32 timer driver +// +// Monotonic time via SysTick interpolation: the SysTick handler increments +// a tick counter, and mtime_read() combines the tick count with the current +// SysTick value for full 16 MHz resolution. +// +// SysTick keeps running during WFI (SLEEP mode) since AHB stays active, +// unlike DWT CYCCNT which stops on sleep. +module urt.driver.stm32.timer; + +import core.volatile; + +@nogc nothrow: + +enum uint mtime_freq_hz = 16_000_000; +enum bool has_mtime = true; +enum bool has_rtc = false; +enum bool has_mcycle = false; +enum bool has_timer_stop = false; +enum bool has_wfi_sleep = false; + +// SysTick registers (ARM standard) +private enum ulong SYST_CSR = 0xE000E010; +private enum ulong SYST_RVR = 0xE000E014; +private enum ulong SYST_CVR = 0xE000E018; + +private enum uint CSR_ENABLE = 1 << 0; +private enum uint CSR_TICKINT = 1 << 1; +private enum uint CSR_CLKSOURCE = 1 << 2; + +// 20 Hz tick: reload = 16_000_000 / 20 - 1 = 799_999 +private enum uint SYSTICK_RELOAD = 799_999; + +alias TimerCallback = void function() @nogc nothrow; + +private __gshared TimerCallback tick_callback; +private __gshared uint tick_count; + +void timer_init(uint reload_value) +{ + volatileStore(cast(uint*)SYST_RVR, reload_value & 0x00FFFFFF); + volatileStore(cast(uint*)SYST_CVR, 0); + volatileStore(cast(uint*)SYST_CSR, CSR_ENABLE | CSR_TICKINT | CSR_CLKSOURCE); +} + +void timer_set_periodic(uint period_us, TimerCallback cb) +{ + tick_callback = cb; + timer_init(period_us * 16); +} + +// Full-resolution monotonic time by combining SysTick overflow count with +// the current down-counter value. Retry loop handles the race where +// SysTick fires between reading tick_count and CVR. +ulong mtime_read() +{ + uint t1, cvr, t2; + do + { + t1 = volatileLoad(&tick_count); + cvr = volatileLoad(cast(uint*)SYST_CVR); + t2 = volatileLoad(&tick_count); + } + while (t1 != t2); + return cast(ulong)t1 * (SYSTICK_RELOAD + 1) + (SYSTICK_RELOAD - cvr); +} + +extern(C) void SysTick_Handler() @nogc nothrow +{ + volatileStore(&tick_count, volatileLoad(&tick_count) + 1); + if (tick_callback !is null) + tick_callback(); +} diff --git a/src/urt/driver/stm32/uart.d b/src/urt/driver/stm32/uart.d new file mode 100644 index 0000000..e6908a8 --- /dev/null +++ b/src/urt/driver/stm32/uart.d @@ -0,0 +1,260 @@ +// STM32 UART driver +// +// Supports both F4 (legacy SR/DR register layout) and F7 (ISR/RDR/TDR). +// USART1-6 on F4, USART1-6 + UART7-8 on F7. +// +// Default console: USART1 on PA9 (TX) / PA10 (RX), configured in sys_init. +module urt.driver.stm32.uart; + +import core.volatile; + +import urt.driver.uart : Parity, StopBits, UartConfig; + +nothrow @nogc: + +version (STM32F7) + enum uint num_uarts = 8; +else + enum uint num_uarts = 6; + +enum uint uart_clock_hz = 16_000_000; +enum bool has_irq_driven_uart = false; +enum bool has_dma_driven_uart = false; + +// F4 uses legacy register layout (SR/DR), F7 uses new layout (ISR/RDR/TDR). +// Register offsets and UE bit position differ; status bit positions are the same. +version (STM32F4) enum legacy_usart = true; +else enum legacy_usart = false; + +// Status bits (same positions in F4 SR and F7 ISR) +private enum +{ + ST_TXE = 1 << 7, + ST_RXNE = 1 << 5, + ST_TC = 1 << 6, + ST_ORE = 1 << 3, + ST_FE = 1 << 1, + ST_PE = 1 << 0, +} + +// CR1 bits shared between F4 and F7 +private enum +{ + CR1_TE = 1 << 3, + CR1_RE = 1 << 2, +} + +// F4: UE is bit 13 in CR1 at offset 0x0C +// F7: UE is bit 0 in CR1 at offset 0x00 +static if (legacy_usart) + private enum uint CR1_UE = 1 << 13; +else + private enum uint CR1_UE = 1 << 0; + +// Register offsets +static if (legacy_usart) +{ + private enum { SR = 0x00, DR = 0x04, BRR = 0x08, CR1 = 0x0C, CR2 = 0x10, CR3 = 0x14 } +} +else +{ + private enum { CR1 = 0x00, CR2 = 0x04, CR3 = 0x08, BRR = 0x0C, ISR = 0x1C, ICR = 0x20, RDR = 0x24, TDR = 0x28 } +} + +// USART base addresses (F4 and F7 share the same addresses for USART1-6) +private ulong uart_base(uint id) +{ + immutable ulong[8] bases = [ + 0x40011000, // USART1 (APB2) + 0x40004400, // USART2 (APB1) + 0x40004800, // USART3 (APB1) + 0x40004C00, // UART4 (APB1) + 0x40005000, // UART5 (APB1) + 0x40011400, // USART6 (APB2) + 0x40007800, // UART7 (APB1, F7 only) + 0x40007C00, // UART8 (APB1, F7 only) + ]; + return bases[id]; +} + +private void reg_write(ulong base, uint offset, uint val) +{ + volatileStore(cast(uint*)(base + offset), val); +} + +private uint reg_read(ulong base, uint offset) +{ + return volatileLoad(cast(uint*)(base + offset)); +} + +private uint read_status(ulong base) +{ + static if (legacy_usart) + return reg_read(base, SR); + else + return reg_read(base, ISR); +} + +private ubyte read_data(ulong base) +{ + static if (legacy_usart) + return cast(ubyte)(reg_read(base, DR) & 0xFF); + else + return cast(ubyte)(reg_read(base, RDR) & 0xFF); +} + +private void write_data(ulong base, ubyte val) +{ + static if (legacy_usart) + reg_write(base, DR, val); + else + reg_write(base, TDR, val); +} + +bool uart_hw_init(uint id, UartConfig cfg) +{ + immutable base = uart_base(id); + + // Disable UART while configuring + reg_write(base, CR1, 0); + + // Baud rate: BRR = fck / baud (16x oversampling) + immutable uint brr = uart_clock_hz / cfg.baud_rate; + reg_write(base, BRR, brr); + + // Line control: word length, parity, stop bits + uint cr1 = CR1_UE | CR1_TE | CR1_RE; + uint cr2 = 0; + + if (cfg.parity != Parity.none) + { + static if (legacy_usart) + cr1 |= 1 << 10; // PCE: parity enable + else + cr1 |= 1 << 10; // PCE: parity enable + if (cfg.parity == Parity.odd) + { + static if (legacy_usart) + cr1 |= 1 << 9; // PS: odd parity + else + cr1 |= 1 << 9; // PS: odd parity + } + // With parity, need M=1 (9 data bits) to keep 8 data + 1 parity + static if (legacy_usart) + cr1 |= 1 << 12; // M: word length 9 + else + cr1 |= 1 << 12; // M0: word length 9 + } + + if (cfg.stop_bits == StopBits.two) + cr2 |= 2 << 12; // STOP: 2 stop bits + else if (cfg.stop_bits == StopBits.half) + cr2 |= 1 << 12; // STOP: 0.5 stop bits + else if (cfg.stop_bits == StopBits.one_point_five) + cr2 |= 3 << 12; // STOP: 1.5 stop bits + + reg_write(base, CR2, cr2); + reg_write(base, CR3, 0); + reg_write(base, CR1, cr1); + + return true; +} + +bool uart_hw_open(uint id, UartConfig cfg) +{ + return uart_hw_init(id, cfg); +} + +void uart_hw_close(uint id) +{ + reg_write(uart_base(id), CR1, 0); +} + +ptrdiff_t uart_hw_read(uint id, void[] buffer) +{ + immutable base = uart_base(id); + auto buf = cast(ubyte[])buffer; + ptrdiff_t n = 0; + while (n < buf.length) + { + if (!(read_status(base) & ST_RXNE)) + break; + buf[n] = read_data(base); + ++n; + } + return n; +} + +ptrdiff_t uart_hw_write(uint id, const(void)[] data) +{ + immutable base = uart_base(id); + auto buf = cast(const(ubyte)[])data; + ptrdiff_t n = 0; + while (n < buf.length) + { + if (!(read_status(base) & ST_TXE)) + break; + write_data(base, buf[n]); + ++n; + } + return n; +} + +void uart_hw_poll(uint id) {} + +bool uart_hw_check_errors(uint id) +{ + immutable base = uart_base(id); + immutable st = read_status(base); + if (st & (ST_ORE | ST_FE | ST_PE)) + { + static if (legacy_usart) + { + // F4: read SR then DR to clear error flags + reg_read(base, DR); + } + else + { + // F7: write ICR to clear error flags + reg_write(base, ICR, ST_ORE | ST_FE | ST_PE); + } + return true; + } + return false; +} + +ptrdiff_t uart_hw_rx_pending(uint id) +{ + if (read_status(uart_base(id)) & ST_RXNE) + return 1; + return 0; +} + +ptrdiff_t uart_hw_flush(uint id) +{ + immutable base = uart_base(id); + while (!(read_status(base) & ST_TC)) + {} + return 0; +} + +// Blocking puts for early boot (before the serial stream is up) +void uart0_hw_puts(const(char)[] s) +{ + enum ulong base = 0x40011000; // USART1 + foreach (c; s) + { + static if (legacy_usart) + { + while (!(volatileLoad(cast(uint*)(base + SR)) & ST_TXE)) + {} + volatileStore(cast(uint*)(base + DR), cast(uint)c); + } + else + { + while (!(volatileLoad(cast(uint*)(base + ISR)) & ST_TXE)) + {} + volatileStore(cast(uint*)(base + TDR), cast(uint)c); + } + } +} diff --git a/src/urt/driver/timer.d b/src/urt/driver/timer.d new file mode 100644 index 0000000..f5170a7 --- /dev/null +++ b/src/urt/driver/timer.d @@ -0,0 +1,231 @@ +module urt.driver.timer; + +import urt.time : Duration, dur; + +version (BL808_M0) + public import urt.driver.bl618.timer; +else version (BL808) + public import urt.driver.bl808.timer; +else version (BL618) + public import urt.driver.bl618.timer; +else version (Beken) + public import urt.driver.bk7231.timer; +else version (RP2350) + public import urt.driver.rp2350.timer; +else version (STM32) + public import urt.driver.stm32.timer; +else version (Espressif) + public import urt.driver.esp32.timer; +else +{ + enum uint mtime_freq_hz = 0; + enum bool has_mtime = false; + enum bool has_rtc = false; + enum bool has_mcycle = false; + enum bool has_timer_stop = false; + enum bool has_wfi_sleep = false; +} + +nothrow @nogc: + + +alias TimerCallback = void function() nothrow @nogc; + +// ════════════════════════════════════════════════════════════════════ +// Driver API +// ════════════════════════════════════════════════════════════════════ + +// Lifecycle + +void timer_init() +{ + if (_init_refcount++ == 0) + { + version (Beken) + timer_hw_init(); + } +} + +void timer_deinit() +{ + assert(_init_refcount > 0); + if (--_init_refcount == 0) + { + static if (has_timer_stop) + periodic_stop(); + static if (has_rtc) + rtc_stop(); + // TODO: disable timer IRQs at the interrupt controller level + } +} + +// Monotonic clock + +// Read the monotonic tick counter. Units are platform-specific; +// use mtime_freq_hz to convert to real time. +static if (has_mtime) + alias monotonic_read = mtime_read; +else +{ + ulong monotonic_read() + { + assert(false, "monotonic_read not available"); + } +} + +// Read CPU cycle counter (for profiling, NOT timekeeping). +// Stops during WFI, rate changes with clock scaling. +static if (has_mcycle) + alias cycle_read = mcycle_read; +else +{ + ulong cycle_read() + { + assert(false, "cycle_read not available"); + } +} + +// Periodic tick + +// Set up a periodic timer interrupt at the given interval. +void periodic_set(Duration interval, TimerCallback cb) +{ + static if (has_mtime) + { + ulong ticks = interval.as!"nsecs" * mtime_freq_hz / 1_000_000_000; + timer_set_periodic(cast(uint)ticks, cb); + } + else + assert(false, "TODO: periodic_set not available"); +} + +// Stop the periodic timer. +void periodic_stop() +{ + static if (has_timer_stop) + timer_stop(); + else + assert(false, "TODO: periodic_stop not available"); +} + +// One-shot wakeup + +// Schedule a one-shot interrupt at the given absolute tick value. +// Used by sleep() to wake from WFI. +void oneshot_set(ulong tick_value) +{ + static if (has_wfi_sleep) + mtimecmp_write_oneshot(tick_value); + else + assert(false, "TODO: oneshot_set not available"); +} + +// RTC (battery-backed real-time counter) + +// Enable the RTC counter. Does not reset it. +void rtc_start() +{ + static if (has_rtc) + rtc_enable(); + else + assert(false, "TODO: rtc_start not available"); +} + +// Disable and reset the RTC counter to zero. +void rtc_stop() +{ + static if (has_rtc) + rtc_reset(); + else + assert(false, "TODO: rtc_stop not available"); +} + +// Read the RTC tick counter. +static if (has_rtc) + alias rtc_now = rtc_read; +else +{ + ulong rtc_now() + { + assert(false, "TODO: rtc_now not available"); + } +} + +// Access persistent state in battery-retained RAM. +static if (has_rtc) +{ + HbnPersist* persistent_state() + { + return hbn_persist(); + } +} + + +// ════════════════════════════════════════════════════════════════════ +// Tests +// ════════════════════════════════════════════════════════════════════ + +unittest +{ + // Capabilities are consistent + static assert(mtime_freq_hz > 0 || !has_mtime); + + timer_init(); + + // Monotonic clock + static if (has_mtime) + { + ulong t1 = monotonic_read(); + ulong t2 = monotonic_read(); + assert(t2 >= t1); + } + + // Cycle counter + static if (has_mcycle) + { + ulong c1 = cycle_read(); + ulong c2 = cycle_read(); + assert(c2 > c1); + } + + // One-shot wakeup + static if (has_wfi_sleep) + { + // Set a oneshot far in the future, then cancel by setting to max. + // This verifies the register write doesn't crash. + ulong now = monotonic_read(); + oneshot_set(now + mtime_freq_hz); // 1 second from now + oneshot_set(ulong.max); // cancel + } + + // Periodic set/stop (only test on platforms that support stop, + // otherwise we can't clean up) + static if (has_mtime && has_timer_stop) + { + __gshared bool tick_fired = false; + periodic_set(dur!"msecs"(50), () { tick_fired = true; }); + periodic_stop(); + // We can't easily test that the callback fires without + // waiting + running the interrupt, but at least verify + // set/stop don't crash. + } + + // RTC + static if (has_rtc) + { + rtc_start(); + ulong r1 = rtc_now(); + ulong r2 = rtc_now(); + assert(r2 >= r1); + + auto p = persistent_state(); + assert(p !is null); + } + + timer_deinit(); +} + + +private: + +__gshared ubyte _init_refcount; diff --git a/src/urt/driver/uart.d b/src/urt/driver/uart.d new file mode 100644 index 0000000..86f5ab4 --- /dev/null +++ b/src/urt/driver/uart.d @@ -0,0 +1,472 @@ +module urt.driver.uart; + +import urt.result : Result, InternalResult; +import urt.time : Duration; + +version (BL808_M0) + public import urt.driver.bl618.uart; +else version (BL808) + public import urt.driver.bl808.uart; +else version (BL618) + public import urt.driver.bl618.uart; +else version (Beken) + public import urt.driver.bk7231.uart; +else version (RP2350) + public import urt.driver.rp2350.uart; +else version (STM32) + public import urt.driver.stm32.uart; +else version (Espressif) + public import urt.driver.esp32.uart; +else + enum uint num_uarts = 0; + +nothrow @nogc: + + +enum UartError : ubyte +{ + none = 0, + framing = 1 << 0, + parity = 1 << 1, + overrun = 1 << 2, + noise = 1 << 3, + break_ = 1 << 4, +} + +enum StopBits : ubyte +{ + half, + one, + one_point_five, + two, +} + +enum Parity : ubyte +{ + none, + even, + odd, + mark, + space +} + +enum FlowControl : ubyte +{ + none, + hardware, + software, + dsr_dtr, + + rts_cts = hardware, + xon_xoff = software +} + +enum DriveMode : ubyte +{ + polled, // caller must call uart_poll; no interrupts + interrupt, // IRQ-driven FIFO drain into software ring buffer + dma, // DMA circular RX, DMA one-shot TX + auto_, // driver picks best available (dma > interrupt > polled) +} + +struct Rs485Config +{ + bool enabled; + bool de_active_high = true; // DE pin polarity + ubyte de_gpio = ubyte.max; // GPIO pin for driver enable (max = auto/hardware) + ushort de_assert_us; // us to assert DE before first TX bit + ushort de_deassert_us; // us to hold DE after last TX bit + ushort turnaround_us; // minimum idle time between RX end and TX start +} + +struct UartConfig +{ + uint baud_rate = 115200; + ubyte data_bits = 8; + StopBits stop_bits = StopBits.one; + Parity parity = Parity.none; + FlowControl flow_control = FlowControl.none; + DriveMode drive_mode = DriveMode.auto_; + ubyte tx_gpio = ubyte.max; // GPIO pin for TX (max = platform default) + ubyte rx_gpio = ubyte.max; // GPIO pin for RX (max = platform default) + ubyte rts_gpio = ubyte.max; // GPIO pin for RTS (max = platform default) + ubyte cts_gpio = ubyte.max; // GPIO pin for CTS (max = platform default) + Rs485Config rs485; +} + +// Called from ISR/DMA when received data is available in the RX buffer. +// rx_avail: number of bytes ready to read. +alias UartRxCallback = void function(Uart uart, size_t rx_avail) nothrow @nogc; + +// Called from ISR/DMA when TX buffer space becomes available (e.g. FIFO +// drains below threshold). The callee should feed more data via uart_write. +// tx_avail: number of bytes that can be written to the TX buffer. +alias UartTxCallback = void function(Uart uart, size_t tx_avail) nothrow @nogc; + +// Called from ISR to pull the next chunk of data for an async transmit. +// offset: bytes already supplied so far (including initial buffer). +// The driver provides a buffer; the callee fills it and returns how many +// bytes were written. Called repeatedly until the transfer length is met. +alias UartTxSupplyCallback = size_t function(Uart uart, size_t offset, void[] buf) nothrow @nogc; + +// Called when the RX line has been idle for the configured threshold. +// Used for RS-485 frame boundary detection and bus arbitration. +alias UartLineIdleCallback = void function(Uart uart) nothrow @nogc; + +// Caller-owned write operation token. Pass to any write call to track +// completion and support cancellation. The driver writes status from +// ISR context; the caller polls it from main context. +struct UartWriteOp +{ + enum Status : ubyte + { + idle, // not submitted + pending, // driver is working on it + complete, // all bytes consumed / transmitted + cancelled, // cancelled via uart_write_cancel + error, // hardware error during transfer + } + + alias Callback = void function(Uart* uart, ref UartWriteOp op) nothrow @nogc; + + Status status; + size_t bytes_sent; // updated by driver as transfer progresses + void* user_data; + Callback cb; + + bool is_pending() const => status == Status.pending; + bool is_done() const => status >= Status.complete; +} + +struct Uart +{ + ubyte port = ubyte.max; +} + +bool is_open(ref const Uart uart) +{ + return uart.port != ubyte.max; +} + + +// ════════════════════════════════════════════════════════════════════ +// Error type +// ════════════════════════════════════════════════════════════════════ + +UartError uart_result(Result result) +{ + return cast(UartError)result.system_code; +} + + +// ════════════════════════════════════════════════════════════════════ +// Implementation +// ════════════════════════════════════════════════════════════════════ + +// Lifecycle + +void uart_init() +{ + if (_init_refcount++ == 0) + { + // TODO: enable clocks/power for UART peripheral block + } +} + +void uart_deinit() +{ + assert(_init_refcount > 0); + if (--_init_refcount == 0) + { + // TODO: disable clocks/power for UART peripheral block + } +} + +// Port operations + +Result uart_open(ref Uart uart, ubyte port, ref const UartConfig cfg, size_t buf_size = 0, UartRxCallback rx_cb = null, UartTxCallback tx_cb = null) +{ + static if (num_uarts == 0) + assert(false, "no UART on this platform"); + else + { + if (port >= num_uarts) + return InternalResult.invalid_parameter; + + if (!uart_hw_open(port, cfg)) + return InternalResult.failed; + + uart.port = port; + return Result.success; + } +} + +Result uart_reconfigure(ref Uart uart, ref const UartConfig cfg) +{ + assert(false, "TODO: uart_reconfigure (hot reconfigure)"); +} + +void uart_close(ref Uart uart) +{ + static if (num_uarts == 0) + assert(false, "no UART on this platform"); + else + uart_hw_close(uart.port); + uart.port = ubyte.max; +} + +// Single-byte I/O + +bool uart_putc(ref Uart uart, ubyte c) +{ + static if (num_uarts == 0) + assert(false, "no UART on this platform"); + else + return uart_hw_write(uart.port, (cast(void*)&c)[0 .. 1]) == 1; +} + +bool uart_getc(ref Uart uart, out ubyte c) +{ + static if (num_uarts == 0) + assert(false, "no UART on this platform"); + else + return uart_hw_read(uart.port, (cast(void*)&c)[0 .. 1]) == 1; +} + +// Bulk I/O + +ptrdiff_t uart_read(ref Uart uart, void[] buffer, Duration timeout = Duration.zero) +{ + static if (num_uarts == 0) + assert(false, "no UART on this platform"); + else + { + if (timeout != Duration.zero) + assert(false, "TODO: uart_read with timeout"); + uart_hw_poll(uart.port); + return uart_hw_read(uart.port, buffer); + } +} + +ptrdiff_t uart_write(ref Uart uart, const(void)[] data, UartWriteOp* op = null) +{ + static if (num_uarts == 0) + assert(false, "no UART on this platform"); + else + { + auto n = uart_hw_write(uart.port, data); + if (op !is null) + { + op.bytes_sent = n > 0 ? cast(size_t)n : 0; + op.status = n >= 0 ? UartWriteOp.Status.complete : UartWriteOp.Status.error; + if (op.cb !is null) + op.cb(&uart, *op); + } + return n; + } +} + +ptrdiff_t uart_writev(ref Uart uart, const(void[])[] buffers, UartWriteOp* op = null) +{ + static if (num_uarts == 0) + assert(false, "no UART on this platform"); + else + { + ptrdiff_t total = 0; + foreach (buf; buffers) + { + auto n = uart_hw_write(uart.port, buf); + if (n < 0) + { + if (op !is null) + { + op.bytes_sent = cast(size_t)total; + op.status = UartWriteOp.Status.error; + if (op.cb !is null) + op.cb(&uart, *op); + } + return n; + } + total += n; + if (n < buf.length) + break; + } + if (op !is null) + { + op.bytes_sent = cast(size_t)total; + op.status = UartWriteOp.Status.complete; + if (op.cb !is null) + op.cb(&uart, *op); + } + return total; + } +} + +ptrdiff_t uart_write_async(ref Uart uart, size_t total_len, UartTxSupplyCallback supply_cb, const(void)[] initial = null, UartWriteOp* op = null) +{ + assert(false, "TODO: uart_write_async (needs ISR integration)"); +} + +void uart_write_cancel(ref Uart uart, UartWriteOp* op) +{ + assert(false, "TODO: uart_write_cancel"); +} + +// Buffer queries + +size_t uart_rx_available(ref const Uart uart) +{ + static if (num_uarts == 0) + assert(false, "no UART on this platform"); + else + { + auto n = uart_hw_rx_pending(uart.port); + return n > 0 ? cast(size_t)n : 0; + } +} + +size_t uart_tx_available(ref const Uart uart) +{ + assert(false, "TODO: uart_tx_available"); +} + +size_t uart_tx_queued(ref const Uart uart) +{ + assert(false, "TODO: uart_tx_queued"); +} + +// Buffer control + +void uart_rx_flush(ref Uart uart) +{ + assert(false, "TODO: uart_rx_flush"); +} + +void uart_tx_flush(ref Uart uart) +{ + static if (num_uarts == 0) + assert(false, "no UART on this platform"); + else + uart_hw_flush(uart.port); +} + +void uart_tx_drain(ref Uart uart) +{ + assert(false, "TODO: uart_tx_drain (block until TX idle)"); +} + +void uart_tx_abort(ref Uart uart) +{ + assert(false, "TODO: uart_tx_abort"); +} + +// Line status + +bool uart_line_idle(ref const Uart uart) +{ + assert(false, "TODO: uart_line_idle"); +} + +void uart_set_idle_threshold(ref Uart uart, uint bit_times) +{ + assert(false, "TODO: uart_set_idle_threshold"); +} + +void uart_set_idle_callback(ref Uart uart, UartLineIdleCallback cb) +{ + assert(false, "TODO: uart_set_idle_callback"); +} + +// TX timing + +void uart_tx_gap(ref Uart uart, Duration gap) +{ + assert(false, "TODO: uart_tx_gap"); +} + +void uart_send_break(ref Uart uart, Duration duration) +{ + assert(false, "TODO: uart_send_break"); +} + +// Error and status + +UartError uart_check_errors(ref Uart uart) +{ + static if (num_uarts == 0) + assert(false, "no UART on this platform"); + else + return uart_hw_check_errors(uart.port) ? UartError.framing : UartError.none; +} + +void uart_poll(ref Uart uart) +{ + static if (num_uarts == 0) + assert(false, "no UART on this platform"); + else + uart_hw_poll(uart.port); +} + +// Early boot (pre-driver, polled, blocking) + +void uart0_putc(ubyte c) +{ + static if (num_uarts == 0) {} + else uart0_puts((cast(char*)&c)[0 .. 1]); +} + +void uart0_puts(const(char)[] s) +{ + static if (num_uarts == 0) {} + else uart0_hw_puts(s); +} + + +// ════════════════════════════════════════════════════════════════════ +// Tests +// ════════════════════════════════════════════════════════════════════ + +unittest +{ + static if (num_uarts > 0) + { + Uart u; + UartConfig cfg; + + uart_init(); + + // Out-of-range port + auto r = uart_open(u, cast(ubyte)num_uarts, cfg); + assert(!r); + assert(!u.is_open); + + // Open/close each valid port (skip port 0 -- it's usually the console) + foreach (p; 1 .. num_uarts) + { + Uart port; + auto r2 = uart_open(port, cast(ubyte)p, cfg); + assert(r2, "uart_open failed"); + assert(port.is_open); + assert(port.port == p); + + // RX should be empty on a freshly opened port + assert(uart_rx_available(port) == 0); + + // Poll should not crash + uart_poll(port); + + // Check errors on a clean port + assert(uart_check_errors(port) == UartError.none); + + uart_close(port); + assert(!port.is_open); + } + + uart_deinit(); + } +} + + +private: + +__gshared ubyte _init_refcount; diff --git a/src/urt/driver/wifi.d b/src/urt/driver/wifi.d new file mode 100644 index 0000000..643e1bc --- /dev/null +++ b/src/urt/driver/wifi.d @@ -0,0 +1,489 @@ +module urt.driver.wifi; + +import urt.result : Result, InternalResult; + +version (Espressif) + public import urt.driver.esp32.wifi; +else + enum uint num_wifi = 0; + +nothrow @nogc: + + +// ════════════════════════════════════════════════════════════════════ +// Types +// ════════════════════════════════════════════════════════════════════ + +enum WifiError : ubyte +{ + none, + auth_failed, + no_ap, // target AP not found + assoc_failed, // association rejected + timeout, + tx_failed, + internal, +} + +// Virtual interface type within a radio. +enum WifiVif : ubyte +{ + sta, + ap, +} + +enum WifiMode : ubyte +{ + none, // radio off, no virtual interfaces active + monitor, // radio on, raw 802.11 only - no stack + sta, // station only + ap, // access point only + apsta, // concurrent AP + STA +} + +enum WifiAuth : ubyte +{ + open, + wep, + wpa_psk, + wpa2_psk, + wpa_wpa2_psk, + wpa3_psk, + wpa2_wpa3_psk, + wpa2_enterprise, + wpa3_enterprise, +} + +enum WifiBand : ubyte +{ + any, + band_2g4, // 2.4 GHz + band_5g, // 5 GHz + band_6g, // 6 GHz (Wi-Fi 6E) +} + +enum WifiBandwidth : ubyte +{ + bw_20mhz, + bw_40mhz, + bw_80mhz, + bw_160mhz, +} + +enum WifiEvent : ubyte +{ + sta_connected, + sta_disconnected, + ap_started, + ap_stopped, + ap_sta_connected, // a client joined our AP + ap_sta_disconnected, // a client left our AP + scan_done, +} + +struct WifiConfig +{ + byte tx_power; // dBm (0 = platform default) + ubyte channel; // fixed channel (0 = auto) + WifiBand band; + ubyte[2] country; // ISO 3166-1 alpha-2 (e.g. "US"), 0 = default +} + +struct WifiStaConfig +{ + const(char)[] ssid; + const(char)[] password; + ubyte[6] bssid; // filter by BSSID (all zeros = any) + WifiBand band; + bool pmf_required; // protected management frames +} + +struct WifiApConfig +{ + const(char)[] ssid; + const(char)[] password; + WifiAuth auth = WifiAuth.wpa2_wpa3_psk; + ubyte channel; // 0 = auto + ubyte max_clients = 4; + bool hidden; // suppress SSID broadcast + WifiBandwidth bandwidth; +} + +struct WifiScanConfig +{ + const(char)[] ssid; // filter by SSID (empty = all) + ubyte[6] bssid; // filter by BSSID (all zeros = any) + ubyte channel; // scan single channel (0 = all) + WifiBand band; + bool passive; // passive scan (listen only, no probe requests) + ushort dwell_ms; // per-channel dwell time (0 = platform default) +} + +struct WifiScanResult +{ + ubyte[6] bssid; + ubyte channel; + byte rssi; // dBm + WifiAuth auth; + WifiBand band; + WifiBandwidth bandwidth; + ubyte ssid_len; + char[32] ssid_buf; + + const(char)[] ssid() const pure nothrow @nogc + => ssid_buf[0 .. ssid_len]; +} + +struct WifiStaInfo +{ + ubyte[6] mac; + byte rssi; +} + +// Called from ISR/driver when an Ethernet frame is received on a +// virtual interface. Data includes the 14-byte Ethernet header. +alias WifiRxCallback = void function(Wifi wifi, WifiVif vif, const(ubyte)[] data) nothrow @nogc; + +// Called from ISR/driver when a raw 802.11 frame is received +// (promiscuous/monitor tap). Data is the full 802.11 frame +// starting at the MAC header. Delivered independently of the +// Ethernet RX callback - both can be active simultaneously. +alias WifiRawRxCallback = void function(Wifi wifi, const(ubyte)[] frame, byte rssi, ubyte channel) nothrow @nogc; + +// Called when a wifi event occurs. Replaces per-event callbacks +// to match the single-callback pattern used by the router layer. +alias WifiEventCallback = void function(Wifi wifi, WifiEvent event, const(void)* data) nothrow @nogc; + +struct Wifi +{ + ubyte port = ubyte.max; +} + +bool is_open(ref const Wifi wifi) +{ + return wifi.port != ubyte.max; +} + + +// ════════════════════════════════════════════════════════════════════ +// Error type +// ════════════════════════════════════════════════════════════════════ + +WifiError wifi_result(Result result) +{ + return cast(WifiError)result.system_code; +} + + +// ════════════════════════════════════════════════════════════════════ +// Implementation +// ════════════════════════════════════════════════════════════════════ + +// Lifecycle + +void wifi_init() +{ + if (_init_refcount++ == 0) + { + // TODO: enable clocks/power for WiFi peripheral block + } +} + +void wifi_deinit() +{ + assert(_init_refcount > 0); + if (--_init_refcount == 0) + { + // TODO: disable clocks/power for WiFi peripheral block + } +} + +// Radio operations + +Result wifi_open(ref Wifi wifi, ubyte port, ref const WifiConfig cfg) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + { + if (port >= num_wifi) + return InternalResult.invalid_parameter; + + if (!wifi_hw_open(port, cfg)) + return InternalResult.failed; + + wifi.port = port; + return Result.success; + } +} + +void wifi_close(ref Wifi wifi) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + wifi_hw_close(wifi.port); + wifi.port = ubyte.max; +} + +Result wifi_set_mode(ref Wifi wifi, WifiMode mode) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + { + if (!wifi_hw_set_mode(wifi.port, mode)) + return InternalResult.failed; + return Result.success; + } +} + +// Station (client) operations + +Result wifi_sta_configure(ref Wifi wifi, ref const WifiStaConfig cfg) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + { + if (!wifi_hw_sta_configure(wifi.port, cfg)) + return InternalResult.failed; + return Result.success; + } +} + +// Begin association. Completion is signalled via WifiEvent.sta_connected +// or WifiEvent.sta_disconnected through the event callback. +Result wifi_sta_connect(ref Wifi wifi) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + { + if (!wifi_hw_sta_connect(wifi.port)) + return InternalResult.failed; + return Result.success; + } +} + +Result wifi_sta_disconnect(ref Wifi wifi) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + { + if (!wifi_hw_sta_disconnect(wifi.port)) + return InternalResult.failed; + return Result.success; + } +} + +// Access point operations + +Result wifi_ap_configure(ref Wifi wifi, ref const WifiApConfig cfg) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + { + if (!wifi_hw_ap_configure(wifi.port, cfg)) + return InternalResult.failed; + return Result.success; + } +} + +// Query stations currently connected to our AP. +// Returns the number of entries written (up to buf.length). +size_t wifi_ap_get_clients(ref Wifi wifi, WifiStaInfo[] buf) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + return wifi_hw_ap_get_clients(wifi.port, buf); +} + +// Scanning + +Result wifi_scan_start(ref Wifi wifi, ref const WifiScanConfig cfg) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + { + if (!wifi_hw_scan_start(wifi.port, cfg)) + return InternalResult.failed; + return Result.success; + } +} + +void wifi_scan_stop(ref Wifi wifi) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + wifi_hw_scan_stop(wifi.port); +} + +// Retrieve scan results after WifiEvent.scan_done. +// Returns the number of entries written (up to buf.length). +size_t wifi_scan_get_results(ref Wifi wifi, WifiScanResult[] buf) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + return wifi_hw_scan_get_results(wifi.port, buf); +} + +// Frame TX/RX + +// Transmit an Ethernet frame (including 14-byte header) on a +// virtual interface. +Result wifi_tx(ref Wifi wifi, WifiVif vif, const(ubyte)[] data) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + { + if (!wifi_hw_tx(wifi.port, vif, data)) + return InternalResult.failed; + return Result.success; + } +} + +void wifi_set_rx_callback(ref Wifi wifi, WifiRxCallback cb) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + wifi_hw_set_rx_callback(wifi.port, cb); +} + +// Raw 802.11 frame TX/RX (monitor/promiscuous) + +// Transmit a raw 802.11 frame. Data must include the full MAC header. +// Requires monitor mode or promiscuous capability on the platform. +Result wifi_raw_tx(ref Wifi wifi, const(ubyte)[] frame) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + { + if (!wifi_hw_raw_tx(wifi.port, frame)) + return InternalResult.failed; + return Result.success; + } +} + +void wifi_set_raw_rx_callback(ref Wifi wifi, WifiRawRxCallback cb) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + wifi_hw_set_raw_rx_callback(wifi.port, cb); +} + +// Queries + +Result wifi_get_mac(ref Wifi wifi, WifiVif vif, ref ubyte[6] mac) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + { + if (!wifi_hw_get_mac(wifi.port, vif, mac)) + return InternalResult.failed; + return Result.success; + } +} + +ubyte wifi_get_channel(ref const Wifi wifi) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + return wifi_hw_get_channel(wifi.port); +} + +// STA-only: signal strength of current connection. +byte wifi_get_rssi(ref Wifi wifi) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + return wifi_hw_get_rssi(wifi.port); +} + +Result wifi_set_tx_power(ref Wifi wifi, byte power_dbm) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + { + if (!wifi_hw_set_tx_power(wifi.port, power_dbm)) + return InternalResult.failed; + return Result.success; + } +} + +// Events + +void wifi_set_event_callback(ref Wifi wifi, WifiEventCallback cb) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + wifi_hw_set_event_callback(wifi.port, cb); +} + +// Poll (for platforms without native event delivery) + +void wifi_poll(ref Wifi wifi) +{ + static if (num_wifi == 0) + assert(false, "no WiFi on this platform"); + else + wifi_hw_poll(wifi.port); +} + + +// ════════════════════════════════════════════════════════════════════ +// Tests +// ════════════════════════════════════════════════════════════════════ + +unittest +{ + static if (num_wifi > 0) + { + Wifi w; + WifiConfig cfg; + + wifi_init(); + + // Out-of-range port + auto r = wifi_open(w, cast(ubyte)num_wifi, cfg); + assert(!r); + assert(!w.is_open); + + // Open/close each valid port + foreach (p; 0 .. num_wifi) + { + Wifi port; + auto r2 = wifi_open(port, cast(ubyte)p, cfg); + assert(r2, "wifi_open failed"); + assert(port.is_open); + assert(port.port == p); + + wifi_poll(port); + + wifi_close(port); + assert(!port.is_open); + } + + wifi_deinit(); + } +} + + +private: + +__gshared ubyte _init_refcount; diff --git a/src/urt/driver/windows/alloc.d b/src/urt/driver/windows/alloc.d new file mode 100644 index 0000000..5642470 --- /dev/null +++ b/src/urt/driver/windows/alloc.d @@ -0,0 +1,71 @@ +module urt.driver.windows.alloc; + +version (Windows): + +import urt.mem.alloc : MemFlags; + +nothrow @nogc: + + +enum has_realloc = true; +enum has_expand = true; +enum has_memsize = true; +enum has_exec = true; +enum has_retain = false; +enum has_memflags = false; + +void[] _alloc(size_t size, size_t alignment, MemFlags) pure +{ + void* p = _aligned_malloc(size, alignment); + return p ? p[0 .. size] : null; +} + +void _free(void* ptr) pure +{ + _aligned_free(ptr); +} + +void[] _realloc(void[] mem, size_t new_size, size_t alignment, MemFlags) pure +{ + void* p = _aligned_realloc(mem.ptr, new_size, alignment); + return p ? p[0 .. new_size] : null; +} + +void[] _expand(void[] mem, size_t new_size) pure +{ + if (new_size <= _aligned_msize(mem.ptr)) + return mem.ptr[0 .. new_size]; + return null; +} + +size_t _memsize(void* ptr) pure +{ + return _aligned_msize(ptr); +} + +void[] _alloc_exec(size_t size) pure +{ + void* p = VirtualAlloc(null, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + return p ? p[0 .. size] : null; +} + +void _free_exec(void[] mem) pure +{ + cast(void)VirtualFree(mem.ptr, 0, MEM_RELEASE); +} + + +private: + +extern(C) void* _aligned_malloc(size_t size, size_t alignment) pure; +extern(C) void* _aligned_realloc(void* memblock, size_t size, size_t alignment) pure; +extern(C) void _aligned_free(void* memblock) pure; +extern(C) size_t _aligned_msize(void* memblock) pure; + +extern(Windows) void* VirtualAlloc(void* addr, size_t size, uint type, uint protect) pure; +extern(Windows) bool VirtualFree(void* addr, size_t size, uint type) pure; + +enum MEM_COMMIT = 0x1000; +enum MEM_RESERVE = 0x2000; +enum MEM_RELEASE = 0x8000; +enum PAGE_EXECUTE_READWRITE = 0x40; diff --git a/src/urt/driver/windows/ble.d b/src/urt/driver/windows/ble.d new file mode 100644 index 0000000..1638ae6 --- /dev/null +++ b/src/urt/driver/windows/ble.d @@ -0,0 +1,1987 @@ +// Windows BLE driver -- WinRT Bluetooth LE APIs +// +// Uses Windows.Devices.Bluetooth.* WinRT classes via COM vtable calls. +// All WinRT async operations are polled from ble_hw_poll(). WinRT +// callbacks (advertisements, notifications, connection status) fire on +// thread pool threads and are buffered into thread-safe queues, +// then delivered from ble_hw_poll() on the main loop. +// +// Windows has one BLE radio (port 0). Multiple radios are not +// distinguished by WinRT -- it uses the system default adapter. +module urt.driver.windows.ble; + +version (Windows): + +import urt.atomic : atomicFetchAdd, atomicFetchSub, atomicLoad, atomicStore; +import urt.log; +import urt.mem.allocator : defaultAllocator; +import urt.thread : ThreadSafeQueue; +import urt.uuid : GUID; + +import urt.driver.ble; + +nothrow @nogc: + +alias log = Log!"ble"; + +enum uint num_ble = 1; + + +// ════════════════════════════════════════════════════════════════════ +// Driver API implementation +// ════════════════════════════════════════════════════════════════════ + +bool ble_hw_open(uint port, ref const BLEConfig cfg) +{ + if (_opened) + return true; + + if (!g_winrt.initialized && !g_winrt.init()) + { + log.error("WinRT initialization failed"); + return false; + } + + _opened = true; + return true; +} + +void ble_hw_close(uint port) +{ + if (!_opened) + return; + + ble_hw_scan_stop(port); + stop_all_publishers(); + + // disconnect all sessions + foreach (ref s; _sessions[0 .. _num_sessions]) + { + if (s.active) + release_session(&s); + } + _num_sessions = 0; + + cleanup_connect(); + + _opened = false; + _scan_cb = null; + _conn_cb = null; + _discover_cb = null; + _read_cb = null; + _write_cb = null; + _notify_cb = null; +} + +// --- Scanning --- + +bool ble_hw_scan_start(uint port, ref const BLEScanConfig cfg) +{ + if (_watcher !is null) + return true; // already scanning + + _watcher = g_winrt.activate!IBluetoothLEAdvertisementWatcher( + "Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementWatcher"w, + &IID_IBluetoothLEAdvertisementWatcher); + + if (_watcher is null) + { + log.error("failed to create advertisement watcher"); + return false; + } + + _watcher.put_ScanningMode(cfg.active ? BluetoothLEScanningMode.active : BluetoothLEScanningMode.passive); + + _adv_handler = defaultAllocator().allocT!AdvertisementReceivedHandler; + _adv_handler.callback = &on_advertisement_received; + + EventRegistrationToken token; + if (_watcher.add_Received(cast(IUnknown)_adv_handler, &token) < 0) + { + log.error("failed to subscribe to advertisements"); + return false; + } + _received_token = token; + + // register Stopped handler to detect unexpected watcher shutdown + atomicStore(_watcher_stopped, 0u); + _stopped_handler = defaultAllocator().allocT!WatcherStoppedHandler; + _stopped_handler.flag = &_watcher_stopped; + EventRegistrationToken stopped_token; + _watcher.add_Stopped(cast(IUnknown)_stopped_handler, &stopped_token); + _stopped_token = stopped_token; + + if (_watcher.Start() < 0) + { + log.error("failed to start scanner"); + return false; + } + + return true; +} + +void ble_hw_scan_stop(uint port) +{ + if (_watcher !is null) + { + _watcher.Stop(); + _watcher.remove_Received(_received_token); + _watcher.remove_Stopped(_stopped_token); + _watcher.Release(); + _watcher = null; + } + if (_adv_handler !is null) + { + defaultAllocator().freeT(_adv_handler); + _adv_handler = null; + } + if (_stopped_handler !is null) + { + defaultAllocator().freeT(_stopped_handler); + _stopped_handler = null; + } + atomicStore(_watcher_stopped, 0u); +} + +// --- Advertising --- + +BLEAdv ble_hw_adv_start(uint port, ref const BLEAdvConfig cfg) +{ + if (_num_publishers >= max_publishers) + return BLEAdv.init; + + auto publisher = g_winrt.activate!IBluetoothLEAdvertisementPublisher( + "Windows.Devices.Bluetooth.Advertisement.BluetoothLEAdvertisementPublisher"w, + &IID_IBluetoothLEAdvertisementPublisher); + + if (publisher is null) + { + log.error("failed to create advertisement publisher"); + return BLEAdv.init; + } + + IBluetoothLEAdvertisement adv; + publisher.get_Advertisement(&adv); + + if (adv !is null) + { + set_adv_data_from_raw(adv, cfg.adv_data); + adv.Release(); + } + + if (publisher.Start() < 0) + { + log.error("failed to start advertising"); + publisher.Release(); + return BLEAdv.init; + } + + ubyte id = _next_adv_id++; + _publishers[_num_publishers++] = AdvSlot(id, publisher); + return BLEAdv(id); +} + +void ble_hw_adv_stop(uint port, BLEAdv adv) +{ + foreach (i, ref slot; _publishers[0 .. _num_publishers]) + { + if (slot.id == adv.id) + { + slot.publisher.Stop(); + slot.publisher.Release(); + _publishers[i] = _publishers[_num_publishers - 1]; + _num_publishers--; + return; + } + } +} + +// --- Connection --- + +bool ble_hw_connect(uint port, ref const ubyte[6] peer_addr, BLEAddrType addr_type, ref const BLEConnConfig cfg) +{ + if (_pending_connect.async_op !is null) + { + log.warning("connection already in progress"); + return false; + } + + if (_device_statics is null) + { + _device_statics = g_winrt.get_factory!IBluetoothLEDeviceStatics( + "Windows.Devices.Bluetooth.BluetoothLEDevice"w, + &IID_IBluetoothLEDeviceStatics); + + if (_device_statics is null) + { + log.error("failed to get BluetoothLEDevice statics"); + return false; + } + } + + ulong ble_addr = mac_to_ble_addr(peer_addr); + + IInspectable async_op; + if (_device_statics.FromBluetoothAddressAsync(ble_addr, &async_op) < 0 || async_op is null) + { + log.error("FromBluetoothAddressAsync failed"); + return false; + } + + _pending_connect.async_op = async_op; + _pending_connect.peer_addr = peer_addr; + return true; +} + +void ble_hw_connect_cancel(uint port) +{ + cleanup_connect(); +} + +bool ble_hw_disconnect(uint port, BLEConn conn) +{ + auto s = find_session(conn.id); + if (s is null) + return false; + + release_session(s); + remove_session(conn.id); + return true; +} + +// --- GATT discovery --- + +bool ble_hw_gatt_discover(uint port, BLEConn conn) +{ + auto s = find_session(conn.id); + if (s is null || s.device3 is null) + return false; + + IInspectable gatt_op; + s.device3.GetGattServicesWithCacheModeAsync(1, &gatt_op); // 1 = Uncached + if (gatt_op is null) + return false; + + _pending_discover.async_op = gatt_op; + _pending_discover.conn_id = conn.id; + _pending_discover.phase = DiscoverPhase.services; + s.num_chars = 0; + + return true; +} + +// --- GATT read/write --- + +bool ble_hw_gatt_read(uint port, BLEConn conn, ushort handle) +{ + if (_num_pending_gatt >= max_gatt_ops) + return false; + + auto s = find_session(conn.id); + if (s is null) + return false; + + auto gc = find_session_char(s, handle); + if (gc is null || gc.characteristic is null) + return false; + + IInspectable async_op; + gc.characteristic.ReadValueWithCacheModeAsync(1, &async_op); // Uncached + if (async_op is null) + return false; + + _pending_gatt[_num_pending_gatt++] = PendingGattOp( + async_op, conn.id, handle, GattOpType.read); + return true; +} + +bool ble_hw_gatt_write(uint port, BLEConn conn, ushort handle, const(ubyte)[] data, bool with_response) +{ + if (_num_pending_gatt >= max_gatt_ops) + return false; + + auto s = find_session(conn.id); + if (s is null) + return false; + + auto gc = find_session_char(s, handle); + if (gc is null || gc.characteristic is null) + return false; + + auto buf = defaultAllocator().allocT!MemoryBuffer; + if (buf is null) + return false; + + // clone data -- packet payload may be freed before async completes + ubyte* data_copy = cast(ubyte*)defaultAllocator().alloc(data.length).ptr; + if (data_copy is null && data.length > 0) + { + defaultAllocator().freeT(buf); + return false; + } + data_copy[0 .. data.length] = data[]; + buf.set(data_copy[0 .. data.length]); + + IInspectable async_op; + if (with_response) + gc.characteristic.WriteValueAsync(cast(IBuffer)buf, &async_op); + else + gc.characteristic.WriteValueWithOptionAsync(cast(IBuffer)buf, 1, &async_op); // WriteWithoutResponse + + if (async_op is null) + { + buf.Release(); // frees both MemoryBuffer and data_copy + return false; + } + + _pending_gatt[_num_pending_gatt++] = PendingGattOp( + async_op, conn.id, handle, + with_response ? GattOpType.write : GattOpType.write_no_response); + return true; +} + +// --- Notifications --- + +bool ble_hw_gatt_subscribe(uint port, BLEConn conn, ushort handle, bool enable) +{ + auto s = find_session(conn.id); + if (s is null) + return false; + + auto gc = find_session_char(s, handle); + if (gc is null || gc.characteristic is null) + return false; + + if (enable) + { + if (gc.notify_handler !is null) + return true; // already subscribed + + auto handler = defaultAllocator().allocT!GattValueChangedHandler; + handler.conn_id = conn.id; + handler.attr_handle = handle; + handler.callback = &on_gatt_notification; + + EventRegistrationToken token; + if (gc.characteristic.add_ValueChanged(cast(IUnknown)handler, &token) < 0) + { + defaultAllocator().freeT(handler); + return false; + } + gc.notify_handler = handler; + gc.notify_token = token; + + // write CCCD + GattCharacteristicProperties props; + gc.characteristic.get_CharacteristicProperties(&props); + + auto cccd_value = (props & GattCharacteristicProperties.notify) != 0 + ? GattClientCharacteristicConfigurationDescriptorValue.notify + : GattClientCharacteristicConfigurationDescriptorValue.indicate; + + IInspectable async_op; + gc.characteristic.WriteClientCharacteristicConfigurationDescriptorAsync(cccd_value, &async_op); + if (async_op !is null) + (cast(IUnknown)async_op).Release(); // fire and forget + } + else + { + if (gc.notify_handler is null) + return true; // not subscribed + + gc.characteristic.remove_ValueChanged(gc.notify_token); + defaultAllocator().freeT(gc.notify_handler); + gc.notify_handler = null; + + // write CCCD to disable + IInspectable async_op; + gc.characteristic.WriteClientCharacteristicConfigurationDescriptorAsync( + GattClientCharacteristicConfigurationDescriptorValue.none, &async_op); + if (async_op !is null) + (cast(IUnknown)async_op).Release(); // fire and forget + } + + return true; +} + +// --- Queries --- + +bool ble_hw_get_mac(uint port, ref ubyte[6] mac) +{ + // WinRT doesn't easily expose the local adapter MAC. + // Return a placeholder; the interface layer can override. + mac = [0, 0, 0, 0, 0, 0]; + return false; +} + +byte ble_hw_get_rssi(uint port, BLEConn conn) +{ + // WinRT doesn't provide per-connection RSSI queries + return -128; +} + +// --- Callbacks --- + +void ble_hw_set_scan_callback(uint port, BLEScanCallback cb) { _scan_cb = cb; } +void ble_hw_set_conn_callback(uint port, BLEConnCallback cb) { _conn_cb = cb; } +void ble_hw_set_discover_callback(uint port, BLEDiscoverCallback cb) { _discover_cb = cb; } +void ble_hw_set_read_callback(uint port, BLEReadCallback cb) { _read_cb = cb; } +void ble_hw_set_write_callback(uint port, BLEWriteCallback cb) { _write_cb = cb; } +void ble_hw_set_notify_callback(uint port, BLENotifyCallback cb) { _notify_cb = cb; } + +// --- Poll --- + +void ble_hw_poll(uint port) +{ + auto ble = BLE(0); + + // check if watcher stopped unexpectedly (e.g. radio disabled) + if (atomicLoad(_watcher_stopped) != 0) + { + log.warning("BLE scanner stopped unexpectedly"); + atomicStore(_watcher_stopped, 0u); + } + + // drain scan results + BLEAdvReport report = void; + while (_scan_ring.dequeue(&report)) + { + if (_scan_cb !is null) + _scan_cb(ble, report); + } + + // poll pending connection + poll_connect(ble); + + // poll GATT discovery + poll_discover(ble); + + // poll GATT read/write ops + poll_gatt(ble); + + // drain notification events + NotifyEvent evt = void; + while (_notify_ring.dequeue(&evt)) + { + if (_notify_cb !is null) + _notify_cb(ble, BLEConn(evt.conn_id), evt.handle, evt.data[0 .. evt.data_len]); + } + + // drain disconnection events + ubyte conn_id = void; + while (_disconn_ring.dequeue(&conn_id)) + { + auto s = find_session(conn_id); + if (s !is null) + { + if (_conn_cb !is null) + _conn_cb(ble, BLEConn(conn_id), false, BLEError.none); + release_session(s); + remove_session(conn_id); + } + } +} + + +// ════════════════════════════════════════════════════════════════════ +// Internal state +// ════════════════════════════════════════════════════════════════════ + +private: + +enum max_sessions = 8; +enum max_chars_per_session = 32; +enum max_gatt_ops = 8; +enum max_publishers = 8; + +struct AdvSlot +{ + ubyte id; + IBluetoothLEAdvertisementPublisher publisher; +} + +enum GattOpType : ubyte { read, write, write_no_response } +enum DiscoverPhase : ubyte { idle, services, chars } + +struct SessionChar +{ + ushort handle; + GUID service_uuid; + GUID char_uuid; + GattCharacteristicProperties properties; + IGattCharacteristic characteristic; + GattValueChangedHandler notify_handler; + EventRegistrationToken notify_token; +} + +struct WinSession +{ + bool active; + ubyte id; + IBluetoothLEDevice device; + IBluetoothLEDevice3 device3; + ConnectionStatusHandler conn_handler; + EventRegistrationToken conn_status_token; + SessionChar[max_chars_per_session] chars; + ubyte num_chars; +} + +struct PendingConnect +{ + IInspectable async_op; + ubyte[6] peer_addr; +} + +struct PendingDiscover +{ + IInspectable async_op; + IVectorView_IInspectable services; + uint service_count; + uint current_service; + GUID current_service_uuid; + ubyte conn_id; + DiscoverPhase phase; +} + +struct PendingGattOp +{ + IInspectable async_op; + ubyte conn_id; + ushort handle; + GattOpType op_type; +} + +struct NotifyEvent +{ + ubyte conn_id; + ushort handle; + ubyte data_len; + ubyte[247] data; +} + +// --- module-level state --- + +__gshared bool _opened; +__gshared ubyte _next_conn_id; + +// sessions +__gshared WinSession[max_sessions] _sessions; +__gshared ubyte _num_sessions; + +// callbacks +__gshared BLEScanCallback _scan_cb; +__gshared BLEConnCallback _conn_cb; +__gshared BLEDiscoverCallback _discover_cb; +__gshared BLEReadCallback _read_cb; +__gshared BLEWriteCallback _write_cb; +__gshared BLENotifyCallback _notify_cb; + +// scanner +__gshared IBluetoothLEAdvertisementWatcher _watcher; +__gshared AdvertisementReceivedHandler _adv_handler; +__gshared EventRegistrationToken _received_token; +__gshared WatcherStoppedHandler _stopped_handler; +__gshared EventRegistrationToken _stopped_token; +shared uint _watcher_stopped; + +// advertisers +__gshared AdvSlot[max_publishers] _publishers; +__gshared ubyte _num_publishers; +__gshared ubyte _next_adv_id; + +// device factory +__gshared IBluetoothLEDeviceStatics _device_statics; + +// pending connect +__gshared PendingConnect _pending_connect; + +// pending discover +__gshared PendingDiscover _pending_discover; + +// pending GATT ops +__gshared PendingGattOp[max_gatt_ops] _pending_gatt; +__gshared ubyte _num_pending_gatt; + +// thread-safe event queues (written from WinRT threads, read from main) +__gshared ThreadSafeQueue!(32, BLEAdvReport) _scan_ring; +__gshared ThreadSafeQueue!(32, NotifyEvent) _notify_ring; +__gshared ThreadSafeQueue!(8, ubyte) _disconn_ring; + + +// ════════════════════════════════════════════════════════════════════ +// Session management +// ════════════════════════════════════════════════════════════════════ + +WinSession* find_session(ubyte id) +{ + foreach (ref s; _sessions[0 .. _num_sessions]) + { + if (s.active && s.id == id) + return &s; + } + return null; +} + +SessionChar* find_session_char(WinSession* s, ushort handle) +{ + foreach (ref c; s.chars[0 .. s.num_chars]) + { + if (c.handle == handle) + return &c; + } + return null; +} + +WinSession* alloc_session() +{ + if (_num_sessions >= max_sessions) + return null; + + // find an ID not currently in use + ubyte id = _next_conn_id; + outer: foreach (_; 0 .. 256) + { + foreach (ref s; _sessions[0 .. _num_sessions]) + if (s.id == id) + { + id++; + continue outer; + } + break; + } + _next_conn_id = cast(ubyte)(id + 1); + + auto s = &_sessions[_num_sessions++]; + s.active = true; + s.id = id; + s.num_chars = 0; + return s; +} + +void remove_session(ubyte id) +{ + foreach (i, ref s; _sessions[0 .. _num_sessions]) + { + if (s.id == id) + { + _sessions[i] = _sessions[_num_sessions - 1]; + _num_sessions--; + return; + } + } +} + +void release_session(WinSession* s) +{ + if (s.device !is null && s.conn_handler !is null) + { + s.device.remove_ConnectionStatusChanged(s.conn_status_token); + defaultAllocator().freeT(s.conn_handler); + s.conn_handler = null; + } + + foreach (ref gc; s.chars[0 .. s.num_chars]) + { + if (gc.notify_handler !is null) + { + gc.characteristic.remove_ValueChanged(gc.notify_token); + defaultAllocator().freeT(gc.notify_handler); + gc.notify_handler = null; + } + if (gc.characteristic !is null) + { + gc.characteristic.Release(); + gc.characteristic = null; + } + } + + if (s.device3 !is null) + { + s.device3.Release(); + s.device3 = null; + } + if (s.device !is null) + { + s.device.Release(); + s.device = null; + } + s.active = false; +} + +void stop_all_publishers() +{ + foreach (ref slot; _publishers[0 .. _num_publishers]) + { + slot.publisher.Stop(); + slot.publisher.Release(); + } + _num_publishers = 0; +} + +void cleanup_connect() +{ + if (_pending_connect.async_op !is null) + { + _pending_connect.async_op.Release(); + _pending_connect.async_op = null; + } +} + + +// ════════════════════════════════════════════════════════════════════ +// Async polling (main thread) +// ════════════════════════════════════════════════════════════════════ + +void poll_connect(BLE ble) +{ + if (_pending_connect.async_op is null) + return; + + auto async_info = qi!IAsyncInfo(_pending_connect.async_op, &IID_IAsyncInfo); + if (async_info is null) + return; + scope(exit) async_info.Release(); + + AsyncStatus status; + async_info.get_Status(&status); + + if (status == AsyncStatus.started) + return; + + if (status != AsyncStatus.completed) + { + cleanup_connect(); + if (_conn_cb !is null) + _conn_cb(ble, BLEConn(ubyte.max), false, BLEError.not_found); + return; + } + + auto async_op = cast(IAsyncOperation_BluetoothLEDevice)cast(void*)_pending_connect.async_op; + IBluetoothLEDevice device; + async_op.GetResults(&device); + + auto peer_addr = _pending_connect.peer_addr; + cleanup_connect(); + + if (device is null) + { + if (_conn_cb !is null) + _conn_cb(ble, BLEConn(ubyte.max), false, BLEError.not_found); + return; + } + + auto s = alloc_session(); + if (s is null) + { + device.Release(); + if (_conn_cb !is null) + _conn_cb(ble, BLEConn(ubyte.max), false, BLEError.internal); + return; + } + + s.device = device; + s.device3 = qi!IBluetoothLEDevice3(device, &IID_IBluetoothLEDevice3); + + // register connection status handler + auto handler = defaultAllocator().allocT!ConnectionStatusHandler; + handler.conn_id = s.id; + handler.callback = &on_connection_status_changed; + s.device.add_ConnectionStatusChanged(cast(IUnknown)handler, &s.conn_status_token); + s.conn_handler = handler; + + log.info("connected to ", peer_addr); + + if (_conn_cb !is null) + _conn_cb(ble, BLEConn(s.id), true, BLEError.none); +} + +void poll_discover(BLE ble) +{ + if (_pending_discover.phase == DiscoverPhase.idle) + return; + if (_pending_discover.async_op is null && _pending_discover.services is null) + return; + if (_pending_discover.async_op is null) + return; + + auto async_info = qi!IAsyncInfo(_pending_discover.async_op, &IID_IAsyncInfo); + if (async_info is null) + return; + scope(exit) async_info.Release(); + + AsyncStatus status; + async_info.get_Status(&status); + + if (status == AsyncStatus.started) + return; + + IInspectable result_raw; + if (status == AsyncStatus.completed) + { + if (_pending_discover.services is null) + { + auto async_op = cast(IAsyncOperation_GattDeviceServicesResult)cast(void*)_pending_discover.async_op; + IGattDeviceServicesResult svc_result; + async_op.GetResults(&svc_result); + result_raw = cast(IInspectable)cast(void*)svc_result; + } + else + { + auto async_op = cast(IAsyncOperation_GattCharacteristicsResult)cast(void*)_pending_discover.async_op; + IGattCharacteristicsResult chr_result; + async_op.GetResults(&chr_result); + result_raw = cast(IInspectable)cast(void*)chr_result; + } + } + + // release async op + _pending_discover.async_op.Release(); + _pending_discover.async_op = null; + + auto conn_id = _pending_discover.conn_id; + auto s = find_session(conn_id); + + if (_pending_discover.services is null) + { + // phase 1: service list + if (result_raw is null) + { + finish_discover(ble, conn_id, BLEError.protocol); + return; + } + + auto svc_result = cast(IGattDeviceServicesResult)cast(void*)result_raw; + GattCommunicationStatus gatt_status; + svc_result.get_Status(&gatt_status); + + if (gatt_status != GattCommunicationStatus.success) + { + result_raw.Release(); + finish_discover(ble, conn_id, BLEError.protocol); + return; + } + + IInspectable services_raw; + svc_result.get_Services(&services_raw); + result_raw.Release(); + + if (services_raw is null) + { + finish_discover(ble, conn_id, BLEError.none); + return; + } + + auto services = cast(IVectorView_IInspectable)cast(void*)services_raw; + uint count; + services.get_Size(&count); + + _pending_discover.services = services; + _pending_discover.service_count = count; + _pending_discover.current_service = 0; + _pending_discover.phase = DiscoverPhase.chars; + + discover_next_service(); + } + else + { + // phase 2: characteristics for a service + if (result_raw !is null && s !is null) + { + auto chars_result = cast(IGattCharacteristicsResult)cast(void*)result_raw; + GattCommunicationStatus gatt_status; + chars_result.get_Status(&gatt_status); + + if (gatt_status == GattCommunicationStatus.success) + { + IInspectable chars_raw; + chars_result.get_Characteristics(&chars_raw); + if (chars_raw !is null) + { + auto chars = cast(IVectorView_IInspectable)cast(void*)chars_raw; + uint count; + chars.get_Size(&count); + + foreach (j; 0 .. count) + { + IInspectable char_raw; + chars.GetAt(j, &char_raw); + if (char_raw is null) + continue; + + auto chr = cast(IGattCharacteristic)cast(void*)char_raw; + if (s.num_chars < max_chars_per_session) + { + auto ci = &s.chars[s.num_chars++]; + chr.get_AttributeHandle(&ci.handle); + ci.service_uuid = _pending_discover.current_service_uuid; + chr.get_Uuid(&ci.char_uuid); + chr.get_CharacteristicProperties(&ci.properties); + ci.characteristic = chr; + } + else + (cast(IUnknown)chr).Release(); + } + chars_raw.Release(); + } + } + result_raw.Release(); + } + + _pending_discover.current_service++; + if (!discover_next_service()) + finish_discover(ble, conn_id, BLEError.none); + } +} + +bool discover_next_service() +{ + auto services = _pending_discover.services; + while (_pending_discover.current_service < _pending_discover.service_count) + { + IInspectable svc_raw; + services.GetAt(_pending_discover.current_service, &svc_raw); + if (svc_raw is null) + { + _pending_discover.current_service++; + continue; + } + + auto svc = cast(IGattDeviceService)cast(void*)svc_raw; + svc.get_Uuid(&_pending_discover.current_service_uuid); + + auto svc3 = qi!IGattDeviceService3(svc_raw, &IID_IGattDeviceService3); + svc_raw.Release(); + + if (svc3 is null) + { + _pending_discover.current_service++; + continue; + } + + IInspectable chars_op; + svc3.GetCharacteristicsAsync(&chars_op); + svc3.Release(); + + if (chars_op is null) + { + _pending_discover.current_service++; + continue; + } + + _pending_discover.async_op = chars_op; + return true; + } + + // all done + if (_pending_discover.services !is null) + { + (cast(IUnknown)_pending_discover.services).Release(); + _pending_discover.services = null; + } + return false; +} + +void finish_discover(BLE ble, ubyte conn_id, BLEError error) +{ + if (_pending_discover.services !is null) + { + (cast(IUnknown)_pending_discover.services).Release(); + _pending_discover.services = null; + } + _pending_discover.phase = DiscoverPhase.idle; + + if (_discover_cb !is null) + { + auto s = find_session(conn_id); + if (s !is null && error == BLEError.none) + { + BLEGattChar[max_chars_per_session] chars = void; + foreach (i; 0 .. s.num_chars) + { + chars[i].handle = s.chars[i].handle; + chars[i].cccd_handle = 0; // WinRT handles CCCD internally + chars[i].service_uuid = s.chars[i].service_uuid; + chars[i].char_uuid = s.chars[i].char_uuid; + chars[i].properties = cast(GattCharProps)s.chars[i].properties; + } + _discover_cb(ble, BLEConn(conn_id), chars[0 .. s.num_chars], BLEError.none); + } + else + _discover_cb(ble, BLEConn(conn_id), null, error); + } +} + +void poll_gatt(BLE ble) +{ + uint i = 0; + while (i < _num_pending_gatt) + { + auto pg = &_pending_gatt[i]; + + auto async_info = qi!IAsyncInfo(pg.async_op, &IID_IAsyncInfo); + if (async_info is null) + { + i++; + continue; + } + + AsyncStatus status; + async_info.get_Status(&status); + async_info.Release(); + + if (status == AsyncStatus.started) + { + i++; + continue; + } + + auto conn = BLEConn(pg.conn_id); + bool success = false; + const(ubyte)[] data; + IBuffer value_buf; + + if (status == AsyncStatus.completed) + { + if (pg.op_type == GattOpType.read) + { + auto async_op = cast(IAsyncOperation_GattReadResult)cast(void*)pg.async_op; + IGattReadResult read_result; + async_op.GetResults(&read_result); + + if (read_result !is null) + { + GattCommunicationStatus gatt_status; + read_result.get_Status(&gatt_status); + if (gatt_status == GattCommunicationStatus.success) + { + read_result.get_Value(&value_buf); + data = get_buffer_bytes(value_buf); + success = true; + } + read_result.Release(); + } + } + else + { + auto async_op = cast(IAsyncOperation_GattCommunicationStatus)cast(void*)pg.async_op; + GattCommunicationStatus gatt_status; + async_op.GetResults(&gatt_status); + success = gatt_status == GattCommunicationStatus.success; + } + } + + // fire callback + if (pg.op_type == GattOpType.read) + { + if (_read_cb !is null) + _read_cb(ble, conn, pg.handle, data, success ? BLEError.none : BLEError.protocol); + } + else + { + if (_write_cb !is null) + _write_cb(ble, conn, pg.handle, success ? BLEError.none : BLEError.protocol); + } + + if (value_buf !is null) + value_buf.Release(); + pg.async_op.Release(); + + // compact array + --_num_pending_gatt; + if (i < _num_pending_gatt) + _pending_gatt[i] = _pending_gatt[_num_pending_gatt]; + // don't increment i -- swapped element needs checking + } +} + + +// ════════════════════════════════════════════════════════════════════ +// WinRT thread callbacks (fire on thread pool) +// ════════════════════════════════════════════════════════════════════ + +void on_advertisement_received(ubyte[6] addr, byte rssi, bool connectable, bool is_scan_response, const(ubyte)[] ad_payload) +{ + BLEAdvReport report = void; + report.addr = addr; + report.addr_type = BLEAddrType.public_; + report.adv_type = connectable ? BLEAdvType.connectable : BLEAdvType.nonconnectable; + report.rssi = rssi; + report.tx_power = -128; + ubyte len = ad_payload.length > 62 ? 62 : cast(ubyte)ad_payload.length; + report.data_len = len; + if (len > 0) + report.data_buf[0 .. len] = cast(const(ubyte)[])ad_payload[0 .. len]; + _scan_ring.enqueue(report); +} + +void on_gatt_notification(ubyte conn_id, ushort attr_handle, const(ubyte)[] data) +{ + NotifyEvent evt = void; + evt.conn_id = conn_id; + evt.handle = attr_handle; + ubyte len = data.length > 247 ? 247 : cast(ubyte)data.length; + evt.data_len = len; + if (len > 0) + evt.data[0 .. len] = cast(const(ubyte)[])data[0 .. len]; + _notify_ring.enqueue(evt); +} + +void on_connection_status_changed(ubyte conn_id, int status) +{ + if (status == 0) // Disconnected + _disconn_ring.enqueue(conn_id); +} + + +// ════════════════════════════════════════════════════════════════════ +// Helpers +// ════════════════════════════════════════════════════════════════════ + +T qi(T)(IUnknown obj, const(GUID)* iid) +{ + if (obj is null) + return null; + void* result; + HRESULT hr = obj.QueryInterface(iid, &result); + if (hr < 0) + return null; + return cast(T)cast(void*)result; +} + +ulong mac_to_ble_addr(ref const ubyte[6] mac) +{ + return (cast(ulong)mac[0] << 40) | (cast(ulong)mac[1] << 32) | + (cast(ulong)mac[2] << 24) | (cast(ulong)mac[3] << 16) | + (cast(ulong)mac[4] << 8) | mac[5]; +} + +void ble_addr_to_mac(ulong addr, ref ubyte[6] mac) +{ + mac[0] = cast(ubyte)(addr >> 40); + mac[1] = cast(ubyte)(addr >> 32); + mac[2] = cast(ubyte)(addr >> 24); + mac[3] = cast(ubyte)(addr >> 16); + mac[4] = cast(ubyte)(addr >> 8); + mac[5] = cast(ubyte)(addr); +} + +const(ubyte)[] get_buffer_bytes(IBuffer buf) +{ + if (buf is null) + return null; + + uint len; + if (buf.get_Length(&len) < 0) + return null; + if (len == 0) + return null; + + auto access = qi!IBufferByteAccess(buf, &IID_IBufferByteAccess); + if (access is null) + return null; + scope(exit) access.Release(); + + ubyte* ptr; + if (access.Buffer(&ptr) < 0 || ptr is null) + return null; + + return ptr[0 .. len]; +} + +void set_adv_data_from_raw(IBluetoothLEAdvertisement adv, const(ubyte)[] raw) +{ + uint offset = 0; + while (offset < raw.length) + { + if (offset + 1 >= raw.length) + break; + ubyte len = raw[offset++]; + if (len == 0 || offset + len > raw.length) + break; + ubyte ad_type = raw[offset]; + const(ubyte)[] ad_data = raw[offset + 1 .. offset + len]; + offset += len; + + // set local name if present + if (ad_type == 0x09 || ad_type == 0x08) // complete/shortened local name + { + wchar[64] wname = void; + uint wlen = cast(uint)ad_data.length; + if (wlen > 64) wlen = 64; + foreach (i; 0 .. wlen) + wname[i] = ad_data[i]; + HSTRING hname = g_winrt.make_string(wname[0 .. wlen]); + if (hname !is null) + { + adv.put_LocalName(hname); + g_winrt.WindowsDeleteString(hname); + } + } + } +} + + +// ════════════════════════════════════════════════════════════════════ +// WinRT bootstrap +// ════════════════════════════════════════════════════════════════════ + +struct WinRT +{ +nothrow @nogc: + bool initialized; + + import urt.internal.sys.windows.windef : HMODULE; + private HMODULE _lib; + + extern(Windows) HRESULT function(uint initType) RoInitialize; + extern(Windows) HRESULT function(HSTRING classId, IInspectable* instance) RoActivateInstance; + extern(Windows) HRESULT function(HSTRING classId, const(GUID)* iid, void** factory) RoGetActivationFactory; + extern(Windows) HRESULT function(const(wchar)* str, uint len, HSTRING* out_) WindowsCreateString; + extern(Windows) HRESULT function(HSTRING str) WindowsDeleteString; + extern(Windows) const(wchar)* function(HSTRING str, uint* len) WindowsGetStringRawBuffer; + + bool init() + { + import urt.internal.sys.windows.winbase : LoadLibrary, FreeLibrary, GetProcAddress; + + auto lib = LoadLibrary("combase.dll"); + if (!lib) + { + log.error("failed to load combase.dll"); + return false; + } + + RoInitialize = cast(typeof(RoInitialize)) GetProcAddress(lib, "RoInitialize"); + RoActivateInstance = cast(typeof(RoActivateInstance)) GetProcAddress(lib, "RoActivateInstance"); + RoGetActivationFactory = cast(typeof(RoGetActivationFactory)) GetProcAddress(lib, "RoGetActivationFactory"); + WindowsCreateString = cast(typeof(WindowsCreateString)) GetProcAddress(lib, "WindowsCreateString"); + WindowsDeleteString = cast(typeof(WindowsDeleteString)) GetProcAddress(lib, "WindowsDeleteString"); + WindowsGetStringRawBuffer = cast(typeof(WindowsGetStringRawBuffer)) GetProcAddress(lib, "WindowsGetStringRawBuffer"); + + if (!RoInitialize || !RoActivateInstance || !RoGetActivationFactory || + !WindowsCreateString || !WindowsDeleteString || !WindowsGetStringRawBuffer) + { + log.error("failed to resolve WinRT functions from combase.dll"); + FreeLibrary(lib); + return false; + } + + HRESULT hr = RoInitialize(1); // RO_INIT_MULTITHREADED + if (hr < 0 && hr != cast(HRESULT)0x80010106) // RPC_E_CHANGED_MODE is ok + { + log.error("RoInitialize failed: ", hr); + FreeLibrary(lib); + return false; + } + + _lib = lib; + initialized = true; + log.info("WinRT initialized"); + return true; + } + + HSTRING make_string(const(wchar)[] s) + { + HSTRING h; + if (WindowsCreateString(s.ptr, cast(uint)s.length, &h) < 0) + return null; + return h; + } + + T activate(T : IInspectable)(const(wchar)[] className, const(GUID)* iid) + { + HSTRING cls = make_string(className); + if (!cls) + return null; + scope(exit) WindowsDeleteString(cls); + + IInspectable inspectable; + if (RoActivateInstance(cls, &inspectable) < 0 || inspectable is null) + return null; + scope(exit) inspectable.Release(); + + void* result; + if (inspectable.QueryInterface(iid, &result) < 0) + return null; + return cast(T)cast(void*)result; + } + + T get_factory(T)(const(wchar)[] className, const(GUID)* iid) + { + HSTRING cls = make_string(className); + if (!cls) + return null; + scope(exit) WindowsDeleteString(cls); + + void* result; + if (RoGetActivationFactory(cls, iid, &result) < 0) + return null; + return cast(T)cast(void*)result; + } +} + +__gshared WinRT g_winrt; + + +// ════════════════════════════════════════════════════════════════════ +// COM/WinRT types and interfaces +// ════════════════════════════════════════════════════════════════════ + +alias HRESULT = int; +alias ULONG = uint; +alias BOOL = int; +alias HSTRING = void*; + +struct EventRegistrationToken { long value; } + +enum AsyncStatus : int { started = 0, completed = 1, canceled = 2, error = 3 } +enum BluetoothLEScanningMode : int { passive = 0, active = 1 } +enum GattCommunicationStatus : int { success = 0, unreachable = 1, protocol_error = 2, access_denied = 3 } + +enum GattCharacteristicProperties : uint +{ + none = 0, broadcast = 0x0001, read = 0x0002, write_without_response = 0x0004, + write = 0x0008, notify = 0x0010, indicate = 0x0020, + authenticated_signed_writes = 0x0040, extended_properties = 0x0080, + reliable_write = 0x0100, writable_auxiliaries = 0x0200, +} + +enum GattClientCharacteristicConfigurationDescriptorValue : int { none = 0, notify = 1, indicate = 2 } + +// GUIDs +static immutable IID_IUnknown = GUID(0x00000000, 0x0000, 0x0000, [0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46]); +static immutable IID_IInspectable = GUID(0xAF86E2E0, 0xB12D, 0x4C6A, [0x9C,0x5A,0xD7,0xAA,0x65,0x10,0x1E,0x90]); +static immutable IID_IAgileObject = GUID(0x94EA2B94, 0xE9CC, 0x49E0, [0xC0,0xFF,0xEE,0x64,0xCA,0x8F,0x5B,0x90]); +static immutable IID_IAsyncInfo = GUID(0x00000036, 0x0000, 0x0000, [0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46]); + +static immutable IID_IBluetoothLEAdvertisementWatcher = GUID(0xA6AC336F, 0xF3D3, 0x4297, [0x8D,0x6C,0xC8,0x1E,0xA6,0x62,0x3F,0x40]); +static immutable IID_IBluetoothLEAdvertisementReceivedEventArgs = GUID(0x27987DDF, 0xE596, 0x41BE, [0x8D,0x43,0x9E,0x67,0x31,0xD4,0xA9,0x13]); +static immutable IID_IBluetoothLEAdvertisementReceivedEventArgs2 = GUID(0x12D9C87B, 0x0399, 0x5F0E, [0xA3,0x48,0x53,0xB0,0x2B,0x6B,0x16,0x2E]); +static immutable IID_IBluetoothLEAdvertisement = GUID(0x066FB2B7, 0x33D1, 0x4E7D, [0x83,0x67,0xCF,0x81,0xD0,0xF7,0x96,0x53]); +static immutable IID_IBluetoothLEDevice = GUID(0xB5EE2F7B, 0x4AD8, 0x4642, [0xAC,0x48,0x80,0xA0,0xB5,0x00,0xE8,0x87]); +static immutable IID_IBluetoothLEDeviceStatics = GUID(0xC8CF1A19, 0xF0B6, 0x4BF0, [0x86,0x89,0x41,0x30,0x3D,0xE2,0xD9,0xF4]); +static immutable IID_IBluetoothLEDevice3 = GUID(0xAEE9E493, 0x44AC, 0x40DC, [0xAF,0x33,0xB2,0xC1,0x3C,0x01,0xCA,0x46]); +static immutable IID_IGattDeviceServicesResult = GUID(0x171DD3EE, 0x016D, 0x419D, [0x83,0x8A,0x57,0x6C,0xF4,0x75,0xA3,0xD8]); +static immutable IID_IGattDeviceService3 = GUID(0xB293A950, 0x0C53, 0x437C, [0xA9,0xB3,0x5C,0x32,0x10,0xC6,0xE5,0x69]); +static immutable IID_IGattCharacteristic = GUID(0x59CB50C1, 0x5934, 0x4F68, [0xA1,0x98,0xEB,0x86,0x4F,0xA4,0x4E,0x6B]); +static immutable IID_IGattReadResult = GUID(0x63A66F08, 0x1AEA, 0x4C4C, [0xA5,0x0F,0x97,0xBA,0xE4,0x74,0xB3,0x48]); +static immutable IID_IBluetoothLEAdvertisementPublisher = GUID(0xCDE820F9, 0xD9FA, 0x43D6, [0xA2,0x64,0xDD,0xD8,0xB7,0xDA,0x8B,0x78]); +static immutable IID_IBufferByteAccess = GUID(0x905A0FEF, 0xBC53, 0x11DF, [0x8C,0x49,0x00,0x1E,0x4F,0xC6,0x86,0xDA]); +static immutable IID_TypedEventHandler_Watcher_Received = GUID(0x90EB4ECA, 0xD465, 0x5EA0, [0xA6,0x1C,0x03,0x3C,0x8C,0x5E,0xCE,0xF2]); +static immutable IID_TypedEventHandler_Gatt_ValueChanged = GUID(0xC1F420F6, 0x6292, 0x5760, [0xA2,0xC9,0x9D,0xDF,0x98,0x68,0x3C,0xFC]); +static immutable IID_TypedEventHandler_Device_ConnectionStatus = GUID(0x24A901AD, 0x910F, 0x5C29, [0xB2,0x36,0x80,0x3C,0xC0,0x30,0x60,0xFE]); +static immutable IID_TypedEventHandler_Watcher_Stopped = GUID(0x9936A4DB, 0xDC99, 0x55C3, [0x9E,0x9B,0xBF,0x48,0x54,0xBD,0x9F,0x0B]); + + +// --- COM interfaces --- + +extern(Windows): + +interface IUnknown +{ +nothrow @nogc: + HRESULT QueryInterface(const(GUID)* riid, void** ppv); + ULONG AddRef(); + ULONG Release(); +} + +interface IInspectable : IUnknown +{ +nothrow @nogc: + HRESULT GetIids(uint* count, GUID** iids); + HRESULT GetRuntimeClassName(HSTRING* name); + HRESULT GetTrustLevel(int* level); +} + +interface IAsyncInfo : IInspectable +{ +nothrow @nogc: + HRESULT get_Id(uint* id); + HRESULT get_Status(AsyncStatus* status); + HRESULT get_ErrorCode(HRESULT* code); + HRESULT Cancel(); + HRESULT Close(); +} + +interface IAsyncOperation_BluetoothLEDevice : IInspectable +{ +nothrow @nogc: + HRESULT put_Completed(IUnknown handler); + HRESULT get_Completed(IUnknown* handler); + HRESULT GetResults(IBluetoothLEDevice* result); +} + +interface IAsyncOperation_GattDeviceServicesResult : IInspectable +{ +nothrow @nogc: + HRESULT put_Completed(IUnknown handler); + HRESULT get_Completed(IUnknown* handler); + HRESULT GetResults(IGattDeviceServicesResult* result); +} + +interface IAsyncOperation_GattCharacteristicsResult : IInspectable +{ +nothrow @nogc: + HRESULT put_Completed(IUnknown handler); + HRESULT get_Completed(IUnknown* handler); + HRESULT GetResults(IGattCharacteristicsResult* result); +} + +interface IAsyncOperation_GattReadResult : IInspectable +{ +nothrow @nogc: + HRESULT put_Completed(IUnknown handler); + HRESULT get_Completed(IUnknown* handler); + HRESULT GetResults(IGattReadResult* result); +} + +interface IAsyncOperation_GattCommunicationStatus : IInspectable +{ +nothrow @nogc: + HRESULT put_Completed(IUnknown handler); + HRESULT get_Completed(IUnknown* handler); + HRESULT GetResults(GattCommunicationStatus* result); +} + +interface IBluetoothLEAdvertisementWatcher : IInspectable +{ +nothrow @nogc: + HRESULT get_MinSamplingInterval(long* value); + HRESULT get_MaxSamplingInterval(long* value); + HRESULT get_MinOutOfRangeTimeout(long* value); + HRESULT get_MaxOutOfRangeTimeout(long* value); + HRESULT get_Status(int* value); + HRESULT get_ScanningMode(BluetoothLEScanningMode* value); + HRESULT put_ScanningMode(BluetoothLEScanningMode value); + HRESULT get_SignalStrengthFilter(IInspectable* value); + HRESULT put_SignalStrengthFilter(IInspectable value); + HRESULT get_AdvertisementFilter(IInspectable* value); + HRESULT put_AdvertisementFilter(IInspectable value); + HRESULT Start(); + HRESULT Stop(); + HRESULT add_Received(IUnknown handler, EventRegistrationToken* token); + HRESULT remove_Received(EventRegistrationToken token); + HRESULT add_Stopped(IUnknown handler, EventRegistrationToken* token); + HRESULT remove_Stopped(EventRegistrationToken token); +} + +interface IBluetoothLEAdvertisementReceivedEventArgs : IInspectable +{ +nothrow @nogc: + HRESULT get_RawSignalStrengthInDBm(short* value); + HRESULT get_BluetoothAddress(ulong* value); + HRESULT get_AdvertisementType(int* value); + HRESULT get_Timestamp(long* value); + HRESULT get_Advertisement(IBluetoothLEAdvertisement* value); +} + +interface IBluetoothLEAdvertisementReceivedEventArgs2 : IInspectable +{ +nothrow @nogc: + HRESULT get_BluetoothAddressType(int* value); + HRESULT get_TransmitPowerLevelInDBm(IInspectable* value); + HRESULT get_IsAnonymous(BOOL* value); + HRESULT get_IsConnectable(BOOL* value); + HRESULT get_IsScannable(BOOL* value); + HRESULT get_IsDirected(BOOL* value); + HRESULT get_IsScanResponse(BOOL* value); +} + +interface IBluetoothLEAdvertisement : IInspectable +{ +nothrow @nogc: + HRESULT get_Flags(IInspectable* value); + HRESULT put_Flags(IInspectable value); + HRESULT get_LocalName(HSTRING* value); + HRESULT put_LocalName(HSTRING value); + HRESULT get_ServiceUuids(IInspectable* value); + HRESULT get_ManufacturerData(IInspectable* value); + HRESULT get_DataSections(IInspectable* value); + HRESULT GetManufacturerDataByCompanyId(ushort companyId, IInspectable* dataList); + HRESULT GetSectionsByType(ubyte type_, IInspectable* sectionList); +} + +interface IVectorView_IInspectable : IInspectable +{ +nothrow @nogc: + HRESULT GetAt(uint index, IInspectable* item); + HRESULT get_Size(uint* size); + HRESULT IndexOf(IInspectable value, uint* index, BOOL* found); + HRESULT GetMany(uint startIndex, uint capacity, IInspectable* items, uint* actual); +} + +interface IBuffer : IInspectable +{ +nothrow @nogc: + HRESULT get_Capacity(uint* value); + HRESULT get_Length(uint* value); + HRESULT put_Length(uint value); +} + +interface IBufferByteAccess : IUnknown +{ +nothrow @nogc: + HRESULT Buffer(ubyte** value); +} + +interface IGattDeviceServicesResult : IInspectable +{ +nothrow @nogc: + HRESULT get_Status(GattCommunicationStatus* value); + HRESULT get_ProtocolError(IInspectable* value); + HRESULT get_Services(IInspectable* value); +} + +interface IGattDeviceService : IInspectable +{ +nothrow @nogc: + HRESULT GetCharacteristics(GUID characteristicUuid, IInspectable* value); + HRESULT GetIncludedServices(GUID serviceUuid, IInspectable* value); + HRESULT get_DeviceId(HSTRING* value); + HRESULT get_Uuid(GUID* value); + HRESULT get_AttributeHandle(ushort* value); +} + +interface IGattDeviceService3 : IInspectable +{ +nothrow @nogc: + HRESULT get_DeviceAccessInformation(IInspectable* value); + HRESULT get_Session(IInspectable* value); + HRESULT get_SharingMode(int* value); + HRESULT RequestAccessAsync(IInspectable* operation); + HRESULT OpenAsync(int sharingMode, IInspectable* operation); + HRESULT GetCharacteristicsAsync(IInspectable* operation); + HRESULT GetCharacteristicsWithCacheModeAsync(int cacheMode, IInspectable* operation); + HRESULT GetCharacteristicsForUuidAsync(GUID uuid, IInspectable* operation); + HRESULT GetCharacteristicsForUuidWithCacheModeAsync(GUID uuid, int cacheMode, IInspectable* operation); + HRESULT GetIncludedServicesAsync(IInspectable* operation); + HRESULT GetIncludedServicesWithCacheModeAsync(int cacheMode, IInspectable* operation); +} + +interface IGattCharacteristicsResult : IInspectable +{ +nothrow @nogc: + HRESULT get_Status(GattCommunicationStatus* value); + HRESULT get_ProtocolError(IInspectable* value); + HRESULT get_Characteristics(IInspectable* value); +} + +interface IGattCharacteristic : IInspectable +{ +nothrow @nogc: + HRESULT GetDescriptors(GUID descriptorUuid, IInspectable* value); + HRESULT get_CharacteristicProperties(GattCharacteristicProperties* value); + HRESULT get_ProtectionLevel(int* value); + HRESULT put_ProtectionLevel(int value); + HRESULT get_UserDescription(HSTRING* value); + HRESULT get_Uuid(GUID* value); + HRESULT get_AttributeHandle(ushort* value); + HRESULT get_PresentationFormats(IInspectable* value); + HRESULT ReadValueAsync(IInspectable* operation); + HRESULT ReadValueWithCacheModeAsync(int cacheMode, IInspectable* operation); + HRESULT WriteValueAsync(IBuffer value, IInspectable* operation); + HRESULT WriteValueWithOptionAsync(IBuffer value, int writeOption, IInspectable* operation); + HRESULT ReadClientCharacteristicConfigurationDescriptorAsync(IInspectable* operation); + HRESULT WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue value, IInspectable* operation); + HRESULT add_ValueChanged(IUnknown handler, EventRegistrationToken* token); + HRESULT remove_ValueChanged(EventRegistrationToken token); +} + +interface IGattReadResult : IInspectable +{ +nothrow @nogc: + HRESULT get_Status(GattCommunicationStatus* value); + HRESULT get_Value(IBuffer* value); +} + +interface IGattValueChangedEventArgs : IInspectable +{ +nothrow @nogc: + HRESULT get_CharacteristicValue(IBuffer* value); + HRESULT get_Timestamp(long* value); +} + +interface IBluetoothLEAdvertisementPublisher : IInspectable +{ +nothrow @nogc: + HRESULT get_Status(int* value); + HRESULT get_Advertisement(IBluetoothLEAdvertisement* value); + HRESULT Start(); + HRESULT Stop(); + HRESULT add_StatusChanged(IUnknown handler, EventRegistrationToken* token); + HRESULT remove_StatusChanged(EventRegistrationToken token); +} + +interface IBluetoothLEDeviceStatics : IInspectable +{ +nothrow @nogc: + HRESULT FromIdAsync(HSTRING deviceId, IInspectable* operation); + HRESULT FromBluetoothAddressAsync(ulong bluetoothAddress, IInspectable* operation); + HRESULT GetDeviceSelector(HSTRING* selector); +} + +interface IBluetoothLEDevice : IInspectable +{ +nothrow @nogc: + HRESULT get_DeviceId(HSTRING* value); + HRESULT get_Name(HSTRING* value); + HRESULT get_GattServices(IInspectable* value); + HRESULT get_ConnectionStatus(int* value); + HRESULT get_BluetoothAddress(ulong* value); + HRESULT GetGattService(GUID serviceUuid, IInspectable* service); + HRESULT add_NameChanged(IUnknown handler, EventRegistrationToken* token); + HRESULT remove_NameChanged(EventRegistrationToken token); + HRESULT add_GattServicesChanged(IUnknown handler, EventRegistrationToken* token); + HRESULT remove_GattServicesChanged(EventRegistrationToken token); + HRESULT add_ConnectionStatusChanged(IUnknown handler, EventRegistrationToken* token); + HRESULT remove_ConnectionStatusChanged(EventRegistrationToken token); +} + +interface IBluetoothLEDevice3 : IInspectable +{ +nothrow @nogc: + HRESULT get_DeviceAccessInformation(IInspectable* value); + HRESULT RequestAccessAsync(IInspectable* operation); + HRESULT GetGattServicesAsync(IInspectable* operation); + HRESULT GetGattServicesWithCacheModeAsync(int cacheMode, IInspectable* operation); + HRESULT GetGattServicesForUuidAsync(GUID serviceUuid, IInspectable* operation); + HRESULT GetGattServicesForUuidWithCacheModeAsync(GUID serviceUuid, int cacheMode, IInspectable* operation); +} + +// Handler interfaces +interface IAdvertisementReceivedHandler : IUnknown { nothrow @nogc: HRESULT Invoke(IInspectable sender, IInspectable args); } +interface IGattValueChangedHandler : IUnknown { nothrow @nogc: HRESULT Invoke(IInspectable sender, IInspectable args); } +interface IConnectionStatusHandler : IUnknown { nothrow @nogc: HRESULT Invoke(IInspectable sender, IInspectable args); } +interface IWatcherStoppedHandler : IUnknown { nothrow @nogc: HRESULT Invoke(IInspectable sender, IInspectable args); } + + +// ════════════════════════════════════════════════════════════════════ +// COM implementation classes +// ════════════════════════════════════════════════════════════════════ + +class ComObject : IInspectable +{ +nothrow @nogc: + protected shared uint _ref_count = 1; + + HRESULT QueryInterface(const(GUID)* riid, void** ppv) + { + if (*riid == IID_IUnknown || *riid == IID_IInspectable || *riid == IID_IAgileObject) + { + *ppv = cast(void*)cast(IUnknown)this; + AddRef(); + return 0; + } + *ppv = null; + return 0x80004002; // E_NOINTERFACE + } + + ULONG AddRef() { return atomicFetchAdd(_ref_count, 1) + 1; } + ULONG Release() + { + auto prev = atomicFetchSub(_ref_count, 1); + if (prev == 1) + { + defaultAllocator().freeT(this); + return 0; + } + return prev - 1; + } + + HRESULT GetIids(uint* count, GUID** iids) { *count = 0; *iids = null; return 0; } + HRESULT GetRuntimeClassName(HSTRING* name) { *name = null; return 0; } + HRESULT GetTrustLevel(int* level) { *level = 0; return 0; } +} + + +class MemoryBuffer : ComObject, IBuffer, IBufferByteAccess +{ +nothrow @nogc: + private ubyte* _data; + private uint _length; + private uint _capacity; + + void set(const(ubyte)[] data) + { + _data = cast(ubyte*)data.ptr; + _length = cast(uint)data.length; + _capacity = cast(uint)data.length; + } + + override ULONG Release() + { + auto prev = atomicFetchSub(_ref_count, 1); + if (prev == 1) + { + if (_data !is null) + defaultAllocator().free(_data[0 .. _capacity]); + defaultAllocator().freeT(this); + return 0; + } + return prev - 1; + } + + override HRESULT QueryInterface(const(GUID)* riid, void** ppv) + { + if (*riid == IID_IBufferByteAccess) + { + *ppv = cast(void*)cast(IBufferByteAccess)this; + AddRef(); + return 0; + } + return super.QueryInterface(riid, ppv); + } + + HRESULT get_Capacity(uint* value) { *value = _capacity; return 0; } + HRESULT get_Length(uint* value) { *value = _length; return 0; } + HRESULT put_Length(uint value) { _length = value; return 0; } + HRESULT Buffer(ubyte** value) { *value = _data; return 0; } +} + + +class AdvertisementReceivedHandler : ComObject, IAdvertisementReceivedHandler +{ +nothrow @nogc: + extern(D) void function(ubyte[6] addr, byte rssi, bool connectable, bool is_scan_response, const(ubyte)[] ad_payload) nothrow @nogc callback; + + override HRESULT QueryInterface(const(GUID)* riid, void** ppv) + { + if (*riid == IID_TypedEventHandler_Watcher_Received) + { + *ppv = cast(void*)cast(IUnknown)this; + AddRef(); + return 0; + } + return super.QueryInterface(riid, ppv); + } + + HRESULT Invoke(IInspectable sender, IInspectable args_raw) + { + if (!callback) + return 0; + + auto args = qi!IBluetoothLEAdvertisementReceivedEventArgs(args_raw, &IID_IBluetoothLEAdvertisementReceivedEventArgs); + if (args is null) + return 0; + scope(exit) args.Release(); + + ulong addr; + short rssi; + IBluetoothLEAdvertisement adv; + args.get_BluetoothAddress(&addr); + args.get_RawSignalStrengthInDBm(&rssi); + args.get_Advertisement(&adv); + + bool connectable = false; + bool is_scan_response = false; + auto args2 = qi!IBluetoothLEAdvertisementReceivedEventArgs2(args_raw, &IID_IBluetoothLEAdvertisementReceivedEventArgs2); + if (args2 !is null) + { + BOOL conn, scan_rsp; + args2.get_IsConnectable(&conn); + args2.get_IsScanResponse(&scan_rsp); + connectable = conn != 0; + is_scan_response = scan_rsp != 0; + args2.Release(); + } + + ubyte[6] mac; + ble_addr_to_mac(addr, mac); + + // serialize AD sections to raw bytes + ubyte[62] payload = void; + uint payload_len = serialize_adv(adv, payload[]); + if (adv !is null) + adv.Release(); + + callback(mac, cast(byte)rssi, connectable, is_scan_response, payload[0 .. payload_len]); + return 0; + } +} + + +class ConnectionStatusHandler : ComObject, IConnectionStatusHandler +{ +nothrow @nogc: + extern(D) void function(ubyte conn_id, int status) nothrow @nogc callback; + ubyte conn_id; + + override HRESULT QueryInterface(const(GUID)* riid, void** ppv) + { + if (*riid == IID_TypedEventHandler_Device_ConnectionStatus) + { + *ppv = cast(void*)cast(IUnknown)this; + AddRef(); + return 0; + } + return super.QueryInterface(riid, ppv); + } + + HRESULT Invoke(IInspectable sender, IInspectable args) + { + if (!callback) + return 0; + + // query connection status from sender (IBluetoothLEDevice) + auto dev = qi!IBluetoothLEDevice(sender, &IID_IBluetoothLEDevice); + if (dev !is null) + { + int conn_status; + dev.get_ConnectionStatus(&conn_status); + dev.Release(); + callback(conn_id, conn_status); + } + return 0; + } +} + + +class GattValueChangedHandler : ComObject, IGattValueChangedHandler +{ +nothrow @nogc: + extern(D) void function(ubyte conn_id, ushort attr_handle, const(ubyte)[] data) nothrow @nogc callback; + ubyte conn_id; + ushort attr_handle; + + override HRESULT QueryInterface(const(GUID)* riid, void** ppv) + { + if (*riid == IID_TypedEventHandler_Gatt_ValueChanged) + { + *ppv = cast(void*)cast(IUnknown)this; + AddRef(); + return 0; + } + return super.QueryInterface(riid, ppv); + } + + HRESULT Invoke(IInspectable sender, IInspectable args_raw) + { + if (!callback) + return 0; + + auto args = cast(IGattValueChangedEventArgs)cast(void*)args_raw; + if (args is null) + return 0; + + IBuffer value_buf; + args.get_CharacteristicValue(&value_buf); + const(ubyte)[] data = get_buffer_bytes(value_buf); + + callback(conn_id, attr_handle, data); + + if (value_buf !is null) + value_buf.Release(); + return 0; + } +} + + +class WatcherStoppedHandler : ComObject, IWatcherStoppedHandler +{ +nothrow @nogc: + shared(uint)* flag; + + override HRESULT QueryInterface(const(GUID)* riid, void** ppv) + { + if (*riid == IID_TypedEventHandler_Watcher_Stopped) + { + *ppv = cast(void*)cast(IUnknown)this; + AddRef(); + return 0; + } + return super.QueryInterface(riid, ppv); + } + + HRESULT Invoke(IInspectable sender, IInspectable args) + { + if (flag !is null) + atomicStore(*flag, 1u); + return 0; + } +} + + +// serialize WinRT advertisement to raw AD bytes [len][type][data]... +uint serialize_adv(IBluetoothLEAdvertisement adv, ubyte[] buf) +{ + if (adv is null) + return 0; + + uint offset = 0; + + // local name + HSTRING hname; + adv.get_LocalName(&hname); + if (hname !is null) + { + uint len; + const(wchar)* raw = g_winrt.WindowsGetStringRawBuffer(hname, &len); + if (raw && len > 0) + { + uint copy_len = len < 64 ? len : 64; + ubyte name_len = cast(ubyte)(copy_len + 1); + if (offset + 1 + name_len <= buf.length) + { + buf[offset++] = name_len; + buf[offset++] = 0x09; // complete local name + foreach (i; 0 .. copy_len) + buf[offset++] = cast(ubyte)raw[i]; + } + } + g_winrt.WindowsDeleteString(hname); + } + + // AD data sections + IInspectable sections_raw; + adv.get_DataSections(§ions_raw); + if (sections_raw !is null) + { + auto sections = cast(IVectorView_IInspectable)cast(void*)sections_raw; + uint count; + sections.get_Size(&count); + + foreach (i; 0 .. count) + { + IInspectable item; + sections.GetAt(i, &item); + if (item is null) + continue; + + auto section = cast(IBluetoothLEAdvertisementDataSection)cast(void*)item; + ubyte dt; + section.get_DataType(&dt); + + IBuffer dbuf; + section.get_Data(&dbuf); + const(ubyte)[] data = get_buffer_bytes(dbuf); + + if (data.length > 0) + { + ubyte sec_len = cast(ubyte)(data.length + 1); + if (offset + 1 + sec_len <= buf.length) + { + buf[offset++] = sec_len; + buf[offset++] = dt; + buf[offset .. offset + data.length] = cast(const(ubyte)[])data[]; + offset += cast(uint)data.length; + } + } + + if (dbuf !is null) + dbuf.Release(); + item.Release(); + } + sections_raw.Release(); + } + + return offset; +} + +interface IBluetoothLEAdvertisementDataSection : IInspectable +{ +nothrow @nogc: + HRESULT get_DataType(ubyte* value); + HRESULT put_DataType(ubyte value); + HRESULT get_Data(IBuffer* value); + HRESULT put_Data(IBuffer value); +} diff --git a/src/urt/driver/windows/exception.d b/src/urt/driver/windows/exception.d new file mode 100644 index 0000000..433929c --- /dev/null +++ b/src/urt/driver/windows/exception.d @@ -0,0 +1,1590 @@ +/// Windows exception driver. +/// +/// Three compiler/arch flavours of EH runtime coexist, selected by inner +/// version blocks: +/// version (LDC) - MSVC C++ SEH (RaiseException + table-based unwind) +/// version (Win32) - DMD SEH (_d_framehandler chain walk) +/// version (Win64) - DMD DEH (RBP-chain + ._deh section tables) +/// +/// Stack-trace capture / symbol resolution uses DbgHelp (debug-only). +/// Ported from druntime. +module urt.driver.windows.exception; + +version (Windows): + +import urt.internal.exception : ClassInfo, Resolved, _d_isbaseof, _d_createTrace, terminate; + +nothrow @nogc: + + +// ══════════════════════════════════════════════════════════════════════ +// Stack trace support (DbgHelp, debug only) +// ══════════════════════════════════════════════════════════════════════ +// +// ╔═══════════════════════════════════════════════════════════════════╗ +// ║ !!! TODO !!! THREADING IS NOT SUPPORTED. ║ +// ║ ║ +// ║ DbgHelp is NOT thread-safe. All calls to SymFromAddr, ║ +// ║ SymGetLineFromAddr64, SymInitialize, and StackWalk64 must be ║ +// ║ serialized with a CRITICAL_SECTION (or equivalent) before this ║ +// ║ program can use threads. The static scratch buffer inside ║ +// ║ _resolve_address is also shared across callers - replace with ║ +// ║ per-thread storage or a mutex when threading lands. ║ +// ╚═══════════════════════════════════════════════════════════════════╝ + +// --- DbgHelp types --------------------------------------------------- + +private struct SYMBOL_INFOA +{ + uint SizeOfStruct; + uint TypeIndex; + ulong[2] Reserved; + uint Index; + uint Size; + ulong ModBase; + uint Flags; + ulong Value; + ulong Address; + uint Register; + uint Scope; + uint Tag; + uint NameLen; + uint MaxNameLen; + char[1] Name; // variable-length +} + +private struct IMAGEHLP_LINEA64 +{ + uint SizeOfStruct; + void* Key; + uint LineNumber; + const(char)* FileName; + ulong Address; +} + +// --- StackWalk64 types ----------------------------------------------- + +private alias HANDLE = void*; +private enum AddrModeFlat = 3; + +private struct ADDRESS64 +{ + ulong Offset; + ushort Segment; + uint Mode; +} + +private struct STACKFRAME64 +{ + ADDRESS64 AddrPC; + ADDRESS64 AddrReturn; + ADDRESS64 AddrFrame; + ADDRESS64 AddrStack; + ADDRESS64 AddrBStore; + void* FuncTableEntry; + ulong[4] Params; + int Far; + int Virtual; + ulong[3] Reserved; + ubyte[96] KdHelp; // KDHELP64, opaque +} + +// CONTEXT - opaque aligned buffer, accessed via offset constants. +version (Win64) +{ + private enum CONTEXT_SIZE = 1232; + private enum CTX_FLAGS_OFF = 48; + private enum CTX_IP_OFF = 248; // Rip + private enum CTX_SP_OFF = 152; // Rsp + private enum CTX_FP_OFF = 160; // Rbp + private enum CTX_FULL = 0x10000B; + private enum MACHINE_TYPE = 0x8664; // IMAGE_FILE_MACHINE_AMD64 +} +else +{ + private enum CONTEXT_SIZE = 716; + private enum CTX_FLAGS_OFF = 0; + private enum CTX_IP_OFF = 184; // Eip + private enum CTX_SP_OFF = 196; // Esp + private enum CTX_FP_OFF = 180; // Ebp + private enum CTX_FULL = 0x10007; + private enum MACHINE_TYPE = 0x014C; // IMAGE_FILE_MACHINE_I386 +} + +// --- Function pointer types ------------------------------------------ + +extern(Windows) nothrow @nogc +{ + private alias SymInitializeFn = int function(HANDLE, const(char)*, int); + private alias SymSetOptionsFn = uint function(uint); + private alias SymFromAddrFn = int function(HANDLE, ulong, ulong*, SYMBOL_INFOA*); + private alias SymGetLineFromAddr64Fn = int function(HANDLE, ulong, uint*, IMAGEHLP_LINEA64*); + private alias FuncTableAccessFn = void* function(HANDLE, ulong); + private alias GetModuleBaseFn = ulong function(HANDLE, ulong); + private alias StackWalk64Fn = int function( + uint machineType, HANDLE hProcess, HANDLE hThread, + STACKFRAME64* stackFrame, void* contextRecord, + void* readMemory, FuncTableAccessFn funcTableAccess, + GetModuleBaseFn getModuleBase, void* translateAddress); +} + +// --- Globals --------------------------------------------------------- + +private __gshared bool _dbg_inited; +private __gshared bool _dbg_available; +private __gshared HANDLE _dbg_process; +private __gshared SymFromAddrFn _sym_from_addr; +private __gshared SymGetLineFromAddr64Fn _sym_get_line; +private __gshared StackWalk64Fn _stack_walk64; +private __gshared FuncTableAccessFn _func_table_access64; +private __gshared GetModuleBaseFn _get_module_base64; + +// --- Initialization -------------------------------------------------- + +private void dbghelp_init() @trusted +{ + if (_dbg_inited) + return; + _dbg_inited = true; + + auto hDbg = loadLibraryA("dbghelp.dll"); + if (hDbg is null) + return; + + auto sym_init = cast(SymInitializeFn) getProcAddress(hDbg, "SymInitialize"); + auto sym_set_opt = cast(SymSetOptionsFn) getProcAddress(hDbg, "SymSetOptions"); + _sym_from_addr = cast(SymFromAddrFn) getProcAddress(hDbg, "SymFromAddr"); + _sym_get_line = cast(SymGetLineFromAddr64Fn) getProcAddress(hDbg, "SymGetLineFromAddr64"); + _stack_walk64 = cast(StackWalk64Fn) getProcAddress(hDbg, "StackWalk64"); + _func_table_access64 = cast(FuncTableAccessFn) getProcAddress(hDbg, "SymFunctionTableAccess64"); + _get_module_base64 = cast(GetModuleBaseFn) getProcAddress(hDbg, "SymGetModuleBase64"); + + if (sym_init is null || sym_set_opt is null || _sym_from_addr is null) + return; + + // SYMOPT_DEFERRED_LOAD | SYMOPT_LOAD_LINES + sym_set_opt(0x00000004 | 0x00000010); + + _dbg_process = cast(HANDLE) cast(size_t)-1; // GetCurrentProcess() pseudo-handle + if (!sym_init(_dbg_process, null, 1)) // fInvadeProcess = TRUE + return; + + _dbg_available = true; +} + +// --- Kernel32/ntdll imports ------------------------------------------ + +pragma(mangle, "LoadLibraryA") +extern(Windows) private void* loadLibraryA(const(char)*); + +pragma(mangle, "GetProcAddress") +extern(Windows) private void* getProcAddress(void*, const(char)*); + +pragma(mangle, "RtlCaptureStackBackTrace") +extern(Windows) private ushort rtlCaptureStackBackTrace( + uint FramesToSkip, uint FramesToCapture, void** BackTrace, uint* BackTraceHash); + +pragma(mangle, "RtlCaptureContext") +extern(Windows) private void rtlCaptureContext(void* contextRecord); + +pragma(mangle, "GetCurrentThread") +extern(Windows) private HANDLE getCurrentThread(); + +// --- StackWalk64 fallback -------------------------------------------- + +private size_t stack_walk64_capture(ref void*[32] addrs) @trusted +{ + dbghelp_init(); + + if (_stack_walk64 is null || _func_table_access64 is null || _get_module_base64 is null) + return 0; + + align(16) ubyte[CONTEXT_SIZE] ctx = 0; + *cast(uint*)(ctx.ptr + CTX_FLAGS_OFF) = CTX_FULL; + rtlCaptureContext(ctx.ptr); + + STACKFRAME64 sf; + version (Win64) + { + sf.AddrPC.Offset = *cast(ulong*)(ctx.ptr + CTX_IP_OFF); + sf.AddrFrame.Offset = *cast(ulong*)(ctx.ptr + CTX_FP_OFF); + sf.AddrStack.Offset = *cast(ulong*)(ctx.ptr + CTX_SP_OFF); + } + else + { + sf.AddrPC.Offset = *cast(uint*)(ctx.ptr + CTX_IP_OFF); + sf.AddrFrame.Offset = *cast(uint*)(ctx.ptr + CTX_FP_OFF); + sf.AddrStack.Offset = *cast(uint*)(ctx.ptr + CTX_SP_OFF); + } + sf.AddrPC.Mode = AddrModeFlat; + sf.AddrFrame.Mode = AddrModeFlat; + sf.AddrStack.Mode = AddrModeFlat; + + auto hThread = getCurrentThread(); + size_t n = 0; + + // Skip internal frames (_d_createTrace + stack_walk64_capture + RtlCaptureContext) + uint skip = 3; + + while (n < 32) + { + if (!_stack_walk64(MACHINE_TYPE, _dbg_process, hThread, + &sf, ctx.ptr, null, _func_table_access64, _get_module_base64, null)) + break; + if (sf.AddrPC.Offset == 0) + break; + if (skip > 0) + { + --skip; + continue; + } + addrs[n++] = cast(void*) cast(size_t) sf.AddrPC.Offset; + } + return n; +} + + +// --- Driver interface ------------------------------------------------- +// +// All three primitives assume they are called through a one-level public +// wrapper in urt.internal.exception (kept non-inlined via pragma(inline, +// false)). That wrapper's frame is accounted for in the skip counts +// below. Direct callers (like _d_createTrace) get the same semantics +// because their frame substitutes for the missing wrapper. + +/// Capture the caller's call stack. First entry = return address of the +/// function that called the public `capture_trace` wrapper. +size_t _capture_trace(void*[] addrs) @trusted +{ + if (addrs.length == 0) + return 0; + auto count = cast(uint) addrs.length; + // +2: skip _capture_trace itself + the public wrapper. addrs[0] + // is then the PC inside USER (where USER called capture_trace). + auto n = rtlCaptureStackBackTrace(2, count, addrs.ptr, null); + if (n == 0 && addrs.length >= 32) + { + void*[32] scratch = void; + auto k = stack_walk64_capture(scratch); + auto copy = k < addrs.length ? k : addrs.length; + addrs[0 .. copy] = scratch[0 .. copy]; + n = cast(ushort) copy; + } + return n; +} + +/// Return the return address of the `skip`-th frame above the public +/// `caller_address` wrapper's caller. `skip=0` is the call site of +/// that caller - useful from inside an allocator to find the alloc +/// site. +void* _caller_address(uint skip) @trusted +{ + void* addr; + // +3: skip _caller_address itself + public wrapper + USER's own + // frame. With skip=0 the returned PC is in USER's caller - the + // semantic the doc promises ("call site of the caller", useful for + // allocation-site tracking). + if (rtlCaptureStackBackTrace(skip + 3, 1, &addr, null) == 0) + return null; + return addr; +} + +/// Resolve addr to symbol + optional file + line via DbgHelp. +/// Returned slices are owned by a static buffer - copy if you need +/// them across another call. +bool _resolve_address(void* addr, out Resolved r) @trusted +{ + import urt.mem : strlen; + + dbghelp_init(); + if (!_dbg_available) + return false; + + static align(8) ubyte[SYMBOL_INFOA.sizeof + 256] sym_buf; + sym_buf[] = 0; + auto p_sym = cast(SYMBOL_INFOA*) sym_buf.ptr; + p_sym.SizeOfStruct = SYMBOL_INFOA.sizeof; + p_sym.MaxNameLen = 256; + + ulong disp; + if (!_sym_from_addr(_dbg_process, cast(ulong) addr, &disp, p_sym)) + return false; + r.name = p_sym.Name.ptr[0 .. strlen(p_sym.Name.ptr)]; + r.offset = cast(size_t) disp; + + uint disp32; + IMAGEHLP_LINEA64 line_info; + line_info.SizeOfStruct = IMAGEHLP_LINEA64.sizeof; + if (_sym_get_line !is null && + _sym_get_line(_dbg_process, cast(ulong) addr, &disp32, &line_info)) + { + r.file = line_info.FileName[0 .. strlen(line_info.FileName)]; + r.line = line_info.LineNumber; + } + return true; +} + +/// Resolve many addresses. DbgHelp has no batch primitive - loop. +/// Returns false if DbgHelp is unavailable; otherwise true (individual +/// failed entries stay `Resolved.init` thanks to `out` auto-zeroing). +bool _resolve_batch(const(void*)[] addrs, Resolved[] results) @trusted +{ + dbghelp_init(); + if (!_dbg_available) + return false; + foreach (i, a; addrs) + cast(void) _resolve_address(cast(void*) a, results[i]); + return true; +} + +// ══════════════════════════════════════════════════════════════════════ +// Exception-handling runtime (compiler-specific) +// ══════════════════════════════════════════════════════════════════════ + +version (GDC) +{ + static assert(false, "GDC exception runtime not ported"); +} +else version (LDC) +{ + +// ---------------------------------------------------------------------- +// LDC MSVC SEH exception handling +// ---------------------------------------------------------------------- + + +// --- Windows ABI types --------------------------------------------------- + +private alias DWORD = uint; +private alias ULONG_PTR = size_t; + +extern(Windows) void RaiseException(DWORD dwExceptionCode, DWORD dwExceptionFlags, DWORD nNumberOfArguments, ULONG_PTR* lpArguments); + +// --- MSVC SEH structures ------------------------------------------------- + +// On Win64, type info pointers are 32-bit offsets from a heap base. +version (Win64) + private struct ImgPtr(T) { uint offset; } +else + private alias ImgPtr(T) = T*; + +private alias PMFN = ImgPtr!(void function(void*)); + +private struct TypeDescriptor +{ + uint hash; + void* spare; + char[1] name; // variable-size, zero-terminated +} + +private struct PMD +{ + int mdisp; + int pdisp; + int vdisp; +} + +private struct CatchableType +{ + uint properties; + ImgPtr!TypeDescriptor pType; + PMD thisDisplacement; + int sizeOrOffset; + PMFN copyFunction; +} + +private enum CT_IsSimpleType = 0x00000001; + +private struct CatchableTypeArray +{ + int nCatchableTypes; + ImgPtr!CatchableType[1] arrayOfCatchableTypes; // variable size +} + +private struct _ThrowInfo +{ + uint attributes; + PMFN pmfnUnwind; + PMFN pForwardCompat; + ImgPtr!CatchableTypeArray pCatchableTypeArray; +} + +private struct CxxExceptionInfo +{ + size_t Magic; + Throwable* pThrowable; + _ThrowInfo* ThrowInfo; + version (Win64) void* ImgBase; +} + +private enum int STATUS_MSC_EXCEPTION = 0xe0000000 | ('m' << 16) | ('s' << 8) | ('c' << 0); +private enum EXCEPTION_NONCONTINUABLE = 0x01; +private enum EH_MAGIC_NUMBER1 = 0x19930520; + + +// --- EH Heap (image-relative pointer support) ---------------------------- + +version (Win64) +{ + private struct EHHeap + { + nothrow @nogc: + + void* base; + size_t capacity; + size_t length; + + void initialize(size_t initial_capacity) + { + import urt.mem : alloc; + base = alloc(initial_capacity).ptr; + capacity = initial_capacity; + length = size_t.sizeof; // offset 0 reserved (null sentinel) + } + + size_t alloc(size_t size) + { + import urt.mem : alloc, memcpy; + auto offset = length; + enum align_mask = size_t.sizeof - 1; + auto new_length = (length + size + align_mask) & ~align_mask; + auto new_capacity = capacity; + while (new_length > new_capacity) + new_capacity *= 2; + if (new_capacity != capacity) + { + auto new_base = alloc(new_capacity).ptr; + memcpy(new_base, base, length); + // Old base leaks - may be referenced by in-flight exceptions. + base = new_base; + capacity = new_capacity; + } + length = new_length; + return offset; + } + } + + private __gshared EHHeap _eh_heap; + + private ImgPtr!T eh_malloc(T)(size_t size = T.sizeof) + { + return ImgPtr!T(cast(uint) _eh_heap.alloc(size)); + } + + private T* to_pointer(T)(ImgPtr!T img_ptr) + { + return cast(T*)(cast(ubyte*) _eh_heap.base + img_ptr.offset); + } +} +else // Win32 +{ + private ImgPtr!T eh_malloc(T)(size_t size = T.sizeof) + { + import urt.mem : alloc; + return cast(T*)alloc(size).ptr; + } + + private T* to_pointer(T)(T* img_ptr) + { + return img_ptr; + } +} + + +// --- ThrowInfo cache (simple linear-scan arrays) ------------------------- +// No mutex - OpenWatt's exception paths are single-threaded. + +private enum EH_CACHE_SIZE = 64; + +private __gshared ClassInfo[EH_CACHE_SIZE] _throw_info_keys; +private __gshared ImgPtr!_ThrowInfo[EH_CACHE_SIZE] _throw_info_vals; +private __gshared size_t _throw_info_len; + +private __gshared ClassInfo[EH_CACHE_SIZE] _catchable_keys; +private __gshared ImgPtr!CatchableType[EH_CACHE_SIZE] _catchable_vals; +private __gshared size_t _catchable_len; + +private __gshared bool _eh_initialized; + +private void ensure_eh_init() +{ + if (_eh_initialized) + return; + version (Win64) + _eh_heap.initialize(0x10000); + _eh_initialized = true; +} + + +// --- ThrowInfo generation ------------------------------------------------ + +private ImgPtr!CatchableType get_catchable_type(ClassInfo ti) +{ + import urt.mem : memcpy; + + foreach (i; 0 .. _catchable_len) + if (_catchable_keys[i] is ti) + return _catchable_vals[i]; + + const sz = TypeDescriptor.sizeof + ti.name.length + 1; + auto td = eh_malloc!TypeDescriptor(sz); + auto ptd = td.to_pointer; + + ptd.hash = 0; + ptd.spare = null; + ptd.name.ptr[0] = 'D'; + memcpy(ptd.name.ptr + 1, ti.name.ptr, ti.name.length); + ptd.name.ptr[ti.name.length + 1] = 0; + + auto ct = eh_malloc!CatchableType(); + ct.to_pointer[0] = CatchableType( + CT_IsSimpleType, td, PMD(0, -1, 0), + cast(int) size_t.sizeof, PMFN.init); + + if (_catchable_len < EH_CACHE_SIZE) + { + _catchable_keys[_catchable_len] = ti; + _catchable_vals[_catchable_len] = ct; + ++_catchable_len; + } + return ct; +} + +private ImgPtr!_ThrowInfo get_throw_info(ClassInfo ti) +{ + foreach (i; 0 .. _throw_info_len) + if (_throw_info_keys[i] is ti) + return _throw_info_vals[i]; + + int classes = 0; + for (ClassInfo tic = ti; tic !is null; tic = tic.base) + ++classes; + + const arr_size = int.sizeof + classes * ImgPtr!(CatchableType).sizeof; + ImgPtr!CatchableTypeArray cta = eh_malloc!CatchableTypeArray(arr_size); + to_pointer(cta).nCatchableTypes = classes; + + size_t c = 0; + for (ClassInfo tic = ti; tic !is null; tic = tic.base) + cta.to_pointer.arrayOfCatchableTypes.ptr[c++] = get_catchable_type(tic); + + auto tinf = eh_malloc!_ThrowInfo(); + *(tinf.to_pointer) = _ThrowInfo(0, PMFN.init, PMFN.init, cta); + + if (_throw_info_len < EH_CACHE_SIZE) + { + _throw_info_keys[_throw_info_len] = ti; + _throw_info_vals[_throw_info_len] = tinf; + ++_throw_info_len; + } + return tinf; +} + + +// --- Exception stack (thread-local) -------------------------------------- + +private struct ExceptionStack +{ +nothrow @nogc: + + void push(Throwable e) + { + if (_length == _cap) + grow(); + _p[_length++] = e; + } + + Throwable pop() + { + return _p[--_length]; + } + + void shrink(size_t sz) + { + while (_length > sz) + _p[--_length] = null; + } + + ref inout(Throwable) opIndex(size_t idx) inout + { + return _p[idx]; + } + + size_t find(Throwable e) + { + for (size_t i = _length; i > 0;) + if (_p[--i] is e) + return i; + return ~cast(size_t) 0; + } + + @property size_t length() const { return _length; } + +private: + + void grow() + { + import urt.mem : alloc, free, memcpy; + immutable ncap = _cap ? 2 * _cap : 16; + auto p = cast(Throwable*)alloc(ncap * size_t.sizeof).ptr; + if (_length > 0) + memcpy(p, _p, _length * size_t.sizeof); + if (_p !is null) + free((cast(void*)_p)[0 .. _cap * size_t.sizeof]); + _p = p; + _cap = ncap; + } + + size_t _length; + Throwable* _p; + size_t _cap; +} + +private ExceptionStack _exception_stack; + + +// --- Exception chaining -------------------------------------------------- + +private Throwable chain_exceptions(Throwable e, Throwable t) +{ + if (!cast(Error) e) + if (auto err = cast(Error) t) + { + err.bypassedException = e; + return err; + } + return Throwable.chainTogether(e, t); +} + + +// --- Core SEH API -------------------------------------------------------- + +extern(C) void _d_throw_exception(Throwable throwable) +{ + if (throwable is null || typeid(throwable) is null) + { + terminate(); + assert(0); + } + + auto refcount = throwable.refcount(); + if (refcount) + throwable.refcount() = refcount + 1; + + ensure_eh_init(); + + _exception_stack.push(throwable); + _d_createTrace(throwable, null); + + CxxExceptionInfo info; + info.Magic = EH_MAGIC_NUMBER1; + info.pThrowable = &throwable; + info.ThrowInfo = get_throw_info(typeid(throwable)).to_pointer; + version (Win64) + info.ImgBase = _eh_heap.base; + + RaiseException(STATUS_MSC_EXCEPTION, EXCEPTION_NONCONTINUABLE, + info.sizeof / size_t.sizeof, cast(ULONG_PTR*) &info); +} + +extern(C) Throwable _d_eh_enter_catch(void* ptr, ClassInfo catch_type) +{ + if (ptr is null) + return null; + + auto e = *(cast(Throwable*) ptr); + size_t pos = _exception_stack.find(e); + if (pos >= _exception_stack.length()) + return null; // not a D exception + + auto caught = e; + + // Chain inner unhandled exceptions as collateral + for (size_t p = pos + 1; p < _exception_stack.length(); ++p) + e = chain_exceptions(e, _exception_stack[p]); + _exception_stack.shrink(pos); + + if (e !is caught) + { + if (_d_isbaseof(typeid(e), catch_type)) + *cast(Throwable*) ptr = e; + else + _d_throw_exception(e); // rethrow collateral + } + return e; +} + +extern(C) bool _d_enter_cleanup(void* ptr) @trusted +{ + // Prevents LLVM from optimizing away cleanup (finally) blocks. + return true; +} + +extern(C) void _d_leave_cleanup(void* ptr) @trusted +{ +} + +} // version (LDC) +else version (Win32) +{ + +// ---------------------------------------------------------------------- +// DMD Win32 SEH-based exception handling +// ---------------------------------------------------------------------- + + +// ---------------------------------------------------------------------- +// Win32 SEH-based exception handling +// ---------------------------------------------------------------------- + +// Windows types (inlined to avoid core.sys.windows dependency) +alias DWORD = uint; +alias BYTE = ubyte; +alias PVOID = void*; +alias ULONG_PTR = size_t; + +enum size_t EXCEPTION_MAXIMUM_PARAMETERS = 15; +enum DWORD EXCEPTION_NONCONTINUABLE = 1; +enum EXCEPTION_UNWIND = 6; +enum EXCEPTION_COLLATERAL = 0x100; + +enum DWORD STATUS_DIGITAL_MARS_D_EXCEPTION = (3 << 30) | (1 << 29) | (0 << 28) | ('D' << 16) | 1; + +struct EXCEPTION_RECORD +{ + DWORD ExceptionCode; + DWORD ExceptionFlags; + EXCEPTION_RECORD* ExceptionRecord; + PVOID ExceptionAddress; + DWORD NumberParameters; + ULONG_PTR[EXCEPTION_MAXIMUM_PARAMETERS] ExceptionInformation; +} + +enum MAXIMUM_SUPPORTED_EXTENSION = 512; + +struct FLOATING_SAVE_AREA +{ + DWORD ControlWord, StatusWord, TagWord; + DWORD ErrorOffset, ErrorSelector; + DWORD DataOffset, DataSelector; + BYTE[80] RegisterArea; + DWORD Cr0NpxState; +} + +struct CONTEXT +{ + DWORD ContextFlags; + DWORD Dr0, Dr1, Dr2, Dr3, Dr6, Dr7; + FLOATING_SAVE_AREA FloatSave; + DWORD SegGs, SegFs, SegEs, SegDs; + DWORD Edi, Esi, Ebx, Edx, Ecx, Eax; + DWORD Ebp, Eip, SegCs, EFlags, Esp, SegSs; + BYTE[MAXIMUM_SUPPORTED_EXTENSION] ExtendedRegisters; +} + +struct EXCEPTION_POINTERS +{ + EXCEPTION_RECORD* ExceptionRecord; + CONTEXT* ContextRecord; +} + +enum EXCEPTION_DISPOSITION +{ + ExceptionContinueExecution, + ExceptionContinueSearch, + ExceptionNestedException, + ExceptionCollidedUnwind, +} + +alias LanguageSpecificHandler = extern(C) + EXCEPTION_DISPOSITION function( + EXCEPTION_RECORD* exceptionRecord, + DEstablisherFrame* frame, + CONTEXT* context, + void* dispatcherContext); + +extern(Windows) void RaiseException(DWORD, DWORD, DWORD, void*); +extern(Windows) void RtlUnwind(void* targetFrame, void* targetIp, EXCEPTION_RECORD* pExceptRec, void* valueForEAX); + +extern(C) extern __gshared DWORD _except_list; // FS:[0] + +// Data structures - compiler-generated exception handler tables (Win32) + +struct DEstablisherFrame +{ + DEstablisherFrame* prev; + LanguageSpecificHandler handler; + DWORD table_index; + DWORD ebp; +} + +struct DHandlerInfo +{ + int prev_index; + uint cioffset; // offset to DCatchInfo data from start of table + void* finally_code; // pointer to finally code (!=null if try-finally) +} + +struct DHandlerTable +{ + void* fptr; // pointer to start of function + uint espoffset; + uint retoffset; + DHandlerInfo[1] handler_info; +} + +struct DCatchBlock +{ + ClassInfo type; + uint bpoffset; // EBP offset of catch var + void* code; // catch handler code pointer +} + +struct DCatchInfo +{ + uint ncatches; + DCatchBlock[1] catch_block; +} + +// InFlight exception list (per-stack, swapped on fiber switches) + +EXCEPTION_RECORD* inflight_exception_list = null; + +extern(C) void* _d_eh_swapContext(void* newContext) +{ + auto old = inflight_exception_list; + inflight_exception_list = cast(EXCEPTION_RECORD*) newContext; + return old; +} + +private EXCEPTION_RECORD* skip_collateral_exceptions(EXCEPTION_RECORD* n) @trusted +{ + while (n.ExceptionRecord && n.ExceptionFlags & EXCEPTION_COLLATERAL) + n = n.ExceptionRecord; + return n; +} + +// SEH to D exception translation + +private Throwable _d_translate_se_to_d_exception(EXCEPTION_RECORD* exceptionRecord, CONTEXT*) @trusted +{ + if (exceptionRecord.ExceptionCode == STATUS_DIGITAL_MARS_D_EXCEPTION) + return cast(Throwable) cast(void*)(exceptionRecord.ExceptionInformation[0]); + + // Non-D (hardware) exceptions: cannot create Error objects without GC. + terminate(); + assert(0); +} + +// _d_throwc - throw a D exception via Windows SEH + +private void throw_impl(Throwable h) @trusted +{ + auto refcount = h.refcount(); + if (refcount) + h.refcount() = refcount + 1; + + _d_createTrace(h, null); + RaiseException(STATUS_DIGITAL_MARS_D_EXCEPTION, EXCEPTION_NONCONTINUABLE, 1, cast(void*)&h); +} + +extern(C) void _d_throwc(Throwable h) @trusted +{ + version (D_InlineAsm_X86) + asm @nogc nothrow + { + naked; + enter 0, 0; + mov EAX, [EBP + 8]; + call throw_impl; + leave; + ret; + } +} + +// _d_framehandler - SEH frame handler called by OS for each frame. +// The handler table address is passed in EAX by compiler-generated thunks. + +extern(C) EXCEPTION_DISPOSITION _d_framehandler( + EXCEPTION_RECORD* exceptionRecord, + DEstablisherFrame* frame, + CONTEXT* context, + void* dispatcherContext) @trusted +{ + DHandlerTable* handler_table; + asm @nogc nothrow { mov handler_table, EAX; } + + if (exceptionRecord.ExceptionFlags & EXCEPTION_UNWIND) + { + // Unwind phase: call all finally blocks in this frame + _d_local_unwind(handler_table, frame, -1, &unwindCollisionExceptionHandler); + } + else + { + // Search phase: look for a matching catch handler + + EXCEPTION_RECORD* master = null; + ClassInfo master_class_info = null; + + int prev_ndx; + for (auto ndx = frame.table_index; ndx != -1; ndx = prev_ndx) + { + auto phi = &handler_table.handler_info.ptr[ndx]; + prev_ndx = phi.prev_index; + + if (phi.cioffset) + { + auto pci = cast(DCatchInfo*)(cast(ubyte*) handler_table + phi.cioffset); + auto ncatches = pci.ncatches; + + foreach (i; 0 .. ncatches) + { + auto pcb = &pci.catch_block.ptr[i]; + + // Walk the collateral exception chain to find the master + EXCEPTION_RECORD* er = exceptionRecord; + master = null; + master_class_info = null; + + for (;;) + { + if (er.ExceptionCode == STATUS_DIGITAL_MARS_D_EXCEPTION) + { + ClassInfo ci = (**(cast(ClassInfo**)(er.ExceptionInformation[0]))); + if (!master && !(er.ExceptionFlags & EXCEPTION_COLLATERAL)) + { + master = er; + master_class_info = ci; + break; + } + if (_d_isbaseof(ci, typeid(Error))) + { + master = er; + master_class_info = ci; + } + } + else + { + // Non-D exception - cannot translate without GC + terminate(); + } + + if (!(er.ExceptionFlags & EXCEPTION_COLLATERAL)) + break; + + if (er.ExceptionRecord) + er = er.ExceptionRecord; + else + er = inflight_exception_list; + } + + if (_d_isbaseof(master_class_info, pcb.type)) + { + // Found matching catch handler + + auto original_exception = skip_collateral_exceptions(exceptionRecord); + if (original_exception.ExceptionRecord is null + && !(exceptionRecord is inflight_exception_list)) + original_exception.ExceptionRecord = inflight_exception_list; + inflight_exception_list = exceptionRecord; + + // Global unwind: call finally blocks in intervening frames + _d_global_unwind(frame, exceptionRecord); + + // Local unwind: call finally blocks skipped in this frame + _d_local_unwind(handler_table, frame, ndx, + &searchCollisionExceptionHandler); + + frame.table_index = prev_ndx; + + // Build D exception chain from SEH records + EXCEPTION_RECORD* z = exceptionRecord; + Throwable prev = null; + Error master_error = null; + + for (;;) + { + Throwable w = _d_translate_se_to_d_exception(z, context); + if (z == master && (z.ExceptionFlags & EXCEPTION_COLLATERAL)) + master_error = cast(Error) w; + prev = Throwable.chainTogether(w, prev); + if (!(z.ExceptionFlags & EXCEPTION_COLLATERAL)) + break; + z = z.ExceptionRecord; + } + + Throwable pti; + if (master_error) + { + master_error.bypassedException = prev; + pti = master_error; + } + else + pti = prev; + + inflight_exception_list = z.ExceptionRecord; + + // Initialize catch variable and jump to handler + int regebp = cast(int)&frame.ebp; + *cast(Object*)(regebp + pcb.bpoffset) = pti; + + { + uint catch_esp; + alias fp_t = void function(); + fp_t catch_addr = cast(fp_t) pcb.code; + catch_esp = regebp - handler_table.espoffset - fp_t.sizeof; + asm @nogc nothrow + { + mov EAX, catch_esp; + mov ECX, catch_addr; + mov [EAX], ECX; + mov EBP, regebp; + mov ESP, EAX; + ret; + } + } + } + } + } + } + } + return EXCEPTION_DISPOSITION.ExceptionContinueSearch; +} + +// Exception filter for __try/__except around Dmain + +extern(C) int _d_exception_filter(EXCEPTION_POINTERS* eptrs, int retval, Object* exception_object) @trusted +{ + *exception_object = _d_translate_se_to_d_exception(eptrs.ExceptionRecord, eptrs.ContextRecord); + return retval; +} + +// Collision exception handlers + +extern(C) EXCEPTION_DISPOSITION searchCollisionExceptionHandler( + EXCEPTION_RECORD* exceptionRecord, + DEstablisherFrame*, + CONTEXT*, + void* dispatcherContext) @trusted +{ + if (!(exceptionRecord.ExceptionFlags & EXCEPTION_UNWIND)) + { + auto n = skip_collateral_exceptions(exceptionRecord); + n.ExceptionFlags |= EXCEPTION_COLLATERAL; + return EXCEPTION_DISPOSITION.ExceptionContinueSearch; + } + // Collision during SEARCH phase - restart from 'frame' + *(cast(void**) dispatcherContext) = dispatcherContext; // frame + return EXCEPTION_DISPOSITION.ExceptionCollidedUnwind; +} + +extern(C) EXCEPTION_DISPOSITION unwindCollisionExceptionHandler( + EXCEPTION_RECORD* exceptionRecord, + DEstablisherFrame* frame, + CONTEXT*, + void* dispatcherContext) @trusted +{ + if (!(exceptionRecord.ExceptionFlags & EXCEPTION_UNWIND)) + { + auto n = skip_collateral_exceptions(exceptionRecord); + n.ExceptionFlags |= EXCEPTION_COLLATERAL; + return EXCEPTION_DISPOSITION.ExceptionContinueSearch; + } + // Collision during UNWIND phase - restart from 'frame.prev' + *(cast(void**) dispatcherContext) = frame.prev; + return EXCEPTION_DISPOSITION.ExceptionCollidedUnwind; +} + +// Local unwind - run finally blocks in the current frame + +extern(C) void _d_local_unwind(DHandlerTable* handler_table, DEstablisherFrame* frame, int stop_index, LanguageSpecificHandler collision_handler) @trusted +{ + // Install collision handler on SEH chain + asm @nogc nothrow + { + push dword ptr -1; + push dword ptr 0; + push collision_handler; + push dword ptr FS:_except_list; + mov FS:_except_list, ESP; + } + + for (auto i = frame.table_index; i != -1 && i != stop_index;) + { + auto phi = &handler_table.handler_info.ptr[i]; + i = phi.prev_index; + + if (phi.finally_code) + { + auto catch_ebp = &frame.ebp; + auto blockaddr = phi.finally_code; + asm @nogc nothrow + { + push EBX; + mov EBX, blockaddr; + push EBP; + mov EBP, catch_ebp; + call EBX; + pop EBP; + pop EBX; + } + } + } + + // Remove collision handler from SEH chain + asm @nogc nothrow + { + pop FS:_except_list; + add ESP, 12; + } +} + +// Global unwind - thin wrapper around RtlUnwind + +extern(C) int _d_global_unwind(DEstablisherFrame* pFrame, EXCEPTION_RECORD* eRecord) @trusted +{ + asm @nogc nothrow + { + naked; + push EBP; + mov EBP, ESP; + push ECX; + push EBX; + push ESI; + push EDI; + push EBP; + push 0; + push dword ptr 12[EBP]; // eRecord + call __system_unwind; + jmp __unwind_exit; + __system_unwind: + push dword ptr 8[EBP]; // pFrame + call RtlUnwind; + __unwind_exit: + pop EBP; + pop EDI; + pop ESI; + pop EBX; + pop ECX; + mov ESP, EBP; + pop EBP; + ret; + } +} + +// Local unwind for goto/return across finally blocks + +extern(C) void _d_local_unwind2() @trusted +{ + asm @nogc nothrow + { + naked; + jmp _d_localUnwindForGoto; + } +} + +extern(C) void _d_localUnwindForGoto(DHandlerTable* handler_table, DEstablisherFrame* frame, int stop_index) @trusted +{ + _d_local_unwind(handler_table, frame, stop_index, &searchCollisionExceptionHandler); +} + +// Monitor handler stubs (for synchronized blocks) + +extern(C) EXCEPTION_DISPOSITION _d_monitor_handler(EXCEPTION_RECORD* exceptionRecord, DEstablisherFrame*, CONTEXT*, void*) @trusted +{ + if (exceptionRecord.ExceptionFlags & EXCEPTION_UNWIND) + { + // TODO: _d_monitorexit(cast(Object)cast(void*)frame.table_index); + } + return EXCEPTION_DISPOSITION.ExceptionContinueSearch; +} + +extern(C) void _d_monitor_prolog(void*, void*, Object) @trusted +{ + // TODO: _d_monitorenter(h); +} + +extern(C) void _d_monitor_epilog(void*, void*, Object) @trusted +{ + // TODO: _d_monitorexit(h); +} + + + +} // version (Win32) +else version (Win64) +{ + +// ---------------------------------------------------------------------- +// DMD Win64 - RBP-chain walking with ._deh section tables +// ---------------------------------------------------------------------- + + +// ---------------------------------------------------------------------- +// Win64 - RBP-chain walking with ._deh section tables +// ---------------------------------------------------------------------- + +// Data structures - compiler-generated exception handler tables + +struct DHandlerInfo +{ + uint offset; // offset from function address to start of guarded section + uint endoffset; // offset of end of guarded section + int prev_index; // previous table index + uint cioffset; // offset to DCatchInfo data from start of table (!=0 if try-catch) + size_t finally_offset; // offset to finally code to execute (!=0 if try-finally) +} + +struct DHandlerTable +{ + uint espoffset; // offset of ESP from EBP + uint retoffset; // offset from start of function to return code + size_t nhandlers; // dimension of handler_info[] + DHandlerInfo[1] handler_info; +} + +struct DCatchBlock +{ + ClassInfo type; // catch type + size_t bpoffset; // EBP offset of catch var + size_t codeoffset; // catch handler offset +} + +struct DCatchInfo +{ + size_t ncatches; // number of catch blocks + DCatchBlock[1] catch_block; +} + +struct FuncTable +{ + void* fptr; // pointer to start of function + DHandlerTable* handlertable; + uint fsize; // size of function in bytes +} + +// InFlight exception tracking (per-stack, swapped on fiber switches) + +private struct InFlight +{ + InFlight* next; + void* addr; + Throwable t; +} + +private __gshared InFlight* __inflight = null; + +extern(C) void* _d_eh_swapContext(void* newContext) +{ + auto old = __inflight; + __inflight = cast(InFlight*) newContext; + return old; +} + +// DEH section scanning - find exception handler tables in the binary + +version (Windows) + extern(C) extern __gshared ubyte __ImageBase; + +private __gshared immutable(FuncTable)* _deh_start; +private __gshared immutable(FuncTable)* _deh_end; + +private void ensure_deh_loaded() @trusted +{ + if (_deh_start !is null) + return; + + version (Windows) + { + auto section = find_pe_section(cast(void*) &__ImageBase, "._deh\0\0\0"); + if (section.length) + { + _deh_start = cast(immutable(FuncTable)*) section.ptr; + _deh_end = cast(immutable(FuncTable)*)(section.ptr + section.length); + } + } + else version (linux) + { + // TODO: ELF section scanning for .deh + } +} + +/// PE section lookup - duplicated from urt.package to avoid import cycle. +private void[] find_pe_section(void* imageBase, string name) @trusted +{ + if (name.length > 8) + return null; + + auto base = cast(ubyte*) imageBase; + if (base[0] != 0x4D || base[1] != 0x5A) + return null; + + auto lfanew = *cast(int*)(base + 0x3C); + auto pe = base + lfanew; + if (pe[0] != 'P' || pe[1] != 'E' || pe[2] != 0 || pe[3] != 0) + return null; + + auto fileHeader = pe + 4; + ushort numSections = *cast(ushort*)(fileHeader + 2); + ushort optHeaderSize = *cast(ushort*)(fileHeader + 16); + auto sections = fileHeader + 20 + optHeaderSize; + + foreach (i; 0 .. numSections) + { + auto sec = sections + i * 40; + auto secName = (cast(char*) sec)[0 .. 8]; + bool match = true; + foreach (j; 0 .. 8) + { + if (secName[j] != name[j]) + { + match = false; + break; + } + } + if (match) + { + auto virtualSize = *cast(uint*)(sec + 8); + auto virtualAddress = *cast(uint*)(sec + 12); + return (base + virtualAddress)[0 .. virtualSize]; + } + } + return null; +} + +// Handler table lookup + +immutable(FuncTable)* __eh_finddata(void* address) @trusted +{ + ensure_deh_loaded(); + if (_deh_start is null) + return null; + return __eh_finddata_range(address, _deh_start, _deh_end); +} + +immutable(FuncTable)* __eh_finddata_range(void* address, immutable(FuncTable)* pstart, immutable(FuncTable)* pend) @trusted +{ + for (auto ft = pstart; ; ft++) + { + Lagain: + if (ft >= pend) + break; + + version (Win64) + { + // MS Linker sometimes inserts zero padding between .obj sections + if (ft.fptr == null) + { + ft = cast(immutable(FuncTable)*)(cast(void**) ft + 1); + goto Lagain; + } + } + + immutable(void)* fptr = ft.fptr; + version (Win64) + { + // Follow JMP indirection from /DEBUG linker + if ((cast(ubyte*) fptr)[0] == 0xE9) + fptr = fptr + 5 + *cast(int*)(fptr + 1); + } + + if (fptr <= address && address < cast(void*)(cast(char*) fptr + ft.fsize)) + return ft; + } + return null; +} + +// Stack frame walking + +size_t __eh_find_caller(size_t regbp, size_t* pretaddr) @trusted +{ + size_t bp = *cast(size_t*) regbp; + + if (bp) + { + // Stack grows downward - new BP must be above old + if (bp <= regbp) + terminate(); + + *pretaddr = *cast(size_t*)(regbp + size_t.sizeof); + } + return bp; +} + +// _d_throwc - the core throw implementation (RBP-chain walking) + +alias fp_t = int function(); + +extern(C) void _d_throwc(Throwable h) @trusted +{ + size_t regebp; + + version (D_InlineAsm_X86) + asm nothrow @nogc { mov regebp, EBP; } + else version (D_InlineAsm_X86_64) + asm nothrow @nogc { mov regebp, RBP; } + + // Increment reference count if refcounted + auto refcount = h.refcount(); + if (refcount) + h.refcount() = refcount + 1; + + _d_createTrace(h, null); + + while (1) + { + size_t retaddr; + + regebp = __eh_find_caller(regebp, &retaddr); + if (!regebp) + break; + + auto func_table = __eh_finddata(cast(void*) retaddr); + auto handler_table = func_table ? func_table.handlertable : null; + if (!handler_table) + continue; + + auto funcoffset = cast(size_t) func_table.fptr; + version (Win64) + { + // Follow JMP indirection from /DEBUG linker + if ((cast(ubyte*) funcoffset)[0] == 0xE9) + funcoffset = funcoffset + 5 + *cast(int*)(funcoffset + 1); + } + + // Find start index for retaddr in handler table + auto dim = handler_table.nhandlers; + auto index = -1; + for (uint i = 0; i < dim; i++) + { + auto phi = &handler_table.handler_info.ptr[i]; + if (retaddr > funcoffset + phi.offset && + retaddr <= funcoffset + phi.endoffset) + index = i; + } + + // Handle inflight exception chaining + if (dim) + { + auto phi = &handler_table.handler_info.ptr[index + 1]; + auto prev = cast(InFlight*) &__inflight; + auto curr = prev.next; + + if (curr !is null && curr.addr == cast(void*)(funcoffset + phi.finally_offset)) + { + auto e = cast(Error) h; + if (e !is null && (cast(Error) curr.t) is null) + { + e.bypassedException = curr.t; + prev.next = curr.next; + } + else + { + h = Throwable.chainTogether(curr.t, h); + prev.next = curr.next; + } + } + } + + // Walk handler table entries + int prev_ndx; + for (auto ndx = index; ndx != -1; ndx = prev_ndx) + { + auto phi = &handler_table.handler_info.ptr[ndx]; + prev_ndx = phi.prev_index; + + if (phi.cioffset) + { + // Catch handler + auto pci = cast(DCatchInfo*)(cast(char*) handler_table + phi.cioffset); + auto ncatches = pci.ncatches; + + for (uint i = 0; i < ncatches; i++) + { + auto ci = **cast(ClassInfo**) h; + auto pcb = &pci.catch_block.ptr[i]; + + if (_d_isbaseof(ci, pcb.type)) + { + // Initialize catch variable + *cast(void**)(regebp + pcb.bpoffset) = cast(void*) h; + + // Jump to catch block - does not return + { + size_t catch_esp; + fp_t catch_addr; + + catch_addr = cast(fp_t)(funcoffset + pcb.codeoffset); + catch_esp = regebp - handler_table.espoffset - fp_t.sizeof; + + version (D_InlineAsm_X86) + asm nothrow @nogc + { + mov EAX, catch_esp; + mov ECX, catch_addr; + mov [EAX], ECX; + mov EBP, regebp; + mov ESP, EAX; + ret; + } + else version (D_InlineAsm_X86_64) + asm nothrow @nogc + { + mov RAX, catch_esp; + mov RCX, catch_esp; + mov RCX, catch_addr; + mov [RAX], RCX; + mov RBP, regebp; + mov RSP, RAX; + ret; + } + } + } + } + } + else if (phi.finally_offset) + { + // Finally block + auto blockaddr = cast(void*)(funcoffset + phi.finally_offset); + InFlight inflight; + + inflight.addr = blockaddr; + inflight.next = __inflight; + inflight.t = h; + __inflight = &inflight; + + version (D_InlineAsm_X86) + asm nothrow @nogc + { + push EBX; + mov EBX, blockaddr; + push EBP; + mov EBP, regebp; + call EBX; + pop EBP; + pop EBX; + } + else version (D_InlineAsm_X86_64) + asm nothrow @nogc + { + sub RSP, 8; + push RBX; + mov RBX, blockaddr; + push RBP; + mov RBP, regebp; + call RBX; + pop RBP; + pop RBX; + add RSP, 8; + } + + if (__inflight is &inflight) + __inflight = __inflight.next; + } + } + } + terminate(); +} + + +} // version (Win64) diff --git a/src/urt/driver/windows/fibre.d b/src/urt/driver/windows/fibre.d new file mode 100644 index 0000000..81aca31 --- /dev/null +++ b/src/urt/driver/windows/fibre.d @@ -0,0 +1,92 @@ +module urt.driver.windows.fibre; + +version (Windows): + +import urt.fibre : cothread_t, coentry_t; +import urt.mem; +import urt.internal.sys.windows.winbase; + +nothrow @nogc: + + +version (X86_64) +{ + import urt.system : NT_TIB, __readgsqword; + + void* GetCurrentFiber() + => cast(void*)__readgsqword(NT_TIB.FiberData.offsetof); + + void* GetFiberData() + => *cast(void**)__readgsqword(NT_TIB.FiberData.offsetof); +} +else version (X86) +{ + import urt.system : NT_TIB, __readfsdword; + + void* GetCurrentFiber() + => cast(void*)__readfsdword(NT_TIB.FiberData.offsetof); + + void* GetFiberData() + => *cast(void**)__readfsdword(NT_TIB.FiberData.offsetof); +} + +struct co_fibre_data +{ + void* fiber; + void* user_data; + uint stack_size; + coentry_t coentry; +} +co_fibre_data thread_fiber_data; + +inout(co_fibre_data)* co_get_fibre_data(inout cothread_t fibre) pure + => cast(co_fibre_data*)fibre; + +cothread_t co_active() +{ + if(!thread_fiber_data.fiber) + thread_fiber_data.fiber = ConvertThreadToFiber(&thread_fiber_data); + return GetFiberData(); +} + +void* co_data() + => (cast(co_fibre_data*)GetFiberData()).user_data; + +cothread_t co_derive(void[] memory, coentry_t entry, void* data) +{ + // Windows fibers do not allow users to supply their own memory + return null; +} + +cothread_t co_create(size_t stack_size, coentry_t entry, void* data) +{ + assert(stack_size <= uint.max, "Stack size too large"); + + co_active(); + + extern(Windows) static void co_thunk(void* codata) + { + (cast(co_fibre_data*)codata).coentry(); + assert(false, "Error: returned from fibre!"); + } + + auto fdata = defaultAllocator().allocT!co_fibre_data(); + fdata.user_data = data; + fdata.coentry = entry; + fdata.stack_size = cast(uint)stack_size; + fdata.fiber = CreateFiber(stack_size, &co_thunk, fdata); + return fdata; +} + +void co_delete(cothread_t cothread) +{ + auto fdata = cast(co_fibre_data*)cothread; + DeleteFiber(fdata.fiber); + defaultAllocator().freeT(fdata); +} + +void co_switch(cothread_t cothread) +{ + auto fdata = cast(co_fibre_data*)cothread; + SwitchToFiber(fdata.fiber); +} diff --git a/src/urt/encoding.d b/src/urt/encoding.d index 36ced1b..c748e7a 100644 --- a/src/urt/encoding.d +++ b/src/urt/encoding.d @@ -3,18 +3,28 @@ module urt.encoding; nothrow @nogc: -enum Hex(const char[] s) = (){ ubyte[s.length / 2] r; ptrdiff_t len = hex_decode(s, r); assert(len == r.sizeof, "Not a hex string!"); return r; }(); -enum Base64(const char[] s) = (){ ubyte[base64_decode_length(s)] r; ptrdiff_t len = base64_decode(s, r); assert(len == r.sizeof, "Not a base64 string!"); return r; }(); +enum Base64Decode(string str) = () { ubyte[base64_decode_length(str.length)] r; size_t len = base64_decode(str, r[]); assert(len == r.sizeof, "Not a base64 string: " ~ str); return r; }(); +enum HexDecode(string str) = () { ubyte[hex_decode_length(str.length)] r; size_t len = hex_decode(str, r[]); assert(len == r.sizeof, "Not a hex string: " ~ str); return r; }(); +enum URLDecode(string str) = () { char[url_decode_length(str)] r; size_t len = url_decode(str, r[]); assert(len == r.sizeof, "Not a URL encoded string: " ~ str); return r; }(); -ptrdiff_t base64_encode_length(size_t sourceLength) pure - => (sourceLength + 2) / 3 * 4; +size_t base64_encode_length(bool url = false)(size_t source_length) pure +{ + static if (url) + return (source_length * 4 + 2) / 3; // no padding + else + return (source_length + 2) / 3 * 4; +} + +size_t base64_encode_length(bool url = false)(const void[] data) pure + => base64_encode_length!url(data.length); -ptrdiff_t base64_encode(const void[] data, char[] result) pure +ptrdiff_t base64_encode(bool url = false)(const void[] data, char[] result) pure { + immutable(char)* table = url ? &base64url[0] : &base64[0]; auto src = cast(const(ubyte)[])data; size_t len = data.length; - size_t out_len = base64_encode_length(len); + size_t out_len = base64_encode_length!url(len); if (result.length < out_len) return -1; @@ -27,71 +37,111 @@ ptrdiff_t base64_encode(const void[] data, char[] result) pure ubyte b1 = src[i++]; ubyte b2 = src[i++]; - result[j++] = base64[b0 >> 2]; - result[j++] = base64[((b0 & 0x03) << 4) | (b1 >> 4)]; - result[j++] = base64[((b1 & 0x0F) << 2) | (b2 >> 6)]; - result[j++] = base64[b2 & 0x3F]; + result[j++] = table[b0 >> 2]; + result[j++] = table[((b0 & 0x03) << 4) | (b1 >> 4)]; + result[j++] = table[((b1 & 0x0F) << 2) | (b2 >> 6)]; + result[j++] = table[b2 & 0x3F]; } if (i < len) { ubyte b0 = src[i++]; - result[j++] = base64[b0 >> 2]; + result[j++] = table[b0 >> 2]; if (i < len) { ubyte b1 = src[i]; - result[j++] = base64[((b0 & 0x03) << 4) | (b1 >> 4)]; - result[j++] = base64[((b1 & 0x0F) << 2)]; + result[j++] = table[((b0 & 0x03) << 4) | (b1 >> 4)]; + result[j++] = table[((b1 & 0x0F) << 2)]; } else { - result[j++] = base64[((b0 & 0x03) << 4)]; - result[j++] = '='; + result[j++] = table[((b0 & 0x03) << 4)]; + static if (!url) + result[j++] = '='; } - result[j] = '='; + static if (!url) + result[j] = '='; } return out_len; } -ptrdiff_t base64_decode_length(size_t sourceLength) pure -=> sourceLength / 4 * 3; +size_t base64_decode_length(bool url = false)(size_t source_length) pure +{ + static if (url) + { + size_t remainder = source_length % 4; + return source_length / 4 * 3 + (remainder > 0 ? remainder - 1 : 0); + } + else + return source_length / 4 * 3; +} + +size_t base64_decode_length(bool url = false)(const char[] data) pure + => base64_decode_length!url(data.length); -ptrdiff_t base64_decode(const char[] data, void[] result) pure +ptrdiff_t base64_decode(bool url = false)(const char[] data, void[] result) pure { + static if (url) + { + enum uint offset = 45; + enum uint map_size = 78; + alias map = base64url_map; + } + else + { + enum uint offset = 43; + enum uint map_size = 80; + alias map = base64_map; + } + size_t len = data.length; auto dest = cast(ubyte[])result; - size_t out_len = base64_decode_length(len); - if (data[len - 1] == '=') - out_len--; - if (data[len - 2] == '=') - out_len--; + size_t out_len; + + static if (url) + { + size_t remainder = len % 4; + if (remainder == 1) + return -1; + out_len = len / 4 * 3 + (remainder > 0 ? remainder - 1 : 0); + } + else + { + out_len = len / 4 * 3; + if (len >= 1 && data[len - 1] == '=') + --out_len; + if (len >= 2 && data[len - 2] == '=') + --out_len; + } if (result.length < out_len) return -1; + static if (url) + size_t full_len = len / 4 * 4; + else + size_t full_len = len; + size_t i = 0; size_t j = 0; - while (i < len) + while (i < full_len) { - // TODO: this could be faster by using more memory, store a full 256-byte table and no comparisons... - uint b0 = data[i++] - 43; - uint b1 = data[i++] - 43; - uint b2 = data[i++] - 43; - uint b3 = data[i++] - 43; - if (b0 >= 80) + if (i > full_len - 4) return -1; - if (b1 >= 80) - return -1; - if (b2 >= 80) - return -1; - if (b3 >= 80) + + // TODO: this could be faster by using more memory, store a full 256-byte table and no comparisons... + uint b0 = data[i++] - offset; + uint b1 = data[i++] - offset; + uint b2 = data[i++] - offset; + uint b3 = data[i++] - offset; + if (b0 >= map_size || b1 >= map_size || b2 >= map_size || b3 >= map_size) return -1; - b0 = base64_map.ptr[b0]; - b1 = base64_map.ptr[b1]; - b2 = base64_map.ptr[b2]; - b3 = base64_map.ptr[b3]; + b0 = map[b0]; + b1 = map[b1]; + b2 = map[b2]; + b3 = map[b3]; dest[j++] = cast(ubyte)((b0 << 2) | (b1 >> 4)); if (b2 != 64) @@ -100,15 +150,56 @@ ptrdiff_t base64_decode(const char[] data, void[] result) pure dest[j++] = cast(ubyte)((b2 << 6) | b3); } + static if (url) + { + if (i < len) + { + uint b0 = data[i++] - offset; + uint b1 = data[i++] - offset; + if (b0 >= map_size || b1 >= map_size) + return -1; + b0 = map[b0]; + b1 = map[b1]; + dest[j++] = cast(ubyte)((b0 << 2) | (b1 >> 4)); + + if (i < len) + { + uint b2 = data[i] - offset; + if (b2 >= map_size) + return -1; + b2 = map[b2]; + dest[j++] = cast(ubyte)((b1 << 4) | (b2 >> 2)); + } + } + } + return out_len; } +size_t base64url_encode_length(size_t source_length) pure +=> base64_encode_length!true(source_length); + +size_t base64url_encode_length(const void[] data) pure +=> base64_encode_length!true(data); + +alias base64url_encode = base64_encode!true; + +size_t base64url_decode_length(size_t source_length) pure + => base64_decode_length!true(source_length); + +size_t base64url_decode_length(const char[] data) pure + => base64_decode_length!true(data); + +alias base64url_decode = base64_decode!true; + unittest { immutable ubyte[12] data = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C]; char[16] encoded = void; ubyte[12] decoded = void; + static assert(Base64Decode!"AQIDBAUGBwgJCgsM" == data[]); + size_t len = base64_encode(data, encoded); assert(len == 16); assert(encoded == "AQIDBAUGBwgJCgsM"); @@ -130,9 +221,81 @@ unittest assert(len == 10); assert(data[0..10] == decoded[0..10]); -// static assert(Base64!"012345" == [0x01, 0x23, 0x45]); + // base64url: different alphabet (+/ → -_) and no padding + // [0..3] all-62, [3..6] all-63, [6..9] mixed 62/63 + immutable ubyte[9] urldata = [0xFB, 0xEF, 0xBE, 0xFF, 0xFF, 0xFF, 0xFB, 0xFF, 0xBF]; + + len = base64_encode(urldata[0..3], encoded[0..4]); + assert(len == 4); + assert(encoded[0..4] == "++++"); + len = base64url_encode(urldata[0..3], encoded[0..4]); + assert(len == 4); + assert(encoded[0..4] == "----"); + + len = base64_encode(urldata[3..6], encoded[0..4]); + assert(len == 4); + assert(encoded[0..4] == "////"); + len = base64url_encode(urldata[3..6], encoded[0..4]); + assert(len == 4); + assert(encoded[0..4] == "____"); + + len = base64_encode(urldata[6..9], encoded[0..4]); + assert(len == 4); + assert(encoded[0..4] == "+/+/"); + len = base64url_encode(urldata[6..9], encoded[0..4]); + assert(len == 4); + assert(encoded[0..4] == "-_-_"); + + // decode roundtrips + len = base64_decode("++++", decoded[0..3]); + assert(len == 3); + assert(decoded[0..3] == urldata[0..3]); + len = base64url_decode("----", decoded[0..3]); + assert(len == 3); + assert(decoded[0..3] == urldata[0..3]); + len = base64_decode("+/+/", decoded[0..3]); + assert(len == 3); + assert(decoded[0..3] == urldata[6..9]); + len = base64url_decode("-_-_", decoded[0..3]); + assert(len == 3); + assert(decoded[0..3] == urldata[6..9]); + + // padding vs no-padding: 1 byte → /w== vs _w + len = base64_encode(urldata[3..4], encoded[0..4]); + assert(len == 4); + assert(encoded[0..4] == "/w=="); + len = base64_decode(encoded[0..4], decoded[0..1]); + assert(len == 1); + assert(decoded[0] == 0xFF); + + len = base64url_encode(urldata[3..4], encoded[0..2]); + assert(len == 2); + assert(encoded[0..2] == "_w"); + len = base64url_decode(encoded[0..2], decoded[0..1]); + assert(len == 1); + assert(decoded[0] == 0xFF); + + // padding vs no-padding: 2 bytes → ++8= vs --8 + len = base64_encode(urldata[0..2], encoded[0..4]); + assert(len == 4); + assert(encoded[0..4] == "++8="); + len = base64_decode(encoded[0..4], decoded[0..2]); + assert(len == 2); + assert(decoded[0..2] == urldata[0..2]); + + len = base64url_encode(urldata[0..2], encoded[0..3]); + assert(len == 3); + assert(encoded[0..3] == "--8"); + len = base64url_decode(encoded[0..3], decoded[0..2]); + assert(len == 2); + assert(decoded[0..2] == urldata[0..2]); } +size_t hex_encode_length(size_t sourceLength) pure + => sourceLength * 2; + +size_t hex_encode_length(const void[] data) pure + => data.length * 2; ptrdiff_t hex_encode(const void[] data, char[] result) pure { @@ -142,9 +305,15 @@ ptrdiff_t hex_encode(const void[] data, char[] result) pure return toHexString(data, result).length; } +size_t hex_decode_length(size_t sourceLength) pure + => sourceLength / 2; + +size_t hex_decode_length(const char[] data) pure + => data.length / 2; + ptrdiff_t hex_decode(const char[] data, void[] result) pure { - import urt.string.ascii : isHex; + import urt.string.ascii : is_hex; if (data.length & 1) return -1; @@ -157,7 +326,7 @@ ptrdiff_t hex_decode(const char[] data, void[] result) pure { ubyte c0 = data[i]; ubyte c1 = data[i + 1]; - if (!c0.isHex || !c1.isHex) + if (!c0.is_hex || !c1.is_hex) return -1; if ((c0 | 0x20) >= 'a') @@ -180,25 +349,25 @@ unittest char[24] encoded = void; ubyte[12] decoded = void; + static assert(HexDecode!"0102030405060708090A0B0C" == data); + size_t len = hex_encode(data, encoded); assert(len == 24); assert(encoded == "0102030405060708090A0B0C"); len = hex_decode(encoded, decoded); assert(len == 12); assert(data == decoded); - - static assert(Hex!"012345" == [0x01, 0x23, 0x45]); } -ptrdiff_t url_encode_length(const char[] data) pure +size_t url_encode_length(const char[] data) pure { - import urt.string.ascii : isURL; + import urt.string.ascii : is_url; size_t len = 0; foreach (c; data) { - if (c.isURL || c == ' ') + if (c.is_url || c == ' ') ++len; else len += 3; @@ -208,14 +377,14 @@ ptrdiff_t url_encode_length(const char[] data) pure ptrdiff_t url_encode(const char[] data, char[] result) pure { - import urt.string.ascii : isURL, hexDigits; + import urt.string.ascii : is_url, hex_digits; size_t j = 0; for (size_t i = 0; i < data.length; ++i) { char c = data[i]; - if (c.isURL || c == ' ') + if (c.is_url || c == ' ') { if (j == result.length) return -1; @@ -226,15 +395,15 @@ ptrdiff_t url_encode(const char[] data, char[] result) pure if (j + 2 == result.length) return -1; result[j++] = '%'; - result[j++] = hexDigits[c >> 4]; - result[j++] = hexDigits[c & 0xF]; + result[j++] = hex_digits[c >> 4]; + result[j++] = hex_digits[c & 0xF]; } } return j; } -ptrdiff_t url_decode_length(const char[] data) pure +size_t url_decode_length(const char[] data) pure { size_t len = 0; for (size_t i = 0; i < data.length;) @@ -250,7 +419,7 @@ ptrdiff_t url_decode_length(const char[] data) pure ptrdiff_t url_decode(const char[] data, char[] result) pure { - import urt.string.ascii : isHex; + import urt.string.ascii : is_hex; size_t j = 0; for (size_t i = 0; i < data.length; ++i) @@ -268,7 +437,7 @@ ptrdiff_t url_decode(const char[] data, char[] result) pure ubyte c0 = data[i + 1]; ubyte c1 = data[i + 2]; - if (!c0.isHex || !c1.isHex) + if (!c0.is_hex || !c1.is_hex) return -1; i += 2; @@ -290,6 +459,8 @@ ptrdiff_t url_decode(const char[] data, char[] result) pure unittest { + static assert(URLDecode!"Hello%2C+World%21" == "Hello, World!"); + char[13] data = "Hello, World!"; char[17] encoded = void; char[13] decoded = void; @@ -308,10 +479,19 @@ unittest private: __gshared immutable char[64] base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -__gshared immutable ubyte[80] base64_map = [ 62, 0, 0, 0, 63, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 64, 0, 0, - 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, - 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, +__gshared immutable ubyte[80] base64_map = [ 62, 0, 0, 0, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 64, 0, 0, + 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, + 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 +]; + +__gshared immutable char[64] base64url = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; +__gshared immutable ubyte[78] base64url_map = [ 62, 0, 0, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 63, + 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 ]; diff --git a/src/urt/endian.d b/src/urt/endian.d index e76f671..904eb2d 100644 --- a/src/urt/endian.d +++ b/src/urt/endian.d @@ -4,20 +4,20 @@ import urt.processor; import urt.traits; public import urt.processor : LittleEndian; -public import urt.util : byteReverse; +public import urt.util : byte_reverse; pure nothrow @nogc: // load from byte arrays pragma(inline, true) T endianToNative(T, bool little)(ref const ubyte[1] bytes) - if (T.sizeof == 1 && isIntegral!T) + if (T.sizeof == 1 && is_integral!T) { return cast(T)bytes[0]; } ushort endianToNative(T, bool little)(ref const ubyte[2] bytes) - if (T.sizeof == 2 && isIntegral!T) + if (T.sizeof == 2 && is_integral!T) { if (__ctfe || !SupportUnalignedLoadStore) { @@ -33,12 +33,12 @@ ushort endianToNative(T, bool little)(ref const ubyte[2] bytes) static if (LittleEndian == little) return *cast(ushort*)bytes.ptr; else - return byteReverse(*cast(ushort*)bytes.ptr); + return byte_reverse(*cast(ushort*)bytes.ptr); } } uint endianToNative(T, bool little)(ref const ubyte[4] bytes) - if (T.sizeof == 4 && isIntegral!T) + if (T.sizeof == 4 && is_integral!T) { if (__ctfe || !SupportUnalignedLoadStore) { @@ -54,20 +54,20 @@ uint endianToNative(T, bool little)(ref const ubyte[4] bytes) static if (LittleEndian == little) return *cast(uint*)bytes.ptr; else - return byteReverse(*cast(uint*)bytes.ptr); + return byte_reverse(*cast(uint*)bytes.ptr); } } ulong endianToNative(T, bool little)(ref const ubyte[8] bytes) - if (T.sizeof == 8 && isIntegral!T) + if (T.sizeof == 8 && is_integral!T) { if (__ctfe || !SupportUnalignedLoadStore) { // ctfe can't do the memory reinterpreting static if (little) - return cast(T)(bytes[0] | bytes[1] << 8 | bytes[2] << 16 | bytes[3] << 24 | cast(ulong)bytes[4] << 32 | cast(ulong)bytes[5] << 40 | cast(ulong)bytes[6] << 48 | cast(ulong)bytes[7] << 56); + return cast(T)(bytes[0] | bytes[1] << 8 | bytes[2] << 16 | ulong(bytes[3]) << 24 | ulong(bytes[4]) << 32 | ulong(bytes[5]) << 40 | ulong(bytes[6]) << 48 | ulong(bytes[7]) << 56); else - return cast(T)(cast(ulong)bytes[0] << 56 | cast(ulong)bytes[1] << 48 | cast(ulong)bytes[2] << 40 | cast(ulong)bytes[3] << 32 | bytes[4] << 24 | bytes[5] << 16 | bytes[6] << 8 | bytes[7]); + return cast(T)(ulong(bytes[0]) << 56 | ulong(bytes[1]) << 48 | ulong(bytes[2]) << 40 | ulong(bytes[3]) << 32 | ulong(bytes[4]) << 24 | bytes[5] << 16 | bytes[6] << 8 | bytes[7]); } static if (SupportUnalignedLoadStore) { @@ -75,15 +75,15 @@ ulong endianToNative(T, bool little)(ref const ubyte[8] bytes) static if (LittleEndian == little) return *cast(ulong*)bytes.ptr; else - return byteReverse(*cast(ulong*)bytes.ptr); + return byte_reverse(*cast(ulong*)bytes.ptr); } } pragma(inline, true) T endianToNative(T, bool little)(ref const ubyte[T.sizeof] bytes) - if (!isIntegral!T && !is(T == struct) && !is(T == U[N], U, size_t N)) + if (!is_integral!T && !is(T == struct) && !is(T == U[N], U, size_t N)) { - import urt.meta : intForWidth; - alias U = intForWidth!(T.sizeof*8); + import urt.meta : IntForWidth; + alias U = IntForWidth!(T.sizeof*8); U u = endianToNative!(U, little)(bytes); return *cast(T*)&u; } @@ -96,12 +96,12 @@ T endianToNative(T, bool little)(ref const ubyte[T.sizeof] bytes) static assert(!is(U == class) && !is(U == interface) && !is(U == V*, V), T.stringof ~ " is not POD"); static if (U.sizeof == 1) - r = *cast(T*)&bytes; + return *cast(T*)&bytes; else { T r; - for (size_t i = 0, j = 0; i < N; ++i, j += T.sizeof) - r[i] = endianToNative!(U, little)(bytes.ptr[j .. j + T.sizeof][0 .. T.sizeof]); + for (size_t i = 0, j = 0; i < N; ++i, j += U.sizeof) + r[i] = endianToNative!(U, little)(bytes.ptr[j .. j + U.sizeof][0 .. U.sizeof]); return r; } } @@ -156,7 +156,7 @@ ubyte[2] nativeToEndian(bool little)(ushort u) static if (SupportUnalignedLoadStore) { static if (LittleEndian != little) - u = byteReverse(u); + u = byte_reverse(u); else pragma(inline, true); return *cast(ubyte[2]*)&u; @@ -176,7 +176,7 @@ ubyte[4] nativeToEndian(bool little)(uint u) static if (SupportUnalignedLoadStore) { static if (LittleEndian != little) - u = byteReverse(u); + u = byte_reverse(u); else pragma(inline, true); return *cast(ubyte[4]*)&u; @@ -196,7 +196,7 @@ ubyte[8] nativeToEndian(bool little)(ulong u) static if (SupportUnalignedLoadStore) { static if (LittleEndian != little) - u = byteReverse(u); + u = byte_reverse(u); else pragma(inline, true); return *cast(ubyte[8]*)&u; @@ -204,12 +204,11 @@ ubyte[8] nativeToEndian(bool little)(ulong u) } pragma(inline, true) auto nativeToEndian(bool little, T)(T val) - if (!isIntegral!T && !is(T == struct) && !is(T == U[N], U, size_t N)) + if (!is_integral!T && !is(T == struct) && !is(T == U[N], U, size_t N)) { - import urt.meta : intForWidth; - alias U = intForWidth!(T.sizeof*8); - U r = nativeToEndian!little(*cast(U*)&val); - return *cast(T*)&r; + import urt.meta : IntForWidth; + alias U = IntForWidth!(T.sizeof*8); + return nativeToEndian!little(*cast(U*)&val); } ubyte[T.sizeof] nativeToEndian(bool little, T)(auto ref const T data) @@ -261,36 +260,36 @@ ubyte[T.sizeof] nativeToLittleEndian(T)(auto ref const T data) // load/store from/to memory void storeBigEndian(T)(T* target, const T val) - if (isSomeInt!T || is(T == float)) + if (is_some_int!T || is(T == float) || is(T == double)) { version (BigEndian) *target = val; else - *target = byteReverse(val); + *target = byte_reverse(val); } void storeLittleEndian(T)(T* target, const T val) - if (isSomeInt!T || is(T == float)) + if (is_some_int!T || is(T == float) || is(T == double)) { version (LittleEndian) *target = val; else - *target = byteReverse(val); + *target = byte_reverse(val); } T loadBigEndian(T)(const(T)* src) - if (isSomeInt!T || is(T == float)) + if (is_some_int!T || is(T == float) || is(T == double)) { version (BigEndian) return *src; else - return byteReverse(*src); + return byte_reverse(*src); } T loadLittleEndian(T)(const(T)* src) - if (isSomeInt!T || is(T == float)) + if (is_some_int!T || is(T == float) || is(T == double)) { version (LittleEndian) return *src; else - return byteReverse(*src); + return byte_reverse(*src); } diff --git a/src/urt/exception.d b/src/urt/exception.d new file mode 100644 index 0000000..f98ffbf --- /dev/null +++ b/src/urt/exception.d @@ -0,0 +1,63 @@ +/// Assert handler registration for uRT. +module urt.exception; + +nothrow @nogc: + + +alias AssertHandler = void function(string file, size_t line, string msg) nothrow @nogc; + +AssertHandler assert_handler() @property nothrow @nogc @trusted + => _assert_handler; + +void assert_handler(AssertHandler handler) @property nothrow @nogc @trusted +{ + if (handler is null) + _assert_handler = &urt_assert; + else + _assert_handler = handler; +} + + +private: + +__gshared AssertHandler _assert_handler = &urt_assert; + +void urt_assert(string file, size_t line, string msg) nothrow @nogc +{ + if (msg.length == 0) + msg = "Assertion failed"; + + version (BareMetal) + { + import urt.driver.uart : uart0_puts; + import urt.mem.temp : tconcat; + uart0_puts(tconcat("\n*** ASSERT: ", msg, " at ", file, ':', line, '\n')); + while (true) {} + } + else version (Espressif) + { + import urt.io : writef_to, WriteTarget; + writef_to!(WriteTarget.stdout, true)("*** ASSERT: {2} at {0}:{1}", file, line, msg); + import urt.internal.stdc.stdlib : exit; + exit(-1); + } + else + { + debug + { + import urt.io : writef_to, WriteTarget; + import urt.dbg; + + version (Windows) + writef_to!(WriteTarget.debugstring, true)("{0}({1}): {2}", file, line, msg); + writef_to!(WriteTarget.stdout, true)("{0}({1}): {2}", file, line, msg); + + breakpoint(); + } + else + { + import urt.internal.stdc.stdlib : exit; + exit(-1); + } + } +} diff --git a/src/urt/fibre.d b/src/urt/fibre.d index 574e551..535acec 100644 --- a/src/urt/fibre.d +++ b/src/urt/fibre.d @@ -2,7 +2,7 @@ module urt.fibre; import urt.mem; import urt.time; -import urt.util : isAligned, max; +import urt.util : is_aligned, max; version (Windows) version = UseWindowsFibreAPI; @@ -37,24 +37,24 @@ alias YieldHandler = ResumeHandler function(ref Fibre yielding, AwakenEvent awak struct Fibre { -@nogc: +nothrow @nogc: this() @disable; this(ref typeof(this)) @disable; // disable copy this(typeof(this)) @disable; // disable move - this(size_t stackSize) nothrow + this(size_t stackSize) { if (!mainFibre) mainFibre = co_active(); // TODO: i think it's a bug that this stuff isn't initialised! - isDelegate = false; + is_delegate = false; abortRequested = false; finished = true; // init in a state ready to be recycled... aborted = false; - static void fibreFunc() + static void fibreFunc() nothrow { import urt.system : abort; @@ -62,7 +62,7 @@ struct Fibre while (true) { try { - if (thisFibre.isDelegate) + if (thisFibre.is_delegate) { FibreEntryDelegate dg; dg.ptr = thisFibre.userData; @@ -84,6 +84,8 @@ struct Fibre } catch (Throwable e) { + import urt.io; + writef_to!(WriteTarget.stderr, true)("abort: {0}:{1} - {2}", e.file, e.line, e.msg); abort(); } @@ -100,14 +102,14 @@ struct Fibre fibre = co_create(stackSize, &fibreFunc, &this); } - this(FibreEntryDelegate fibreEntry, YieldHandler yieldHandler, size_t stackSize = DefaultStackSize) nothrow + this(FibreEntryDelegate fibreEntry, YieldHandler yieldHandler, size_t stackSize = DefaultStackSize) { this(cast(FibreEntryFunc)fibreEntry.funcptr, yieldHandler, fibreEntry.ptr, stackSize); - isDelegate = true; + is_delegate = true; } - this(FibreEntryFunc fibreEntry, YieldHandler yieldHandler, void* userData = null, size_t stackSize = DefaultStackSize) nothrow + this(FibreEntryFunc fibreEntry, YieldHandler yieldHandler, void* userData = null, size_t stackSize = DefaultStackSize) { this(stackSize); @@ -118,7 +120,7 @@ struct Fibre finished = false; } - ~this() nothrow + ~this() { assert(co_active() != fibre, "Can't delete the current fibre!"); @@ -131,12 +133,12 @@ struct Fibre co_delete(fibre); } - void resume() nothrow + void resume() { co_switch(fibre); } - void abort() nothrow + void abort() { assert(co_active() == mainFibre, "Can't abort when active; use urt.fibre.abort() instead."); @@ -145,31 +147,31 @@ struct Fibre co_switch(fibre); } - void recycle(FibreEntryDelegate fibreEntry) pure nothrow + void recycle(FibreEntryDelegate fibreEntry) pure { assert(isFinished(), "Can't recycle a fibre that hasn't finished yet!"); this.fibreEntry = cast(FibreEntryFunc)fibreEntry.funcptr; userData = fibreEntry.ptr; - isDelegate = true; + is_delegate = true; abortRequested = false; finished = false; aborted = false; } - void recycle(FibreEntryFunc fibreEntry, void* userData = null) pure nothrow + void recycle(FibreEntryFunc fibreEntry, void* userData = null) pure { assert(isFinished(), "Can't recycle a fibre that hasn't finished yet!"); this.fibreEntry = fibreEntry; this.userData = userData; - isDelegate = false; + is_delegate = false; abortRequested = false; finished = false; aborted = false; } - void reset() pure nothrow + void reset() pure { assert(isFinished(), "Can't restart a fibre that hasn't finished yet!"); @@ -178,13 +180,13 @@ struct Fibre aborted = false; } - bool isFinished() const pure nothrow + bool isFinished() const pure => finished; - bool wasAborted() const pure nothrow + bool wasAborted() const pure => aborted; - size_t stackSize() const pure nothrow + size_t stackSize() const pure { assert(fibre, "Fibre not created!"); auto fdata = co_get_fibre_data(fibre); @@ -198,7 +200,7 @@ private: YieldHandler yieldHandler; cothread_t fibre; - bool isDelegate; + bool is_delegate; bool abortRequested; bool finished; bool aborted; @@ -291,7 +293,7 @@ class AbortException : Exception } } -void* mainFibre = null; +__gshared void* mainFibre = null; AbortException abortException; @@ -337,95 +339,13 @@ unittest //------------------------------------------- nothrow: -alias cothread_t = void*; -alias coentry_t = void function() @nogc; +package alias cothread_t = void*; +package alias coentry_t = void function() nothrow @nogc; version (UseWindowsFibreAPI) -{ - import core.sys.windows.winbase; - - version (X86_64) - { - import urt.system : NT_TIB, __readgsqword; - - void* GetCurrentFiber() - => cast(void*)__readgsqword(NT_TIB.FiberData.offsetof); - - void* GetFiberData() - => *cast(void**)__readgsqword(NT_TIB.FiberData.offsetof); - } - else version (X86) - { - import urt.system : NT_TIB, __readfsdword; - - void* GetCurrentFiber() - => cast(void*)__readfsdword(NT_TIB.FiberData.offsetof); - - void* GetFiberData() - => *cast(void**)__readfsdword(NT_TIB.FiberData.offsetof); - } - - struct co_fibre_data - { - void* fiber; - void* user_data; - uint stack_size; - coentry_t coentry; - } - co_fibre_data thread_fiber_data; - - private inout(co_fibre_data)* co_get_fibre_data(inout cothread_t fibre) pure - => cast(co_fibre_data*)fibre; - - cothread_t co_active() - { - if(!thread_fiber_data.fiber) - thread_fiber_data.fiber = ConvertThreadToFiber(&thread_fiber_data); - return GetFiberData(); - } - - void* co_data() - => (cast(co_fibre_data*)GetFiberData()).user_data; - - cothread_t co_derive(void[] memory, coentry_t entry, void* data) - { - // Windows fibers do not allow users to supply their own memory - return null; - } - - cothread_t co_create(size_t stack_size, coentry_t entry, void* data) - { - assert(stack_size <= uint.max, "Stack size too large"); - - co_active(); - - extern(Windows) static void co_thunk(void* codata) - { - (cast(co_fibre_data*)codata).coentry(); - assert(false, "Error: returned from fibre!"); - } - - auto fdata = defaultAllocator().allocT!co_fibre_data(); - fdata.user_data = data; - fdata.coentry = entry; - fdata.stack_size = cast(uint)stack_size; - fdata.fiber = CreateFiber(stack_size, &co_thunk, fdata); - return fdata; - } - - void co_delete(cothread_t cothread) - { - auto fdata = cast(co_fibre_data*)cothread; - DeleteFiber(fdata.fiber); - defaultAllocator().freeT(fdata); - } - - void co_switch(cothread_t cothread) - { - auto fdata = cast(co_fibre_data*)cothread; - SwitchToFiber(fdata.fiber); - } -} + public import urt.driver.windows.fibre; +else version (FreeRTOS) + public import urt.driver.freertos.fibre; else { align(16) struct co_fibre_data @@ -570,7 +490,7 @@ else void co_init_stack(void* base, void* top, coentry_t entry) { - assert(isAligned!16(base) && isAligned!16(top), "Stack must be aligned to 16 bytes"); + assert(is_aligned!16(base) && is_aligned!16(top), "Stack must be aligned to 16 bytes"); void** sp = cast(void**)top; // seek to top of stack *--sp = &crash; // crash if entrypoint returns @@ -741,7 +661,7 @@ else void co_init_stack(void* base, void* top, coentry_t entry) { - assert(isAligned!16(base) && isAligned!16(top), "Stack must be aligned to 16 bytes"); + assert(is_aligned!16(base) && is_aligned!16(top), "Stack must be aligned to 16 bytes"); void** p = cast(void**)base; p[8] = cast(void*)top; // starting sp @@ -751,10 +671,13 @@ else pragma(inline, false) extern(C) void co_swap(cothread_t newCtx, cothread_t oldCtx) @naked { - // just for thumb-1, thumb-2 can to the 32bit thing below.. somehow... apparently...? + // thumb-mode: compiler is generating Thumb instructions (Cortex-M micros) + // thumb2: ISA supports Thumb-2 (true for both Cortex-M4+ and Cortex-A7+) + // We check thumb (active mode), not thumb2 (capability), to avoid + // using the Thumb path on full ARM cores that can use stmia with sp. static if (ProcFeatures.thumb && !ProcFeatures.thumb2) { - static assert(false, "TODO: this needs to be tested somehow... thumb instructions are offset by 1 byte."); + static assert(false, "TODO: Thumb-1 context switch needs testing"); asm nothrow @nogc { ` @@ -776,7 +699,29 @@ else ldmia r0!, {r4-r7} ldmia r1!, {pc} ` - // bx lr ; TODO: why is this even here? the prior instruction loads `pc`... maybe it's a hint to the branch predictor? +// bx lr ; TODO: why is this even here? the prior instruction loads `pc`... maybe it's a hint to the branch predictor? + : // no outputs + : // "r"(newCtx), "r"(oldCtx) // function is @naked, so the ABI takes care of this + : "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "sp", "lr", "memory"; + } + } + else static if (ProcFeatures.thumb) + { + // Thumb mode (Cortex-M): SP cannot appear in stm/ldm register lists. + // Layout: [r4..r11](0-28), [sp](32), [lr/pc](36) - matches co_init_stack + asm nothrow @nogc + { + ` + stmia r1!, {r4-r11} + mov r2, sp + str r2, [r1] + str lr, [r1, #4] + ldmia r0!, {r4-r11} + ldr r2, [r0] + ldr r3, [r0, #4] + mov sp, r2 + bx r3 + ` : // no outputs : // "r"(newCtx), "r"(oldCtx) // function is @naked, so the ABI takes care of this : "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "sp", "lr", "memory"; @@ -784,6 +729,7 @@ else } else { + // ARM mode: SP can appear in stm/ldm asm nothrow @nogc { ` @@ -804,7 +750,7 @@ else void co_init_stack(void* base, void* top, coentry_t entry) { - assert(isAligned!16(base) && isAligned!16(top), "Stack must be aligned to 16 bytes"); + assert(is_aligned!16(base) && is_aligned!16(top), "Stack must be aligned to 16 bytes"); void** p = cast(void**)base; p[0] = cast(void*)top; // x16 (stack pointer) @@ -850,6 +796,350 @@ else } } } + else version (RISCV64) + { + // RISC-V 64-bit: callee-saved sp, ra, s0-s11 (14 regs × 8 bytes) + // With D extension: also fs0-fs11 (12 regs × 8 bytes) + version (D_HardFloat) + enum SaveStateLen = 26; // 14 integer + 12 FP + else + enum SaveStateLen = 14; + + void co_init_stack(void* base, void* top, coentry_t entry) + { + assert(is_aligned!16(base) && is_aligned!16(top), "Stack must be aligned to 16 bytes"); + + void** p = cast(void**)base; + p[0] = cast(void*)top; // starting sp + p[1] = entry; // starting ra (entry point) + p[2] = cast(void*)top; // starting s0 (frame pointer) + } + + version (D_HardFloat) + { + pragma(inline, false) + extern(C) void co_swap(cothread_t newCtx, cothread_t oldCtx) @naked + { + asm nothrow @nogc + { + ` + sd sp, 0(a1) + sd ra, 8(a1) + sd s0, 16(a1) + sd s1, 24(a1) + sd s2, 32(a1) + sd s3, 40(a1) + sd s4, 48(a1) + sd s5, 56(a1) + sd s6, 64(a1) + sd s7, 72(a1) + sd s8, 80(a1) + sd s9, 88(a1) + sd s10, 96(a1) + sd s11, 104(a1) + fsd fs0, 112(a1) + fsd fs1, 120(a1) + fsd fs2, 128(a1) + fsd fs3, 136(a1) + fsd fs4, 144(a1) + fsd fs5, 152(a1) + fsd fs6, 160(a1) + fsd fs7, 168(a1) + fsd fs8, 176(a1) + fsd fs9, 184(a1) + fsd fs10, 192(a1) + fsd fs11, 200(a1) + ld sp, 0(a0) + ld s0, 16(a0) + ld s1, 24(a0) + ld s2, 32(a0) + ld s3, 40(a0) + ld s4, 48(a0) + ld s5, 56(a0) + ld s6, 64(a0) + ld s7, 72(a0) + ld s8, 80(a0) + ld s9, 88(a0) + ld s10, 96(a0) + ld s11, 104(a0) + fld fs0, 112(a0) + fld fs1, 120(a0) + fld fs2, 128(a0) + fld fs3, 136(a0) + fld fs4, 144(a0) + fld fs5, 152(a0) + fld fs6, 160(a0) + fld fs7, 168(a0) + fld fs8, 176(a0) + fld fs9, 184(a0) + fld fs10, 192(a0) + fld fs11, 200(a0) + ld ra, 8(a0) + ret + ` + : // no outputs + : // a0=newCtx, a1=oldCtx (@naked, ABI handles register assignment) + : "memory"; + } + } + } + else + { + pragma(inline, false) + extern(C) void co_swap(cothread_t newCtx, cothread_t oldCtx) @naked + { + asm nothrow @nogc + { + ` + sd sp, 0(a1) + sd ra, 8(a1) + sd s0, 16(a1) + sd s1, 24(a1) + sd s2, 32(a1) + sd s3, 40(a1) + sd s4, 48(a1) + sd s5, 56(a1) + sd s6, 64(a1) + sd s7, 72(a1) + sd s8, 80(a1) + sd s9, 88(a1) + sd s10, 96(a1) + sd s11, 104(a1) + ld sp, 0(a0) + ld s0, 16(a0) + ld s1, 24(a0) + ld s2, 32(a0) + ld s3, 40(a0) + ld s4, 48(a0) + ld s5, 56(a0) + ld s6, 64(a0) + ld s7, 72(a0) + ld s8, 80(a0) + ld s9, 88(a0) + ld s10, 96(a0) + ld s11, 104(a0) + ld ra, 8(a0) + ret + ` + : // no outputs + : // a0=newCtx, a1=oldCtx (@naked, ABI handles register assignment) + : "memory"; + } + } + } + } + else version (RISCV32) + { + // SaveStateLen must be defined before co_active_buffer (line 444) + version (RISCV32E) + enum SaveStateLen = 4; // RV32E: sp, ra, s0-s1 + else version (D_HardFloat) + enum SaveStateLen = 26; // RV32I: 14 integer + 12 FP + else + enum SaveStateLen = 14; // RV32I: sp, ra, s0-s11 + + version (RISCV32E) + { + void co_init_stack(void* base, void* top, coentry_t entry) + { + assert(is_aligned!16(base) && is_aligned!16(top), "Stack must be aligned to 16 bytes"); + + void** p = cast(void**)base; + p[0] = cast(void*)top; // starting sp + p[1] = entry; // starting ra (entry point) + p[2] = cast(void*)top; // starting s0 (frame pointer) + } + + pragma(inline, false) + extern(C) void co_swap(cothread_t newCtx, cothread_t oldCtx) @naked + { + asm nothrow @nogc + { + ` + sw sp, 0(a1) + sw ra, 4(a1) + sw s0, 8(a1) + sw s1, 12(a1) + lw sp, 0(a0) + lw s0, 8(a0) + lw s1, 12(a0) + lw ra, 4(a0) + ret + ` + : // no outputs + : // a0=newCtx, a1=oldCtx (@naked, ABI handles register assignment) + : "memory"; + } + } + } + else + { + + void co_init_stack(void* base, void* top, coentry_t entry) + { + assert(is_aligned!16(base) && is_aligned!16(top), "Stack must be aligned to 16 bytes"); + + void** p = cast(void**)base; + p[0] = cast(void*)top; // starting sp + p[1] = entry; // starting ra (entry point) + p[2] = cast(void*)top; // starting s0 (frame pointer) + } + + version (D_HardFloat) + { + pragma(inline, false) + extern(C) void co_swap(cothread_t newCtx, cothread_t oldCtx) @naked + { + asm nothrow @nogc + { + ` + sw sp, 0(a1) + sw ra, 4(a1) + sw s0, 8(a1) + sw s1, 12(a1) + sw s2, 16(a1) + sw s3, 20(a1) + sw s4, 24(a1) + sw s5, 28(a1) + sw s6, 32(a1) + sw s7, 36(a1) + sw s8, 40(a1) + sw s9, 44(a1) + sw s10, 48(a1) + sw s11, 52(a1) + fsw fs0, 56(a1) + fsw fs1, 60(a1) + fsw fs2, 64(a1) + fsw fs3, 68(a1) + fsw fs4, 72(a1) + fsw fs5, 76(a1) + fsw fs6, 80(a1) + fsw fs7, 84(a1) + fsw fs8, 88(a1) + fsw fs9, 92(a1) + fsw fs10, 96(a1) + fsw fs11, 100(a1) + lw sp, 0(a0) + lw s0, 8(a0) + lw s1, 12(a0) + lw s2, 16(a0) + lw s3, 20(a0) + lw s4, 24(a0) + lw s5, 28(a0) + lw s6, 32(a0) + lw s7, 36(a0) + lw s8, 40(a0) + lw s9, 44(a0) + lw s10, 48(a0) + lw s11, 52(a0) + flw fs0, 56(a0) + flw fs1, 60(a0) + flw fs2, 64(a0) + flw fs3, 68(a0) + flw fs4, 72(a0) + flw fs5, 76(a0) + flw fs6, 80(a0) + flw fs7, 84(a0) + flw fs8, 88(a0) + flw fs9, 92(a0) + flw fs10, 96(a0) + flw fs11, 100(a0) + lw ra, 4(a0) + ret + ` + : // no outputs + : // a0=newCtx, a1=oldCtx (@naked, ABI handles register assignment) + : "memory"; + } + } + } + else + { + pragma(inline, false) + extern(C) void co_swap(cothread_t newCtx, cothread_t oldCtx) @naked + { + asm nothrow @nogc + { + ` + sw sp, 0(a1) + sw ra, 4(a1) + sw s0, 8(a1) + sw s1, 12(a1) + sw s2, 16(a1) + sw s3, 20(a1) + sw s4, 24(a1) + sw s5, 28(a1) + sw s6, 32(a1) + sw s7, 36(a1) + sw s8, 40(a1) + sw s9, 44(a1) + sw s10, 48(a1) + sw s11, 52(a1) + lw sp, 0(a0) + lw s0, 8(a0) + lw s1, 12(a0) + lw s2, 16(a0) + lw s3, 20(a0) + lw s4, 24(a0) + lw s5, 28(a0) + lw s6, 32(a0) + lw s7, 36(a0) + lw s8, 40(a0) + lw s9, 44(a0) + lw s10, 48(a0) + lw s11, 52(a0) + lw ra, 4(a0) + ret + ` + : // no outputs + : // a0=newCtx, a1=oldCtx (@naked, ABI handles register assignment) + : "memory"; + } + } + } + } + } + else version (Xtensa) + { + // Xtensa call0 ABI: callee-saved a0 (return addr), a1 (sp), a12-a15 + // TODO: if windowed ABI is used, need register window spill (entry/retw) + enum SaveStateLen = 6; + + void co_init_stack(void* base, void* top, coentry_t entry) + { + assert(is_aligned!16(base) && is_aligned!16(top), "Stack must be aligned to 16 bytes"); + + void** p = cast(void**)base; + p[0] = cast(void*)top; // starting a1 (sp) + p[1] = entry; // starting a0 (return address) + } + + pragma(inline, false) + extern(C) void co_swap(cothread_t newCtx, cothread_t oldCtx) @naked + { + asm nothrow @nogc + { + ` + s32i a1, a3, 0 + s32i a0, a3, 4 + s32i a12, a3, 8 + s32i a13, a3, 12 + s32i a14, a3, 16 + s32i a15, a3, 20 + l32i a1, a2, 0 + l32i a12, a2, 8 + l32i a13, a2, 12 + l32i a14, a2, 16 + l32i a15, a2, 20 + l32i a0, a2, 4 + ret + ` + : // no outputs + : // a2=newCtx, a3=oldCtx (@naked, call0 ABI) + : "memory"; + } + } + } else static assert(false, "TODO: implement for other architectures!"); } diff --git a/src/urt/file.d b/src/urt/file.d index e7ec4d3..94c3af4 100644 --- a/src/urt/file.d +++ b/src/urt/file.d @@ -12,10 +12,10 @@ alias SystemTime = void; version(Windows) { - import core.sys.windows.winbase; - import core.sys.windows.windows; - import core.sys.windows.windef : MAX_PATH; - import core.sys.windows.winnt; + import urt.internal.sys.windows.winbase; + import urt.internal.sys.windows; + import urt.internal.sys.windows.windef : MAX_PATH; + import urt.internal.sys.windows.winnt; import urt.string : twstringz; // TODO: remove this when LDC/GDC are up to date... @@ -26,12 +26,8 @@ version(Windows) } else version (Posix) { - import core.stdc.errno; - import core.sys.posix.dirent; - import core.sys.posix.fcntl; - import core.sys.posix.stdlib; - import core.sys.posix.sys.stat; - import core.sys.posix.unistd; + import urt.internal.sys.posix; + import urt.internal.stdc.errno; import urt.mem.temp : tconcat; import urt.string : tstringz; @@ -43,6 +39,11 @@ else version (Posix) enum POSIX_FADV_RANDOM = 1; enum POSIX_FADV_SEQUENTIAL = 2; extern(C) int posix_fadvise(int fd, off_t offset, off_t len, int advice) nothrow @nogc; + extern(C) int rename(scope const char*, scope const char*) nothrow @nogc; +} +else version (FreeStanding) +{ + // No filesystem on bare-metal } else { @@ -107,8 +108,10 @@ struct File void* handle = INVALID_HANDLE_VALUE; else version (Posix) int fd = -1; + else version (FreeStanding) + int fd = -1; else - static assert(0, "Not implemented"); + static assert(0, "File: not implemented for this platform"); } bool file_exists(const(char)[] path) @@ -120,12 +123,11 @@ bool file_exists(const(char)[] path) } else version (Posix) { - import core.sys.posix.sys.stat; stat_t st; return stat(path.tstringz, &st) == 0 && S_ISREG(st.st_mode); } else - static assert(0, "Not implemented"); + return false; } Result delete_file(const(char)[] path) @@ -133,17 +135,17 @@ Result delete_file(const(char)[] path) version (Windows) { if (!DeleteFileW(path.twstringz)) - return Win32Result(GetLastError()); + return getlasterror_result(); } else version (Posix) { if (unlink(path.tstringz) == -1) - return PosixResult(errno); + return errno_result(); } else - static assert(0, "Not implemented"); + return InternalResult.unsupported; // no filesystem on this platform - return Result.Success; + return Result.success; } Result rename_file(const(char)[] oldPath, const(char)[] newPath) @@ -151,18 +153,17 @@ Result rename_file(const(char)[] oldPath, const(char)[] newPath) version (Windows) { if (!MoveFileW(oldPath.twstringz, newPath.twstringz)) - return Win32Result(GetLastError()); + return getlasterror_result(); } else version (Posix) { - import core.sys.posix.stdio; - if (int result = rename(oldPath.tstringz, newPath.tstringz)!= 0) - return PosixResult(result); + if (int result = rename(oldPath.tstringz, newPath.tstringz) != 0) + return posix_result(result); } else - static assert(0, "Not implemented"); + return InternalResult.unsupported; // no filesystem on this platform - return Result.Success; + return Result.success; } Result copy_file(const(char)[] oldPath, const(char)[] newPath, bool overwriteExisting = false) @@ -170,7 +171,7 @@ Result copy_file(const(char)[] oldPath, const(char)[] newPath, bool overwriteExi version (Windows) { if (!CopyFileW(oldPath.twstringz, newPath.twstringz, !overwriteExisting)) - return Win32Result(GetLastError()); + return getlasterror_result(); } else version (Posix) { @@ -178,9 +179,9 @@ Result copy_file(const(char)[] oldPath, const(char)[] newPath, bool overwriteExi assert(false); } else - static assert(0, "Not implemented"); + return InternalResult.unsupported; // no filesystem on this platform - return Result.Success; + return Result.success; } Result get_path(ref const File file, ref char[] buffer) @@ -193,11 +194,11 @@ Result get_path(ref const File file, ref char[] buffer) DWORD dwPathLen = tmp.length - 1; DWORD result = GetFinalPathNameByHandleW(cast(HANDLE)file.handle, tmp.ptr, dwPathLen, FILE_NAME_OPENED); if (result == 0 || result > dwPathLen) - return Win32Result(GetLastError()); + return getlasterror_result(); - size_t pathLen = tmp[0..result].uniConvert(buffer); + size_t pathLen = tmp[0..result].uni_convert(buffer); if (!pathLen) - return InternalResult(InternalCode.BufferTooSmall); + return InternalResult.buffer_too_small; if (buffer.length >= 4 && buffer[0..4] == `\\?\`) buffer = buffer[4..pathLen]; else @@ -210,10 +211,10 @@ Result get_path(ref const File file, ref char[] buffer) char[PATH_MAX] src = void; int r = fcntl(file.fd, F_GETPATH, src.ptr); if (r == -1) - return PosixResult(errno); + return errno_result(); size_t l = strlen(src.ptr); if (l > buffer.length) - return InternalResult(InternalCode.BufferTooSmall); + return InternalResult.buffer_too_small; buffer[0..l] = src[0..l]; buffer = buffer[0..l]; } @@ -221,18 +222,18 @@ Result get_path(ref const File file, ref char[] buffer) { ptrdiff_t r = readlink(tconcat("/proc/self/fd/", file.fd, '\0').ptr, buffer.ptr, buffer.length); if (r == -1) - return PosixResult(errno); + return errno_result(); if (r == buffer.length) { // TODO: if r == buffer.length, truncation MAY have occurred, but also maybe not... // is there any way to fix this? for now, we'll just assume it did and return an error - return InternalResult(InternalCode.BufferTooSmall); + return InternalResult.buffer_too_small; } buffer = buffer[0..r]; } else - static assert(0, "Not implemented"); - return Result.Success; + return InternalResult.unsupported; // no filesystem on this platform + return Result.success; } Result set_file_times(ref File file, const SystemTime* createTime, const SystemTime* accessTime, const SystemTime* writeTime); @@ -243,7 +244,7 @@ Result get_file_attributes(const(char)[] path, out FileAttributes outAttributes) { WIN32_FILE_ATTRIBUTE_DATA attrData = void; if (!GetFileAttributesExW(path.twstringz, GET_FILEEX_INFO_LEVELS.GetFileExInfoStandard, &attrData)) - return Win32Result(GetLastError()); + return getlasterror_result(); outAttributes.attributes = FileAttributeFlag.None; if ((attrData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) == FILE_ATTRIBUTE_HIDDEN) @@ -268,9 +269,9 @@ Result get_file_attributes(const(char)[] path, out FileAttributes outAttributes) assert(false); } else - static assert(0, "Not implemented"); + return InternalResult.unsupported; // no filesystem on this platform - return Result.Success; + return Result.success; } Result get_attributes(ref const File file, out FileAttributes outAttributes) @@ -282,9 +283,9 @@ Result get_attributes(ref const File file, out FileAttributes outAttributes) FILE_BASIC_INFO basicInfo = void; FILE_STANDARD_INFO standardInfo = void; if (!GetFileInformationByHandleEx(cast(HANDLE)file.handle, FILE_INFO_BY_HANDLE_CLASS.FileBasicInfo, &basicInfo, FILE_BASIC_INFO.sizeof)) - return Win32Result(GetLastError()); + return getlasterror_result(); if (!GetFileInformationByHandleEx(cast(HANDLE)file.handle, FILE_INFO_BY_HANDLE_CLASS.FileStandardInfo, &standardInfo, FILE_STANDARD_INFO.sizeof)) - return Win32Result(GetLastError()); + return getlasterror_result(); outAttributes.attributes = FileAttributeFlag.None; if ((basicInfo.FileAttributes & FILE_ATTRIBUTE_HIDDEN) == FILE_ATTRIBUTE_HIDDEN) @@ -303,7 +304,7 @@ Result get_attributes(ref const File file, out FileAttributes outAttributes) outAttributes.accessTime = SysTime(basicInfo.LastAccessTime.QuadPart); outAttributes.writeTime = SysTime(basicInfo.LastWriteTime.QuadPart); - return Result.Success; + return Result.success; +/ } else version (Posix) @@ -312,18 +313,21 @@ Result get_attributes(ref const File file, out FileAttributes outAttributes) assert(false); } else - static assert(0, "Not implemented"); + return InternalResult.unsupported; // no filesystem on this platform - return InternalResult(InternalCode.Unsupported); + return InternalResult.unsupported; } void[] load_file(const(char)[] path, NoGCAllocator allocator = defaultAllocator()) { File f; Result r = f.open(path, FileOpenMode.ReadExisting); - if (!r && r.get_FileResult == FileResult.NotFound) - return null; - assert(r, "TODO: handle error"); + if (!r) + { + if (r.file_result == FileResult.NotFound) + return null; + return null; // TODO: are there any errors we can handle? + } ulong size = f.get_size(); assert(size <= size_t.max, "File is too large"); void[] buffer = allocator.alloc(cast(size_t)size); @@ -334,6 +338,47 @@ void[] load_file(const(char)[] path, NoGCAllocator allocator = defaultAllocator( return buffer[0..bytesRead]; } +Result save_file(const(char)[] path, const(void)[] data) +{ + File f; + Result r = f.open(path, FileOpenMode.WriteTruncate); + if (!r) + return r; + size_t written; + r = f.write(data, written); + f.close(); + if (!r) + return r; + if (written != data.length) + return InternalResult.failed; + return Result.success; +} + +Result create_directory(const(char)[] path) +{ + Result r; + version (Windows) + { + if (CreateDirectoryW(path.twstringz, null)) + return Result.success; + r = getlasterror_result(); + } + else version (Posix) + { + if (!urt.internal.sys.posix.mkdir(tconcat(path, "\0").ptr, 493 /* 0755 */) != 0) + return Result.success; + r = errno_result(); + } + else + { + return InternalResult.unsupported; // no filesystem on this platform + } + + if (r == InternalResult.already_exists) + return Result.success; + return r; +} + Result open(ref File file, const(char)[] path, FileOpenMode mode, FileOpenFlags openFlags = FileOpenFlags.None) { version (Windows) @@ -379,7 +424,7 @@ Result open(ref File file, const(char)[] path, FileOpenMode mode, FileOpenFlags dwCreationDisposition = OPEN_ALWAYS; break; default: - return InternalResult(InternalCode.InvalidParameter); + return InternalResult.invalid_parameter; } uint dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL; @@ -392,7 +437,7 @@ Result open(ref File file, const(char)[] path, FileOpenMode mode, FileOpenFlags file.handle = CreateFileW(path.twstringz, dwDesiredAccess, dwShareMode, null, dwCreationDisposition, dwFlagsAndAttributes, null); if (file.handle == INVALID_HANDLE_VALUE) - return Win32Result(GetLastError()); + return getlasterror_result(); if (mode == FileOpenMode.WriteAppend || mode == FileOpenMode.ReadWriteAppend) SetFilePointer(file.handle, 0, null, FILE_END); @@ -429,7 +474,7 @@ Result open(ref File file, const(char)[] path, FileOpenMode mode, FileOpenFlags flags = O_RDWR | O_APPEND | O_CREAT; break; default: - return InternalResult(InternalCode.InvalidParameter); + return InternalResult.invalid_parameter; } flags |= O_CLOEXEC; @@ -439,9 +484,9 @@ Result open(ref File file, const(char)[] path, FileOpenMode mode, FileOpenFlags flags |= O_DIRECT; } - int fd = core.sys.posix.fcntl.open(path.tstringz, flags, 0b110_110_110); + int fd = urt.internal.sys.posix.open(path.tstringz, flags, 0b110_110_110); if (fd < 0) - return PosixResult(errno); + return errno_result(); file.fd = fd; version (Darwin) { @@ -465,9 +510,9 @@ Result open(ref File file, const(char)[] path, FileOpenMode mode, FileOpenFlags lseek(file.fd, 0, SEEK_END); } else - static assert(0, "Not implemented"); + return InternalResult.unsupported; // no filesystem on this platform - return Result.Success; + return Result.success; } bool is_open(ref const File file) @@ -477,7 +522,7 @@ bool is_open(ref const File file) else version (Posix) return file.fd != -1; else - static assert(0, "Not implemented"); + return false; } void close(ref File file) @@ -493,11 +538,9 @@ void close(ref File file) { if (file.fd == -1) return; - core.sys.posix.unistd.close(file.fd); + urt.internal.sys.posix.close(file.fd); file.fd = -1; } - else - static assert(0, "Not implemented"); } ulong get_size(ref const File file) @@ -517,7 +560,7 @@ ulong get_size(ref const File file) return fs.st_size; } else - static assert(0, "Not implemented"); + return 0; } Result set_size(ref File file, ulong size) @@ -532,7 +575,7 @@ Result set_size(ref File file, ulong size) if (size > curFileSize) { if (!file.set_pos(curFileSize)) - return Win32Result(GetLastError()); + return getlasterror_result(); // zero-fill char[4096] buf = void; @@ -555,19 +598,19 @@ Result set_size(ref File file, ulong size) else { if (!file.set_pos(size)) - return Win32Result(GetLastError()); + return getlasterror_result(); if (!SetEndOfFile(file.handle)) - return Win32Result(GetLastError()); + return getlasterror_result(); } } else version (Posix) { if (ftruncate(file.fd, size)) - return PosixResult(errno); + return errno_result(); } else - static assert(0, "Not implemented"); - return Result.Success; + return InternalResult.unsupported; // no filesystem on this platform + return Result.success; } ulong get_pos(ref const File file) @@ -583,7 +626,7 @@ ulong get_pos(ref const File file) else version (Posix) return lseek(file.fd, 0, SEEK_CUR); else - static assert(0, "Not implemented"); + return 0; } Result set_pos(ref File file, ulong offset) @@ -593,17 +636,17 @@ Result set_pos(ref File file, ulong offset) LARGE_INTEGER liDistanceToMove = void; liDistanceToMove.QuadPart = offset; if (!SetFilePointerEx(file.handle, liDistanceToMove, null, FILE_BEGIN)) - return Win32Result(GetLastError()); + return getlasterror_result(); } else version (Posix) { off_t rc = lseek(file.fd, offset, SEEK_SET); if (rc < 0) - return PosixResult(errno); + return errno_result(); } else - static assert(0, "Not implemented"); - return Result.Success; + return InternalResult.unsupported; // no filesystem on this platform + return Result.success; } Result read(ref File file, void[] buffer, out size_t bytesRead) @@ -616,20 +659,20 @@ Result read(ref File file, void[] buffer, out size_t bytesRead) if (!ReadFile(file.handle, buffer.ptr, cast(uint)min(buffer.length, uint.max), &dwBytesRead, null)) { DWORD lastError = GetLastError(); - return (lastError == ERROR_BROKEN_PIPE) ? Result.Success : Win32Result(lastError); + return (lastError == ERROR_BROKEN_PIPE) ? Result.success : win32_result(lastError); } bytesRead = dwBytesRead; } else version (Posix) { - ptrdiff_t n = core.sys.posix.unistd.read(file.fd, buffer.ptr, buffer.length); + ptrdiff_t n = urt.internal.sys.posix.read(file.fd, buffer.ptr, buffer.length); if (n < 0) - return PosixResult(errno); + return errno_result(); bytesRead = n; } else - static assert(0, "Not implemented"); - return Result.Success; + return InternalResult.unsupported; // no filesystem on this platform + return Result.success; } Result read_at(ref File file, void[] buffer, ulong offset, out size_t bytesRead) @@ -637,7 +680,7 @@ Result read_at(ref File file, void[] buffer, ulong offset, out size_t bytesRead) version (Windows) { if (buffer.length > DWORD.max) - return InternalResult(InternalCode.InvalidParameter); + return InternalResult.invalid_parameter; OVERLAPPED o; o.Offset = cast(DWORD)offset; @@ -646,8 +689,9 @@ Result read_at(ref File file, void[] buffer, ulong offset, out size_t bytesRead) DWORD dwBytesRead; if (!ReadFile(file.handle, buffer.ptr, cast(DWORD)buffer.length, &dwBytesRead, &o)) { - if (GetLastError() != ERROR_HANDLE_EOF) - return Win32Result(GetLastError()); + Result error = getlasterror_result(); + if (error.system_code != ERROR_HANDLE_EOF) + return error; } bytesRead = dwBytesRead; } @@ -655,12 +699,12 @@ Result read_at(ref File file, void[] buffer, ulong offset, out size_t bytesRead) { ssize_t n = pread(file.fd, buffer.ptr, buffer.length, offset); if (n < 0) - return PosixResult(errno); + return errno_result(); bytesRead = n; } else - static assert(0, "Not implemented"); - return Result.Success; + return InternalResult.unsupported; // no filesystem on this platform + return Result.success; } Result write(ref File file, const(void)[] data, out size_t bytesWritten) @@ -669,19 +713,19 @@ Result write(ref File file, const(void)[] data, out size_t bytesWritten) { DWORD dwBytesWritten; if (!WriteFile(file.handle, data.ptr, cast(uint)data.length, &dwBytesWritten, null)) - return Win32Result(GetLastError()); + return getlasterror_result(); bytesWritten = dwBytesWritten; } else version (Posix) { - ptrdiff_t n = core.sys.posix.unistd.write(file.fd, data.ptr, data.length); + ptrdiff_t n = urt.internal.sys.posix.write(file.fd, data.ptr, data.length); if (n < 0) - return PosixResult(errno); + return errno_result(); bytesWritten = n; } else - static assert(0, "Not implemented"); - return Result.Success; + return InternalResult.unsupported; // no filesystem on this platform + return Result.success; } Result write_at(ref File file, const(void)[] data, ulong offset, out size_t bytesWritten) @@ -689,7 +733,7 @@ Result write_at(ref File file, const(void)[] data, ulong offset, out size_t byte version (Windows) { if (data.length > DWORD.max) - return InternalResult(InternalCode.InvalidParameter); + return InternalResult.invalid_parameter; OVERLAPPED o; o.Offset = cast(DWORD)offset; @@ -697,19 +741,19 @@ Result write_at(ref File file, const(void)[] data, ulong offset, out size_t byte DWORD dwBytesWritten; if (!WriteFile(file.handle, data.ptr, cast(DWORD)data.length, &dwBytesWritten, &o)) - return Win32Result(GetLastError()); + return getlasterror_result(); bytesWritten = dwBytesWritten; } else version (Posix) { ptrdiff_t n = pwrite(file.fd, data.ptr, data.length, offset); if (n < 0) - return PosixResult(errno); + return errno_result(); bytesWritten = n; } else - static assert(0, "Not implemented"); - return Result.Success; + return InternalResult.unsupported; // no filesystem on this platform + return Result.success; } Result flush(ref File file) @@ -717,23 +761,23 @@ Result flush(ref File file) version (Windows) { if (!FlushFileBuffers(file.handle)) - return Win32Result(GetLastError()); + return getlasterror_result(); } else version (Posix) { if (fsync(file.fd)) - return PosixResult(errno); + return errno_result(); } else - static assert(0, "Not implemented"); - return Result.Success; + return InternalResult.unsupported; // no filesystem on this platform + return Result.success; } -FileResult get_FileResult(Result result) +FileResult file_result(Result result) { version (Windows) { - switch (result.systemCode) + switch (result.system_code) { case ERROR_SUCCESS: return FileResult.Success; case ERROR_DISK_FULL: return FileResult.DiskFull; @@ -748,7 +792,7 @@ FileResult get_FileResult(Result result) else version (Posix) { static assert(EAGAIN == EWOULDBLOCK, "Expect EGAIN and EWOULDBLOCK are the same value"); - switch (result.systemCode) + switch (result.system_code) { case 0: return FileResult.Success; case ENOSPC: return FileResult.DiskFull; @@ -760,7 +804,7 @@ FileResult get_FileResult(Result result) } } else - static assert(0, "Not implemented"); + return FileResult.Failure; // no filesystem on this platform } Result get_temp_filename(ref char[] buffer, const(char)[] dstDir, const(char)[] prefix) @@ -771,46 +815,36 @@ Result get_temp_filename(ref char[] buffer, const(char)[] dstDir, const(char)[] wchar[MAX_PATH] tmp = void; if (!GetTempFileNameW(dstDir.twstringz, prefix.twstringz, 0, tmp.ptr)) - return Win32Result(GetLastError()); + return getlasterror_result(); size_t resLen = wcslen(tmp.ptr); - resLen = tmp[((dstDir.length == 0 && tmp[0] == '\\') ? 1 : 0)..resLen].uniConvert(buffer); + resLen = tmp[((dstDir.length == 0 && tmp[0] == '\\') ? 1 : 0)..resLen].uni_convert(buffer); if (resLen == 0) { DeleteFileW(tmp.ptr); - return InternalResult(InternalCode.BufferTooSmall); + return InternalResult.buffer_too_small; } buffer = buffer[0 .. resLen]; } else version (Posix) { // Construct a format string which will be the supplied dir with prefix and 8 generated random characters - char[] fn = tconcat(dstDir, (dstDir.length && dstDir[$-1] != '/') ? "/" : "", prefix, "XXXXXX\0"); + char[] fn = cast(char[])tconcat(dstDir, (dstDir.length && dstDir[$-1] != '/') ? "/" : "", prefix, "XXXXXX\0"); File file; file.fd = mkstemp(fn.ptr); if (file.fd == -1) - return PosixResult(errno); + return errno_result(); Result r = get_path(file, buffer); - core.sys.posix.unistd.close(file.fd); + urt.internal.sys.posix.close(file.fd); return r; } else - static assert(0, "Not implemented"); - return Result.Success; -} - -version (Windows) -{ - Result Win32Result(uint err) - => Result(err); -} -else version (Posix) -{ - Result PosixResult(int err) - => Result(err); + return InternalResult.unsupported; // no filesystem on this platform + return Result.success; } -unittest +version (FreeStanding) {} +else unittest { import urt.string; @@ -831,4 +865,32 @@ unittest assert(!file.is_open); assert(filename.delete_file()); + + // create a temp file with known content + filename = buffer[]; + assert(get_temp_filename(filename, "", "stat_test")); + + assert(file.open(filename, FileOpenMode.WriteTruncate)); + + // write exactly 42 bytes + ubyte[42] data; + size_t written; + assert(file.write(data[], written)); + assert(written == 42); + + // get_size exercises fstat + st_size + assert(file.get_size() == 42); + + // set_size exercises ftruncate, then fstat again + assert(file.set_size(100)); + assert(file.get_size() == 100); + + file.close(); + + // file_exists exercises stat + S_ISREG + st_mode + assert(file_exists(filename)); + + // clean up and verify + assert(filename.delete_file()); + assert(!file_exists(filename)); } diff --git a/src/urt/format/json.d b/src/urt/format/json.d index 544fbdf..3067f76 100644 --- a/src/urt/format/json.d +++ b/src/urt/format/json.d @@ -5,6 +5,7 @@ import urt.conv; import urt.lifetime; import urt.kvp; import urt.mem.allocator; +import urt.si.unit; import urt.string; import urt.string.format; @@ -13,38 +14,19 @@ public import urt.variant; nothrow @nogc: -Variant parseJson(const(char)[] text) +Variant parse_json(const(char)[] text) { - return parseNode(text); + return parse_node(text); } -ptrdiff_t writeJson(ref const Variant val, char[] buffer, bool dense = false, uint level = 0, uint indent = 2) +ptrdiff_t write_json(ref const Variant val, char[] buffer, bool dense = false, uint level = 0, uint indent = 2) { final switch (val.type) { case Variant.Type.Null: - if (!buffer.ptr) - return 4; - if (buffer.length < 4) - return -1; - buffer[0 .. 4] = "null"; - return 4; - - case Variant.Type.False: - if (!buffer.ptr) - return 5; - if (buffer.length < 5) - return -1; - buffer[0 .. 5] = "false"; - return 5; - case Variant.Type.True: - if (!buffer.ptr) - return 4; - if (buffer.length < 4) - return -1; - buffer[0 .. 4] = "true"; - return 4; + case Variant.Type.False: + return val.toString(buffer, null, null); case Variant.Type.Map: case Variant.Type.Array: @@ -52,6 +34,8 @@ ptrdiff_t writeJson(ref const Variant val, char[] buffer, bool dense = false, ui { ptrdiff_t len; size_t itemCount = val.type == Variant.Type.Map ? val.count /2 : val.count; + if (itemCount == 0) + return 2; // "[]" / "{}" if (dense) { // open/close brackets + comma-space separators @@ -76,9 +60,9 @@ ptrdiff_t writeJson(ref const Variant val, char[] buffer, bool dense = false, ui int inc = val.type == Variant.Type.Map ? 2 : 1; for (uint i = 0; i < val.count; i += inc) { - len += writeJson(val.value.n[i], null, dense, level + indent, indent); + len += write_json(val.value.n[i], null, dense, level + indent, indent); if (val.type == Variant.Type.Map) - len += writeJson(val.value.n[i + 1], null, dense, level + indent, indent); + len += write_json(val.value.n[i + 1], null, dense, level + indent, indent); } return len; } @@ -86,6 +70,12 @@ ptrdiff_t writeJson(ref const Variant val, char[] buffer, bool dense = false, ui ptrdiff_t written = 0; if (!buffer.append(written, val.type == Variant.Type.Map ? '{' : '[')) return -1; + if (val.count == 0) + { + if (!buffer.append(written, val.type == Variant.Type.Map ? '}' : ']')) + return -1; + return written; + } int inc = val.type == Variant.Type.Map ? 2 : 1; for (uint i = 0; i < val.count; i += inc) { @@ -99,7 +89,7 @@ ptrdiff_t writeJson(ref const Variant val, char[] buffer, bool dense = false, ui if (!buffer.newline(written, level + indent)) return -1; } - ptrdiff_t len = writeJson(val.value.n[i], buffer[written .. $], dense, level + indent, indent); + ptrdiff_t len = write_json(val.value.n[i], buffer[written .. $], dense, level + indent, indent); if (len < 0) return -1; written += len; @@ -107,7 +97,7 @@ ptrdiff_t writeJson(ref const Variant val, char[] buffer, bool dense = false, ui { if (!buffer.append(written, ':') || (!dense && !buffer.append(written, ' '))) return -1; - len = writeJson(val.value.n[i + 1], buffer[written .. $], dense, level + indent, indent); + len = write_json(val.value.n[i + 1], buffer[written .. $], dense, level + indent, indent); if (len < 0) return -1; written += len; @@ -119,40 +109,159 @@ ptrdiff_t writeJson(ref const Variant val, char[] buffer, bool dense = false, ui return -1; return written; - case Variant.Type.String: + case Variant.Type.Buffer: + if (!val.isString) + { + import urt.encoding; + + // emit raw buffer as base64 + const data = val.asBuffer(); + size_t enc_len = base64_encode_length(data.length); + if (buffer.ptr) + { + if (buffer.length < 2 + enc_len) + return -1; + buffer[0] = '"'; + ptrdiff_t r = data.base64_encode(buffer[1 .. 1 + enc_len]); + if (r != enc_len) + return -2; + buffer[1 + enc_len] = '"'; + } + return 2 + enc_len; + } + const char[] s = val.asString(); - if (buffer.ptr) + + if (!buffer.ptr) { - if (buffer.length < s.length + 2) + size_t len = 0; + foreach (c; s) + { + if (c < 0x20) + { + if (c == '\n' || c == '\r' || c == '\t' || c == '\b' || c == '\f') + len += 2; + else + len += 6; + } + else if (c == '"' || c == '\\') + len += 2; + else + len += 1; + } + return len + 2; + } + + if (buffer.length < s.length + 2) + return -1; + + buffer[0] = '"'; + // escape strings + size_t offset = 1; + foreach (c; s) + { + char sub = void; + if (c < 0x20) + { + if (c == '\n') + sub = 'n'; + else if (c == '\r') + sub = 'r'; + else if (c == '\t') + sub = 't'; + else if (c == '\b') + sub = 'b'; + else if (c == '\f') + sub = 'f'; + else + { + if (buffer.length < offset + 7) + return -1; + buffer[offset .. offset + 4] = "\\u00"; + offset += 4; + buffer[offset++] = hex_digits[c >> 4]; + buffer[offset++] = hex_digits[c & 0xF]; + continue; + } + } + else if (c == '"' || c == '\\') + sub = c; + else + { + if (buffer.length < offset + 2) + return -1; + buffer[offset++] = c; + continue; + } + + // write escape sequence + if (buffer.length < offset + 3) return -1; - buffer[0] = '"'; - buffer[1 .. 1 + s.length] = s[]; - buffer[1 + s.length] = '"'; + buffer[offset++] = '\\'; + buffer[offset++] = sub; } - return s.length + 2; + buffer[offset++] = '"'; + return offset; case Variant.Type.Number: import urt.conv; - if (val.isQuantity()) - assert(false, "TODO: implement quantity formatting for JSON"); + char[] number_buffer = buffer; + bool is_q = val.isQuantity(); + size_t u_len = 0; + float pre_scale = void; + if (is_q) + { + if (buffer.ptr) + { + if (buffer.length < 15) // {"q":x,"u":"x"} + return -1; + number_buffer = buffer[5..$]; + } + u_len = val.get_unit.format_unit(null, pre_scale, true); // TODO: should we give false (force pre-scale) here? + if (u_len <= 0) + return u_len; + + // TODO: apply the prescale... (if we change the above bool to false) + } + size_t len = 0; if (val.isDouble()) - return val.asDouble().formatFloat(buffer); + len += val.asDouble().format_float(number_buffer); + else if (val.isUlong()) + len += val.asUlong().format_uint(number_buffer); + else + len += val.asLong().format_int(number_buffer); + if (len <= 0 || !is_q) + return len; - // TODO: parse args? - //format + size_t result_len = 5 + len + 6 + u_len + 2; // {"q":x,"u":"x"} + if (!buffer.ptr) + return result_len; + if (buffer.length < result_len) + return -1; - if (val.isUlong()) - return val.asUlong().formatUint(buffer); - return val.asLong().formatInt(buffer); + size_t offset = 5 + len; + buffer[0..5] = "{\"q\":"; + buffer[offset..offset + 6] = ",\"u\":\""; + val.get_unit.format_unit(buffer[offset + 6..$], pre_scale, true); // TODO: should we give false (force pre-scale) here? + buffer[result_len-2..result_len] = "\"}"; + return result_len; case Variant.Type.User: - // in order to text-ify a user type, we probably need a hash table of text-ify functions, which - // we can lookup by the typeId... - // the tricky bit is, we need to init the table based on instantiations of constructors for each T... - // maybe an object with static constructor for each instantiation, which will hook it into the table? - assert(false, "TODO..."); + // for custom types, we'll use the type's regular string format into a json string + if (!buffer.ptr) + return val.toString(null, null, null) + 2; + if (buffer.length < 1) + return -1; + buffer[0] = '\"'; + ptrdiff_t len = val.toString(buffer[1 .. $], null, null); + if (len < 0) + return len; + if (buffer.length < len + 2) + return -1; + buffer[1 + len] = '\"'; + return len + 2; } } @@ -179,7 +288,7 @@ unittest ] }`; - Variant root = parseJson(doc); + Variant root = parse_json(doc); // check the data was parsed correctly... assert(root["nothing"].isNull); @@ -197,18 +306,18 @@ unittest char[1024] buffer = void; // check the dense writer... - assert(root["children"].writeJson(null, true) == 61); - assert(root["children"].writeJson(buffer, true) == 61); + assert(root["children"].write_json(null, true) == 61); + assert(root["children"].write_json(buffer, true) == 61); assert(buffer[0 .. 61] == `[{"name":"Jane Doe", "age":12}, {"name":"Jack Doe", "age":8}]`); // check the expanded writer - assert(root["children"].writeJson(null, false, 0, 1) == 83); - assert(root["children"].writeJson(buffer, false, 0, 1) == 83); + assert(root["children"].write_json(null, false, 0, 1) == 83); + assert(root["children"].write_json(buffer, false, 0, 1) == 83); assert(buffer[0 .. 83] == "[\n {\n \"name\": \"Jane Doe\",\n \"age\": 12\n },\n {\n \"name\": \"Jack Doe\",\n \"age\": 8\n }\n]"); // check indentation works properly - assert(root["children"].writeJson(null, false, 0, 2) == 95); - assert(root["children"].writeJson(buffer, false, 0, 2) == 95); + assert(root["children"].write_json(null, false, 0, 2) == 95); + assert(root["children"].write_json(buffer, false, 0, 2) == 95); assert(buffer[0 .. 95] == "[\n {\n \"name\": \"Jane Doe\",\n \"age\": 12\n },\n {\n \"name\": \"Jack Doe\",\n \"age\": 8\n }\n]"); // fabricate a JSON object @@ -220,7 +329,7 @@ unittest assert(write[0].asInt == 42); assert(write[1]["wow"].isTrue); assert(write[1]["bogus"].asBool == false); - assert(write.writeJson(buffer, true) == 33); + assert(write.write_json(buffer, true) == 33); assert(buffer[0 .. 33] == "[42, {\"wow\":true, \"bogus\":false}]"); } @@ -244,7 +353,7 @@ ptrdiff_t newline(char[] buffer, ref ptrdiff_t offset, int level) return true; } -Variant parseNode(ref const(char)[] text) +Variant parse_node(ref const(char)[] text) { text = text.trimFront(); @@ -269,20 +378,56 @@ Variant parseNode(ref const(char)[] text) { assert(text.length > 1); size_t i = 1; + Array!(char, 0) tmp; // TODO: needs a generous stack buffer! while (i < text.length && text[i] != '"') { - // TODO: we need to collapse the escape sequence, so we need to copy the string somewhere >_< - // ...overwrite the source buffer? if (text[i] == '\\') { - assert(i + 1 < text.length); - i += 2; + if (tmp.empty) + { + tmp.reserve(256); + tmp = text[1 .. i]; + } + if (++i == text.length) + break; + if (text[i] == 'u') + { + import urt.conv : parse_uint; + if (++i + 4 >= text.length) + break; + size_t taken; + ulong code = text[i .. i + 4].parse_uint(&taken, 16); + if (taken != 4) + break; + i += 4; + dchar c = cast(dchar)code; + if ((c >> 11) == 0x1B) + { + if (code >= 0xDC00) + break; // low surrogate without preceding high surrogate + if (i + 6 >= text.length || text[i] != '\\' || text[i+1] != 'u') + break; + code = text[i + 2 .. i + 6].parse_uint(&taken, 16); + if (taken != 4 || (code >> 10) != 0x37) + break; + c = 0x10000 + ((c & 0x3FF) << 10 | (cast(uint)code & 0x3FF)); + i += 6; + } + tmp ~= c; + } + else + goto do_concat; + } + else if (!tmp.empty) + { + do_concat: + tmp ~= text[i++]; } else - i++; + ++i; } assert(i < text.length); - Variant node = Variant(text[1 .. i]); + Variant node = Variant(tmp.empty ? text[1 .. i] : tmp[]); text = text[i + 1 .. $]; return node; } @@ -307,7 +452,7 @@ Variant parseNode(ref const(char)[] text) else expectComma = true; - tmp ~= parseNode(text); + tmp ~= parse_node(text); if (!isArray) { assert(tmp.back().isString()); @@ -315,7 +460,7 @@ Variant parseNode(ref const(char)[] text) text = text.trimFront; assert(text.length > 0 && text[0] == ':'); text = text[1 .. $].trimFront; - tmp ~= parseNode(text); + tmp ~= parse_node(text); } } assert(text.length > 0); @@ -326,33 +471,30 @@ Variant parseNode(ref const(char)[] text) r.flags = Variant.Flags.Map; return r; } - else if (text[0].isNumeric || (text[0] == '-' && text.length > 1 && text[1].isNumeric)) + else if (text[0].is_numeric || (text[0] == '-' && text.length > 1 && text[1].is_numeric)) { - bool neg = text[0] == '-'; size_t taken = void; - ulong div = void; - ulong value = text[neg .. $].parseUintWithDecimal(div, &taken, 10); + int e = void; + long value = text.parse_int_with_exponent(e, &taken, 10); assert(taken > 0); - text = text[taken + neg .. $]; + text = text[taken .. $]; - if (div > 1) - { - double d = cast(double)value; - if (neg) - d = -d; - d /= div; - return Variant(d); - } - else + // let's work out if value*10^^e is an integer? + bool is_integer = e >= 0; + for (; e > 0; --e) { - if (neg) + if (value < 0 ? (value < long.min / 10) : (value > long.max / 10)) { - assert(value <= long.max + 1); - return Variant(-cast(long)value); + is_integer = false; + break; } - else - return Variant(value); + value *= 10; } + + if (is_integer) + return Variant(value); + else + return Variant(value * 10.0^^e); } else assert(false, "Invalid JSON!"); diff --git a/src/urt/hash.d b/src/urt/hash.d index 7c068e6..95e47ed 100644 --- a/src/urt/hash.d +++ b/src/urt/hash.d @@ -6,27 +6,28 @@ version = BranchIsFasterThanMod; nothrow @nogc: -alias fnv1aHash = fnv1Hash!(uint, true); -alias fnv1aHash64 = fnv1Hash!(ulong, true); +alias fnv1a = fnv1!(uint, true); +alias fnv1a64 = fnv1!(ulong, true); -T fnv1Hash(T, bool alternate)(const ubyte[] s) pure nothrow @nogc +template fnv1_initial(T) +{ + static if (is(T == ushort)) + enum T fnv1_initial = 0x811C; + else static if (is(T == uint)) + enum T fnv1_initial = 0x811C9DC5; + else static if (is(T == ulong)) + enum T fnv1_initial = 0xCBF29CE484222325; +} + +T fnv1(T, bool alternate)(const ubyte[] s, T hash = fnv1_initial!T) pure if (is(T == ushort) || is(T == uint) || is(T == ulong)) { static if (is(T == ushort)) - { - enum T prime = 0x0101; // 16-bit FNV prime - T hash = 0x811C; // 16-bit FNV offset basis - } + enum T prime = 0x0101; else static if (is(T == uint)) - { - enum T prime = 0x01000193; // 32-bit FNV prime - T hash = 0x811C9DC5; // 32-bit FNV offset basis - } + enum T prime = 0x01000193; else static if (is(T == ulong)) - { - enum T prime = 0x100000001B3; // 64-bit FNV prime - T hash = 0XCBF29CE484222325; // 64-bit FNV offset basis - } + enum T prime = 0x100000001B3; const ubyte* p = s.ptr; for (size_t i = 0; i < s.length; ++i) @@ -47,111 +48,157 @@ T fnv1Hash(T, bool alternate)(const ubyte[] s) pure nothrow @nogc unittest { - enum hash = fnv1aHash(cast(ubyte[])"hello world"); + enum hash = fnv1a(cast(ubyte[])"hello world"); static assert(hash == 0xD58B3FA7); + + enum h1 = fnv1a(cast(ubyte[])"hello "); + enum h2 = fnv1a(cast(ubyte[])"world", h1); + static assert(h2 == hash); } -uint adler32(const void[] data) +version (Espressif) { - enum A32_BASE = 65521; + private extern(C) uint mz_adler32(uint adler, const ubyte* ptr, size_t buf_len) pure nothrow @nogc; - assert(data.length <= int.max, "Data length must be less than or equal to int.max"); + uint adler32(const void[] data, uint init = 1) pure + { + return mz_adler32(init, cast(const ubyte*)data.ptr, data.length); + } +} +else +{ + uint adler32(const void[] data, uint init = 1) pure + { + enum A32_BASE = 65521; - uint s1 = 1; - uint s2 = 0; + assert(data.length <= int.max, "Data length must be less than or equal to int.max"); - version (SmallSize) - { - foreach (ubyte b; cast(ubyte[])data) + uint s1 = init & 0xFFFF; + uint s2 = (init >> 16) & 0xFFFF; + + version (SmallSize) { - version (BranchIsFasterThanMod) + foreach (ubyte b; cast(ubyte[])data) { - s1 += b; - s2 += s1; - if (s1 >= A32_BASE) - s1 -= A32_BASE; - if (s2 >= A32_BASE) - s2 -= A32_BASE; + version (BranchIsFasterThanMod) + { + s1 += b; + s2 += s1; + if (s1 >= A32_BASE) + s1 -= A32_BASE; + if (s2 >= A32_BASE) + s2 -= A32_BASE; + } + else + { + s1 = (s1 + b) % A32_BASE; + s2 = (s2 + s1) % A32_BASE; + } } - else + } + else + { + enum A32_NMAX = 5552; + + const(ubyte)* buf = cast(const ubyte*)data.ptr; + int length = cast(int)data.length; + + while (length > 0) { - s1 = (s1 + b) % A32_BASE; - s2 = (s2 + s1) % A32_BASE; + int k = length < A32_NMAX ? length : A32_NMAX; + int i; + + for (i = k / 16; i; --i, buf += 16) + { + s1 += buf[0]; + s2 += s1; + s1 += buf[1]; + s2 += s1; + s1 += buf[2]; + s2 += s1; + s1 += buf[3]; + s2 += s1; + s1 += buf[4]; + s2 += s1; + s1 += buf[5]; + s2 += s1; + s1 += buf[6]; + s2 += s1; + s1 += buf[7]; + s2 += s1; + + s1 += buf[8]; + s2 += s1; + s1 += buf[9]; + s2 += s1; + s1 += buf[10]; + s2 += s1; + s1 += buf[11]; + s2 += s1; + s1 += buf[12]; + s2 += s1; + s1 += buf[13]; + s2 += s1; + s1 += buf[14]; + s2 += s1; + s1 += buf[15]; + s2 += s1; + } + + for (i = k & 0xF; i; --i) + { + s1 += *buf++; + s2 += s1; + } + + s1 %= A32_BASE; + s2 %= A32_BASE; + + length -= k; } } + + return (s2 << 16) | s1; } - else - { - enum A32_NMAX = 5552; +} - const(ubyte)* buf = cast(const ubyte*)data.ptr; - int length = cast(int)data.length; +unittest +{ + // empty data + assert(adler32(null) == 1); - while (length > 0) - { - int k = length < A32_NMAX ? length : A32_NMAX; - int i; + // RFC 1950 test vectors + assert(adler32("Wikipedia") == 0x11E6_0398); - for (i = k / 16; i; --i, buf += 16) - { - s1 += buf[0]; - s2 += s1; - s1 += buf[1]; - s2 += s1; - s1 += buf[2]; - s2 += s1; - s1 += buf[3]; - s2 += s1; - s1 += buf[4]; - s2 += s1; - s1 += buf[5]; - s2 += s1; - s1 += buf[6]; - s2 += s1; - s1 += buf[7]; - s2 += s1; - - s1 += buf[8]; - s2 += s1; - s1 += buf[9]; - s2 += s1; - s1 += buf[10]; - s2 += s1; - s1 += buf[11]; - s2 += s1; - s1 += buf[12]; - s2 += s1; - s1 += buf[13]; - s2 += s1; - s1 += buf[14]; - s2 += s1; - s1 += buf[15]; - s2 += s1; - } + // single byte + assert(adler32("a") == 0x0062_0062); - for (i = k & 0xF; i; --i) - { - s1 += *buf++; - s2 += s1; - } + // full string + assert(adler32("Hello, World!") == 0x1F9E_046A); - s1 %= A32_BASE; - s2 %= A32_BASE; + // progressive accumulation + uint a1 = adler32("Hello, "); + assert(a1 == 0x09EE_0241); + assert(adler32("World!", a1) == 0x1F9E_046A); - length -= k; - } - } + // all zeros + ubyte[1024] zeros; + assert(adler32(zeros[]) == 0x0400_0001); - return (s2 << 16) | s1; + // known value: "123456789" + assert(adler32("123456789") == 0x091E_01DE); } -ushort internet_checksum(const void[] data, ushort initial = 0xFFFF) +// NOTE: progressive accumulation via `initial` works only when prior chunks have even length!!! +// odd-length chunks misalign the 16-bit word pairing. fixing this requires carrying a pending byte between calls :/ +// maybe there's some way to protect against misuse? +ushort internet_checksum(const void[] data, ushort initial = 0xFFFF) pure { auto bytes = cast(const(const ubyte)[])data; - uint sum = ~initial; + uint sum = initial ^ 0xFFFF; while (bytes.length > 1) { sum += (bytes.ptr[0] << 8) | bytes.ptr[1]; @@ -165,3 +212,49 @@ ushort internet_checksum(const void[] data, ushort initial = 0xFFFF) return cast(ushort)~sum; } + +unittest +{ + // Empty input: checksum of nothing is 0xFFFF (~0). + assert(internet_checksum(null) == 0xFFFF); + + // RFC 1071 §B vector. + static immutable ubyte[8] rfc1071 = [0x00, 0x01, 0xF2, 0x03, 0xF4, 0xF5, 0xF6, 0xF7]; + assert(internet_checksum(rfc1071[]) == 0x220D); + + // Real IPv4 header (checksum field zeroed). + static immutable ubyte[20] ip_hdr = [ + 0x45, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x06, 0x00, 0x00, + 0xC0, 0xA8, 0x00, 0xC8, 0xC0, 0xA8, 0x03, 0x0C, + ]; + ushort cs = internet_checksum(ip_hdr[]); + assert(cs == 0xF5A7); + + // Patching the computed checksum back in must verify to zero. + ubyte[20] verified = ip_hdr; + verified[10] = cast(ubyte)(cs >> 8); + verified[11] = cast(ubyte)cs; + assert(internet_checksum(verified[]) == 0); + + // Progressive accumulation across even-length chunks must match all-at-once. + ushort first = internet_checksum(ip_hdr[0 .. 10]); + ushort whole = internet_checksum(ip_hdr[10 .. $], first); + assert(whole == cs); + + // Odd-length input: trailing byte is treated as the high byte of a 16-bit word. + static immutable ubyte[3] odd = [0xAA, 0xBB, 0xCC]; + assert(internet_checksum(odd[]) == 0x8943); + + // Chained checksum across two buffers must equal the concatenated buffer's + // checksum, when the prior result is passed directly as `initial`. (This + // is the pseudo-header + segment pattern used by TCP/UDP.) + static immutable ubyte[6] part_a = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC]; + static immutable ubyte[6] part_b = [0xDE, 0xF0, 0x11, 0x22, 0x33, 0x44]; + ubyte[12] joined; + joined[0 .. 6] = part_a; + joined[6 .. 12] = part_b; + ushort partial = internet_checksum(part_a[]); + ushort chained = internet_checksum(part_b[], partial); + assert(chained == internet_checksum(joined[])); +} diff --git a/src/urt/inet.d b/src/urt/inet.d index 969def7..7f5af5a 100644 --- a/src/urt/inet.d +++ b/src/urt/inet.d @@ -11,11 +11,11 @@ nothrow @nogc: enum AddressFamily : byte { - Unknown = -1, - Unspecified = 0, - Unix, - IPv4, - IPv6, + unknown = -1, + unspecified = 0, + unix, + ipv4, + ipv6, } enum WellKnownPort : ushort @@ -34,8 +34,8 @@ enum WellKnownPort : ushort MDNS = 5353, } -enum IPAddr IPAddrLit(string addr) = () { IPAddr a; size_t taken = a.fromString(addr); assert(taken == addr.length, "Not an IPv4 address"); return a; }(); -//enum IPv6Addr IPv6AddrLit(string addr) = () { IPv6Addr a; size_t taken = a.fromString(addr); assert(taken == addr.length, "Not an IPv6 address"); return a; }(); +enum IPAddr IPAddrLit(string addr) = () { IPAddr a; size_t taken = a.fromString(addr); assert(taken == addr.length, "Not an ipv4 address"); return a; }(); +enum IPv6Addr IPv6AddrLit(string addr) = () { IPv6Addr a; size_t taken = a.fromString(addr); assert(taken == addr.length, "Not an ipv6 address"); return a; }(); struct IPAddr { @@ -56,13 +56,13 @@ nothrow @nogc: this.b = b; } - bool isMulticast() const pure + bool is_multicast() const pure => (b[0] & 0xF0) == 224; - bool isLoopback() const pure + bool is_loopback() const pure => b[0] == 127; - bool isLinkLocal() const pure + bool is_link_local() const pure => (b[0] == 169 && b[1] == 254); - bool isPrivate() const pure + bool is_private() const pure => (b[0] == 192 && b[1] == 168) || b[0] == 10 || (b[0] == 172 && (b[1] & 0xF) == 16); bool opCast(T : bool)() const pure @@ -74,6 +74,16 @@ nothrow @nogc: bool opEquals(const(ubyte)[4] bytes) const pure => b == bytes; + int opCmp(ref const IPAddr rhs) const pure + { + uint a = loadBigEndian(&address), b = loadBigEndian(&rhs.address); + if (a < b) + return -1; + else if (a > b) + return 1; + return 0; + } + IPAddr opUnary(string op : "~")() const pure { IPAddr r; @@ -97,26 +107,26 @@ nothrow @nogc: size_t toHash() const pure { - import urt.hash : fnv1aHash, fnv1aHash64; + import urt.hash : fnv1a, fnv1a64; static if (size_t.sizeof > 4) - return fnv1aHash64(b[]); + return fnv1a64(b[]); else - return fnv1aHash(b[]); + return fnv1a(b[]); } - ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const pure + ptrdiff_t toString(char[] buffer, const(char)[], const(FormatArg)[]) const pure { - char[15] stackBuffer = void; - char[] tmp = buffer.length < stackBuffer.sizeof ? stackBuffer : buffer; + char[15] stack_buffer = void; + char[] tmp = buffer.length < stack_buffer.sizeof ? stack_buffer : buffer; size_t offset = 0; for (int i = 0; i < 4; i++) { if (i > 0) tmp[offset++] = '.'; - offset += b[i].formatInt(tmp[offset..$]); + offset += b[i].format_uint(tmp[offset..$]); } - if (buffer.ptr && tmp.ptr == stackBuffer.ptr) + if (buffer.ptr && tmp.ptr == stack_buffer.ptr) { if (buffer.length < offset) return -1; @@ -129,22 +139,22 @@ nothrow @nogc: { ubyte[4] t; size_t offset = 0, len; - ulong i = s[offset..$].parseInt(&len); + ulong i = s[offset..$].parse_uint(&len); offset += len; if (len == 0 || i > 255 || s.length < offset + 1 || s[offset++] != '.') return -1; t[0] = cast(ubyte)i; - i = s[offset..$].parseInt(&len); + i = s[offset..$].parse_uint(&len); offset += len; if (len == 0 || i > 255 || s.length < offset + 1 || s[offset++] != '.') return -1; t[1] = cast(ubyte)i; - i = s[offset..$].parseInt(&len); + i = s[offset..$].parse_uint(&len); offset += len; if (len == 0 || i > 255 || s.length < offset + 1 || s[offset++] != '.') return -1; t[2] = cast(ubyte)i; - i = s[offset..$].parseInt(&len); + i = s[offset..$].parse_uint(&len); offset += len; if (len == 0 || i > 255) return -1; @@ -153,14 +163,17 @@ nothrow @nogc: return offset; } - auto __debugOverview() + version (Windows) { - import urt.mem; - char[] buffer = cast(char[])tempAllocator.alloc(15); - ptrdiff_t len = toString(buffer, null, null); - return buffer[0 .. len]; + auto __debugOverview() + { + import urt.mem; + char[] buffer = debug_alloc!char(15); + ptrdiff_t len = toString(buffer, null, null); + return buffer[0 .. len]; + } + auto __debugExpanded() => b[]; } - auto __debugExpanded() => b[]; } @@ -180,13 +193,13 @@ nothrow @nogc: this.s = s; } - bool isGlobal() const pure + bool is_global() const pure => (s[0] & 0xE000) == 0x2000; - bool isLinkLocal() const pure + bool is_link_local() const pure => (s[0] & 0xFFC0) == 0xFE80; - bool isMulticast() const pure + bool is_multicast() const pure => (s[0] & 0xFF00) == 0xFF00; - bool isUniqueLocal() const pure + bool is_unique_local() const pure => (s[0] & 0xFE00) == 0xFC00; bool opCast(T : bool)() const pure @@ -198,6 +211,18 @@ nothrow @nogc: bool opEquals(const(ushort)[8] words) const pure => s == words; + int opCmp(ref const IPv6Addr rhs) const pure + { + for (int i = 0; i < 8; i++) + { + if (s[i] < rhs.s[i]) + return -1; + else if (s[i] > rhs.s[i]) + return 1; + } + return 0; + } + IPv6Addr opUnary(string op : "~")() const pure { IPv6Addr r; @@ -206,7 +231,7 @@ nothrow @nogc: return r; } - IPv6Addr opBinary(string op)(const IPv6Addr rhs) pure + IPv6Addr opBinary(string op)(const IPv6Addr rhs) const pure if (op == "&" || op == "|" || op == "^") { IPv6Addr t; @@ -224,19 +249,19 @@ nothrow @nogc: size_t toHash() const pure { - import urt.hash : fnv1aHash, fnv1aHash64; + import urt.hash : fnv1a, fnv1a64; static if (size_t.sizeof > 4) - return fnv1aHash64(cast(ubyte[])s[]); + return fnv1a64(cast(ubyte[])s[]); else - return fnv1aHash(cast(ubyte[])s[]); + return fnv1a(cast(ubyte[])s[]); } - ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const pure + ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] format_args) const pure { import urt.string.ascii; // find consecutive zeroes... - int skipFrom = 0; + int skip_from = 0; int[8] z; for (int i = 0; i < 8; i++) { @@ -247,15 +272,15 @@ nothrow @nogc: if (z[j] != 0) { ++z[j]; - if (z[j] > z[skipFrom]) - skipFrom = j; + if (z[j] > z[skip_from]) + skip_from = j; } else break; } z[i] = 1; - if (z[i] > z[skipFrom]) - skipFrom = i; + if (z[i] > z[skip_from]) + skip_from = i; } } @@ -266,16 +291,16 @@ nothrow @nogc: { if (i > 0) tmp[offset++] = ':'; - if (z[skipFrom] > 1 && i == skipFrom) + if (z[skip_from] > 1 && i == skip_from) { if (i == 0) tmp[offset++] = ':'; - i += z[skipFrom]; + i += z[skip_from]; if (i == 8) tmp[offset++] = ':'; continue; } - offset += s[i].formatInt(tmp[offset..$], 16); + offset += s[i].format_uint(tmp[offset..$], 16); ++i; } @@ -284,48 +309,87 @@ nothrow @nogc: if (buffer.length < offset) return -1; foreach (i, c; tmp[0 .. offset]) - buffer[i] = c.toLower; + buffer[i] = c.to_lower; } return offset; } ptrdiff_t fromString(const(char)[] str) { - ushort[8] t; - size_t offset = 0; - assert(false); + ushort[8][2] t = void; + ubyte[2] count; + int part = 0; + + size_t offset = 0, len; + while (offset < str.length) + { + if (offset < str.length - 1 && str[offset] == ':' && str[offset + 1] == ':') + { + if (part != 0) + return -1; + part = 1; + offset += 2; + if (offset == str.length) + break; + } + else if (count[part] > 0) + { + if (str[offset] != ':') + break; + if (++offset == str.length) + return -1; + } + if (str[offset] == ':') + return -1; + ulong i = str[offset..$].parse_uint(&len, 16); + if (len == 0) + break; + if (i > ushort.max || count[0] + count[1] == 8) + return -1; + t[part][count[part]++] = cast(ushort)i; + offset += len; + } + if (part == 0 && count[0] != 8) + return -1; + + s[0 .. count[0]] = t[0][0 .. count[0]]; + s[count[0] .. 8 - count[1]] = 0; + s[8 - count[1] .. 8] = t[1][0 .. count[1]]; return offset; } - auto __debugOverview() + version (Windows) { - import urt.mem; - char[] buffer = cast(char[])tempAllocator.alloc(39); - ptrdiff_t len = toString(buffer, null, null); - return buffer[0 .. len]; + auto __debugOverview() + { + import urt.mem; + char[] buffer = debug_alloc!char(39); + ptrdiff_t len = toString(buffer, null, null); + return buffer[0 .. len]; + } + auto __debugExpanded() => s[]; } - auto __debugExpanded() => s[]; } -struct IPSubnet +struct IPNetworkAddress { nothrow @nogc: - enum multicast = IPSubnet(IPAddr(224, 0, 0, 0), 4); - enum loopback = IPSubnet(IPAddr(127, 0, 0, 0), 8); - enum linkLocal = IPSubnet(IPAddr(169, 254, 0, 0), 16); - enum privateA = IPSubnet(IPAddr(10, 0, 0, 0), 8); - enum privateB = IPSubnet(IPAddr(172, 16, 0, 0), 12); - enum privateC = IPSubnet(IPAddr(192, 168, 0, 0), 16); + enum multicast = IPNetworkAddress(IPAddr(224, 0, 0, 0), 4); + enum loopback = IPNetworkAddress(IPAddr(127, 0, 0, 0), 8); + enum linklocal = IPNetworkAddress(IPAddr(169, 254, 0, 0), 16); + enum private_a = IPNetworkAddress(IPAddr(10, 0, 0, 0), 8); + enum private_b = IPNetworkAddress(IPAddr(172, 16, 0, 0), 12); + enum private_c = IPNetworkAddress(IPAddr(192, 168, 0, 0), 16); // TODO: ya know, this is gonna align to 4-bytes anyway... // we could store the actual mask in the native endian, and then clz to recover the prefix len in one opcode IPAddr addr; IPAddr mask; - ubyte prefixLen() @property const pure + ubyte prefix_len() @property const pure => clz(~loadBigEndian(&mask.address)); - void prefixLen(ubyte len) @property pure + void prefix_len(ubyte len) @property pure { if (len == 0) mask.address = 0; @@ -333,36 +397,36 @@ nothrow @nogc: storeBigEndian(&mask.address, 0xFFFFFFFF << (32 - len)); } - this(IPAddr addr, ubyte prefixLen) + this(IPAddr addr, ubyte prefix_len) { this.addr = addr; - this.prefixLen = prefixLen; + this.prefix_len = prefix_len; } - IPAddr netMask() const pure + IPAddr net_mask() const pure => mask; bool contains(IPAddr ip) const pure - => (ip & netMask()) == addr; + => (ip & mask) == get_network(); - IPAddr getNetwork(IPAddr ip) const pure - => ip & mask; - IPAddr getLocal(IPAddr ip) const pure - => ip & ~mask; + IPAddr get_network() const pure + => addr & mask; + IPAddr get_local() const pure + => addr & ~mask; size_t toHash() const pure - => addr.toHash() ^ prefixLen; + => addr.toHash() ^ prefix_len; - ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const pure + ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] format_args) const pure { - char[18] stackBuffer = void; - char[] tmp = buffer.length < stackBuffer.sizeof ? stackBuffer : buffer; + char[18] stack_buffer = void; + char[] tmp = buffer.length < stack_buffer.sizeof ? stack_buffer : buffer; size_t offset = addr.toString(tmp, null, null); tmp[offset++] = '/'; - offset += prefixLen.formatInt(tmp[offset..$]); + offset += prefix_len.format_uint(tmp[offset..$]); - if (buffer.ptr && tmp.ptr == stackBuffer.ptr) + if (buffer.ptr && tmp.ptr == stack_buffer.ptr) { if (buffer.length < offset) return -1; @@ -378,80 +442,91 @@ nothrow @nogc: if (taken < 0 || s.length <= taken + 1 || s[taken++] != '/') return -1; size_t t; - ulong plen = s[taken..$].parseInt(&t); + ulong plen = s[taken..$].parse_uint(&t); if (t == 0 || plen > 32) return -1; addr = a; - prefixLen = cast(ubyte)plen; + prefix_len = cast(ubyte)plen; return taken + t; } - auto __debugOverview() + version (Windows) { - import urt.mem; - char[] buffer = cast(char[])tempAllocator.alloc(18); - ptrdiff_t len = toString(buffer, null, null); - return buffer[0 .. len]; + auto __debugOverview() + { + import urt.mem; + char[] buffer = debug_alloc!char(18); + ptrdiff_t len = toString(buffer, null, null); + return buffer[0 .. len]; + } } } -struct IPv6Subnet +struct IPv6NetworkAddress { nothrow @nogc: - enum global = IPv6Subnet(IPv6Addr(0x2000, 0, 0, 0, 0, 0, 0, 0), 3); - enum linkLocal = IPv6Subnet(IPv6Addr(0xFE80, 0, 0, 0, 0, 0, 0, 0), 10); - enum multicast = IPv6Subnet(IPv6Addr(0xFF00, 0, 0, 0, 0, 0, 0, 0), 8); - enum uniqueLocal = IPv6Subnet(IPv6Addr(0xFC00, 0, 0, 0, 0, 0, 0, 0), 7); + enum global = IPv6NetworkAddress(IPv6Addr(0x2000, 0, 0, 0, 0, 0, 0, 0), 3); + enum linklocal = IPv6NetworkAddress(IPv6Addr(0xFE80, 0, 0, 0, 0, 0, 0, 0), 10); + enum multicast = IPv6NetworkAddress(IPv6Addr(0xFF00, 0, 0, 0, 0, 0, 0, 0), 8); + enum uniquelocal = IPv6NetworkAddress(IPv6Addr(0xFC00, 0, 0, 0, 0, 0, 0, 0), 7); IPv6Addr addr; - ubyte prefixLen; + ubyte prefix_len; - this(IPv6Addr addr, ubyte prefixLen) + this(IPv6Addr addr, ubyte prefix_len) { this.addr = addr; - this.prefixLen = prefixLen; + this.prefix_len = prefix_len; } - IPv6Addr netMask() const pure + IPv6Addr net_mask() const pure { IPv6Addr r; - int i, j = prefixLen / 16; + int i, j = prefix_len / 16; while (i < j) r.s[i++] = 0xFFFF; - r.s[i++] = cast(ushort)(0xFFFF << (16 - (prefixLen % 16))); - while (i < 8) r.s[i++] = 0; + if (j < 8) + { + r.s[i++] = cast(ushort)(0xFFFF << (16 - (prefix_len % 16))); + while (i < 8) r.s[i++] = 0; + } return r; } bool contains(IPv6Addr ip) const pure { - uint n = prefixLen / 16; + uint n = prefix_len / 16; uint i = 0; for (; i < n; ++i) if (ip.s[i] != addr.s[i]) return false; - if (prefixLen % 16) + if (prefix_len % 16) { - uint s = 16 - (prefixLen % 16); + uint s = 16 - (prefix_len % 16); if (ip.s[i] >> s != addr.s[i] >> s) return false; } return true; } + IPv6Addr get_network() const pure + => addr & net_mask(); + IPv6Addr get_local() const pure + => addr & ~net_mask(); + size_t toHash() const pure - => addr.toHash() ^ prefixLen; + => addr.toHash() ^ prefix_len; - ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const pure + ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] format_args) const pure { - char[42] stackBuffer = void; - char[] tmp = buffer.length < stackBuffer.sizeof ? stackBuffer : buffer; + char[42] stack_buffer = void; + char[] tmp = buffer.length < stack_buffer.sizeof ? stack_buffer : buffer; size_t offset = addr.toString(tmp, null, null); tmp[offset++] = '/'; - offset += prefixLen.formatInt(tmp[offset..$]); + offset += prefix_len.format_uint(tmp[offset..$]); - if (buffer.ptr && tmp.ptr == stackBuffer.ptr) + if (buffer.ptr && tmp.ptr == stack_buffer.ptr) { if (buffer.length < offset) return -1; @@ -467,20 +542,23 @@ nothrow @nogc: if (taken < 0 || s.length <= taken + 1 || s[taken++] != '/') return -1; size_t t; - ulong plen = s[taken..$].parseInt(&t); - if (t == 0 || plen > 32) + ulong plen = s[taken..$].parse_uint(&t); + if (t == 0 || plen > 128) return -1; addr = a; - prefixLen = cast(ubyte)plen; + prefix_len = cast(ubyte)plen; return taken + t; } - auto __debugOverview() + version (Windows) { - import urt.mem; - char[] buffer = cast(char[])tempAllocator.alloc(42); - ptrdiff_t len = toString(buffer, null, null); - return buffer[0 .. len]; + auto __debugOverview() + { + import urt.mem; + char[] buffer = debug_alloc!char(42); + ptrdiff_t len = toString(buffer, null, null); + return buffer[0 .. len]; + } } } @@ -503,7 +581,7 @@ nothrow @nogc: { IPv6Addr addr; ushort port; - uint flowInfo; + uint flow_info; uint scopeId; } struct Addr @@ -517,37 +595,89 @@ nothrow @nogc: this(IPAddr addr, ushort port) { - family = AddressFamily.IPv4; + family = AddressFamily.ipv4; this._a.ipv4.addr = addr; this._a.ipv4.port = port; } - this(IPv6Addr addr, ushort port) + this(IPv6Addr addr, ushort port, int flow_info = 0, uint scopeId = 0) { - family = AddressFamily.IPv6; + family = AddressFamily.ipv6; this._a.ipv6.addr = addr; this._a.ipv6.port = port; + this._a.ipv6.flow_info = flow_info; + this._a.ipv6.scopeId = scopeId; + } + + inout(IPv4)* as_ipv4() inout pure + => family == AddressFamily.ipv4 ? &_a.ipv4 : null; + + inout(IPv6)* as_ipv6() inout pure + => family == AddressFamily.ipv6 ? &_a.ipv6 : null; + + bool opCast(T : bool)() const pure + => family > AddressFamily.Unspecified; + + bool opEquals(ref const InetAddress rhs) const pure + { + if (family != rhs.family) + return false; + switch (family) + { + case AddressFamily.ipv4: + return _a.ipv4 == rhs._a.ipv4; + case AddressFamily.ipv6: + return _a.ipv6 == rhs._a.ipv6; + default: + return true; + } + } + + int opCmp(ref const InetAddress rhs) const pure + { + if (family != rhs.family) + return family < rhs.family ? -1 : 1; + switch (family) + { + case AddressFamily.ipv4: + int c = _a.ipv4.addr.opCmp(rhs._a.ipv4.addr); + return c != 0 ? c : _a.ipv4.port - rhs._a.ipv4.port; + case AddressFamily.ipv6: + int c = _a.ipv6.addr.opCmp(rhs._a.ipv6.addr); + if (c != 0) + return c; + if (_a.ipv6.port == rhs._a.ipv6.port) + { + if (_a.ipv6.flow_info == rhs._a.ipv6.flow_info) + return _a.ipv6.scopeId - rhs._a.ipv6.scopeId; + return _a.ipv6.flow_info - rhs._a.ipv6.flow_info; + } + return _a.ipv6.port - rhs._a.ipv6.port; + default: + return 0; + } + return 0; } size_t toHash() const pure { - if (family == AddressFamily.IPv4) + if (family == AddressFamily.ipv4) return _a.ipv4.addr.toHash() ^ _a.ipv4.port; else return _a.ipv6.addr.toHash() ^ _a.ipv6.port; } - ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const pure + ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] format_args) const pure { - char[47] stackBuffer = void; - char[] tmp = buffer.length < stackBuffer.sizeof ? stackBuffer : buffer; + char[47] stack_buffer = void; + char[] tmp = buffer.length < stack_buffer.sizeof ? stack_buffer : buffer; size_t offset = void; - if (family == AddressFamily.IPv4) + if (family == AddressFamily.ipv4) { offset = _a.ipv4.addr.toString(tmp, null, null); tmp[offset++] = ':'; - offset += _a.ipv4.port.formatInt(tmp[offset..$]); + offset += _a.ipv4.port.format_uint(tmp[offset..$]); } else { @@ -555,10 +685,10 @@ nothrow @nogc: offset = 1 + _a.ipv6.addr.toString(tmp[1 .. $], null, null); tmp[offset++] = ']'; tmp[offset++] = ':'; - offset += _a.ipv6.port.formatInt(tmp[offset..$]); + offset += _a.ipv6.port.format_uint(tmp[offset..$]); } - if (buffer.ptr && tmp.ptr == stackBuffer.ptr) + if (buffer.ptr && tmp.ptr == stack_buffer.ptr) { if (buffer.length < offset) return -1; @@ -577,10 +707,10 @@ nothrow @nogc: // take address if (s.length >= 4 && (s[1] == '.' || s[2] == '.' || s[3] == '.')) - af = AddressFamily.IPv4; + af = AddressFamily.ipv4; else - af = AddressFamily.IPv6; - if (af == AddressFamily.IPv4) + af = AddressFamily.ipv6; + if (af == AddressFamily.ipv4) { taken = a4.fromString(s); if (taken < 0) @@ -602,8 +732,8 @@ nothrow @nogc: if (s.length > taken && s[taken] == ':') { size_t t; - ulong p = s[++taken..$].parseInt(&t); - if (t == 0 || p > 0xFFFF) + ulong p = s[++taken..$].parse_uint(&t); + if (t == 0 || p > ushort.max) return -1; taken += t; port = cast(ushort)p; @@ -611,7 +741,7 @@ nothrow @nogc: // success! store results.. family = af; - if (af == AddressFamily.IPv4) + if (af == AddressFamily.ipv4) { _a.ipv4.addr = a4; _a.ipv4.port = port; @@ -620,18 +750,21 @@ nothrow @nogc: { _a.ipv6.addr = a6; _a.ipv6.port = port; - _a.ipv6.flowInfo = 0; + _a.ipv6.flow_info = 0; _a.ipv6.scopeId = 0; } return taken; } - auto __debugOverview() + version (Windows) { - import urt.mem; - char[] buffer = cast(char[])tempAllocator.alloc(47); - ptrdiff_t len = toString(buffer, null, null); - return buffer[0 .. len]; + auto __debugOverview() + { + import urt.mem; + char[] buffer = debug_alloc!char(47); + ptrdiff_t len = toString(buffer, null, null); + return buffer[0 .. len]; + } } } @@ -641,8 +774,12 @@ unittest char[64] tmp; assert(~IPAddr(255, 255, 248, 0) == IPAddr(0, 0, 7, 255)); + assert((IPAddr(255, 255, 248, 0) & IPAddr(255, 0, 255, 255)) == IPAddr(255, 0, 248, 0)); + assert((IPAddr(255, 255, 248, 0) | IPAddr(255, 0, 255, 255)) == IPAddr(255, 255, 255, 255)); assert((IPAddr(255, 255, 248, 0) ^ IPAddr(255, 0, 255, 255)) == IPAddr(0, 255, 7, 255)); - assert(IPSubnet(IPAddr(), 21).netMask() == IPAddr(0xFF, 0xFF, 0xF8, 0)); + assert(IPNetworkAddress(IPAddr(), 21).net_mask() == IPAddr(0xFF, 0xFF, 0xF8, 0)); + assert(IPNetworkAddress(IPAddr(192, 168, 0, 10), 24).get_network() == IPAddr(192, 168, 0, 0)); + assert(IPNetworkAddress(IPAddr(192, 168, 0, 10), 24).get_local() == IPAddr(0, 0, 0, 10)); assert(tmp[0 .. IPAddr(192, 168, 0, 1).toString(tmp, null, null)] == "192.168.0.1"); assert(tmp[0 .. IPAddr(0, 0, 0, 0).toString(tmp, null, null)] == "0.0.0.0"); @@ -653,16 +790,24 @@ unittest addr |= IPAddr(1, 2, 3, 4); assert(addr == IPAddr(1, 2, 3, 4)); - assert(tmp[0 .. IPSubnet(IPAddr(192, 168, 0, 0), 24).toString(tmp, null, null)] == "192.168.0.0/24"); - assert(tmp[0 .. IPSubnet(IPAddr(0, 0, 0, 0), 0).toString(tmp, null, null)] == "0.0.0.0/0"); + assert(tmp[0 .. IPNetworkAddress(IPAddr(192, 168, 0, 0), 24).toString(tmp, null, null)] == "192.168.0.0/24"); + assert(tmp[0 .. IPNetworkAddress(IPAddr(0, 0, 0, 0), 0).toString(tmp, null, null)] == "0.0.0.0/0"); - IPSubnet subnet; - assert(subnet.fromString("192.168.0.0/24") == 14 && subnet == IPSubnet(IPAddr(192, 168, 0, 0), 24)); - assert(subnet.fromString("0.0.0.0/0") == 9 && subnet == IPSubnet(IPAddr(0, 0, 0, 0), 0)); + IPNetworkAddress subnet; + assert(subnet.fromString("192.168.0.0/24") == 14 && subnet == IPNetworkAddress(IPAddr(192, 168, 0, 0), 24)); + assert(subnet.fromString("0.0.0.0/0") == 9 && subnet == IPNetworkAddress(IPAddr(0, 0, 0, 0), 0)); + assert(subnet.fromString("1.2.3.4") == -1); + assert(subnet.fromString("1.2.3.4/33") == -1); + assert(subnet.fromString("1.2.3.4/a") == -1); assert(~IPv6Addr(0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFF0, 0, 0, 0) == IPv6Addr(0, 0, 0, 0, 0xF, 0xFFFF, 0xFFFF, 0xFFFF)); + assert((IPv6Addr(0xFFFF, 0, 1, 2, 3, 4, 5, 6) & IPv6Addr(0xFF00, 0, 3, 0, 0, 0, 0, 2)) == IPv6Addr(0xFF00, 0, 1, 0, 0, 0, 0, 2)); + assert((IPv6Addr(0xFFFF, 0, 1, 2, 3, 4, 5, 6) | IPv6Addr(0xFF00, 0, 3, 0, 0, 0, 0, 2)) == IPv6Addr(0xFFFF, 0, 3, 2, 3, 4, 5, 6)); assert((IPv6Addr(0xFFFF, 0, 1, 2, 3, 4, 5, 6) ^ IPv6Addr(0xFF00, 0, 3, 0, 0, 0, 0, 2)) == IPv6Addr(0xFF, 0, 2, 2, 3, 4, 5, 4)); - assert(IPv6Subnet(IPv6Addr(), 21).netMask() == IPv6Addr(0xFFFF, 0xF800, 0, 0, 0, 0, 0, 0)); + assert(IPv6NetworkAddress(IPv6Addr(), 21).net_mask() == IPv6Addr(0xFFFF, 0xF800, 0, 0, 0, 0, 0, 0)); + assert(IPv6NetworkAddress(IPv6Addr.loopback, 64).get_network() == IPv6Addr.any); + assert(IPv6NetworkAddress(IPv6Addr(0x2001, 0xdb8, 0, 1, 0, 0, 0, 1), 32).get_network() == IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 0)); + assert(IPv6NetworkAddress(IPv6Addr(0x2001, 0xdb8, 0, 1, 0, 0, 0, 1), 32).get_local() == IPv6Addr(0, 0, 0, 1, 0, 0, 0, 1)); assert(tmp[0 .. IPv6Addr(0x2001, 0xdb8, 0, 1, 0, 0, 0, 1).toString(tmp, null, null)] == "2001:db8:0:1::1"); assert(tmp[0 .. IPv6Addr(0x2001, 0xdb8, 0, 0, 1, 0, 0, 1).toString(tmp, null, null)] == "2001:db8::1:0:0:1"); @@ -670,17 +815,40 @@ unittest assert(tmp[0 .. IPv6Addr(0, 0, 0, 0, 0, 0, 0, 1).toString(tmp, null, null)] == "::1"); assert(tmp[0 .. IPv6Addr(0, 0, 0, 0, 0, 0, 0, 0).toString(tmp, null, null)] == "::"); -// IPv6Addr addr6; -// assert(addr6.fromString("::2") == 3 && addr6 == IPv6Addr(0, 0, 0, 0, 0, 0, 0, 2)); -// assert(addr6.fromString("1::2") == 3 && addr6 == IPv6Addr(1, 0, 0, 0, 0, 0, 0, 2)); -// assert(addr6.fromString("2001:db8::1/24") == 14 && addr6 == IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)); - - assert(tmp[0 .. IPv6Subnet(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 24).toString(tmp, null, null)] == "2001:db8::1/24"); - assert(tmp[0 .. IPv6Subnet(IPv6Addr(), 0).toString(tmp, null, null)] == "::/0"); - -// IPv6Subnet subnet6; -// assert(subnet6.fromString("2001:db8::1/24") == 14 && subnet6 == IPv6Subnet(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 24)); -// assert(subnet6.fromString("::/0") == 4 && subnet6 == IPv6Subnet(IPv6Addr(), 0)); + IPv6Addr addr6; + assert(addr6.fromString("::") == 2 && addr6.s[] == [0,0,0,0,0,0,0,0]); + assert(addr6.fromString("1::") == 3 && addr6.s[] == [1,0,0,0,0,0,0,0]); + assert(addr6.fromString("::2") == 3 && addr6.s[] == [0,0,0,0,0,0,0,2]); + assert(addr6.fromString("1::2") == 4 && addr6.s[] == [1,0,0,0,0,0,0,2]); + assert(addr6.fromString("1:FFFF::2") == 9 && addr6.s[] == [1,0xFFFF,0,0,0,0,0,2]); + assert(addr6.fromString("1:2:3:4:5:6:7:8") == 15 && addr6.s[] == [1,2,3,4,5,6,7,8]); + assert(addr6.fromString("1:2:3:4") == -1); + assert(addr6.fromString("1:2:3:4:5:6:7:8:9") == -1); + assert(addr6.fromString("1:2:3:4:5:6:7:8:") == -1); + assert(addr6.fromString("10000::2") == -1); + assert(addr6.fromString(":2") == -1); + assert(addr6.fromString("2:") == -1); + assert(addr6.fromString(":2:") == -1); + assert(addr6.fromString(":2::") == -1); + assert(addr6.fromString(":2::1") == -1); + assert(addr6.fromString("::2:") == -1); + assert(addr6.fromString("1::2:") == -1); + assert(addr6.fromString("1:::2") == -1); + assert(addr6.fromString("::G") == 2 && addr6 == IPv6Addr(0, 0, 0, 0, 0, 0, 0, 0)); + assert(addr6.fromString("1::2.3") == 4 && addr6 == IPv6Addr(1, 0, 0, 0, 0, 0, 0, 2)); + assert(addr6.fromString("1::2 4") == 4 && addr6 == IPv6Addr(1, 0, 0, 0, 0, 0, 0, 2)); + assert(addr6.fromString("2001:db8::1/24") == 11 && addr6 == IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1)); + + + assert(tmp[0 .. IPv6NetworkAddress(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 24).toString(tmp, null, null)] == "2001:db8::1/24"); + assert(tmp[0 .. IPv6NetworkAddress(IPv6Addr(), 0).toString(tmp, null, null)] == "::/0"); + + IPv6NetworkAddress subnet6; + assert(subnet6.fromString("2001:db8::1/24") == 14 && subnet6 == IPv6NetworkAddress(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 24)); + assert(subnet6.fromString("::/0") == 4 && subnet6 == IPv6NetworkAddress(IPv6Addr(), 0)); + assert(subnet6.fromString("1::2") == -1); + assert(subnet6.fromString("1::2/129") == -1); + assert(subnet6.fromString("1::2/a") == -1); assert(tmp[0 .. InetAddress(IPAddr(192, 168, 0, 1), 12345).toString(tmp, null, null)] == "192.168.0.1:12345"); assert(tmp[0 .. InetAddress(IPAddr(10, 0, 0, 0), 21).toString(tmp, null, null)] == "10.0.0.0:21"); @@ -692,6 +860,82 @@ unittest assert(address.fromString("192.168.0.1:21") == 14 && address == InetAddress(IPAddr(192, 168, 0, 1), 21)); assert(address.fromString("10.0.0.1:12345") == 14 && address == InetAddress(IPAddr(10, 0, 0, 1), 12345)); -// assert(address.fromString("[2001:db8:0:1::1]:12345") == 14 && address == InetAddress(IPv6Addr(0x2001, 0xdb8, 0, 1, 0, 0, 0, 1), 12345)); -// assert(address.fromString("[::]:21") == 14 && address == InetAddress(IPv6Addr(), 21)); + assert(address.fromString("[2001:db8:0:1::1]:12345") == 23 && address == InetAddress(IPv6Addr(0x2001, 0xdb8, 0, 1, 0, 0, 0, 1), 12345)); + assert(address.fromString("[::]:21") == 7 && address == InetAddress(IPv6Addr(), 21)); + assert(address.fromString("[::]:a") == -1); + assert(address.fromString("[::]:65536") == -1); + + // IPAddr sorting tests + { + IPAddr[8] expected = [ + IPAddr(0, 0, 0, 0), + IPAddr(1, 2, 3, 4), + IPAddr(1, 2, 3, 5), + IPAddr(1, 2, 4, 4), + IPAddr(10, 0, 0, 1), + IPAddr(127, 0, 0, 1), + IPAddr(192, 168, 1, 1), + IPAddr(255, 255, 255, 255), + ]; + + for (size_t i = 0; i < expected.length - 1; ++i) + { + assert(expected[i].opCmp(expected[i]) == 0, "IPAddr self-comparison failed"); + assert(expected[i].opCmp(expected[i+1]) < 0, "IPAddr sorting is incorrect"); + assert(expected[i+1].opCmp(expected[i]) > 0, "IPAddr sorting is incorrect"); + } + } + + // IPv6Addr sorting tests + { + IPv6Addr[14] expected = [ + IPv6Addr(0, 0, 0, 0, 0, 0, 0, 0), // :: + IPv6Addr(0, 0, 0, 0, 0, 0, 0, 1), // ::1 + IPv6Addr(0, 0, 0, 0, 0, 0, 0, 2), // ::2 + IPv6Addr(0, 0, 0, 0, 0, 0, 9, 0), // ::9:0 + IPv6Addr(0, 0, 0, 0, 0, 8, 0, 0), // ::8:0:0 + IPv6Addr(0, 0, 0, 0, 7, 0, 0, 0), // ::7:0:0:0 + IPv6Addr(0, 0, 0, 6, 0, 0, 0, 0), // 0:0:0:6:: + IPv6Addr(0, 0, 5, 0, 0, 0, 0, 0), // 0:0:5:: + IPv6Addr(0, 4, 0, 0, 0, 0, 0, 0), // 0:4:: + IPv6Addr(1, 0, 0, 0, 0, 0, 0, 0), // 1:: + IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), + IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 2), + IPv6Addr(0xfe80, 0, 0, 0, 0, 0, 0, 1), + IPv6Addr(0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff), + ]; + + for (size_t i = 0; i < expected.length - 1; ++i) + { + assert(expected[i].opCmp(expected[i]) == 0, "IPv6Addr self-comparison failed"); + assert(expected[i].opCmp(expected[i+1]) < 0, "IPv6Addr sorting is incorrect"); + assert(expected[i+1].opCmp(expected[i]) > 0, "IPv6Addr sorting is incorrect"); + } + } + + // InetAddress sorting tests + { + InetAddress[10] expected = [ + // ipv4 sorted first + InetAddress(IPAddr(10, 0, 0, 1), 80), + InetAddress(IPAddr(127, 0, 0, 1), 8080), + InetAddress(IPAddr(192, 168, 1, 1), 80), + InetAddress(IPAddr(192, 168, 1, 1), 443), + + // ipv6 sorted next + InetAddress(IPv6Addr(1, 0, 0, 0, 0, 0, 0, 0), 1024), + InetAddress(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 80, 0, 0), + InetAddress(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 433, 1, 1), + InetAddress(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 8080, 0, 0), // flow=0, scope=0 + InetAddress(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 8080, 0, 1), // flow=0, scope=1 + InetAddress(IPv6Addr(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 8080, 1, 0), // flow=1, scope=0 + ]; + + for (size_t i = 0; i < expected.length - 1; ++i) + { + assert(expected[i].opCmp(expected[i]) == 0, "InetAddress self-comparison failed"); + assert(expected[i].opCmp(expected[i+1]) < 0, "InetAddress sorting is incorrect"); + assert(expected[i+1].opCmp(expected[i]) > 0, "InetAddress sorting is incorrect"); + } + } } diff --git a/src/urt/internal/aa.d b/src/urt/internal/aa.d new file mode 100644 index 0000000..c8e2fa6 --- /dev/null +++ b/src/urt/internal/aa.d @@ -0,0 +1,1048 @@ +/** + * template implementation of associative arrays. + * + * Copyright: Copyright Digital Mars 2000 - 2015, Steven Schveighoffer 2022. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Martin Nowak, Steven Schveighoffer, Rainer Schuetze + * + * Source: $(DRUNTIMESRC core/internal/_newaa.d) + * + * derived from rt/aaA.d + */ +module urt.internal.aa; + +/// AA version for debuggers, bump whenever changing the layout +immutable int _aaVersion = 1; + +import urt.internal.traits : substInout; + +// grow threshold +private enum GROW_NUM = 4; +private enum GROW_DEN = 5; +// shrink threshold +private enum SHRINK_NUM = 1; +private enum SHRINK_DEN = 8; +// grow factor +private enum GROW_FAC = 4; +// growing the AA doubles it's size, so the shrink threshold must be +// smaller than half the grow threshold to have a hysteresis +static assert(GROW_FAC * SHRINK_NUM * GROW_DEN < GROW_NUM * SHRINK_DEN); +// initial load factor (for literals), mean of both thresholds +private enum INIT_NUM = (GROW_DEN * SHRINK_NUM + GROW_NUM * SHRINK_DEN) / 2; +private enum INIT_DEN = SHRINK_DEN * GROW_DEN; + +private enum INIT_NUM_BUCKETS = 8; +// magic hash constants to distinguish empty, deleted, and filled buckets +private enum HASH_EMPTY = 0; +private enum HASH_DELETED = 0x1; +private enum HASH_FILLED_MARK = size_t(1) << 8 * size_t.sizeof - 1; + +/// AA wrapper +struct AA(K, V) +{ + Impl!(K,V)* impl; + alias impl this; + + @property bool empty() const pure nothrow @nogc @safe + { + pragma(inline, true); + return impl is null || !impl.length; + } + @property size_t length() const pure nothrow @nogc @safe + { + pragma(inline, true); + return impl is null ? 0 : impl.length; + } +} + +/// like urt.internal.traits.Unconst, but stripping inout, too +private template Unconstify(T : const U, U) +{ + static if (is(U == inout V, V)) + alias Unconstify = V; + else + alias Unconstify = U; +} + +ref _refAA(K, V)(ref V[K] aa) @trusted +{ + pragma(inline, true); + return *(cast(AA!(substInout!K, substInout!V)*)&aa); +} + +auto _toAA(K, V)(const V[K] aa) @trusted +{ + pragma(inline, true); + return *(cast(const(AA!(K, V))*)&aa); +} + +auto _toAA(K, V)(inout V[K] aa) @trusted +{ + pragma(inline, true); + return *(cast(inout(AA!(K, V))*)&aa); +} + +// for backward compatibility, but should be deprecated +auto _toAA(K, V)(shared const V[K] aa) @trusted +{ + pragma(inline, true); + return *(cast(AA!(K, V)*)&aa); +} + +// for backward compatibility, but should be deprecated +auto _toAA(K, V)(shared V[K] aa) @trusted +{ + pragma(inline, true); + return *(cast(AA!(K, V)*)&aa); +} + +// resolve ambiguity for immutable converting to const and shared const +auto _toAA(K, V)(immutable V[K] aa) @trusted +{ + pragma(inline, true); + return *(cast(AA!(K, V)*)&aa); +} + +static struct Entry(K, V) +{ + K key; + V value; +} + +// backward compatibility conversions +private ref compat_key(K, K2)(ref K2 key) +{ + pragma(inline, true); + static if (is(K2 == const(char)[]) && is(K == string)) + return (ref (ref return K2 k2) @trusted => *cast(string*)&k2)(key); + else + return key; +} + +private void _aaMove(V)(ref V src, ref V dst) @trusted +{ + import urt.mem : memcpy, memset; + // move without postblit!? + memcpy(&dst, &src, V.sizeof); + static if (__traits(isZeroInit, V)) + memset(&src, 0, V.sizeof); + else + memcpy(&src, &V.init, V.sizeof); +} + +// mimick behaviour of rt.aaA for initialization +Entry!(K, V)* _newEntry(K, V)(ref K key, ref V value) +{ + static if (__traits(compiles, new Entry!(K, V)(key, value))) + { + auto entry = new Entry!(K, V)(key, value); + } + else static if (__traits(compiles, { K k; new Entry!(K, V)(k); })) + { + auto entry = new Entry!(K, V)(key); + _aaMove(value, entry.value); + } + else + { + auto entry = new Entry!(K, V); + _aaMove(key, entry.key); + _aaMove(value, entry.value); + } + return entry; +} + +// mimick behaviour of rt.aaA for initialization +Entry!(K, V)* _newEntry(K, V, K2)(ref K2 key) +{ + static if (__traits(compiles, new Entry!(K, V)(key)) && + !(is(V == struct) && __traits(isNested, V))) // not detected by "compiles" + { + auto entry = new Entry!(K, V)(key); + } + else static if (__traits(compiles, { K2 k; new Entry!(K, V)(k, V.init); })) + { + // with disabled ctor for V + auto entry = new Entry!(K, V)(key, V.init); + } + else + { + // with disabled ctor for K and V + auto entry = new Entry!(K, V); + entry.key = key; + } + static if (!__traits(isZeroInit, V)) + { + () @trusted { (cast(ubyte*)&entry.value)[0..V.sizeof] = 0; }(); + } + return entry; +} + +template pure_hashOf(K) +{ + static if (__traits(compiles, function hash_t(scope const ref K key) pure nothrow @nogc @trusted { return hashOf(cast()key); })) + { + // avoid wrapper call in debug builds if pure nothrow @nogc is inferred + pragma(inline, true) + hash_t pure_hashOf(scope const ref K key) @trusted { return hashOf(cast()key); } + } + else + { + // for backward compatibility, do not require const in hashOf() + hash_t wrap_hashOf(K)(scope const ref K key) @trusted { return hashOf(cast()key); } + enum pure_hashOf = cast(hash_t function(scope ref const K key) pure nothrow @nogc @safe) &wrap_hashOf!K; + } +} + +// for backward compatibilty pretend the comparison is @safe, pure, etc +// this also breaks cyclic inference on recursive data types +template pure_keyEqual(K1, K2 = K1) +{ + static if (__traits(compiles, function bool(ref const K1 k1, ref const K2 k2) pure nothrow @nogc @trusted { return cast()k1 == cast()k2; })) + { + // avoid wrapper call in debug builds if pure nothrow @nogc is inferred + pragma(inline, true) + bool pure_keyEqual(ref const K1 k1, ref const K2 k2) @trusted { return cast()k1 == cast()k2; } + } + else + { + bool keyEqual(ref const K1 k1, ref const K2 k2) @trusted { return cast()k1 == cast()k2; } + enum pure_keyEqual = cast(bool function(ref const K1, ref const K2) pure nothrow @nogc @safe) &keyEqual; + } +} + +private struct Impl(K, V) +{ +private: + alias Bucket = .Bucket!(K, V); + + this(size_t sz /* = INIT_NUM_BUCKETS */) nothrow + { + buckets = alloc_buckets(sz); + first_used = cast(uint) buckets.length; + + // only for binary compatibility + entry_ti = typeid(Entry!(K, V)); + hash_fn = delegate size_t (scope ref const K key) nothrow pure @nogc @safe { + return pure_hashOf!K(key); + }; + + key_sz = cast(uint) K.sizeof; + val_sz = cast(uint) V.sizeof; + val_off = cast(uint) talign(key_sz, V.alignof); + + enum ctflags = () { + import urt.internal.traits; + Impl.Flags flags; + static if (__traits(hasPostblit, K)) + flags |= flags.key_has_postblit; + static if (hasIndirections!K || hasIndirections!V) + flags |= flags.has_pointers; + return flags; + } (); + flags = ctflags; + } + + Bucket[] buckets; + uint used; + uint deleted; + const(TypeInfo) entry_ti; // only for binary compatibility + uint first_used; + immutable uint key_sz; // only for binary compatibility + immutable uint val_sz; // only for binary compatibility + immutable uint val_off; // only for binary compatibility + Flags flags; // only for binary compatibility + size_t delegate(scope ref const K) nothrow pure @nogc @safe hash_fn; + + enum Flags : ubyte + { + none = 0x0, + key_has_postblit = 0x1, + has_pointers = 0x2, + } + + @property size_t length() const pure nothrow @nogc @safe + { + pragma(inline, true); + assert(used >= deleted); + return used - deleted; + } + + @property size_t dim() const pure nothrow @nogc @safe + { + pragma(inline, true); + return buckets.length; + } + + @property size_t mask() const pure nothrow @nogc @safe + { + pragma(inline, true); + return dim - 1; + } + + // find the first slot to insert a value with hash + size_t find_slot_insert(size_t hash) const pure nothrow @nogc @safe + { + for (size_t i = hash & mask, j = 1;; ++j) + { + if (!buckets[i].filled) + return i; + i = (i + j) & mask; + } + } + + // lookup a key + inout(Bucket)* find_slot_lookup(K2)(size_t hash, scope ref const K2 key) inout pure @safe nothrow + { + for (size_t i = hash & mask, j = 1;; ++j) + { + auto b = &buckets[i]; // avoid multiple bounds checks + if (b.hash == hash && b.entry) + if (pure_keyEqual!(K2, K)(key, b.entry.key)) + return b; + if (b.empty) + return null; + i = (i + j) & mask; + } + } + + void grow() pure nothrow @safe + { + // If there are so many deleted entries, that growing would push us + // below the shrink threshold, we just purge deleted entries instead. + if (length * SHRINK_DEN < GROW_FAC * dim * SHRINK_NUM) + resize(dim); + else + resize(GROW_FAC * dim); + } + + void shrink() pure nothrow @safe + { + if (dim > INIT_NUM_BUCKETS) + resize(dim / GROW_FAC); + } + + void resize(size_t ndim) pure nothrow @safe + { + auto obuckets = buckets; + buckets = alloc_buckets(ndim); + + foreach (ref b; obuckets[first_used .. $]) + if (b.filled) + buckets[find_slot_insert(b.hash)] = b; + + first_used = 0; + used -= deleted; + deleted = 0; + obuckets.length = 0; // safe to free b/c impossible to reference, but doesn't really free + } + + void clear() pure nothrow + { + // clear all data, but don't change bucket array length + buckets[first_used .. $] = Bucket.init; + deleted = used = 0; + first_used = cast(uint) dim; + } + + size_t calc_hash(K2)(ref K2 key) const nothrow pure @nogc @safe + { + static if(is(K2* : K*)) // ref compatible? + hash_t hash = pure_hashOf!K(key); + else + hash_t hash = pure_hashOf!K2(key); + // highest bit is set to distinguish empty/deleted from filled buckets + return mix(hash) | HASH_FILLED_MARK; + } + + static Bucket[] alloc_buckets(size_t dim) pure nothrow @safe + { + // could allocate with BlkAttr.NO_INTERIOR, but that does not combine + // well with arrays and type info for precise scanning + return new Bucket[dim]; + } +} + +//============================================================================== +// Bucket +//------------------------------------------------------------------------------ + +private struct Bucket(K, V) +{ +private pure nothrow @nogc: + size_t hash; + Entry!(K, V)* entry; + + @property bool empty() const + { + pragma(inline, true); + return hash == HASH_EMPTY; + } + + @property bool deleted() const + { + pragma(inline, true); + return hash == HASH_DELETED; + } + + @property bool filled() const @safe + { + pragma(inline, true); + return cast(ptrdiff_t) hash < 0; + } +} + +//============================================================================== +// Helper functions +//------------------------------------------------------------------------------ + +private size_t talign(size_t tsize, size_t algn) @safe pure nothrow @nogc +{ + immutable mask = algn - 1; + assert(!(mask & algn)); + return (tsize + mask) & ~mask; +} + +// mix hash to "fix" bad hash functions +private size_t mix(size_t h) @safe pure nothrow @nogc +{ + // final mix function of MurmurHash2 + enum m = 0x5bd1e995; + h ^= h >> 13; + h *= m; + h ^= h >> 15; + return h; +} + +private size_t next_pow2(const size_t n) pure nothrow @nogc @safe +{ + import urt.internal.bitop : bsr; + + if (!n) + return 1; + + const is_power_of_2 = !((n - 1) & n); + return 1 << (bsr(n) + !is_power_of_2); +} + +pure nothrow @nogc unittest +{ + // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + foreach (const n, const pow2; [1, 1, 2, 4, 4, 8, 8, 8, 8, 16]) + assert(next_pow2(n) == pow2); +} + +//============================================================================== +// API Implementation +//------------------------------------------------------------------------------ + +/** Allocate associative array data. + * Called for `new SomeAA` expression. + * Returns: + * A new associative array. + * Note: + * not supported in CTFE + */ +V[K] _d_aaNew(K, V)() +{ + AA!(K, V) aa; + aa.impl = new Impl!(K,V)(INIT_NUM_BUCKETS); + return *cast(V[K]*)&aa; +} + +/// Determine number of entries in associative array. +/// Note: +/// emulated by the compiler during CTFE +size_t _d_aaLen(K, V)(inout V[K] a) +{ + auto aa = _toAA!(K, V)(a); + return aa ? aa.length : 0; +} + +/****************************** + * Lookup key in aa. + * Called only from implementation of (aa[key]) expressions when value is mutable. + * Params: + * aa = associative array + * key = reference to the key value + * found = returns whether the key was found or a new entry was added + * Returns: + * if key was in the aa, a mutable pointer to the existing value. + * If key was not in the aa, a mutable pointer to newly inserted value which + * is set to zero + */ +V* _d_aaGetY(K, V, T : V1[K1], K1, V1, K2)(auto ref scope T aa, auto ref K2 key, out bool found) +{ + ref aax = cast(V[K])cast(V1[K1])aa; // remove outer const from T + return _aaGetX!(K, V)(aax, key, found); +} + +/****************************** + * Lookup key in aa. + * Called only from implementation of require, update and _d_aaGetY + * Params: + * a = associative array + * key = reference to the key value + * found = true if the value was found + * Returns: + * if key was in the aa, a mutable pointer to the existing value. + * If key was not in the aa, a mutable pointer to newly inserted value which + * is set to V.init + */ +V* _aaGetX(K, V, K2)(auto ref scope V[K] a, auto ref K2 key, out bool found) +{ + ref aa = _refAA!(K, V)(a); + + // lazily alloc implementation + if (aa is null) + { + aa.impl = new Impl!(K, V)(INIT_NUM_BUCKETS); + } + + ref key2 = compat_key!(K)(key); + + // get hash and bucket for key + immutable hash = aa.calc_hash(key2); + + // found a value => return it + if (auto p = aa.find_slot_lookup(hash, key2)) + { + found = true; + return &p.entry.value; + } + + auto pi = aa.find_slot_insert(hash); + if (aa.buckets[pi].deleted) + --aa.deleted; + // check load factor and possibly grow + else if (++aa.used * GROW_DEN > aa.dim * GROW_NUM) + { + aa.grow(); + pi = aa.find_slot_insert(hash); + assert(aa.buckets[pi].empty); + } + + // update search cache and allocate entry + aa.first_used = min(aa.first_used, cast(uint)pi); + ref p = aa.buckets[pi]; + p.hash = hash; + p.entry = _newEntry!(K, V)(key2); + return &p.entry.value; +} + +/****************************** + * Lookup key in aa. + * Called only from implementation of (aa[key]) expressions when value is not mutable. + * Params: + * aa = associative array + * key = key value + * Returns: + * pointer to value if present, null otherwise + */ +auto _d_aaGetRvalueX(K, V, K2)(inout V[K] aa, auto ref scope K2 key) +{ + return _d_aaIn(aa, key); +} + +/// ditto +auto _d_aaGetRvalueX(K, V, K2)(shared(V[K]) aa, auto ref scope K2 key) +{ + // accept shared for backward compatibility, should be deprecated + return cast(shared(V)*)_d_aaIn(cast(V[K]) aa, key); +} + +/// ditto +auto _d_aaGetRvalueX(K, V, K2)(shared const(V[K]) aa, auto ref scope K2 key) +{ + // accept shared for backward compatibility, should be deprecated + return cast(const shared(V)*)_d_aaIn(cast(V[K]) aa, key); +} + +/// ditto +auto _d_aaGetRvalueX(K, V, K2)(immutable(V[K]) aa, auto ref scope K2 key) +{ + // resolve ambiguity for immutable converting to const and shared const + return _d_aaIn((() @trusted => cast(V[K]) aa) (), key); +} + +/*********************************** + * Creates a new associative array of the same size and copies the contents of + * the associative array into it. + * Params: + * a = The associative array. + */ +auto _aaDup(T : V[K], K, V)(T a) +{ + auto aa = _toAA!(K, V)(a); + immutable len = aa.length; + if (len == 0) + return null; + + auto impl = new Impl!(K, V)(aa.dim); + // copy the entries + bool same_hash = aa.hash_fn == impl.hash_fn; // can be different if coming from template/rt + foreach (b; aa.buckets[aa.first_used .. $]) + { + if (!b.filled) + continue; + hash_t hash = same_hash ? b.hash : impl.calc_hash(b.entry.key); + auto pi = impl.find_slot_insert(hash); + auto p = &impl.buckets[pi]; + p.hash = hash; + p.entry = new Entry!(K, V)(b.entry.key, b.entry.value); + impl.first_used = min(impl.first_used, cast(uint)pi); + } + impl.used = cast(uint) len; + return () @trusted { return *cast(Unconstify!V[K]*)&impl; }(); +} + +/****************************** + * Lookup key in aa. + * Called only from implementation of (key in aa) expressions. + * Params: + * a = associative array opaque pointer + * key = reference to the key value + * Returns: + * pointer to value if present, null otherwise + */ +auto _d_aaIn(T : V[K], K, V, K2)(inout T a, auto ref scope K2 key) +{ + auto aa = _toAA!(K, V)(a); + if (aa.empty) + return null; + + ref key2 = compat_key!(K)(key); + + immutable hash = aa.calc_hash(key2); + if (auto p = aa.find_slot_lookup(hash, key2)) + return &p.entry.value; + return null; +} + +// fake purity for backward compatibility with runtime hooks +private extern(C) bool gc_inFinalizer() pure nothrow @safe; + +/// Delete entry scope const AA, return true if it was present +auto _d_aaDel(T : V[K], K, V, K2)(T a, auto ref K2 key) +{ + auto aa = _toAA!(K, V)(a); + if (aa.empty) + return false; + + ref key2 = compat_key!(K)(key); + + immutable hash = aa.calc_hash(key2); + if (auto p = aa.find_slot_lookup(hash, key2)) + { + // clear entry + p.hash = HASH_DELETED; + p.entry = null; + + ++aa.deleted; + // `shrink` reallocates, and allocating from a finalizer leads to + // InvalidMemoryError: https://issues.dlang.org/show_bug.cgi?id=21442 + if (aa.length * SHRINK_DEN < aa.dim * SHRINK_NUM && !__ctfe && !gc_inFinalizer()) + aa.shrink(); + + return true; + } + return false; +} + +/// Remove all elements from AA. +void _aaClear(K, V)(V[K] a) +{ + auto aa = _toAA!(K, V)(a); + if (!aa.empty) + { + aa.clear(); + } +} + +/// Rehash AA +V[K] _aaRehash(K, V)(V[K] a) +{ + auto aa = _toAA!(K, V)(a); + if (!aa.empty) + aa.resize(next_pow2(INIT_DEN * aa.length / INIT_NUM)); + return a; +} + +/// Return a GC allocated array of all values +auto _aaValues(K, V)(inout V[K] a) +{ + auto aa = _toAA!(K, V)(a); + if (aa.empty) + return null; + + static if (__traits(compiles, { V val = aa.buckets[0].entry.value; } )) + V[] res; // if value has no const indirections + else + typeof([aa.buckets[0].entry.value]) res; // as mutable as it can get + res = new typeof(res[0])[aa.length]; + + if (false) // never execute, but infer function attributes from this operation + res ~= aa.buckets[0].entry.value; + + size_t i = 0; + foreach (b; aa.buckets[aa.first_used .. $]) + { + if (!b.filled) + continue; + import urt.lifetime; + () @trusted { copyEmplace(b.entry.value, res[i++]); }(); + } + return res; +} + +/// Return a GC allocated array of all keys +auto _aaKeys(K, V)(inout V[K] a) +{ + auto aa = _toAA!(K, V)(a); + if (aa.empty) + return null; + + static if (__traits(compiles, { K key = aa.buckets[0].entry.key; } )) + K[] res; // if key has no const indirections + else + typeof([aa.buckets[0].entry.key]) res; // as mutable as it can get + res = new typeof(res[0])[aa.length]; + + if (false) // never execute, but infer function attributes from this operation + res ~= aa.buckets[0].entry.key; + + size_t i = 0; + foreach (b; aa.buckets[aa.first_used .. $]) + { + if (!b.filled) + continue; + // res ~= b.entry.key; + import urt.lifetime; + () @trusted { copyEmplace(b.entry.key, res[i++]); }(); + } + return res; +} + +/// foreach opApply over all values +/// Note: +/// emulated by the compiler during CTFE +int _d_aaApply(K, V, DG)(inout V[K] a, DG dg) +{ + auto aa = () @trusted { return cast(AA!(K, V))_toAA!(K, V)(a); }(); + if (aa.empty) + return 0; + + foreach (b; aa.buckets) + { + if (!b.filled) + continue; + if (auto res = dg(b.entry.value)) + return res; + } + return 0; +} + +int _d_aaApply(K, V, DG)(shared V[K] a, DG dg) +{ + return _d_aaApply!(K, V, DG)(cast(V[K]) a, dg); +} + +int _d_aaApply(K, V, DG)(shared const V[K] a, DG dg) +{ + return _d_aaApply!(K, V, DG)(cast(const V[K]) a, dg); +} + +int _d_aaApply(K, V, DG)(immutable V[K] a, DG dg) +{ + return _d_aaApply!(K, V, DG)(cast(const V[K]) a, dg); +} + +/// foreach opApply over all key/value pairs +/// Note: +/// emulated by the compiler during CTFE +int _d_aaApply2(K, V, DG)(inout V[K] a, DG dg) +{ + auto aa = () @trusted { return cast(AA!(K, V))_toAA!(K, V)(a); }(); + if (aa.empty) + return 0; + + foreach (b; aa.buckets) + { + if (!b.filled) + continue; + if (auto res = dg(b.entry.key, b.entry.value)) + return res; + } + return 0; +} + +int _d_aaApply2(K, V, DG)(shared V[K] a, DG dg) +{ + return _d_aaApply2!(K, V, DG)(cast(V[K]) a, dg); +} + +int _d_aaApply2(K, V, DG)(shared const V[K] a, DG dg) +{ + return _d_aaApply2!(K, V, DG)(cast(const V[K]) a, dg); +} + +int _d_aaApply2(K, V, DG)(immutable V[K] a, DG dg) +{ + return _d_aaApply2!(K, V, DG)(cast(const V[K]) a, dg); +} + +/** Construct an associative array of type ti from corresponding keys and values. + * Called for an AA literal `[k1:v1, k2:v2]`. + * Params: + * keys = array of keys + * vals = array of values + * Returns: + * A new associative array opaque pointer, or null if `keys` is empty. + */ +Impl!(K, V)* _d_assocarrayliteralTX(K, V)(K[] keys, V[] vals) +{ + assert(keys.length == vals.length); + + immutable length = keys.length; + + if (!length) + return null; + + auto aa = new Impl!(K, V)(next_pow2(INIT_DEN * length / INIT_NUM)); + size_t duplicates = 0; + foreach (i; 0 .. length) + { + immutable hash = aa.calc_hash(keys[i]); + + auto p = aa.find_slot_lookup!K(hash, keys[i]); + if (p) + { + static if (__traits(compiles, p.entry.value = vals[i])) // immutable? + p.entry.value = vals[i]; + else + p.entry = _newEntry!(K, V)(keys[i], vals[i]); + duplicates++; + continue; + } + auto pi = aa.find_slot_insert(hash); + p = &aa.buckets[pi]; + p.hash = hash; + p.entry = _newEntry!(K, V)(keys[i], vals[i]); // todo: move key and value? + aa.first_used = min(aa.first_used, cast(uint)pi); + } + aa.used = cast(uint) (length - duplicates); + return aa; +} + +/// compares 2 AAs for equality +bool _aaEqual(T : AA!(K, V), K, V)(scope T aa1, scope T aa2) +{ + if (aa1 is aa2) + return true; + + immutable len = aa1.length; + if (len != aa2.length) + return false; + + if (!len) // both empty + return true; + + bool same_hash = aa1.hash_fn == aa2.hash_fn; // can be different if coming from template/rt + // compare the entries + foreach (b1; aa1.buckets[aa1.first_used .. $]) + { + if (!b1.filled) + continue; + hash_t hash = same_hash ? b1.hash : aa2.calc_hash(b1.entry.key); + auto pb2 = aa2.find_slot_lookup!K(hash, b1.entry.key); + if (pb2 is null || !pure_keyEqual!(V, V)(b1.entry.value, pb2.entry.value)) // rarely, inference on opEqual breaks builds here + return false; + } + return true; +} + +/// compares 2 AAs for equality (compiler hook) +bool _d_aaEqual(K, V)(scope const V[K] a1, scope const V[K] a2) +{ + scope aa1 = _toAA!(K, V)(a1); + scope aa2 = _toAA!(K, V)(a2); + return _aaEqual(aa1, aa2); +} + +/// callback from TypeInfo_AssociativeArray.equals (ignore const for now) +bool _aaOpEqual(K, V)(scope /* const */ AA!(K, V)* aa1, scope /* const */ AA!(K, V)* aa2) +{ + return _aaEqual(*aa1, *aa2); +} + +/// compute a hash callback from TypeInfo_AssociativeArray.xtoHash (ignore scope const for now) +hash_t _aaGetHash(K, V)(/* scope const */ AA!(K, V)* paa) +{ + const aa = *paa; + + if (aa.empty) + return 0; + + size_t h; + foreach (b; aa.buckets) + { + // use addition here, so that hash is independent of element order + if (b.filled) + h += hashOf(pure_hashOf!V(b.entry.value), pure_hashOf!K(b.entry.key)); + } + + return h; +} + +/** + * _aaRange implements a ForwardRange + */ +struct AARange(K, V) +{ + alias Key = substInout!K; + alias Value = substInout!V; + + Impl!(Key, Value)* impl; + size_t idx; + alias impl this; +} + +AARange!(K, V) _aaRange(K, V)(V[K] a) +{ + auto aa = _toAA!(K, V)(a); + if (!aa) + return AARange!(K, V)(); + + foreach (i; aa.first_used .. aa.dim) + { + if (aa.buckets[i].filled) + return AARange!(K, V)(aa, i); + } + return AARange!(K, V)(aa, aa.dim); +} + +bool _aaRangeEmpty(K, V)(AARange!(K, V) r) +{ + return r.impl is null || r.idx >= r.dim; +} + +K* _aaRangeFrontKey(K, V)(AARange!(K, V) r) +{ + assert(!_aaRangeEmpty(r)); + if (r.idx >= r.dim) + return null; + auto entry = r.buckets[r.idx].entry; + return entry is null ? null : &r.buckets[r.idx].entry.key; +} + +V* _aaRangeFrontValue(K, V)(AARange!(K, V) r) +{ + assert(!_aaRangeEmpty(r)); + if (r.idx >= r.dim) + return null; + + auto entry = r.buckets[r.idx].entry; + return entry is null ? null : &r.buckets[r.idx].entry.value; +} + +void _aaRangePopFront(K, V)(ref AARange!(K, V) r) +{ + if (r.idx >= r.dim) return; + for (++r.idx; r.idx < r.dim; ++r.idx) + { + if (r.buckets[r.idx].filled) + break; + } +} + +// test postblit for AA literals +// Disabled: uses core.memory/GC and AA syntax that can't resolve inside +// this module when compiled as source (circular object ↔ newaa import). +version (none) unittest +{ + import core.memory; + + static struct T + { + ubyte field; + static size_t postblit, dtor; + this(this) + { + ++postblit; + } + + ~this() + { + ++dtor; + } + } + + T t; + auto aa1 = [0 : t, 1 : t]; + assert(T.dtor == 2 && T.postblit == 4); + aa1[0] = t; + assert(T.dtor == 3 && T.postblit == 5); + + T.dtor = 0; + T.postblit = 0; + + auto aa2 = [0 : t, 1 : t, 0 : t]; // literal with duplicate key => value overwritten + assert(T.dtor == 4 && T.postblit == 6); + + T.dtor = 0; + T.postblit = 0; + + auto aa3 = [t : 0]; + assert(T.dtor == 1 && T.postblit == 2); + aa3[t] = 1; + assert(T.dtor == 1 && T.postblit == 2); + aa3.remove(t); + assert(T.dtor == 1 && T.postblit == 2); + aa3[t] = 2; + assert(T.dtor == 1 && T.postblit == 3); + + // dtor will be called by GC finalizers + aa1 = null; + aa2 = null; + aa3 = null; + auto dtor1 = typeid(TypeInfo_AssociativeArray.Entry!(int, T)).xdtor; + GC.runFinalizers((cast(char*)dtor1)[0 .. 1]); + auto dtor2 = typeid(TypeInfo_AssociativeArray.Entry!(T, int)).xdtor; + GC.runFinalizers((cast(char*)dtor2)[0 .. 1]); + assert(T.dtor == 7 && T.postblit == 3); +} + +// create a binary-compatible AA structure that can be used directly as an +// associative array. +// NOTE: this must only be called during CTFE +AA!(K, V) makeAA(K, V)(V[K] src) @trusted +{ + assert(__ctfe, "makeAA Must only be called at compile time"); + // Iterate the built-in AA directly - .keys/.values are UFCS properties + // that require druntime hooks we don't provide. + K[] keys; + V[] values; + foreach (k, v; src) + { + keys ~= k; + values ~= v; + } + auto impl = _d_assocarrayliteralTX!(K, V)(keys, values); + return AA!(K, V)(impl); +} + +// Disabled: AA-indexing lowering inside this module creates a circular +// import (object → core.internal.newaa → object) that prevents template +// resolution when compiled as source rather than a pre-compiled library. +version (none) unittest +{ + static struct Foo + { + ubyte x; + double d; + } + static int[Foo] utaa = [Foo(1, 2.0) : 5]; + auto k = Foo(1, 2.0); + // verify that getHash doesn't match hashOf for Foo + assert(typeid(Foo).getHash(&k) != hashOf(k)); + assert(utaa[Foo(1, 2.0)] == 5); +} diff --git a/src/urt/internal/bitop.d b/src/urt/internal/bitop.d new file mode 100644 index 0000000..cb68fe7 --- /dev/null +++ b/src/urt/internal/bitop.d @@ -0,0 +1,283 @@ +// TODO: DISSOLVE THIS FILE... +module urt.internal.bitop; + +nothrow @nogc @safe: + +version (D_InlineAsm_X86_64) + version = AsmX86; +else version (D_InlineAsm_X86) + version = AsmX86; + +version (X86_64) + version = AnyX86; +else version (X86) + version = AnyX86; + +// Use to implement 64-bit bitops on 32-bit arch. +private union Split64 +{ + ulong u64; + struct + { + version (LittleEndian) + { + uint lo; + uint hi; + } + else + { + uint hi; + uint lo; + } + } + + pragma(inline, true) + this(ulong u64) @safe pure nothrow @nogc + { + if (__ctfe) + { + lo = cast(uint) u64; + hi = cast(uint) (u64 >>> 32); + } + else + this.u64 = u64; + } +} + +/** + * Scans the bits in v starting with bit 0, looking + * for the first set bit. + * Returns: + * The bit number of the first bit set. + * The return value is undefined if v is zero. + */ +int bsf(uint v) pure +{ + pragma(inline, false); // so intrinsic detection will work + return softBsf!uint(v); +} + +/// ditto +int bsf(ulong v) pure +{ + static if (size_t.sizeof == ulong.sizeof) // 64 bit code gen + { + pragma(inline, false); // so intrinsic detection will work + return softBsf!ulong(v); + } + else + { + const sv = Split64(v); + return (sv.lo == 0)? + bsf(sv.hi) + 32 : + bsf(sv.lo); + } +} + +/** + * Scans the bits in v from the most significant bit + * to the least significant bit, looking + * for the first set bit. + * Returns: + * The bit number of the first bit set. + * The return value is undefined if v is zero. + */ +int bsr(uint v) pure +{ + pragma(inline, false); // so intrinsic detection will work + return softBsr!uint(v); +} + +/// ditto +int bsr(ulong v) pure +{ + static if (size_t.sizeof == ulong.sizeof) // 64 bit code gen + { + pragma(inline, false); // so intrinsic detection will work + return softBsr!ulong(v); + } + else + { + const sv = Split64(v); + return (sv.hi == 0)? + bsr(sv.lo) : + bsr(sv.hi) + 32; + } +} + +private alias softBsf(N) = softScan!(N, true); +private alias softBsr(N) = softScan!(N, false); + +private int softScan(N, bool forward)(N v) pure + if (is(N == uint) || is(N == ulong)) +{ + if (!v) + return -1; + + enum mask(ulong lo) = forward ? cast(N) lo : cast(N)~lo; + enum inc(int up) = forward ? up : -up; + + N x; + int ret; + static if (is(N == ulong)) + { + x = v & mask!0x0000_0000_FFFF_FFFFL; + if (x) + { + v = x; + ret = forward ? 0 : 63; + } + else + ret = forward ? 32 : 31; + + x = v & mask!0x0000_FFFF_0000_FFFFL; + if (x) + v = x; + else + ret += inc!16; + } + else static if (is(N == uint)) + { + x = v & mask!0x0000_FFFF; + if (x) + { + v = x; + ret = forward ? 0 : 31; + } + else + ret = forward ? 16 : 15; + } + else + static assert(false); + + x = v & mask!0x00FF_00FF_00FF_00FFL; + if (x) + v = x; + else + ret += inc!8; + + x = v & mask!0x0F0F_0F0F_0F0F_0F0FL; + if (x) + v = x; + else + ret += inc!4; + + x = v & mask!0x3333_3333_3333_3333L; + if (x) + v = x; + else + ret += inc!2; + + x = v & mask!0x5555_5555_5555_5555L; + if (!x) + ret += inc!1; + + return ret; +} + +/** + * Tests the bit. + */ +int bt(const scope size_t* p, size_t bitnum) pure @system +{ + static if (size_t.sizeof == 8) + return ((p[bitnum >> 6] & (1L << (bitnum & 63)))) != 0; + else static if (size_t.sizeof == 4) + return ((p[bitnum >> 5] & (1 << (bitnum & 31)))) != 0; + else + static assert(0); +} + +/** + * Tests and complements the bit. + */ +int btc(size_t* p, size_t bitnum) pure @system; + +/** + * Tests and resets (sets to 0) the bit. + */ +int btr(size_t* p, size_t bitnum) pure @system; + +/** + * Tests and sets the bit. + */ +int bts(size_t* p, size_t bitnum) pure @system; + +/** + * Swaps bytes in a 2 byte ushort. + */ +pragma(inline, false) +ushort byteswap(ushort x) pure +{ + return cast(ushort) (((x >> 8) & 0xFF) | ((x << 8) & 0xFF00u)); +} + +/** + * Swaps bytes in a 4 byte uint end-to-end. + */ +uint bswap(uint v) pure; + +/** + * Swaps bytes in an 8 byte ulong end-to-end. + */ +ulong bswap(ulong v) pure; + +version (DigitalMars) version (AnyX86) @system // not pure +{ + ubyte inp(uint port_address); + ushort inpw(uint port_address); + uint inpl(uint port_address); + ubyte outp(uint port_address, ubyte value); + ushort outpw(uint port_address, ushort value); + uint outpl(uint port_address, uint value); +} + +/** + * Calculates the number of set bits in an integer. + */ +int popcnt(uint x) pure +{ + return softPopcnt!uint(x); +} + +/// ditto +int popcnt(ulong x) pure +{ + static if (size_t.sizeof == uint.sizeof) + { + const sx = Split64(x); + return softPopcnt!uint(sx.lo) + softPopcnt!uint(sx.hi); + } + else static if (size_t.sizeof == ulong.sizeof) + { + return softPopcnt!ulong(x); + } + else + static assert(false); +} + +version (DigitalMars) version (AnyX86) +{ + ushort _popcnt( ushort x ) pure; + int _popcnt( uint x ) pure; + version (X86_64) + { + int _popcnt( ulong x ) pure; + } +} + +private int softPopcnt(N)(N x) pure + if (is(N == uint) || is(N == ulong)) +{ + enum mask1 = cast(N) 0x5555_5555_5555_5555L; + x = x - ((x>>1) & mask1); + enum mask2a = cast(N) 0xCCCC_CCCC_CCCC_CCCCL; + enum mask2b = cast(N) 0x3333_3333_3333_3333L; + x = ((x & mask2a)>>2) + (x & mask2b); + enum mask4 = cast(N) 0x0F0F_0F0F_0F0F_0F0FL; + x = (x + (x >> 4)) & mask4; + enum shiftbits = is(N == uint)? 24 : 56; + enum maskMul = cast(N) 0x0101_0101_0101_0101L; + x = (x * maskMul) >> shiftbits; + return cast(int) x; +} diff --git a/src/urt/internal/dwarfeh.d b/src/urt/internal/dwarfeh.d new file mode 100644 index 0000000..71a4f87 --- /dev/null +++ b/src/urt/internal/dwarfeh.d @@ -0,0 +1,779 @@ +/// DWARF / Itanium-ABI exception-handling runtime. +/// +/// Shared between DMD-Linux and LDC on every non-Windows target +/// (including bare-metal LDC like BL808). pragma(mangle) picks the +/// compiler-specific symbol names: +/// DMD: _d_throwdwarf, __dmd_begin_catch, __dmd_personality_v0 +/// LDC: _d_throw_exception, _d_eh_enter_catch, _d_eh_personality +/// +/// Lives in urt.internal (not urt.driver.posix) so it compiles on bare-metal +/// builds whose Makefile source list excludes urt/driver/posix/**. +/// +/// Ported from druntime rt/dwarfeh.d. +module urt.internal.dwarfeh; + +version (Windows) {} else: + +import urt.internal.exception : ClassInfo, _d_isbaseof, _d_createTrace; + + +// --------------------------------------------------------------------- +// DWARF exception handling (shared by DMD and LDC) +// +// Uses the GCC/DWARF unwinder (libgcc_s / libunwind). +// pragma(mangle) selects the compiler-specific symbol names: +// DMD: _d_throwdwarf, __dmd_begin_catch, __dmd_personality_v0 +// LDC: _d_throw_exception, _d_eh_enter_catch, _d_eh_personality +// +// Ported from druntime rt/dwarfeh.d. +// Copyright: Digital Mars 2015-2016 (original), uRT authors (port). +// License: Boost Software License 1.0 +// --------------------------------------------------------------------- + +// --- libunwind / libgcc_s bindings ----------------------------------- + +private: + +alias _Unwind_Ptr = size_t; +alias _Unwind_Word = size_t; +alias _Unwind_Exception_Class = ulong; +alias _uleb128_t = size_t; +alias _sleb128_t = ptrdiff_t; + +alias _Unwind_Reason_Code = int; +enum +{ + _URC_NO_REASON = 0, + _URC_FOREIGN_EXCEPTION_CAUGHT = 1, + _URC_FATAL_PHASE2_ERROR = 2, + _URC_FATAL_PHASE1_ERROR = 3, + _URC_NORMAL_STOP = 4, + _URC_END_OF_STACK = 5, + _URC_HANDLER_FOUND = 6, + _URC_INSTALL_CONTEXT = 7, + _URC_CONTINUE_UNWIND = 8, +} + +alias _Unwind_Action = int; +enum : _Unwind_Action +{ + _UA_SEARCH_PHASE = 1, + _UA_CLEANUP_PHASE = 2, + _UA_HANDLER_FRAME = 4, + _UA_FORCE_UNWIND = 8, +} + +alias _Unwind_Exception_Cleanup_Fn = extern(C) void function( + _Unwind_Reason_Code, _Unwind_Exception*); + +version (X86_64) +{ + align(16) struct _Unwind_Exception + { + _Unwind_Exception_Class exception_class; + _Unwind_Exception_Cleanup_Fn exception_cleanup; + _Unwind_Word private_1; + _Unwind_Word private_2; + } +} +else +{ + align(8) struct _Unwind_Exception + { + _Unwind_Exception_Class exception_class; + _Unwind_Exception_Cleanup_Fn exception_cleanup; + _Unwind_Word private_1; + _Unwind_Word private_2; + } +} + +struct _Unwind_Context; + +extern(C) nothrow @nogc +{ + _Unwind_Reason_Code _Unwind_RaiseException(_Unwind_Exception*); + void _Unwind_DeleteException(_Unwind_Exception*); + _Unwind_Word _Unwind_GetGR(_Unwind_Context*, int); + void _Unwind_SetGR(_Unwind_Context*, int, _Unwind_Word); + _Unwind_Ptr _Unwind_GetIP(_Unwind_Context*); + _Unwind_Ptr _Unwind_GetIPInfo(_Unwind_Context*, int*); + void _Unwind_SetIP(_Unwind_Context*, _Unwind_Ptr); + void* _Unwind_GetLanguageSpecificData(_Unwind_Context*); + _Unwind_Ptr _Unwind_GetRegionStart(_Unwind_Context*); +} + + +// --- ARM EABI unwinder types ---------------------------------------- + +version (ARM) +{ + alias _Unwind_State = int; + enum : _Unwind_State + { + _US_VIRTUAL_UNWIND_FRAME = 0, + _US_UNWIND_FRAME_STARTING = 1, + _US_UNWIND_FRAME_RESUME = 2, + _US_ACTION_MASK = 3, + _US_FORCE_UNWIND = 8, + } + + extern(C) void _Unwind_Complete(_Unwind_Exception*) nothrow @nogc; +} + +// --- EH register numbers (architecture-specific) -------------------- + +version (X86_64) +{ + enum eh_exception_regno = 0; + enum eh_selector_regno = 1; +} +else version (X86) +{ + enum eh_exception_regno = 0; + enum eh_selector_regno = 2; +} +else version (AArch64) +{ + enum eh_exception_regno = 0; + enum eh_selector_regno = 1; +} +else version (ARM) +{ + enum eh_exception_regno = 0; + enum eh_selector_regno = 1; +} +else version (RISCV64) +{ + enum eh_exception_regno = 10; + enum eh_selector_regno = 11; +} +else version (RISCV32) +{ + enum eh_exception_regno = 10; + enum eh_selector_regno = 11; +} +else version (Xtensa) +{ + enum eh_exception_regno = 2; // a2 + enum eh_selector_regno = 3; // a3 +} +else + static assert(0, "Unknown EH register numbers for this architecture"); + +// --- DWARF encoding constants --------------------------------------- + +enum +{ + DW_EH_PE_FORMAT_MASK = 0x0F, + DW_EH_PE_APPL_MASK = 0x70, + DW_EH_PE_indirect = 0x80, + + DW_EH_PE_omit = 0xFF, + DW_EH_PE_ptr = 0x00, + DW_EH_PE_uleb128 = 0x01, + DW_EH_PE_udata2 = 0x02, + DW_EH_PE_udata4 = 0x03, + DW_EH_PE_udata8 = 0x04, + DW_EH_PE_sleb128 = 0x09, + DW_EH_PE_sdata2 = 0x0A, + DW_EH_PE_sdata4 = 0x0B, + DW_EH_PE_sdata8 = 0x0C, + + DW_EH_PE_absptr = 0x00, + DW_EH_PE_pcrel = 0x10, + DW_EH_PE_textrel = 0x20, + DW_EH_PE_datarel = 0x30, + DW_EH_PE_funcrel = 0x40, + DW_EH_PE_aligned = 0x50, +} + +// --- DMD exception class identifier --------------------------------- + +enum _Unwind_Exception_Class dmd_exception_class = + (cast(_Unwind_Exception_Class)'D' << 56) | + (cast(_Unwind_Exception_Class)'M' << 48) | + (cast(_Unwind_Exception_Class)'D' << 40) | + (cast(_Unwind_Exception_Class)'D' << 24); + +// --- Compiler-specific symbol names --------------------------------- + +version (LDC) +{ + private enum throw_mangle = "_d_throw_exception"; + private enum catch_mangle = "_d_eh_enter_catch"; + private enum personality_mangle = "_d_eh_personality"; +} +else +{ + private enum throw_mangle = "_d_throwdwarf"; + private enum catch_mangle = "__dmd_begin_catch"; + private enum personality_mangle = "__dmd_personality_v0"; +} + +// --- ExceptionHeader ------------------------------------------------ + +struct ExceptionHeader +{ + Throwable object; + _Unwind_Exception exception_object; + + int handler; + const(ubyte)* language_specific_data; + _Unwind_Ptr landing_pad; + + ExceptionHeader* next; + + static ExceptionHeader* stack; // thread-local chain + static ExceptionHeader ehstorage; // pre-allocated (one per thread) + + static ExceptionHeader* create(Throwable o) nothrow @nogc + { + import urt.mem.alloc : alloc; + auto eh = &ehstorage; + if (eh.object) + { + eh = cast(ExceptionHeader*)alloc(ExceptionHeader.sizeof).ptr; + if (!eh) + dwarf_terminate(__LINE__); + } + eh.object = o; + eh.exception_object.exception_class = dmd_exception_class; + return eh; + } + + static void release(ExceptionHeader* eh) nothrow @nogc + { + import urt.mem.alloc : free; + *eh = ExceptionHeader.init; + if (eh != &ehstorage) + free(eh[0..1]); + } + + void push() nothrow @nogc + { + next = stack; + stack = &this; + } + + static ExceptionHeader* pop() nothrow @nogc + { + auto eh = stack; + stack = eh.next; + return eh; + } + + static ExceptionHeader* to_exception_header(_Unwind_Exception* eo) nothrow @nogc + { + return cast(ExceptionHeader*)(cast(void*) eo - ExceptionHeader.exception_object.offsetof); + } +} + +// --- Helpers -------------------------------------------------------- + +_Unwind_Ptr read_unaligned(T, bool consume)(ref const(ubyte)* p) nothrow @nogc @trusted +{ + import urt.processor : SupportUnalignedLoadStore; + static if (SupportUnalignedLoadStore) + T value = *cast(T*) p; + else + { + import urt.mem : memcpy; + T value = void; + memcpy(&value, p, T.sizeof); + } + + static if (consume) + p += T.sizeof; + return cast(_Unwind_Ptr) value; +} + +_uleb128_t u_leb128(const(ubyte)** p) nothrow @nogc +{ + auto q = *p; + _uleb128_t result = 0; + uint shift = 0; + while (1) + { + ubyte b = *q++; + result |= cast(_uleb128_t)(b & 0x7F) << shift; + if ((b & 0x80) == 0) + break; + shift += 7; + } + *p = q; + return result; +} + +_sleb128_t s_leb128(const(ubyte)** p) nothrow @nogc +{ + auto q = *p; + ubyte b; + _sleb128_t result = 0; + uint shift = 0; + while (1) + { + b = *q++; + result |= cast(_sleb128_t)(b & 0x7F) << shift; + shift += 7; + if ((b & 0x80) == 0) + break; + } + if (shift < result.sizeof * 8 && (b & 0x40)) + result |= -(cast(_sleb128_t)1 << shift); + *p = q; + return result; +} + +void dwarf_terminate(uint line) nothrow @nogc +{ + import urt.io : writef_to, WriteTarget; + import urt.internal.stdc.stdlib : abort; + writef_to!(WriteTarget.stderr, true)("dwarfeh({0}) fatal error", line); + abort(); +} + +// --- LSDA scanning -------------------------------------------------- + +enum LsdaResult +{ + not_found, + foreign, + corrupt, + no_action, + cleanup, + handler, +} + +ClassInfo get_class_info(_Unwind_Exception* exception_object, const(ubyte)* current_lsd) nothrow @nogc +{ + ExceptionHeader* eh = ExceptionHeader.to_exception_header(exception_object); + Throwable ehobject = eh.object; + for (ExceptionHeader* ehn = eh.next; ehn; ehn = ehn.next) + { + if (current_lsd != ehn.language_specific_data) + break; + Error e = cast(Error) ehobject; + if (e is null || (cast(Error) ehn.object) !is null) + ehobject = ehn.object; + } + return typeid(ehobject); +} + +int action_table_lookup(_Unwind_Exception* exception_object, uint action_record_ptr, const(ubyte)* p_action_table, const(ubyte)* tt, + ubyte ttype, _Unwind_Exception_Class exception_class, const(ubyte)* lsda) nothrow @nogc +{ + ClassInfo thrown_type; + if (exception_class == dmd_exception_class) + thrown_type = get_class_info(exception_object, lsda); + + for (auto ap = p_action_table + action_record_ptr - 1; 1; ) + { + auto type_filter = s_leb128(&ap); + auto apn = ap; + auto next_record_ptr = s_leb128(&ap); + + if (type_filter <= 0) + return -1; + + _Unwind_Ptr entry; + const(ubyte)* tt2; + switch (ttype & DW_EH_PE_FORMAT_MASK) + { + case DW_EH_PE_sdata2: entry = read_unaligned!(short, false)(tt2 = tt - type_filter * 2); break; + case DW_EH_PE_udata2: entry = read_unaligned!(ushort, false)(tt2 = tt - type_filter * 2); break; + case DW_EH_PE_sdata4: entry = read_unaligned!(int, false)(tt2 = tt - type_filter * 4); break; + case DW_EH_PE_udata4: entry = read_unaligned!(uint, false)(tt2 = tt - type_filter * 4); break; + case DW_EH_PE_sdata8: entry = read_unaligned!(long, false)(tt2 = tt - type_filter * 8); break; + case DW_EH_PE_udata8: entry = read_unaligned!(ulong, false)(tt2 = tt - type_filter * 8); break; + case DW_EH_PE_ptr: if (size_t.sizeof == 8) + goto case DW_EH_PE_udata8; + else + goto case DW_EH_PE_udata4; + default: + return -1; + } + if (!entry) + return -1; + + switch (ttype & DW_EH_PE_APPL_MASK) + { + case DW_EH_PE_absptr: + break; + case DW_EH_PE_pcrel: + entry += cast(_Unwind_Ptr) tt2; + break; + default: + return -1; + } + if (ttype & DW_EH_PE_indirect) + entry = *cast(_Unwind_Ptr*) entry; + + ClassInfo ci = cast(ClassInfo) cast(void*) entry; + + // D exception - check class hierarchy + if (exception_class == dmd_exception_class && _d_isbaseof(thrown_type, ci)) + return cast(int) type_filter; + + if (!next_record_ptr) + return 0; + + ap = apn + next_record_ptr; + } + assert(0); // unreachable - all paths return inside the loop +} + +LsdaResult scan_lsda(const(ubyte)* lsda, _Unwind_Ptr ip, _Unwind_Exception_Class exception_class, bool cleanups_only, bool prefer_handler, + _Unwind_Exception* exception_object, out _Unwind_Ptr landing_pad, out int handler) nothrow @nogc +{ + auto p = lsda; + if (!p) + return LsdaResult.no_action; + + _Unwind_Ptr dw_pe_value(ubyte pe) + { + switch (pe) + { + case DW_EH_PE_sdata2: return read_unaligned!(short, true)(p); + case DW_EH_PE_udata2: return read_unaligned!(ushort, true)(p); + case DW_EH_PE_sdata4: return read_unaligned!(int, true)(p); + case DW_EH_PE_udata4: return read_unaligned!(uint, true)(p); + case DW_EH_PE_sdata8: return read_unaligned!(long, true)(p); + case DW_EH_PE_udata8: return read_unaligned!(ulong, true)(p); + case DW_EH_PE_sleb128: return cast(_Unwind_Ptr) s_leb128(&p); + case DW_EH_PE_uleb128: return cast(_Unwind_Ptr) u_leb128(&p); + case DW_EH_PE_ptr: if (size_t.sizeof == 8) + goto case DW_EH_PE_udata8; + else + goto case DW_EH_PE_udata4; + default: + dwarf_terminate(__LINE__); + return 0; + } + } + + ubyte lp_start = *p++; + + _Unwind_Ptr lp_base = 0; + if (lp_start != DW_EH_PE_omit) + lp_base = dw_pe_value(lp_start); + + ubyte ttype = *p++; + _Unwind_Ptr tt_base = 0; + _Unwind_Ptr tt_offset = 0; + if (ttype != DW_EH_PE_omit) + { + tt_base = u_leb128(&p); + tt_offset = (p - lsda) + tt_base; + } + + ubyte call_site_format = *p++; + _Unwind_Ptr call_site_table_size = dw_pe_value(DW_EH_PE_uleb128); + + _Unwind_Ptr ip_offset = ip - lp_base; + bool no_action = false; + auto tt = lsda + tt_offset; + const(ubyte)* p_action_table = p + call_site_table_size; + + while (1) + { + if (p >= p_action_table) + { + if (p == p_action_table) + break; + return LsdaResult.corrupt; + } + + _Unwind_Ptr call_site_start = dw_pe_value(call_site_format); + _Unwind_Ptr call_site_range = dw_pe_value(call_site_format); + _Unwind_Ptr call_site_lp = dw_pe_value(call_site_format); + _uleb128_t action_record_ptr_val = u_leb128(&p); + + if (ip_offset < call_site_start) + break; + + if (ip_offset < call_site_start + call_site_range) + { + if (action_record_ptr_val) + { + if (cleanups_only) + continue; + + auto h = action_table_lookup(exception_object, cast(uint) action_record_ptr_val, + p_action_table, tt, ttype, exception_class, lsda); + if (h < 0) + return LsdaResult.corrupt; + if (h == 0) + continue; + + no_action = false; + landing_pad = call_site_lp; + handler = h; + } + else if (call_site_lp) + { + if (prefer_handler && handler) + continue; + no_action = false; + landing_pad = call_site_lp; + handler = 0; + } + else + no_action = true; + } + } + + if (no_action) + return LsdaResult.no_action; + + if (landing_pad) + return handler ? LsdaResult.handler : LsdaResult.cleanup; + + return LsdaResult.not_found; +} + +// --- Public API ------------------------------------------------------ + +pragma(mangle, catch_mangle) +extern(C) Throwable dwarfeh_begin_catch(_Unwind_Exception* exception_object) nothrow @nogc +{ + version (ARM) version (LDC) + _Unwind_Complete(exception_object); + + ExceptionHeader* eh = ExceptionHeader.to_exception_header(exception_object); + auto o = eh.object; + eh.object = null; + + if (eh != ExceptionHeader.pop()) + dwarf_terminate(__LINE__); + + _Unwind_DeleteException(&eh.exception_object); + return o; +} + +extern(C) void* _d_eh_swapContextDwarf(void* newContext) nothrow @nogc +{ + auto old = ExceptionHeader.stack; + ExceptionHeader.stack = cast(ExceptionHeader*) newContext; + return old; +} + +pragma(mangle, throw_mangle) +extern(C) void dwarfeh_throw(Throwable o) +{ + import urt.io : writeln_err, writef_to, WriteTarget; + import urt.internal.stdc.stdlib : abort; + + ExceptionHeader* eh = ExceptionHeader.create(o); + eh.push(); + + auto refcount = o.refcount(); + if (refcount) + o.refcount() = refcount + 1; + + extern(C) static void exception_cleanup(_Unwind_Reason_Code reason, + _Unwind_Exception* eo) nothrow @nogc + { + switch (reason) + { + case _URC_FOREIGN_EXCEPTION_CAUGHT: + case _URC_NO_REASON: + ExceptionHeader.release(ExceptionHeader.to_exception_header(eo)); + break; + default: + dwarf_terminate(__LINE__); + } + } + + eh.exception_object.exception_cleanup = &exception_cleanup; + _d_createTrace(o, null); + + auto r = _Unwind_RaiseException(&eh.exception_object); + + // Should not return - if it did, the exception was not caught. + dwarfeh_begin_catch(&eh.exception_object); + writeln_err("uncaught exception reached top of stack"); + auto msg = o.msg; + if (msg.length) + writef_to!(WriteTarget.stderr, true)(" {0}", msg); + abort(); +} + +// Common personality implementation. +_Unwind_Reason_Code dwarfeh_personality_common(_Unwind_Action actions, _Unwind_Exception_Class exception_class, _Unwind_Exception* exception_object, _Unwind_Context* context) nothrow @nogc +{ + const(ubyte)* language_specific_data; + int handler; + _Unwind_Ptr landing_pad; + + language_specific_data = cast(const(ubyte)*) _Unwind_GetLanguageSpecificData(context); + auto Start = _Unwind_GetRegionStart(context); + + // Get IP; use _Unwind_GetIPInfo to handle signal frames correctly. + int ip_before_insn; + auto ip = _Unwind_GetIPInfo(context, &ip_before_insn); + if (!ip_before_insn) + --ip; + + auto result = scan_lsda(language_specific_data, ip - Start, exception_class, + (actions & _UA_FORCE_UNWIND) != 0, + (actions & _UA_SEARCH_PHASE) != 0, + exception_object, + landing_pad, + handler); + landing_pad += Start; + + final switch (result) + { + case LsdaResult.not_found: + dwarf_terminate(__LINE__); + assert(0); + + case LsdaResult.foreign: + dwarf_terminate(__LINE__); + assert(0); + + case LsdaResult.corrupt: + dwarf_terminate(__LINE__); + assert(0); + + case LsdaResult.no_action: + return _URC_CONTINUE_UNWIND; + + case LsdaResult.cleanup: + if (actions & _UA_SEARCH_PHASE) + return _URC_CONTINUE_UNWIND; + break; + + case LsdaResult.handler: + if (actions & _UA_SEARCH_PHASE) + { + if (exception_class == dmd_exception_class) + { + auto eh = ExceptionHeader.to_exception_header(exception_object); + eh.handler = handler; + eh.language_specific_data = language_specific_data; + eh.landing_pad = landing_pad; + } + return _URC_HANDLER_FOUND; + } + break; + } + + // Multiple exceptions in flight - chain them + if (exception_class == dmd_exception_class) + { + auto eh = ExceptionHeader.to_exception_header(exception_object); + auto current_lsd = language_specific_data; + bool bypassed = false; + + while (eh.next) + { + ExceptionHeader* ehn = eh.next; + + Error e = cast(Error) eh.object; + if (e !is null && !cast(Error) ehn.object) + { + current_lsd = ehn.language_specific_data; + eh = ehn; + bypassed = true; + continue; + } + + if (current_lsd != ehn.language_specific_data) + break; + + eh.object = Throwable.chainTogether(ehn.object, eh.object); + + if (ehn.handler != handler && !bypassed) + { + handler = ehn.handler; + eh.handler = handler; + eh.language_specific_data = language_specific_data; + eh.landing_pad = landing_pad; + } + + eh.next = ehn.next; + _Unwind_DeleteException(&ehn.exception_object); + } + + if (bypassed) + { + eh = ExceptionHeader.to_exception_header(exception_object); + Error e = cast(Error) eh.object; + auto ehn = eh.next; + e.bypassedException = ehn.object; + eh.next = ehn.next; + _Unwind_DeleteException(&ehn.exception_object); + } + } + + _Unwind_SetGR(context, eh_exception_regno, cast(_Unwind_Word) exception_object); + _Unwind_SetGR(context, eh_selector_regno, handler); + _Unwind_SetIP(context, landing_pad); + + return _URC_INSTALL_CONTEXT; +} + +// Personality function entry points. +// ARM EABI uses a different calling convention than the standard Itanium ABI. +// Bare-metal ARM uses DWARF EH, not ARM EHABI, so use the standard personality. +version (ARM) +{ + version (Beken) + enum UseArmEhabi = true; + else version (BareMetal) + enum UseArmEhabi = false; + else version (LDC) + enum UseArmEhabi = true; + else + enum UseArmEhabi = false; +} +else + enum UseArmEhabi = false; + +static if (UseArmEhabi) +{ + extern(C) _Unwind_Reason_Code _d_eh_personality(_Unwind_State state, _Unwind_Exception* exception_object, _Unwind_Context* context) nothrow @nogc + { + _Unwind_Action actions; + switch (state & _US_ACTION_MASK) + { + case _US_VIRTUAL_UNWIND_FRAME: + actions = _UA_SEARCH_PHASE; + break; + case _US_UNWIND_FRAME_STARTING: + actions = _UA_CLEANUP_PHASE; + break; + case _US_UNWIND_FRAME_RESUME: + return _URC_CONTINUE_UNWIND; + default: + dwarf_terminate(__LINE__); + return _URC_FATAL_PHASE1_ERROR; + } + if (state & _US_FORCE_UNWIND) + actions |= _UA_FORCE_UNWIND; + + return dwarfeh_personality_common(actions, exception_object.exception_class, + exception_object, context); + } +} +else +{ + pragma(mangle, personality_mangle) + extern(C) _Unwind_Reason_Code dwarfeh_personality(int ver, _Unwind_Action actions, _Unwind_Exception_Class exception_class, _Unwind_Exception* exception_object, _Unwind_Context* context) nothrow @nogc + { + if (ver != 1) + return _URC_FATAL_PHASE1_ERROR; + + return dwarfeh_personality_common(actions, exception_class, + exception_object, context); + } +} + +// LDC-only: trivial cleanup hooks (DWARF handles cleanup via personality). +version (LDC) +{ + extern(C) bool _d_enter_cleanup(void* ptr) nothrow @nogc @trusted => true; + extern(C) void _d_leave_cleanup(void* ptr) nothrow @nogc @trusted {} +} + diff --git a/src/urt/internal/exception.d b/src/urt/internal/exception.d new file mode 100644 index 0000000..5ef5a96 --- /dev/null +++ b/src/urt/internal/exception.d @@ -0,0 +1,506 @@ +module urt.internal.exception; + +version (Windows) + import urt.driver.windows.exception : _capture_trace, _caller_address, _resolve_address, _resolve_batch; +else version (Espressif) + import urt.driver.esp32.exception : _capture_trace, _caller_address, _resolve_address, _resolve_batch; +else version (BareMetal) + import urt.driver.baremetal.exception : _capture_trace, _caller_address, _resolve_address, _resolve_batch; +else + import urt.driver.posix.exception : _capture_trace, _caller_address, _resolve_address, _resolve_batch; + +nothrow @nogc: + + +// Public API + +// Resolved symbol information for a single return address. Fields are +// best-effort - any of them may be empty/zero if the driver can't +// supply them (no on-device symtab, stripped binary, missing DWARF). +// `name` is the raw symbol as the driver sees it - possibly D-mangled +// (`_D...`); pass through `demangle_symbol` before display. +// String slices are owned by driver static/TLS storage - copy before +// the next `_resolve_address`/`_resolve_batch` call. +struct Resolved +{ + const(char)[] name; + const(char)[] file; + const(char)[] dir; + uint line; + size_t offset; // addr - symbol_base +} + +pragma(inline, false) +size_t capture_trace(void*[] addrs) @trusted +{ + return _capture_trace(addrs); +} + +pragma(inline, false) +void* caller_address(uint skip = 0) @trusted +{ + return _caller_address(skip); +} + +bool resolve_address(void* addr, out Resolved r) @trusted +{ + return _resolve_address(addr, r); +} + +bool resolve_batch(const(void*)[] addrs, Resolved[] results) @trusted +{ + assert(addrs.length == results.length); + return _resolve_batch(addrs, results); +} + +version (Tiny) +{ + const(char)[] demangle_symbol(const(char)[] mangled, char[]) @trusted + => mangled; +} +else +{ + const(char)[] demangle_symbol(const(char)[] mangled, char[] buf) @trusted + { + import urt.array : beginsWith; + import urt.conv : parse_uint; + import urt.mem : memcpy; + + if (mangled.length < 3 || !mangled.beginsWith("_D")) + return mangled; + + auto src = mangled[2 .. $]; + size_t pos = 0; + bool first = true; + + while (src.length > 0) + { + auto ch = src[0]; + + if (ch >= '1' && ch <= '9') + { + // LName: decimal length followed by that many characters + size_t taken; + size_t len = cast(size_t) parse_uint(src, &taken); + src = src[taken .. $]; + if (len > src.length || pos + len + 1 > buf.length) + break; + + if (!first) + buf[pos++] = '.'; + first = false; + + buf[pos .. pos + len] = src[0 .. len]; + pos += len; + src = src[len .. $]; + } + else if (ch == 'Q') + { + // Back reference: base-26 offset pointing to an earlier LName. + auto q_pos = cast(size_t)(src.ptr - mangled.ptr); + src = src[1 .. $]; + + size_t ref_val = 0; + while (src.length > 0 && src[0] >= 'A' && src[0] <= 'Z') + { + ref_val = ref_val * 26 + (src[0] - 'A'); + src = src[1 .. $]; + } + if (src.length > 0 && src[0] >= 'a' && src[0] <= 'z') + { + ref_val = ref_val * 26 + (src[0] - 'a'); + src = src[1 .. $]; + } + else + break; // malformed + + if (ref_val >= q_pos) + break; + auto target = mangled[q_pos - ref_val .. $]; + if (target.length == 0 || target[0] < '1' || target[0] > '9') + break; + + size_t taken; + size_t len = cast(size_t) parse_uint(target, &taken); + target = target[taken .. $]; + if (len > target.length || pos + len + 1 > buf.length) + break; + + if (!first) + buf[pos++] = '.'; + first = false; + + buf[pos .. pos + len] = target[0 .. len]; + pos += len; + } + else if (ch == '_' && src.length >= 3 && src[1] == '_' && (src[2] == 'T' || src[2] == 'U')) + { + // Template instance __T/__U: extract name, skip args until Z + src = src[3 .. $]; + + if (src.length > 0 && src[0] >= '1' && src[0] <= '9') + { + size_t taken; + size_t len = cast(size_t) parse_uint(src, &taken); + src = src[taken .. $]; + if (len <= src.length && pos + len + 1 <= buf.length) + { + if (!first) + buf[pos++] = '.'; + first = false; + + buf[pos .. pos + len] = src[0 .. len]; + pos += len; + src = src[len .. $]; + } + } + + int depth = 1; + while (src.length > 0 && depth > 0) + { + if (src[0] == 'Z') + --depth; + else if (src.length >= 3 && src[0] == '_' && src[1] == '_' && (src[2] == 'T' || src[2] == 'U')) + { + ++depth; + src = src[2 .. $]; + } + src = src[1 .. $]; + } + } + else if (ch == '0') + src = src[1 .. $]; // anonymous - skip + else + break; // type signature - done + } + + if (pos == 0) + return mangled; + + // Append $TypeSignature if there's anything left + if (src.length > 0 && pos + 1 + src.length <= buf.length) + { + buf[pos++] = '$'; + buf[pos .. pos + src.length] = src[]; + pos += src.length; + } + + return buf[0 .. pos]; + } +} + +// Print a captured trace to stderr. +// +// formats each frame as: `{dir}/{file}:{line} {name}+0x{offset} [0x{address}]` +// with graceful degradation: +// file:line missing → `??:?` +// symbol missing → drop the `name+0x...` component +// nothing resolved → ` 0x{address}` only +// Stops after emitting `_Dmain` / `main` to hide C runtime tail noise. +version (Tiny) +{ + void print_trace(const(void*)[] addrs) @trusted + { + import urt.io : writef_to, WriteTarget; + + enum addr_fmt = size_t.sizeof == 4 ? "08x" : "016x"; + foreach (addr; addrs) + writef_to!(WriteTarget.stderr, true)(" 0x{0:" ~ addr_fmt ~ "}", cast(size_t) addr); + } +} +else +{ + void print_trace(const(void*)[] addrs) @trusted + { + import urt.io : write_err, writef_to, WriteTarget; + import urt.string : endsWith; + + if (addrs.length == 0) + return; + + const n = addrs.length > max_frames ? max_frames : addrs.length; + + Resolved[max_frames] results; + const have_symbols = _resolve_batch(addrs[0 .. n], results[0 .. n]); + + enum addr_fmt = size_t.sizeof == 4 ? "08x" : "016x"; + + if (!have_symbols) + { + foreach (addr; addrs[0 .. n]) + writef_to!(WriteTarget.stderr, true)(" 0x{0:" ~ addr_fmt ~ "}", cast(size_t) addr); + return; + } + + // Skip internal throw machinery - start after the last matching frame. Matches LDC druntime behavior. + size_t start = 0; + foreach (i; 0 .. n) + { + auto name = results[i].name; + if (name.endsWith("_d_throw_exception") || name.endsWith("_d_throwdwarf")) + start = i + 1; + } + + foreach (i; start .. n) + { + auto addr = addrs[i]; + auto r = &results[i]; + const bool have_any = r.name.length > 0 || r.line > 0; + + if (!have_any) + { + writef_to!(WriteTarget.stderr, true)(" 0x{0:" ~ addr_fmt ~ "}", cast(size_t) addr); + continue; + } + + // file:line (or ??:? when missing) + if (r.line > 0 && r.file.length > 0) + { + if (r.dir.length > 0) + { + const sep = (r.dir[$ - 1] == '/' || r.dir[$ - 1] == '\\') ? "" : "/"; + writef_to!(WriteTarget.stderr, false)(" {0}{1}{2}:{3}", r.dir, sep, r.file, r.line); + } + else + writef_to!(WriteTarget.stderr, false)(" {0}:{1}", r.file, r.line); + } + else + write_err(" ??:?"); + + // symbol+offset (demangled) + if (r.name.length > 0) + { + char[512] dbuf = void; + auto dname = demangle_symbol(r.name, dbuf); + writef_to!(WriteTarget.stderr, false)(" {0}+0x{1:x}", dname, r.offset); + } + + writef_to!(WriteTarget.stderr, true)(" [0x{0:" ~ addr_fmt ~ "}]", cast(size_t) addr); + + // Stop at program entry - hides C runtime tail noise. + if (r.name == "_Dmain" || r.name == "main") + break; + } + } +} + +public void terminate() @trusted +{ + import urt.io : writeln_err; + writeln_err("Unhandled exception -- no catch handler found, terminating."); + + if (_tls_trace.length > 0) + { + writeln_err(" stack trace:"); + print_trace(_tls_trace.addrs[0 .. _tls_trace.length]); + } + + import urt.internal.stdc.stdlib : abort; + abort(); +} + + +// Shared state + +enum max_frames = 32; + +struct StackTraceData +{ + void*[max_frames] addrs; + ubyte length; +} + +private StackTraceData _tls_trace; // static = TLS in D + + +// Druntime hooks (extern(C), linker-visible) + +alias ClassInfo = TypeInfo_Class; + +extern(C) int _d_isbaseof(scope ClassInfo oc, scope const ClassInfo c) pure @trusted +{ + if (oc is c) + return true; + + do + { + if (oc.base is c) + return true; + + foreach (iface; oc.interfaces) + { + if (iface.classinfo is c || _d_isbaseof(iface.classinfo, c)) + return true; + } + + oc = oc.base; + } + while (oc); + + return false; +} + + +extern(C) void _d_createTrace(Throwable, void*) @trusted +{ + debug + _tls_trace.length = cast(ubyte) _capture_trace(_tls_trace.addrs[]); +} + +extern(C) void _d_printLastTrace(Throwable t) @trusted +{ + debug + { + import urt.io : writeln_err, writef_to, WriteTarget; + + if (_tls_trace.length == 0) + return; + + if (t !is null) + { + auto msg = t.msg; + writef_to!(WriteTarget.stderr, true)("Exception: {0}", msg); + } + + writeln_err(" stack trace:"); + print_trace(_tls_trace.addrs[0 .. _tls_trace.length]); + } +} + + +version (unittest) +{ + private bool eh_contains(const(char)[] haystack, const(char)[] needle) @trusted nothrow @nogc + { + if (needle.length == 0) + return true; + if (needle.length > haystack.length) + return false; + foreach (i; 0 .. haystack.length - needle.length + 1) + if (haystack[i .. i + needle.length] == needle) + return true; + return false; + } + + // Skip-count verification layers. Each is `pragma(inline, false)` so + // the frames actually exist at runtime; each assigns to a local + // before returning to defeat tail-call optimisation. Distinct, + // grep-friendly names make the resolved symbols easy to match. + + private pragma(inline, false) + void* eh_ca_layer_0(uint skip) @trusted nothrow @nogc + => caller_address(skip); + + private pragma(inline, false) + void* eh_ca_layer_1(uint skip) @trusted nothrow @nogc + { + auto pc = eh_ca_layer_0(skip); + return pc; + } + + private pragma(inline, false) + void* eh_ca_layer_2(uint skip) @trusted nothrow @nogc + { + auto pc = eh_ca_layer_1(skip); + return pc; + } + + // Demangler target - `.mangleof` gives us the real D-mangled form at + // compile time, so the test is stable across compilers. + private void eh_demangle_target() @trusted nothrow @nogc {} + + // Helper: this unittest function is what we expect to find as the + // caller in the capture_trace and skip-count tests below. Wrapping the + // captures in a private function lets us assert by name match. + private pragma(inline, false) + size_t eh_capture_here(void*[] buf) @trusted nothrow @nogc + => capture_trace(buf); +} + +unittest +{ + // capture_trace produces a non-empty trace whose first frame + // is in the function that called it (eh_capture_here). + + void*[max_frames] buf; + auto n = eh_capture_here(buf[]); + assert(n > 0); + foreach (addr; buf[0 .. n]) + assert(addr !is null); + + // First captured frame should resolve to the immediate caller - + // eh_capture_here. (Skipped on platforms with no symbol table.) + Resolved r; + if (resolve_address(buf[0], r)) + { + char[512] dbuf; + auto name = demangle_symbol(r.name, dbuf); + assert(eh_contains(name, "eh_capture_here"), name); + } + + // caller_address skip walks one frame per increment, starting + // from the caller of the function that called caller_address. + + // From eh_ca_layer_0: + // skip=0 → PC inside eh_ca_layer_1 (layer_0's caller) + // skip=1 → PC inside eh_ca_layer_2 (layer_1's caller) + // skip=2 → PC inside this unittest (layer_2's caller) + auto pc0 = eh_ca_layer_2(0); + auto pc1 = eh_ca_layer_2(1); + auto pc2 = eh_ca_layer_2(2); + + assert(pc0 !is null); + assert(pc1 !is null); + assert(pc2 !is null); + + // Distinct PCs - each skip level yields a different call site. + assert(pc0 != pc1); + assert(pc1 != pc2); + assert(pc0 != pc2); + + // Strong check via the symbol resolver. Bare-metal / ESP32 have no + // on-device symtab and silently skip - the distinct-PC check above + // is the strongest assertion they can verify. + char[512] name; + + if (resolve_address(pc0, r)) + { + auto mangle = demangle_symbol(r.name, name); + assert(eh_contains(mangle, "eh_ca_layer_1"), mangle); + } + if (resolve_address(pc1, r)) + { + auto mangle = demangle_symbol(r.name, name); + assert(eh_contains(mangle, "eh_ca_layer_2"), mangle); + } + + // demangler + + // Non-D / degenerate inputs pass through unchanged. + assert(demangle_symbol("main", name) == "main"); + assert(demangle_symbol("", name) == ""); + assert(demangle_symbol("_D", name) == "_D"); + assert(demangle_symbol("?MyClass@@QAEXXZ", name) == "?MyClass@@QAEXXZ"); + + // Real D mangling via .mangleof - qualified path must contain the + // function name and at least one module-separator dot. + auto dem = demangle_symbol(eh_demangle_target.mangleof, name); + assert(eh_contains(dem, "eh_demangle_target"), dem); + bool has_dot = false; + foreach (c; dem) if (c == '.') + { + has_dot = true; + break; + } + assert(has_dot, dem); + + // Hand-crafted ABI-compliant manglings - parse must recover the + // qualified name prefix regardless of the trailing type signature. + assert(eh_contains(demangle_symbol("_D3foo3barFZv", name), "foo.bar")); + assert(eh_contains(demangle_symbol("_D3foo3bar3bazFZv", name), "foo.bar.baz")); + + // Malformed input must not crash - output is undefined but safe. + demangle_symbol("_D999zzz", name); // LName length overflows src + demangle_symbol("_D3fooQ", name); // Q back-ref with no offset + demangle_symbol("_D__T1aZ", name); // template with minimal content +} diff --git a/src/urt/internal/lifetime.d b/src/urt/internal/lifetime.d new file mode 100644 index 0000000..1f9c3a9 --- /dev/null +++ b/src/urt/internal/lifetime.d @@ -0,0 +1,121 @@ +module urt.internal.lifetime; + +import urt.lifetime : forward; + +/+ +emplaceRef is a package function for druntime internal use. It works like +emplace, but takes its argument by ref (as opposed to "by pointer"). +This makes it easier to use, easier to be safe, and faster in a non-inline +build. +Furthermore, emplaceRef optionally takes a type parameter, which specifies +the type we want to build. This helps to build qualified objects on mutable +buffer, without breaking the type system with unsafe casts. ++/ +void emplaceRef(T, UT, Args...)(ref UT chunk, auto ref Args args) +{ + static if (args.length == 0) + { + static assert(is(typeof({static T i;})), + "Cannot emplace a " ~ T.stringof ~ " because " ~ T.stringof ~ + ".this() is annotated with @disable."); + static if (is(T == class)) static assert(!__traits(isAbstractClass, T), + T.stringof ~ " is abstract and it can't be emplaced"); + emplaceInitializer(chunk); + } + else static if ( + !is(T == struct) && Args.length == 1 /* primitives, enums, arrays */ + || + Args.length == 1 && is(typeof({T t = forward!(args[0]);})) /* conversions */ + || + is(typeof(T(forward!args))) /* general constructors */) + { + static struct S + { + T payload; + this()(auto ref Args args) + { + static if (__traits(compiles, payload = forward!args)) + payload = forward!args; + else + payload = T(forward!args); + } + } + if (__ctfe) + { + static if (__traits(compiles, chunk = T(forward!args))) + chunk = T(forward!args); + else static if (args.length == 1 && __traits(compiles, chunk = forward!(args[0]))) + chunk = forward!(args[0]); + else assert(0, "CTFE emplace doesn't support " + ~ T.stringof ~ " from " ~ Args.stringof); + } + else + { + S* p = () @trusted { return cast(S*) &chunk; }(); + static if (UT.sizeof > 0) + emplaceInitializer(*p); + p.__ctor(forward!args); + } + } + else static if (is(typeof(chunk.__ctor(forward!args)))) + { + // This catches the rare case of local types that keep a frame pointer + emplaceInitializer(chunk); + chunk.__ctor(forward!args); + } + else + { + //We can't emplace. Try to diagnose a disabled postblit. + static assert(!(Args.length == 1 && is(Args[0] : T)), + "Cannot emplace a " ~ T.stringof ~ " because " ~ T.stringof ~ + ".this(this) is annotated with @disable."); + + //We can't emplace. + static assert(false, + T.stringof ~ " cannot be emplaced from " ~ Args[].stringof ~ "."); + } +} + +// ditto +static import urt.traits; +void emplaceRef(UT, Args...)(ref UT chunk, auto ref Args args) + if (is(UT == urt.traits.Unqual!UT)) +{ + emplaceRef!(UT, UT)(chunk, forward!args); +} + +/+ +Emplaces T.init. +In contrast to `emplaceRef(chunk)`, there are no checks for disabled default +constructors etc. ++/ +void emplaceInitializer(T)(scope ref T chunk) nothrow pure @trusted + if (!is(T == const) && !is(T == immutable) && !is(T == inout)) +{ + import urt.internal.traits : hasElaborateAssign; + + static if (__traits(isZeroInit, T)) + { + import urt.mem : memset; + memset(cast(void*) &chunk, 0, T.sizeof); + } + else static if (__traits(isScalar, T) || + T.sizeof <= 16 && !hasElaborateAssign!T && __traits(compiles, (){ T chunk; chunk = T.init; })) + { + chunk = T.init; + } + else static if (__traits(isStaticArray, T)) + { + // For static arrays there is no initializer symbol created. Instead, we emplace elements one-by-one. + foreach (i; 0 .. T.length) + { + emplaceInitializer(chunk[i]); + } + } + else + { + import urt.mem : memcpy; + const initializer = __traits(initSymbol, T); + memcpy(cast(void*)&chunk, initializer.ptr, initializer.length); + } +} diff --git a/src/urt/internal/mbedtls.c b/src/urt/internal/mbedtls.c new file mode 100644 index 0000000..113fa39 --- /dev/null +++ b/src/urt/internal/mbedtls.c @@ -0,0 +1,114 @@ +// C wrappers for mbedtls - sizeof() for opaque types, and wrappers for +// functions that access internal struct layouts D cannot safely replicate. + +#if !defined(_WIN32) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +size_t urt_sizeof_entropy(void) { return sizeof(mbedtls_entropy_context); } +size_t urt_sizeof_ctr_drbg(void) { return sizeof(mbedtls_ctr_drbg_context); } +size_t urt_sizeof_x509_crt(void) { return sizeof(mbedtls_x509_crt); } +size_t urt_sizeof_ssl_context(void) { return sizeof(mbedtls_ssl_context); } +size_t urt_sizeof_ssl_config(void) { return sizeof(mbedtls_ssl_config); } + +// Generate an ECDSA P-256 key into a pk context. +// Handles pk_setup + ecp_gen_key internally so D never needs to access +// mbedtls_ecp_keypair or mbedtls_ecp_group, whose layouts are version-dependent. +int urt_pk_gen_ec_p256_key(mbedtls_pk_context *pk, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng) +{ + int ret = mbedtls_pk_setup(pk, mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY)); + if (ret != 0) + return ret; + // mbedtls_pk_ec() is deprecated in 3.1+ but present in all 2.x and 3.x versions. + return mbedtls_ecp_gen_key(MBEDTLS_ECP_DP_SECP256R1, mbedtls_pk_ec(*pk), f_rng, p_rng); +} + +// Export the public key as an uncompressed EC point (0x04 || X || Y). +// Returns 0 on success with *olen set to the number of bytes written. +// Uses mbedtls_pk_write_pubkey_der to avoid any direct ECP struct member access - +// the SubjectPublicKeyInfo for P-256 always ends with the 65-byte uncompressed point. +int urt_pk_export_pubkey_xy(mbedtls_pk_context *pk, unsigned char *buf, size_t buflen, size_t *olen) +{ + unsigned char der[256]; + int len = mbedtls_pk_write_pubkey_der(pk, der, sizeof(der)); + if (len < 0) + return len; + // SubjectPublicKeyInfo for P-256 ends with BIT STRING { 04 || X || Y } (65 bytes). + if (len < 65 || buflen < 65) + return MBEDTLS_ERR_ECP_BUFFER_TOO_SMALL; + memcpy(buf, der + sizeof(der) - 65, 65); + *olen = 65; + return 0; +} + +// Wrappers for functions whose signatures changed between mbedtls 2.x and 3.x. +// mbedtls 3.x added sig_size parameter to mbedtls_pk_sign (inserted in the middle), +// and f_rng/p_rng to mbedtls_pk_parse_key (appended at end). +// Calling from D with the wrong signature corrupts arguments on the stack. + +// Always signs with SHA-256. The md_type_t enum values changed between +// mbedtls 2.x and 3.x, so we resolve the correct value here in C. +int urt_pk_sign(mbedtls_pk_context *ctx, const unsigned char *hash, size_t hash_len, unsigned char *sig, size_t sig_size, size_t *sig_len, int (*f_rng)(void *, unsigned char *, size_t), void *p_rng) +{ +#if MBEDTLS_VERSION_MAJOR >= 3 + return mbedtls_pk_sign(ctx, MBEDTLS_MD_SHA256, hash, hash_len, sig, sig_size, sig_len, f_rng, p_rng); +#else + (void)sig_size; + return mbedtls_pk_sign(ctx, MBEDTLS_MD_SHA256, hash, hash_len, sig, sig_len, f_rng, p_rng); +#endif +} + +// Import raw EC P-256 key components (d, X, Y) into a pk context. +// Sets up the pk context, group, private key, and public key point. +int urt_pk_import_ec_p256_key(mbedtls_pk_context *pk, const unsigned char *d, size_t d_len, const unsigned char *xy, size_t xy_len) +{ + int ret = mbedtls_pk_setup(pk, mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY)); + if (ret != 0) + return ret; + + mbedtls_ecp_keypair *ec = mbedtls_pk_ec(*pk); + +#if MBEDTLS_VERSION_MAJOR >= 3 + ret = mbedtls_ecp_group_load(&ec->private_grp, MBEDTLS_ECP_DP_SECP256R1); + if (ret != 0) return ret; + ret = mbedtls_mpi_read_binary(&ec->private_d, d, d_len); + if (ret != 0) return ret; + // xy is 0x04 || X || Y (65 bytes) + ret = mbedtls_ecp_point_read_binary(&ec->private_grp, &ec->private_Q, xy, xy_len); +#else + ret = mbedtls_ecp_group_load(&ec->grp, MBEDTLS_ECP_DP_SECP256R1); + if (ret != 0) return ret; + ret = mbedtls_mpi_read_binary(&ec->d, d, d_len); + if (ret != 0) return ret; + ret = mbedtls_ecp_point_read_binary(&ec->grp, &ec->Q, xy, xy_len); +#endif + return ret; +} + +// Export the raw private key scalar d (32 bytes for P-256). +// Accesses version-dependent struct member (d vs private_d). +int urt_pk_export_privkey_d(mbedtls_pk_context *pk, unsigned char *buf, size_t buflen, size_t *olen) +{ + mbedtls_ecp_keypair *ec = mbedtls_pk_ec(*pk); +#if MBEDTLS_VERSION_MAJOR >= 3 + mbedtls_mpi *d = &ec->private_d; +#else + mbedtls_mpi *d = &ec->d; +#endif + size_t len = mbedtls_mpi_size(d); + if (buflen < len) + return MBEDTLS_ERR_ECP_BUFFER_TOO_SMALL; + *olen = len; + return mbedtls_mpi_write_binary(d, buf, len); +} + +#endif diff --git a/src/urt/internal/mbedtls.d b/src/urt/internal/mbedtls.d new file mode 100644 index 0000000..659c17a --- /dev/null +++ b/src/urt/internal/mbedtls.d @@ -0,0 +1,203 @@ +// minimal D bindings for mbedtls - only what pki.d and tls.d need +module urt.internal.mbedtls; + +version (Posix): + +pragma(lib, "mbedtls"); +pragma(lib, "mbedx509"); +pragma(lib, "mbedcrypto"); + +nothrow @nogc: +extern(C): + +// --- PK (public key abstraction) --- + +struct mbedtls_pk_info_t; // opaque + +struct mbedtls_pk_context +{ + const(mbedtls_pk_info_t)* pk_info; + void* pk_ctx; +} + +// mbedtls_pk_type_t and mbedtls_md_type_t enum values changed between 2.x +// and 3.x, so D must not hardcode them. All operations that need these enums +// (pk_setup, pk_sign, etc.) are wrapped in urt/internal/mbedtls.c where the +// correct header values are resolved at compile time. + +void mbedtls_pk_init(mbedtls_pk_context* ctx); +void mbedtls_pk_free(mbedtls_pk_context* ctx); + +// --- wrappers from urt/internal/mbedtls.c --- +// These handle operations whose signatures changed between mbedtls 2.x and 3.x, +// as well as operations that access internal struct layouts. + +int urt_pk_gen_ec_p256_key(mbedtls_pk_context* pk, int function(void*, ubyte*, size_t) nothrow @nogc f_rng, void* p_rng); +int urt_pk_export_pubkey_xy(mbedtls_pk_context* pk, ubyte* buf, size_t buflen, size_t* olen); +int urt_pk_sign(mbedtls_pk_context* ctx, const(ubyte)* hash, size_t hash_len, ubyte* sig, size_t sig_size, size_t* sig_len, int function(void*, ubyte*, size_t) nothrow @nogc f_rng, void* p_rng); +int urt_pk_import_ec_p256_key(mbedtls_pk_context* pk, const(ubyte)* d, size_t d_len, const(ubyte)* xy, size_t xy_len); +int urt_pk_export_privkey_d(mbedtls_pk_context* pk, ubyte* buf, size_t buflen, size_t* olen); + + +// --- X.509 certificate --- + +// mbedtls_x509_crt is complex (~200+ bytes). We only use it through pointers +// allocated via urt/internal/mbedtls.c (urt_mbedtls_x509_crt_new/delete). +struct mbedtls_x509_crt; + +void mbedtls_x509_crt_init(mbedtls_x509_crt* crt); +void mbedtls_x509_crt_free(mbedtls_x509_crt* crt); +int mbedtls_x509_crt_parse_der(mbedtls_x509_crt* chain, const(ubyte)* buf, size_t buflen); +int mbedtls_x509_crt_parse(mbedtls_x509_crt* chain, const(ubyte)* buf, size_t buflen); + + +// --- Entropy & CTR-DRBG (random number generation) --- + +// These are large structs. Allocated via urt/internal/mbedtls.c (urt_mbedtls_entropy_new/delete, urt_mbedtls_ctr_drbg_new/delete). +struct mbedtls_entropy_context; +struct mbedtls_ctr_drbg_context; + +void mbedtls_entropy_init(mbedtls_entropy_context* ctx); +void mbedtls_entropy_free(mbedtls_entropy_context* ctx); +int mbedtls_entropy_func(void* data, ubyte* output, size_t len); + +void mbedtls_ctr_drbg_init(mbedtls_ctr_drbg_context* ctx); +void mbedtls_ctr_drbg_free(mbedtls_ctr_drbg_context* ctx); +int mbedtls_ctr_drbg_seed(mbedtls_ctr_drbg_context* ctx, int function(void*, ubyte*, size_t) nothrow @nogc f_entropy, void* p_entropy, const(ubyte)* custom, size_t len); +int mbedtls_ctr_drbg_random(void* p_rng, ubyte* output, size_t output_len); + + +// --- SSL/TLS --- + +struct mbedtls_ssl_context; +struct mbedtls_ssl_config; + +enum MBEDTLS_SSL_IS_CLIENT = 0; +enum MBEDTLS_SSL_IS_SERVER = 1; +enum MBEDTLS_SSL_TRANSPORT_STREAM = 0; +enum MBEDTLS_SSL_PRESET_DEFAULT = 0; + +enum MBEDTLS_ERR_SSL_WANT_READ = -0x6900; +enum MBEDTLS_ERR_SSL_WANT_WRITE = -0x6880; +enum MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY = -0x7880; + +void mbedtls_ssl_init(mbedtls_ssl_context* ssl); +void mbedtls_ssl_free(mbedtls_ssl_context* ssl); +int mbedtls_ssl_setup(mbedtls_ssl_context* ssl, const(mbedtls_ssl_config)* conf); +void mbedtls_ssl_set_bio(mbedtls_ssl_context* ssl, void* p_bio, int function(void*, const(ubyte)*, size_t) nothrow @nogc f_send, int function(void*, ubyte*, size_t) nothrow @nogc f_recv, void* f_recv_timeout); +int mbedtls_ssl_handshake(mbedtls_ssl_context* ssl); +int mbedtls_ssl_read(mbedtls_ssl_context* ssl, ubyte* buf, size_t len); +int mbedtls_ssl_write(mbedtls_ssl_context* ssl, const(ubyte)* buf, size_t len); +int mbedtls_ssl_close_notify(mbedtls_ssl_context* ssl); +int mbedtls_ssl_set_hostname(mbedtls_ssl_context* ssl, const(char)* hostname); + +void mbedtls_ssl_config_init(mbedtls_ssl_config* conf); +void mbedtls_ssl_config_free(mbedtls_ssl_config* conf); +int mbedtls_ssl_config_defaults(mbedtls_ssl_config* conf, int endpoint, int transport, int preset); +void mbedtls_ssl_conf_rng(mbedtls_ssl_config* conf, int function(void*, ubyte*, size_t) nothrow @nogc f_rng, void* p_rng); +void mbedtls_ssl_conf_ca_chain(mbedtls_ssl_config* conf, mbedtls_x509_crt* ca_chain, void* ca_crl); +int mbedtls_ssl_conf_own_cert(mbedtls_ssl_config* conf, mbedtls_x509_crt* own_cert, mbedtls_pk_context* pk_key); +void mbedtls_ssl_conf_authmode(mbedtls_ssl_config* conf, int authmode); + +alias mbedtls_ssl_conf_sni_cb = int function(void* p_info, mbedtls_ssl_context* ssl, const(ubyte)* name, size_t name_len) nothrow @nogc; +void mbedtls_ssl_conf_sni(mbedtls_ssl_config* conf, mbedtls_ssl_conf_sni_cb f_sni, void* p_sni); + +enum MBEDTLS_SSL_VERIFY_NONE = 0; +enum MBEDTLS_SSL_VERIFY_OPTIONAL = 1; +enum MBEDTLS_SSL_VERIFY_REQUIRED = 2; + + +// --- sizeof() from urt/internal/mbedtls.c (opaque types too complex to replicate) --- + +size_t urt_sizeof_entropy(); +size_t urt_sizeof_ctr_drbg(); +size_t urt_sizeof_x509_crt(); +size_t urt_sizeof_ssl_context(); +size_t urt_sizeof_ssl_config(); + +import urt.mem.alloc; + +mbedtls_entropy_context* urt_entropy_new() +{ + auto ctx = cast(mbedtls_entropy_context*)alloc(urt_sizeof_entropy()).ptr; + if (ctx) + mbedtls_entropy_init(ctx); + return ctx; +} + +void urt_entropy_delete(mbedtls_entropy_context* ctx) +{ + if (ctx) + { + mbedtls_entropy_free(ctx); + free((cast(void*)ctx)[0..urt_sizeof_entropy()]); + } +} + +mbedtls_ctr_drbg_context* urt_ctr_drbg_new() +{ + auto ctx = cast(mbedtls_ctr_drbg_context*)alloc(urt_sizeof_ctr_drbg()).ptr; + if (ctx) + mbedtls_ctr_drbg_init(ctx); + return ctx; +} + +void urt_ctr_drbg_delete(mbedtls_ctr_drbg_context* ctx) +{ + if (ctx) + { + mbedtls_ctr_drbg_free(ctx); + free((cast(void*)ctx)[0..urt_sizeof_ctr_drbg()]); + } +} + +mbedtls_x509_crt* urt_x509_crt_new() +{ + auto ctx = cast(mbedtls_x509_crt*)alloc(urt_sizeof_x509_crt()).ptr; + if (ctx) + mbedtls_x509_crt_init(ctx); + return ctx; +} + +void urt_x509_crt_delete(mbedtls_x509_crt* crt) +{ + if (crt) + { + mbedtls_x509_crt_free(crt); + free((cast(void*)crt)[0..urt_sizeof_x509_crt()]); + } +} + +mbedtls_ssl_context* urt_ssl_new() +{ + auto ctx = cast(mbedtls_ssl_context*)alloc(urt_sizeof_ssl_context()).ptr; + if (ctx) + mbedtls_ssl_init(ctx); + return ctx; +} + +void urt_ssl_delete(mbedtls_ssl_context* ssl) +{ + if (ssl) + { + mbedtls_ssl_free(ssl); + free((cast(void*)ssl)[0..urt_sizeof_ssl_context()]); + } +} + +mbedtls_ssl_config* urt_ssl_config_new() +{ + auto ctx = cast(mbedtls_ssl_config*)alloc(urt_sizeof_ssl_config()).ptr; + if (ctx) + mbedtls_ssl_config_init(ctx); + return ctx; +} + +void urt_ssl_config_delete(mbedtls_ssl_config* conf) +{ + if (conf) + { + mbedtls_ssl_config_free(conf); + free((cast(void*)conf)[0..urt_sizeof_ssl_config()]); + } +} diff --git a/src/urt/internal/os.c b/src/urt/internal/os.c index d6b9c02..8841d6b 100644 --- a/src/urt/internal/os.c +++ b/src/urt/internal/os.c @@ -2,10 +2,21 @@ #if defined(__linux) # define _DEFAULT_SOURCE +# include # include # include +# include # include # include # include # include +# include +# include +# include +# include + +// EWOULDBLOCK is #define EWOULDBLOCK EAGAIN on Linux - ImportC cannot resolve +// chained macros, so re-define as a plain integer. +# undef EWOULDBLOCK +# define EWOULDBLOCK 11 /* same as EAGAIN on Linux */ #endif diff --git a/src/urt/internal/stdc/errno.d b/src/urt/internal/stdc/errno.d new file mode 100644 index 0000000..459f9e7 --- /dev/null +++ b/src/urt/internal/stdc/errno.d @@ -0,0 +1,2487 @@ +/** + * D header file for C99. + * + * $(C_HEADER_DESCRIPTION pubs.opengroup.org/onlinepubs/009695399/basedefs/_errno.h.html, _errno.h) + * + * Copyright: Copyright Sean Kelly 2005 - 2009. + * License: Distributed under the + * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0). + * (See accompanying file LICENSE) + * Authors: Sean Kelly, Alex Rønne Petersen + * Source: $(DRUNTIMESRC core/stdc/_errno.d) + * Standards: ISO/IEC 9899:1999 (E) + */ + +module urt.internal.stdc.errno; + +version (OSX) + version = Darwin; +else version (iOS) + version = Darwin; +else version (TVOS) + version = Darwin; +else version (WatchOS) + version = Darwin; + +version (ARM) version = ARM_Any; +version (AArch64) version = ARM_Any; +version (HPPA) version = HPPA_Any; +version (MIPS32) version = MIPS_Any; +version (MIPS64) version = MIPS_Any; +version (PPC) version = PPC_Any; +version (PPC64) version = PPC_Any; +version (RISCV32) version = RISCV_Any; +version (RISCV64) version = RISCV_Any; +version (S390) version = IBMZ_Any; +version (SPARC) version = SPARC_Any; +version (SPARC64) version = SPARC_Any; +version (SystemZ) version = IBMZ_Any; +version (X86) version = X86_Any; +version (X86_64) version = X86_Any; + +// Picolibc forked from newlib and shares its errno constant values. +// The accessor differs (picolibc uses a plain global, newlib uses __errno()), +version (CRuntime_Picolibc) version = NewlibCompat; +version (CRuntime_Newlib) version = NewlibCompat; + +@trusted: // Only manipulates errno. +nothrow: +@nogc: + +version (CRuntime_Microsoft) +{ + extern(C) + { + ref int _errno(); + alias errno = _errno; + } +} +else version (CRuntime_Glibc) +{ + extern(C) + { + ref int __errno_location(); + alias errno = __errno_location; + } +} +else version (CRuntime_Musl) +{ + extern(C) + { + ref int __errno_location(); + alias errno = __errno_location; + } +} +else version (CRuntime_Picolibc) +{ + version (FreeRTOS) + { + // On FreeRTOS, picolibc defines errno as _Thread_local which conflicts + // with emulated-TLS. Use a C shim to access it indirectly. + extern(C) int* ow_errno_location() nothrow @nogc; + @property ref int errno() nothrow @nogc { return *ow_errno_location(); } + } + else + { + // Bare-metal single-threaded: errno is a plain global. + extern(C) extern int errno; + } +} +else version (CRuntime_Newlib) +{ + extern(C) + { + ref int __errno(); + alias errno = __errno; + } +} +else version (OpenBSD) +{ + // https://github.com/openbsd/src/blob/master/include/errno.h + extern(C) + { + ref int __errno(); + alias errno = __errno; + } +} +else version (NetBSD) +{ + // https://github.com/NetBSD/src/blob/trunk/include/errno.h + extern(C) + { + ref int __errno(); + alias errno = __errno; + } +} +else version (FreeBSD) +{ + extern(C) + { + ref int __error(); + alias errno = __error; + } +} +else version (DragonFlyBSD) +{ + extern(C) + { + pragma(mangle, "errno") int __errno; + ref int __error() { + return __errno; + } + alias errno = __error; + } +} +else version (CRuntime_Bionic) +{ + extern(C) + { + ref int __errno(); + alias errno = __errno; + } +} +else version (CRuntime_UClibc) +{ + extern(C) + { + ref int __errno_location(); + alias errno = __errno_location; + } +} +else version (Darwin) +{ + extern(C) + { + ref int __error(); + alias errno = __error; + } +} +else version (Solaris) +{ + extern(C) + { + ref int ___errno(); + alias errno = ___errno; + } +} +else version (Haiku) +{ + // https://github.com/haiku/haiku/blob/master/headers/posix/errno.h + extern(C) + { + ref int _errnop(); + alias errno = _errnop; + } +} +else +{ + /// + extern(C) pragma(mangle, "getErrno") @property int errno(); + /// + extern(C) pragma(mangle, "setErrno") @property int errno(int n); +} + +extern(C): + + +version (CRuntime_Microsoft) +{ + enum EPERM = 1; /// Operation not permitted + enum ENOENT = 2; /// No such file or directory + enum ESRCH = 3; /// No such process + enum EINTR = 4; /// Interrupted system call + enum EIO = 5; /// I/O error + enum ENXIO = 6; /// No such device or address + enum E2BIG = 7; /// Argument list too long + enum ENOEXEC = 8; /// Exec format error + enum EBADF = 9; /// Bad file number + enum ECHILD = 10; /// No child processes + enum EAGAIN = 11; /// Try again + enum ENOMEM = 12; /// Out of memory + enum EACCES = 13; /// Permission denied + enum EFAULT = 14; /// Bad address + enum EBUSY = 16; /// Device or resource busy + enum EEXIST = 17; /// File exists + enum EXDEV = 18; /// Cross-device link + enum ENODEV = 19; /// No such device + enum ENOTDIR = 20; /// Not a directory + enum EISDIR = 21; /// Is a directory + enum EINVAL = 22; /// Invalid argument + enum ENFILE = 23; /// File table overflow + enum EMFILE = 24; /// Too many open files + enum ENOTTY = 25; /// Not a typewriter + enum EFBIG = 27; /// File too large + enum ENOSPC = 28; /// No space left on device + enum ESPIPE = 29; /// Illegal seek + enum EROFS = 30; /// Read-only file system + enum EMLINK = 31; /// Too many links + enum EPIPE = 32; /// Broken pipe + enum EDOM = 33; /// Math argument out of domain of func + enum ERANGE = 34; /// Math result not representable + enum EDEADLK = 36; /// Resource deadlock would occur + enum ENAMETOOLONG = 38; /// File name too long + enum ENOLCK = 39; /// No record locks available + enum ENOSYS = 40; /// Function not implemented + enum ENOTEMPTY = 41; /// Directory not empty + enum EILSEQ = 42; /// Illegal byte sequence + enum EDEADLOCK = EDEADLK; /// Resource deadlock would occur + + // POSIX compatibility + // See_Also: https://docs.microsoft.com/en-us/cpp/c-runtime-library/errno-constants + enum EADDRINUSE = 100; + enum EADDRNOTAVAIL = 101; + enum EAFNOSUPPORT = 102; + enum EALREADY = 103; + enum EBADMSG = 104; + enum ECANCELED = 105; + enum ECONNABORTED = 106; + enum ECONNREFUSED = 107; + enum ECONNRESET = 108; + enum EDESTADDRREQ = 109; + enum EHOSTUNREACH = 110; + enum EIDRM = 111; + enum EINPROGRESS = 112; + enum EISCONN = 113; + enum ELOOP = 114; + enum EMSGSIZE = 115; + enum ENETDOWN = 116; + enum ENETRESET = 117; + enum ENETUNREACH = 118; + enum ENOBUFS = 119; + enum ENODATA = 120; + enum ENOLINK = 121; + enum ENOMSG = 122; + enum ENOPROTOOPT = 123; + enum ENOSR = 124; + enum ENOSTR = 125; + enum ENOTCONN = 126; + enum ENOTRECOVERABLE = 127; + enum ENOTSOCK = 128; + enum ENOTSUP = 129; + enum EOPNOTSUPP = 130; + enum EOTHER = 131; + enum EOVERFLOW = 132; + enum EOWNERDEAD = 133; + enum EPROTO = 134; + enum EPROTONOSUPPORT = 135; + enum EPROTOTYPE = 136; + enum ETIME = 137; + enum ETIMEDOUT = 138; + enum ETXTBSY = 139; + enum EWOULDBLOCK = 140; +} +else version (NewlibCompat) +{ + enum EPERM = 1; + enum ENOENT = 2; + enum ESRCH = 3; + enum EINTR = 4; + enum EIO = 5; + enum ENXIO = 6; + enum E2BIG = 7; + enum ENOEXEC = 8; + enum EBADF = 9; + enum ECHILD = 10; + enum EAGAIN = 11; + enum ENOMEM = 12; + enum EACCES = 13; + enum EFAULT = 14; + enum EBUSY = 16; + enum EEXIST = 17; + enum EXDEV = 18; + enum ENODEV = 19; + enum ENOTDIR = 20; + enum EISDIR = 21; + enum EINVAL = 22; + enum ENFILE = 23; + enum EMFILE = 24; + enum ENOTTY = 25; + enum ETXTBSY = 26; + enum EFBIG = 27; + enum ENOSPC = 28; + enum ESPIPE = 29; + enum EROFS = 30; + enum EMLINK = 31; + enum EPIPE = 32; + enum EDOM = 33; + enum ERANGE = 34; + enum ENOMSG = 35; + enum EIDRM = 36; + enum EDEADLK = 45; + enum ENOLCK = 46; + enum ENOSTR = 60; + enum ENODATA = 61; + enum ETIME = 62; + enum ENOSR = 63; + enum ENOLINK = 67; + enum EPROTO = 71; + enum EMULTIHOP = 74; + enum EBADMSG = 77; + enum EFTYPE = 79; + enum ENOSYS = 88; + enum ENOTEMPTY = 90; + enum ENAMETOOLONG = 91; + enum ELOOP = 92; + enum EOPNOTSUPP = 95; + enum EPFNOSUPPORT = 96; + enum ECONNRESET = 104; + enum ENOBUFS = 105; + enum EAFNOSUPPORT = 106; + enum EPROTOTYPE = 107; + enum ENOTSOCK = 108; + enum ENOPROTOOPT = 109; + enum ECONNREFUSED = 111; + enum EADDRINUSE = 112; + enum ECONNABORTED = 113; + enum ENETUNREACH = 114; + enum ENETDOWN = 115; + enum ETIMEDOUT = 116; + enum EHOSTDOWN = 117; + enum EHOSTUNREACH = 118; + enum EINPROGRESS = 119; + enum EALREADY = 120; + enum EDESTADDRREQ = 121; + enum EMSGSIZE = 122; + enum EPROTONOSUPPORT = 123; + enum EADDRNOTAVAIL = 125; + enum ENETRESET = 126; + enum EISCONN = 127; + enum ENOTCONN = 128; + enum ETOOMANYREFS = 129; + enum EDQUOT = 132; + enum ESTALE = 133; + enum ENOTSUP = 134; + enum EILSEQ = 138; + enum EOVERFLOW = 139; + enum ECANCELED = 140; + enum ENOTRECOVERABLE = 141; + enum EOWNERDEAD = 142; + + enum EWOULDBLOCK = EAGAIN; + + enum __ELASTERROR = 2000; + + // Linux errno extensions + version (Cygwin) + { + enum ENOTBLK = 15; + enum ECHRNG = 37; + enum EL2NSYNC = 38; + enum EL3HLT = 39; + enum EL3RST = 40; + enum ELNRNG = 41; + enum EUNATCH = 42; + enum ENOCSI = 43; + enum EL2HLT = 44; + enum EBADE = 50; + enum EBADR = 51; + enum EXFULL = 52; + enum ENOANO = 53; + enum EBADRQC = 54; + enum EBADSLT = 55; + enum EDEADLOCK = 56; + enum EBFONT = 57; + enum ENONET = 64; + enum ENOPKG = 65; + enum EREMOTE = 66; + enum EADV = 68; + enum ESRMNT = 69; + enum ECOMM = 70; + enum ELBIN = 75; + enum EDOTDOT = 76; + enum ENOTUNIQ = 80; + enum EBADFD = 81; + enum EREMCHG = 82; + enum ELIBACC = 83; + enum ELIBBAD = 84; + enum ELIBSCN = 85; + enum ELIBMAX = 86; + enum ELIBEXEC = 87; + enum ENMFILE = 89; + enum ESHUTDOWN = 110; + enum ESOCKTNOSUPPORT = 124; + enum EPROCLIM = 130; + enum EUSERS = 131; + enum ENOMEDIUM = 135; + enum ENOSHARE = 136; + enum ECASECLASH = 137; + enum ESTRPIPE = 143; + } +} +else version (linux) +{ + enum EPERM = 1; /// + enum ENOENT = 2; /// + enum ESRCH = 3; /// + enum EINTR = 4; /// + enum EIO = 5; /// + enum ENXIO = 6; /// + enum E2BIG = 7; /// + enum ENOEXEC = 8; /// + enum EBADF = 9; /// + enum ECHILD = 10; /// + enum EAGAIN = 11; /// + enum ENOMEM = 12; /// + enum EACCES = 13; /// + enum EFAULT = 14; /// + enum ENOTBLK = 15; /// + enum EBUSY = 16; /// + enum EEXIST = 17; /// + enum EXDEV = 18; /// + enum ENODEV = 19; /// + enum ENOTDIR = 20; /// + enum EISDIR = 21; /// + enum EINVAL = 22; /// + enum ENFILE = 23; /// + enum EMFILE = 24; /// + enum ENOTTY = 25; /// + enum ETXTBSY = 26; /// + enum EFBIG = 27; /// + enum ENOSPC = 28; /// + enum ESPIPE = 29; /// + enum EROFS = 30; /// + enum EMLINK = 31; /// + enum EPIPE = 32; /// + enum EDOM = 33; /// + enum ERANGE = 34; /// + + version (X86_Any) + { + enum EDEADLK = 35; /// + enum ENAMETOOLONG = 36; /// + enum ENOLCK = 37; /// + enum ENOSYS = 38; /// + enum ENOTEMPTY = 39; /// + enum ELOOP = 40; /// + enum EWOULDBLOCK = EAGAIN; /// + enum ENOMSG = 42; /// + enum EIDRM = 43; /// + enum ECHRNG = 44; /// + enum EL2NSYNC = 45; /// + enum EL3HLT = 46; /// + enum EL3RST = 47; /// + enum ELNRNG = 48; /// + enum EUNATCH = 49; /// + enum ENOCSI = 50; /// + enum EL2HLT = 51; /// + enum EBADE = 52; /// + enum EBADR = 53; /// + enum EXFULL = 54; /// + enum ENOANO = 55; /// + enum EBADRQC = 56; /// + enum EBADSLT = 57; /// + enum EDEADLOCK = EDEADLK; /// + enum EBFONT = 59; /// + enum ENOSTR = 60; /// + enum ENODATA = 61; /// + enum ETIME = 62; /// + enum ENOSR = 63; /// + enum ENONET = 64; /// + enum ENOPKG = 65; /// + enum EREMOTE = 66; /// + enum ENOLINK = 67; /// + enum EADV = 68; /// + enum ESRMNT = 69; /// + enum ECOMM = 70; /// + enum EPROTO = 71; /// + enum EMULTIHOP = 72; /// + enum EDOTDOT = 73; /// + enum EBADMSG = 74; /// + enum EOVERFLOW = 75; /// + enum ENOTUNIQ = 76; /// + enum EBADFD = 77; /// + enum EREMCHG = 78; /// + enum ELIBACC = 79; /// + enum ELIBBAD = 80; /// + enum ELIBSCN = 81; /// + enum ELIBMAX = 82; /// + enum ELIBEXEC = 83; /// + enum EILSEQ = 84; /// + enum ERESTART = 85; /// + enum ESTRPIPE = 86; /// + enum EUSERS = 87; /// + enum ENOTSOCK = 88; /// + enum EDESTADDRREQ = 89; /// + enum EMSGSIZE = 90; /// + enum EPROTOTYPE = 91; /// + enum ENOPROTOOPT = 92; /// + enum EPROTONOSUPPORT = 93; /// + enum ESOCKTNOSUPPORT = 94; /// + enum EOPNOTSUPP = 95; /// + enum ENOTSUP = EOPNOTSUPP; /// + enum EPFNOSUPPORT = 96; /// + enum EAFNOSUPPORT = 97; /// + enum EADDRINUSE = 98; /// + enum EADDRNOTAVAIL = 99; /// + enum ENETDOWN = 100; /// + enum ENETUNREACH = 101; /// + enum ENETRESET = 102; /// + enum ECONNABORTED = 103; /// + enum ECONNRESET = 104; /// + enum ENOBUFS = 105; /// + enum EISCONN = 106; /// + enum ENOTCONN = 107; /// + enum ESHUTDOWN = 108; /// + enum ETOOMANYREFS = 109; /// + enum ETIMEDOUT = 110; /// + enum ECONNREFUSED = 111; /// + enum EHOSTDOWN = 112; /// + enum EHOSTUNREACH = 113; /// + enum EALREADY = 114; /// + enum EINPROGRESS = 115; /// + enum ESTALE = 116; /// + enum EUCLEAN = 117; /// + enum ENOTNAM = 118; /// + enum ENAVAIL = 119; /// + enum EISNAM = 120; /// + enum EREMOTEIO = 121; /// + enum EDQUOT = 122; /// + enum ENOMEDIUM = 123; /// + enum EMEDIUMTYPE = 124; /// + enum ECANCELED = 125; /// + enum ENOKEY = 126; /// + enum EKEYEXPIRED = 127; /// + enum EKEYREVOKED = 128; /// + enum EKEYREJECTED = 129; /// + enum EOWNERDEAD = 130; /// + enum ENOTRECOVERABLE = 131; /// + enum ERFKILL = 132; /// + enum EHWPOISON = 133; /// + } + else version (ARM_Any) + { + enum EDEADLK = 35; /// + enum ENAMETOOLONG = 36; /// + enum ENOLCK = 37; /// + enum ENOSYS = 38; /// + enum ENOTEMPTY = 39; /// + enum ELOOP = 40; /// + enum EWOULDBLOCK = EAGAIN; /// + enum ENOMSG = 42; /// + enum EIDRM = 43; /// + enum ECHRNG = 44; /// + enum EL2NSYNC = 45; /// + enum EL3HLT = 46; /// + enum EL3RST = 47; /// + enum ELNRNG = 48; /// + enum EUNATCH = 49; /// + enum ENOCSI = 50; /// + enum EL2HLT = 51; /// + enum EBADE = 52; /// + enum EBADR = 53; /// + enum EXFULL = 54; /// + enum ENOANO = 55; /// + enum EBADRQC = 56; /// + enum EBADSLT = 57; /// + enum EDEADLOCK = EDEADLK; /// + enum EBFONT = 59; /// + enum ENOSTR = 60; /// + enum ENODATA = 61; /// + enum ETIME = 62; /// + enum ENOSR = 63; /// + enum ENONET = 64; /// + enum ENOPKG = 65; /// + enum EREMOTE = 66; /// + enum ENOLINK = 67; /// + enum EADV = 68; /// + enum ESRMNT = 69; /// + enum ECOMM = 70; /// + enum EPROTO = 71; /// + enum EMULTIHOP = 72; /// + enum EDOTDOT = 73; /// + enum EBADMSG = 74; /// + enum EOVERFLOW = 75; /// + enum ENOTUNIQ = 76; /// + enum EBADFD = 77; /// + enum EREMCHG = 78; /// + enum ELIBACC = 79; /// + enum ELIBBAD = 80; /// + enum ELIBSCN = 81; /// + enum ELIBMAX = 82; /// + enum ELIBEXEC = 83; /// + enum EILSEQ = 84; /// + enum ERESTART = 85; /// + enum ESTRPIPE = 86; /// + enum EUSERS = 87; /// + enum ENOTSOCK = 88; /// + enum EDESTADDRREQ = 89; /// + enum EMSGSIZE = 90; /// + enum EPROTOTYPE = 91; /// + enum ENOPROTOOPT = 92; /// + enum EPROTONOSUPPORT = 93; /// + enum ESOCKTNOSUPPORT = 94; /// + enum EOPNOTSUPP = 95; /// + enum ENOTSUP = EOPNOTSUPP; /// + enum EPFNOSUPPORT = 96; /// + enum EAFNOSUPPORT = 97; /// + enum EADDRINUSE = 98; /// + enum EADDRNOTAVAIL = 99; /// + enum ENETDOWN = 100; /// + enum ENETUNREACH = 101; /// + enum ENETRESET = 102; /// + enum ECONNABORTED = 103; /// + enum ECONNRESET = 104; /// + enum ENOBUFS = 105; /// + enum EISCONN = 106; /// + enum ENOTCONN = 107; /// + enum ESHUTDOWN = 108; /// + enum ETOOMANYREFS = 109; /// + enum ETIMEDOUT = 110; /// + enum ECONNREFUSED = 111; /// + enum EHOSTDOWN = 112; /// + enum EHOSTUNREACH = 113; /// + enum EALREADY = 114; /// + enum EINPROGRESS = 115; /// + enum ESTALE = 116; /// + enum EUCLEAN = 117; /// + enum ENOTNAM = 118; /// + enum ENAVAIL = 119; /// + enum EISNAM = 120; /// + enum EREMOTEIO = 121; /// + enum EDQUOT = 122; /// + enum ENOMEDIUM = 123; /// + enum EMEDIUMTYPE = 124; /// + enum ECANCELED = 125; /// + enum ENOKEY = 126; /// + enum EKEYEXPIRED = 127; /// + enum EKEYREVOKED = 128; /// + enum EKEYREJECTED = 129; /// + enum EOWNERDEAD = 130; /// + enum ENOTRECOVERABLE = 131; /// + enum ERFKILL = 132; /// + enum EHWPOISON = 133; /// + } + else version (HPPA_Any) + { + enum ENOMSG = 35; /// + enum EIDRM = 36; /// + enum ECHRNG = 37; /// + enum EL2NSYNC = 38; /// + enum EL3HLT = 39; /// + enum EL3RST = 40; /// + enum ELNRNG = 41; /// + enum EUNATCH = 42; /// + enum ENOCSI = 43; /// + enum EL2HLT = 44; /// + enum EDEADLK = 45; /// + enum EDEADLOCK = EDEADLK; /// + enum ENOLCK = 46; /// + enum EILSEQ = 47; /// + enum ENONET = 50; /// + enum ENODATA = 51; /// + enum ETIME = 52; /// + enum ENOSR = 53; /// + enum ENOSTR = 54; /// + enum ENOPKG = 55; /// + enum ENOLINK = 57; /// + enum EADV = 58; /// + enum ESRMNT = 59; /// + enum ECOMM = 60; /// + enum EPROTO = 61; /// + enum EMULTIHOP = 64; /// + enum EDOTDOT = 66; /// + enum EBADMSG = 67; /// + enum EUSERS = 68; /// + enum EDQUOT = 69; /// + enum ESTALE = 70; /// + enum EREMOTE = 71; /// + enum EOVERFLOW = 72; /// + enum EBADE = 160; /// + enum EBADR = 161; /// + enum EXFULL = 162; /// + enum ENOANO = 163; /// + enum EBADRQC = 164; /// + enum EBADSLT = 165; /// + enum EBFONT = 166; /// + enum ENOTUNIQ = 167; /// + enum EBADFD = 168; /// + enum EREMCHG = 169; /// + enum ELIBACC = 170; /// + enum ELIBBAD = 171; /// + enum ELIBSCN = 172; /// + enum ELIBMAX = 173; /// + enum ELIBEXEC = 174; /// + enum ERESTART = 175; /// + enum ESTRPIPE = 176; /// + enum EUCLEAN = 177; /// + enum ENOTNAM = 178; /// + enum ENAVAIL = 179; /// + enum EISNAM = 180; /// + enum EREMOTEIO = 181; /// + enum ENOMEDIUM = 182; /// + enum EMEDIUMTYPE = 183; /// + enum ENOKEY = 184; /// + enum EKEYEXPIRED = 185; /// + enum EKEYREVOKED = 186; /// + enum EKEYREJECTED = 187; /// + enum ENOSYM = 215; /// + enum ENOTSOCK = 216; /// + enum EDESTADDRREQ = 217; /// + enum EMSGSIZE = 218; /// + enum EPROTOTYPE = 219; /// + enum ENOPROTOOPT = 220; /// + enum EPROTONOSUPPORT = 221; /// + enum ESOCKTNOSUPPORT = 221; /// + enum EOPNOTSUPP = 223; /// + enum EPFNOSUPPORT = 224; /// + enum EAFNOSUPPORT = 225; /// + enum EADDRINUSE = 226; /// + enum EADDRNOTAVAIL = 227; /// + enum ENETDOWN = 228; /// + enum ENETUNREACH = 229; /// + enum ENETRESET = 230; /// + enum ECONNABORTED = 231; /// + enum ECONNRESET = 232; /// + enum ENOBUFS = 233; /// + enum EISCONN = 234; /// + enum ENOTCONN = 235; /// + enum ESHUTDOWN = 236; /// + enum ETOOMANYREFS = 237; /// + enum ETIMEDOUT = 238; /// + enum ECONNREFUSED = 239; /// + enum EREFUSED = ECONNREFUSED; /// + enum EREMOTERELEASE = 240; /// + enum EHOSTDOWN = 241; /// + enum EHOSTUNREACH = 242; /// + enum EALREADY = 244; /// + enum EINPROGRESS = 245; /// + enum EWOULDBLOCK = EAGAIN; /// + enum ENOTEMPTY = 247; /// + enum ENAMETOOLONG = 248; /// + enum ELOOP = 249; /// + enum ENOSYS = 251; /// + enum ECANCELLED = 253; /// + enum ECANCELED = ECANCELLED; /// + enum EOWNERDEAD = 254; /// + enum ENOTRECOVERABLE = 255; /// + enum ERFKILL = 256; /// + enum EHWPOISON = 257; /// + } + else version (MIPS_Any) + { + enum ENOMSG = 35; /// + enum EIDRM = 36; /// + enum ECHRNG = 37; /// + enum EL2NSYNC = 38; /// + enum EL3HLT = 39; /// + enum EL3RST = 40; /// + enum ELNRNG = 41; /// + enum EUNATCH = 42; /// + enum ENOCSI = 43; /// + enum EL2HLT = 44; /// + enum EDEADLK = 45; /// + enum ENOLCK = 46; /// + enum EBADE = 50; /// + enum EBADR = 51; /// + enum EXFULL = 52; /// + enum ENOANO = 53; /// + enum EBADRQC = 54; /// + enum EBADSLT = 55; /// + enum EDEADLOCK = 56; /// + enum EBFONT = 59; /// + enum ENOSTR = 60; /// + enum ENODATA = 61; /// + enum ETIME = 62; /// + enum ENOSR = 63; /// + enum ENONET = 64; /// + enum ENOPKG = 65; /// + enum EREMOTE = 66; /// + enum ENOLINK = 67; /// + enum EADV = 68; /// + enum ESRMNT = 69; /// + enum ECOMM = 70; /// + enum EPROTO = 71; /// + enum EDOTDOT = 73; /// + enum EMULTIHOP = 74; /// + enum EBADMSG = 77; /// + enum ENAMETOOLONG = 78; /// + enum EOVERFLOW = 79; /// + enum ENOTUNIQ = 80; /// + enum EBADFD = 81; /// + enum EREMCHG = 82; /// + enum ELIBACC = 83; /// + enum ELIBBAD = 84; /// + enum ELIBSCN = 85; /// + enum ELIBMAX = 86; /// + enum ELIBEXEC = 87; /// + enum EILSEQ = 88; /// + enum ENOSYS = 89; /// + enum ELOOP = 90; /// + enum ERESTART = 91; /// + enum ESTRPIPE = 92; /// + enum ENOTEMPTY = 93; /// + enum EUSERS = 94; /// + enum ENOTSOCK = 95; /// + enum EDESTADDRREQ = 96; /// + enum EMSGSIZE = 97; /// + enum EPROTOTYPE = 98; /// + enum ENOPROTOOPT = 99; /// + enum EPROTONOSUPPORT = 120; /// + enum ESOCKTNOSUPPORT = 121; /// + enum EOPNOTSUPP = 122; /// + enum ENOTSUP = EOPNOTSUPP; /// + enum EPFNOSUPPORT = 123; /// + enum EAFNOSUPPORT = 124; /// + enum EADDRINUSE = 125; /// + enum EADDRNOTAVAIL = 126; /// + enum ENETDOWN = 127; /// + enum ENETUNREACH = 128; /// + enum ENETRESET = 129; /// + enum ECONNABORTED = 130; /// + enum ECONNRESET = 131; /// + enum ENOBUFS = 132; /// + enum EISCONN = 133; /// + enum ENOTCONN = 134; /// + enum EUCLEAN = 135; /// + enum ENOTNAM = 137; /// + enum ENAVAIL = 138; /// + enum EISNAM = 139; /// + enum EREMOTEIO = 140; /// + enum EINIT = 141; /// + enum EREMDEV = 142; /// + enum ESHUTDOWN = 143; /// + enum ETOOMANYREFS = 144; /// + enum ETIMEDOUT = 145; /// + enum ECONNREFUSED = 146; /// + enum EHOSTDOWN = 147; /// + enum EHOSTUNREACH = 148; /// + enum EWOULDBLOCK = EAGAIN; /// + enum EALREADY = 149; /// + enum EINPROGRESS = 150; /// + enum ESTALE = 151; /// + enum ECANCELED = 158; /// + enum ENOMEDIUM = 159; /// + enum EMEDIUMTYPE = 160; /// + enum ENOKEY = 161; /// + enum EKEYEXPIRED = 162; /// + enum EKEYREVOKED = 163; /// + enum EKEYREJECTED = 164; /// + enum EOWNERDEAD = 165; /// + enum ENOTRECOVERABLE = 166; /// + enum ERFKILL = 167; /// + enum EHWPOISON = 168; /// + enum EDQUOT = 1133; /// + } + else version (PPC_Any) + { + enum EDEADLK = 35; /// + enum ENAMETOOLONG = 36; /// + enum ENOLCK = 37; /// + enum ENOSYS = 38; /// + enum ENOTEMPTY = 39; /// + enum ELOOP = 40; /// + enum EWOULDBLOCK = EAGAIN; /// + enum ENOMSG = 42; /// + enum EIDRM = 43; /// + enum ECHRNG = 44; /// + enum EL2NSYNC = 45; /// + enum EL3HLT = 46; /// + enum EL3RST = 47; /// + enum ELNRNG = 48; /// + enum EUNATCH = 49; /// + enum ENOCSI = 50; /// + enum EL2HLT = 51; /// + enum EBADE = 52; /// + enum EBADR = 53; /// + enum EXFULL = 54; /// + enum ENOANO = 55; /// + enum EBADRQC = 56; /// + enum EBADSLT = 57; /// + enum EDEADLOCK = 58; /// + enum EBFONT = 59; /// + enum ENOSTR = 60; /// + enum ENODATA = 61; /// + enum ETIME = 62; /// + enum ENOSR = 63; /// + enum ENONET = 64; /// + enum ENOPKG = 65; /// + enum EREMOTE = 66; /// + enum ENOLINK = 67; /// + enum EADV = 68; /// + enum ESRMNT = 69; /// + enum ECOMM = 70; /// + enum EPROTO = 71; /// + enum EMULTIHOP = 72; /// + enum EDOTDOT = 73; /// + enum EBADMSG = 74; /// + enum EOVERFLOW = 75; /// + enum ENOTUNIQ = 76; /// + enum EBADFD = 77; /// + enum EREMCHG = 78; /// + enum ELIBACC = 79; /// + enum ELIBBAD = 80; /// + enum ELIBSCN = 81; /// + enum ELIBMAX = 82; /// + enum ELIBEXEC = 83; /// + enum EILSEQ = 84; /// + enum ERESTART = 85; /// + enum ESTRPIPE = 86; /// + enum EUSERS = 87; /// + enum ENOTSOCK = 88; /// + enum EDESTADDRREQ = 89; /// + enum EMSGSIZE = 90; /// + enum EPROTOTYPE = 91; /// + enum ENOPROTOOPT = 92; /// + enum EPROTONOSUPPORT = 93; /// + enum ESOCKTNOSUPPORT = 94; /// + enum EOPNOTSUPP = 95; /// + enum ENOTSUP = EOPNOTSUPP; /// + enum EPFNOSUPPORT = 96; /// + enum EAFNOSUPPORT = 97; /// + enum EADDRINUSE = 98; /// + enum EADDRNOTAVAIL = 99; /// + enum ENETDOWN = 100; /// + enum ENETUNREACH = 101; /// + enum ENETRESET = 102; /// + enum ECONNABORTED = 103; /// + enum ECONNRESET = 104; /// + enum ENOBUFS = 105; /// + enum EISCONN = 106; /// + enum ENOTCONN = 107; /// + enum ESHUTDOWN = 108; /// + enum ETOOMANYREFS = 109; /// + enum ETIMEDOUT = 110; /// + enum ECONNREFUSED = 111; /// + enum EHOSTDOWN = 112; /// + enum EHOSTUNREACH = 113; /// + enum EALREADY = 114; /// + enum EINPROGRESS = 115; /// + enum ESTALE = 116; /// + enum EUCLEAN = 117; /// + enum ENOTNAM = 118; /// + enum ENAVAIL = 119; /// + enum EISNAM = 120; /// + enum EREMOTEIO = 121; /// + enum EDQUOT = 122; /// + enum ENOMEDIUM = 123; /// + enum EMEDIUMTYPE = 124; /// + enum ECANCELED = 125; /// + enum ENOKEY = 126; /// + enum EKEYEXPIRED = 127; /// + enum EKEYREVOKED = 128; /// + enum EKEYREJECTED = 129; /// + enum EOWNERDEAD = 130; /// + enum ENOTRECOVERABLE = 131; /// + enum ERFKILL = 132; /// + enum EHWPOISON = 133; /// + } + else version (RISCV_Any) + { + enum EDEADLK = 35; /// + enum ENAMETOOLONG = 36; /// + enum ENOLCK = 37; /// + enum ENOSYS = 38; /// + enum ENOTEMPTY = 39; /// + enum ELOOP = 40; /// + enum EWOULDBLOCK = EAGAIN; /// + enum ENOMSG = 42; /// + enum EIDRM = 43; /// + enum ECHRNG = 44; /// + enum EL2NSYNC = 45; /// + enum EL3HLT = 46; /// + enum EL3RST = 47; /// + enum ELNRNG = 48; /// + enum EUNATCH = 49; /// + enum ENOCSI = 50; /// + enum EL2HLT = 51; /// + enum EBADE = 52; /// + enum EBADR = 53; /// + enum EXFULL = 54; /// + enum ENOANO = 55; /// + enum EBADRQC = 56; /// + enum EBADSLT = 57; /// + enum EDEADLOCK = EDEADLK; /// + enum EBFONT = 59; /// + enum ENOSTR = 60; /// + enum ENODATA = 61; /// + enum ETIME = 62; /// + enum ENOSR = 63; /// + enum ENONET = 64; /// + enum ENOPKG = 65; /// + enum EREMOTE = 66; /// + enum ENOLINK = 67; /// + enum EADV = 68; /// + enum ESRMNT = 69; /// + enum ECOMM = 70; /// + enum EPROTO = 71; /// + enum EMULTIHOP = 72; /// + enum EDOTDOT = 73; /// + enum EBADMSG = 74; /// + enum EOVERFLOW = 75; /// + enum ENOTUNIQ = 76; /// + enum EBADFD = 77; /// + enum EREMCHG = 78; /// + enum ELIBACC = 79; /// + enum ELIBBAD = 80; /// + enum ELIBSCN = 81; /// + enum ELIBMAX = 82; /// + enum ELIBEXEC = 83; /// + enum EILSEQ = 84; /// + enum ERESTART = 85; /// + enum ESTRPIPE = 86; /// + enum EUSERS = 87; /// + enum ENOTSOCK = 88; /// + enum EDESTADDRREQ = 89; /// + enum EMSGSIZE = 90; /// + enum EPROTOTYPE = 91; /// + enum ENOPROTOOPT = 92; /// + enum EPROTONOSUPPORT = 93; /// + enum ESOCKTNOSUPPORT = 94; /// + enum EOPNOTSUPP = 95; /// + enum EPFNOSUPPORT = 96; /// + enum EAFNOSUPPORT = 97; /// + enum EADDRINUSE = 98; /// + enum EADDRNOTAVAIL = 99; /// + enum ENETDOWN = 100; /// + enum ENETUNREACH = 101; /// + enum ENETRESET = 102; /// + enum ECONNABORTED = 103; /// + enum ECONNRESET = 104; /// + enum ENOBUFS = 105; /// + enum EISCONN = 106; /// + enum ENOTCONN = 107; /// + enum ESHUTDOWN = 108; /// + enum ETOOMANYREFS = 109; /// + enum ETIMEDOUT = 110; /// + enum ECONNREFUSED = 111; /// + enum EHOSTDOWN = 112; /// + enum EHOSTUNREACH = 113; /// + enum EALREADY = 114; /// + enum EINPROGRESS = 115; /// + enum ESTALE = 116; /// + enum EUCLEAN = 117; /// + enum ENOTNAM = 118; /// + enum ENAVAIL = 119; /// + enum EISNAM = 120; /// + enum EREMOTEIO = 121; /// + enum EDQUOT = 122; /// + enum ENOMEDIUM = 123; /// + enum EMEDIUMTYPE = 124; /// + enum ECANCELED = 125; /// + enum ENOKEY = 126; /// + enum EKEYEXPIRED = 127; /// + enum EKEYREVOKED = 128; /// + enum EKEYREJECTED = 129; /// + enum EOWNERDEAD = 130; /// + enum ENOTRECOVERABLE = 131; /// + enum ERFKILL = 132; /// + enum EHWPOISON = 133; /// + } + else version (SPARC_Any) + { + enum EWOULDBLOCK = EAGAIN; /// + enum EINPROGRESS = 36; /// + enum EALREADY = 37; /// + enum ENOTSOCK = 38; /// + enum EDESTADDRREQ = 39; /// + enum EMSGSIZE = 40; /// + enum EPROTOTYPE = 41; /// + enum ENOPROTOOPT = 42; /// + enum EPROTONOSUPPORT = 43; /// + enum ESOCKTNOSUPPORT = 44; /// + enum EOPNOTSUPP = 45; /// + enum ENOTSUP = EOPNOTSUPP; /// + enum EPFNOSUPPORT = 46; /// + enum EAFNOSUPPORT = 47; /// + enum EADDRINUSE = 48; /// + enum EADDRNOTAVAIL = 49; /// + enum ENETDOWN = 50; /// + enum ENETUNREACH = 51; /// + enum ENETRESET = 52; /// + enum ECONNABORTED = 53; /// + enum ECONNRESET = 54; /// + enum ENOBUFS = 55; /// + enum EISCONN = 56; /// + enum ENOTCONN = 57; /// + enum ESHUTDOWN = 58; /// + enum ETOOMANYREFS = 59; /// + enum ETIMEDOUT = 60; /// + enum ECONNREFUSED = 61; /// + enum ELOOP = 62; /// + enum ENAMETOOLONG = 63; /// + enum EHOSTDOWN = 64; /// + enum EHOSTUNREACH = 65; /// + enum ENOTEMPTY = 66; /// + enum EPROCLIM = 67; /// + enum EUSERS = 68; /// + enum EDQUOT = 69; /// + enum ESTALE = 70; /// + enum EREMOTE = 71; /// + enum ENOSTR = 72; /// + enum ETIME = 73; /// + enum ENOSR = 74; /// + enum ENOMSG = 75; /// + enum EBADMSG = 76; /// + enum EIDRM = 77; /// + enum EDEADLK = 78; /// + enum ENOLCK = 79; /// + enum ENONET = 80; /// + enum ERREMOTE = 81; /// + enum ENOLINK = 82; /// + enum EADV = 83; /// + enum ESRMNT = 84; /// + enum ECOMM = 85; /// + enum EPROTO = 86; /// + enum EMULTIHOP = 87; /// + enum EDOTDOT = 88; /// + enum EREMCHG = 89; /// + enum ENOSYS = 90; /// + enum ESTRPIPE = 91; /// + enum EOVERFLOW = 92; /// + enum EBADFD = 93; /// + enum ECHRNG = 94; /// + enum EL2NSYNC = 95; /// + enum EL3HLT = 96; /// + enum EL3RST = 97; /// + enum ELNRNG = 98; /// + enum EUNATCH = 99; /// + enum ENOCSI = 100; /// + enum EL2HLT = 101; /// + enum EBADE = 102; /// + enum EBADR = 103; /// + enum EXFULL = 104; /// + enum ENOANO = 105; /// + enum EBADRQC = 106; /// + enum EBADSLT = 107; /// + enum EDEADLOCK = 108; /// + enum EBFONT = 109; /// + enum ELIBEXEC = 110; /// + enum ENODATA = 111; /// + enum ELIBBAD = 112; /// + enum ENOPKG = 113; /// + enum ELIBACC = 114; /// + enum ENOTUNIQ = 115; /// + enum ERESTART = 116; /// + enum EUCLEAN = 117; /// + enum ENOTNAM = 118; /// + enum ENAVAIL = 119; /// + enum EISNAM = 120; /// + enum EREMOTEIO = 121; /// + enum EILSEQ = 122; /// + enum ELIBMAX = 123; /// + enum ELIBSCN = 124; /// + enum ENOMEDIUM = 125; /// + enum EMEDIUMTYPE = 126; /// + enum ECANCELED = 127; /// + enum ENOKEY = 128; /// + enum EKEYEXPIRED = 129; /// + enum EKEYREVOKED = 130; /// + enum EKEYREJECTED = 131; /// + enum EOWNERDEAD = 132; /// + enum ENOTRECOVERABLE = 133; /// + enum ERFKILL = 134; /// + enum EHWPOISON = 135; /// + } + else version (IBMZ_Any) + { + enum EDEADLK = 35; /// + enum ENAMETOOLONG = 36; /// + enum ENOLCK = 37; /// + enum ENOSYS = 38; /// + enum ENOTEMPTY = 39; /// + enum ELOOP = 40; /// + enum EWOULDBLOCK = EAGAIN; /// + enum ENOMSG = 42; /// + enum EIDRM = 43; /// + enum ECHRNG = 44; /// + enum EL2NSYNC = 45; /// + enum EL3HLT = 46; /// + enum EL3RST = 47; /// + enum ELNRNG = 48; /// + enum EUNATCH = 49; /// + enum ENOCSI = 50; /// + enum EL2HLT = 51; /// + enum EBADE = 52; /// + enum EBADR = 53; /// + enum EXFULL = 54; /// + enum ENOANO = 55; /// + enum EBADRQC = 56; /// + enum EBADSLT = 57; /// + enum EDEADLOCK = EDEADLK; /// + enum EBFONT = 59; /// + enum ENOSTR = 60; /// + enum ENODATA = 61; /// + enum ETIME = 62; /// + enum ENOSR = 63; /// + enum ENONET = 64; /// + enum ENOPKG = 65; /// + enum EREMOTE = 66; /// + enum ENOLINK = 67; /// + enum EADV = 68; /// + enum ESRMNT = 69; /// + enum ECOMM = 70; /// + enum EPROTO = 71; /// + enum EMULTIHOP = 72; /// + enum EDOTDOT = 73; /// + enum EBADMSG = 74; /// + enum EOVERFLOW = 75; /// + enum ENOTUNIQ = 76; /// + enum EBADFD = 77; /// + enum EREMCHG = 78; /// + enum ELIBACC = 79; /// + enum ELIBBAD = 80; /// + enum ELIBSCN = 81; /// + enum ELIBMAX = 82; /// + enum ELIBEXEC = 83; /// + enum EILSEQ = 84; /// + enum ERESTART = 85; /// + enum ESTRPIPE = 86; /// + enum EUSERS = 87; /// + enum ENOTSOCK = 88; /// + enum EDESTADDRREQ = 89; /// + enum EMSGSIZE = 90; /// + enum EPROTOTYPE = 91; /// + enum ENOPROTOOPT = 92; /// + enum EPROTONOSUPPORT = 93; /// + enum ESOCKTNOSUPPORT = 94; /// + enum EOPNOTSUPP = 95; /// + enum ENOTSUP = EOPNOTSUPP; /// + enum EPFNOSUPPORT = 96; /// + enum EAFNOSUPPORT = 97; /// + enum EADDRINUSE = 98; /// + enum EADDRNOTAVAIL = 99; /// + enum ENETDOWN = 100; /// + enum ENETUNREACH = 101; /// + enum ENETRESET = 102; /// + enum ECONNABORTED = 103; /// + enum ECONNRESET = 104; /// + enum ENOBUFS = 105; /// + enum EISCONN = 106; /// + enum ENOTCONN = 107; /// + enum ESHUTDOWN = 108; /// + enum ETOOMANYREFS = 109; /// + enum ETIMEDOUT = 110; /// + enum ECONNREFUSED = 111; /// + enum EHOSTDOWN = 112; /// + enum EHOSTUNREACH = 113; /// + enum EALREADY = 114; /// + enum EINPROGRESS = 115; /// + enum ESTALE = 116; /// + enum EUCLEAN = 117; /// + enum ENOTNAM = 118; /// + enum ENAVAIL = 119; /// + enum EISNAM = 120; /// + enum EREMOTEIO = 121; /// + enum EDQUOT = 122; /// + enum ENOMEDIUM = 123; /// + enum EMEDIUMTYPE = 124; /// + enum ECANCELED = 125; /// + enum ENOKEY = 126; /// + enum EKEYEXPIRED = 127; /// + enum EKEYREVOKED = 128; /// + enum EKEYREJECTED = 129; /// + enum EOWNERDEAD = 130; /// + enum ENOTRECOVERABLE = 131; /// + enum ERFKILL = 132; /// + enum EHWPOISON = 133; /// + } + else version (LoongArch64) + { + enum EDEADLK = 35; /// + enum ENAMETOOLONG = 36; /// + enum ENOLCK = 37; /// + enum ENOSYS = 38; /// + enum ENOTEMPTY = 39; /// + enum ELOOP = 40; /// + enum EWOULDBLOCK = EAGAIN; /// + enum ENOMSG = 42; /// + enum EIDRM = 43; /// + enum ECHRNG = 44; /// + enum EL2NSYNC = 45; /// + enum EL3HLT = 46; /// + enum EL3RST = 47; /// + enum ELNRNG = 48; /// + enum EUNATCH = 49; /// + enum ENOCSI = 50; /// + enum EL2HLT = 51; /// + enum EBADE = 52; /// + enum EBADR = 53; /// + enum EXFULL = 54; /// + enum ENOANO = 55; /// + enum EBADRQC = 56; /// + enum EBADSLT = 57; /// + enum EDEADLOCK = EDEADLK; /// + enum EBFONT = 59; /// + enum ENOSTR = 60; /// + enum ENODATA = 61; /// + enum ETIME = 62; /// + enum ENOSR = 63; /// + enum ENONET = 64; /// + enum ENOPKG = 65; /// + enum EREMOTE = 66; /// + enum ENOLINK = 67; /// + enum EADV = 68; /// + enum ESRMNT = 69; /// + enum ECOMM = 70; /// + enum EPROTO = 71; /// + enum EMULTIHOP = 72; /// + enum EDOTDOT = 73; /// + enum EBADMSG = 74; /// + enum EOVERFLOW = 75; /// + enum ENOTUNIQ = 76; /// + enum EBADFD = 77; /// + enum EREMCHG = 78; /// + enum ELIBACC = 79; /// + enum ELIBBAD = 80; /// + enum ELIBSCN = 81; /// + enum ELIBMAX = 82; /// + enum ELIBEXEC = 83; /// + enum EILSEQ = 84; /// + enum ERESTART = 85; /// + enum ESTRPIPE = 86; /// + enum EUSERS = 87; /// + enum ENOTSOCK = 88; /// + enum EDESTADDRREQ = 89; /// + enum EMSGSIZE = 90; /// + enum EPROTOTYPE = 91; /// + enum ENOPROTOOPT = 92; /// + enum EPROTONOSUPPORT = 93; /// + enum ESOCKTNOSUPPORT = 94; /// + enum EOPNOTSUPP = 95; /// + enum ENOTSUP = EOPNOTSUPP; /// + enum EPFNOSUPPORT = 96; /// + enum EAFNOSUPPORT = 97; /// + enum EADDRINUSE = 98; /// + enum EADDRNOTAVAIL = 99; /// + enum ENETDOWN = 100; /// + enum ENETUNREACH = 101; /// + enum ENETRESET = 102; /// + enum ECONNABORTED = 103; /// + enum ECONNRESET = 104; /// + enum ENOBUFS = 105; /// + enum EISCONN = 106; /// + enum ENOTCONN = 107; /// + enum ESHUTDOWN = 108; /// + enum ETOOMANYREFS = 109; /// + enum ETIMEDOUT = 110; /// + enum ECONNREFUSED = 111; /// + enum EHOSTDOWN = 112; /// + enum EHOSTUNREACH = 113; /// + enum EALREADY = 114; /// + enum EINPROGRESS = 115; /// + enum ESTALE = 116; /// + enum EUCLEAN = 117; /// + enum ENOTNAM = 118; /// + enum ENAVAIL = 119; /// + enum EISNAM = 120; /// + enum EREMOTEIO = 121; /// + enum EDQUOT = 122; /// + enum ENOMEDIUM = 123; /// + enum EMEDIUMTYPE = 124; /// + enum ECANCELED = 125; /// + enum ENOKEY = 126; /// + enum EKEYEXPIRED = 127; /// + enum EKEYREVOKED = 128; /// + enum EKEYREJECTED = 129; /// + enum EOWNERDEAD = 130; /// + enum ENOTRECOVERABLE = 131; /// + enum ERFKILL = 132; /// + enum EHWPOISON = 133; /// + } + else + { + static assert(false, "Architecture not supported."); + } +} +else version (Darwin) +{ + enum EPERM = 1; /// Operation not permitted + enum ENOENT = 2; /// No such file or directory + enum ESRCH = 3; /// No such process + enum EINTR = 4; /// Interrupted system call + enum EIO = 5; /// Input/output error + enum ENXIO = 6; /// Device not configured + enum E2BIG = 7; /// Argument list too long + enum ENOEXEC = 8; /// Exec format error + enum EBADF = 9; /// Bad file descriptor + enum ECHILD = 10; /// No child processes + enum EDEADLK = 11; /// Resource deadlock avoided + enum ENOMEM = 12; /// Cannot allocate memory + enum EACCES = 13; /// Permission denied + enum EFAULT = 14; /// Bad address + enum EBUSY = 16; /// Device busy + enum EEXIST = 17; /// File exists + enum EXDEV = 18; /// Cross-device link + enum ENODEV = 19; /// Operation not supported by device + enum ENOTDIR = 20; /// Not a directory + enum EISDIR = 21; /// Is a directory + enum EINVAL = 22; /// Invalid argument + enum ENFILE = 23; /// Too many open files in system + enum EMFILE = 24; /// Too many open files + enum ENOTTY = 25; /// Inappropriate ioctl for device + enum ETXTBSY = 26; /// Text file busy + enum EFBIG = 27; /// File too large + enum ENOSPC = 28; /// No space left on device + enum ESPIPE = 29; /// Illegal seek + enum EROFS = 30; /// Read-only file system + enum EMLINK = 31; /// Too many links + enum EPIPE = 32; /// Broken pipe + enum EDOM = 33; /// Numerical argument out of domain + enum ERANGE = 34; /// Result too large + enum EAGAIN = 35; /// Resource temporarily unavailable + enum EWOULDBLOCK = EAGAIN; /// Operation would block + enum EINPROGRESS = 36; /// Operation now in progress + enum EALREADY = 37; /// Operation already in progress + enum ENOTSOCK = 38; /// Socket operation on non-socket + enum EDESTADDRREQ = 39; /// Destination address required + enum EMSGSIZE = 40; /// Message too long + enum EPROTOTYPE = 41; /// Protocol wrong type for socket + enum ENOPROTOOPT = 42; /// Protocol not available + enum EPROTONOSUPPORT = 43; /// Protocol not supported + enum ENOTSUP = 45; /// Operation not supported + enum EOPNOTSUPP = ENOTSUP; /// Operation not supported on socket + enum EAFNOSUPPORT = 47; /// Address family not supported by protocol family + enum EADDRINUSE = 48; /// Address already in use + enum EADDRNOTAVAIL = 49; /// Can't assign requested address + enum ENETDOWN = 50; /// Network is down + enum ENETUNREACH = 51; /// Network is unreachable + enum ENETRESET = 52; /// Network dropped connection on reset + enum ECONNABORTED = 53; /// Software caused connection abort + enum ECONNRESET = 54; /// Connection reset by peer + enum ENOBUFS = 55; /// No buffer space available + enum EISCONN = 56; /// Socket is already connected + enum ENOTCONN = 57; /// Socket is not connected + enum ETIMEDOUT = 60; /// Operation timed out + enum ECONNREFUSED = 61; /// Connection refused + enum ELOOP = 62; /// Too many levels of symbolic links + enum ENAMETOOLONG = 63; /// File name too long + enum EHOSTUNREACH = 65; /// No route to host + enum ENOTEMPTY = 66; /// Directory not empty + enum EDQUOT = 69; /// Disc quota exceeded + enum ESTALE = 70; /// Stale NFS file handle + enum ENOLCK = 77; /// No locks available + enum ENOSYS = 78; /// Function not implemented + enum EOVERFLOW = 84; /// Value too large to be stored in data type + enum ECANCELED = 89; /// Operation canceled + enum EIDRM = 90; /// Identifier removed + enum ENOMSG = 91; /// No message of desired type + enum EILSEQ = 92; /// Illegal byte sequence + enum EBADMSG = 94; /// Bad message + enum EMULTIHOP = 95; /// Reserved + enum ENODATA = 96; /// No message available on STREAM + enum ENOLINK = 97; /// Reserved + enum ENOSR = 98; /// No STREAM resources + enum ENOSTR = 99; /// Not a STREAM + enum EPROTO = 100; /// Protocol error + enum ETIME = 101; /// STREAM ioctl timeout + enum ELAST = 101; /// Must be equal largest errno +} +else version (FreeBSD) +{ + enum EPERM = 1; /// Operation not permitted + enum ENOENT = 2; /// No such file or directory + enum ESRCH = 3; /// No such process + enum EINTR = 4; /// Interrupted system call + enum EIO = 5; /// Input/output error + enum ENXIO = 6; /// Device not configured + enum E2BIG = 7; /// Argument list too long + enum ENOEXEC = 8; /// Exec format error + enum EBADF = 9; /// Bad file descriptor + enum ECHILD = 10; /// No child processes + enum EDEADLK = 11; /// Resource deadlock avoided + enum ENOMEM = 12; /// Cannot allocate memory + enum EACCES = 13; /// Permission denied + enum EFAULT = 14; /// Bad address + enum ENOTBLK = 15; /// Block device required + enum EBUSY = 16; /// Device busy + enum EEXIST = 17; /// File exists + enum EXDEV = 18; /// Cross-device link + enum ENODEV = 19; /// Operation not supported by device + enum ENOTDIR = 20; /// Not a directory + enum EISDIR = 21; /// Is a directory + enum EINVAL = 22; /// Invalid argument + enum ENFILE = 23; /// Too many open files in system + enum EMFILE = 24; /// Too many open files + enum ENOTTY = 25; /// Inappropriate ioctl for device + enum ETXTBSY = 26; /// Text file busy + enum EFBIG = 27; /// File too large + enum ENOSPC = 28; /// No space left on device + enum ESPIPE = 29; /// Illegal seek + enum EROFS = 30; /// Read-only file system + enum EMLINK = 31; /// Too many links + enum EPIPE = 32; /// Broken pipe + enum EDOM = 33; /// Numerical argument out of domain + enum ERANGE = 34; /// Result too large + enum EAGAIN = 35; /// Resource temporarily unavailable + enum EWOULDBLOCK = EAGAIN; /// Operation would block + enum EINPROGRESS = 36; /// Operation now in progress + enum EALREADY = 37; /// Operation already in progress + enum ENOTSOCK = 38; /// Socket operation on non-socket + enum EDESTADDRREQ = 39; /// Destination address required + enum EMSGSIZE = 40; /// Message too long + enum EPROTOTYPE = 41; /// Protocol wrong type for socket + enum ENOPROTOOPT = 42; /// Protocol not available + enum EPROTONOSUPPORT = 43; /// Protocol not supported + enum ENOTSUP = 45; /// Operation not supported + enum EOPNOTSUPP = ENOTSUP; /// Operation not supported on socket + enum EAFNOSUPPORT = 47; /// Address family not supported by protocol family + enum EADDRINUSE = 48; /// Address already in use + enum EADDRNOTAVAIL = 49; /// Can't assign requested address + enum ENETDOWN = 50; /// Network is down + enum ENETUNREACH = 51; /// Network is unreachable + enum ENETRESET = 52; /// Network dropped connection on reset + enum ECONNABORTED = 53; /// Software caused connection abort + enum ECONNRESET = 54; /// Connection reset by peer + enum ENOBUFS = 55; /// No buffer space available + enum EISCONN = 56; /// Socket is already connected + enum ENOTCONN = 57; /// Socket is not connected + enum ESHUTDOWN = 58; /// Can't send after socket shutdown + enum ETOOMANYREFS = 59; /// Too many refrences; can't splice + enum ETIMEDOUT = 60; /// Operation timed out + enum ECONNREFUSED = 61; /// Connection refused + enum ELOOP = 62; /// Too many levels of symbolic links + enum ENAMETOOLONG = 63; /// File name too long + enum EHOSTUNREACH = 65; /// No route to host + enum ENOTEMPTY = 66; /// Directory not empty + enum EPROCLIM = 67; /// Too many processes + enum EUSERS = 68; /// Too many users + enum EDQUOT = 69; /// Disc quota exceeded + enum ESTALE = 70; /// Stale NFS file handle + enum EREMOTE = 71; /// Too many levels of remote in path + enum EBADRPC = 72; /// RPC struct is bad + enum ERPCMISMATCH = 73; /// RPC version wrong + enum EPROGUNAVAIL = 74; /// RPC prog. not avail + enum EPROGMISMATCH = 75; /// Program version wrong + enum EPROCUNAVAIL = 76; /// Bad procedure for program + enum ENOLCK = 77; /// No locks available + enum ENOSYS = 78; /// Function not implemented + enum EFTYPE = 79; /// Inappropriate file type or format + enum EAUTH = 80; /// Authentication error + enum ENEEDAUTH = 81; /// Need authenticator + enum EIDRM = 82; /// Itendifier removed + enum ENOMSG = 83; /// No message of desired type + enum EOVERFLOW = 84; /// Value too large to be stored in data type + enum ECANCELED = 85; /// Operation canceled + enum EILSEQ = 86; /// Illegal byte sequence + enum ENOATTR = 87; /// Attribute not found + enum EDOOFUS = 88; /// Programming error + enum EBADMSG = 89; /// Bad message + enum EMULTIHOP = 90; /// Multihop attempted + enum ENOLINK = 91; /// Link has been severed + enum EPROTO = 92; /// Protocol error + enum ELAST = 92; /// Must be equal largest errno +} +else version (NetBSD) +{ + // http://cvsweb.netbsd.org/bsdweb.cgi/~checkout~/src/sys/sys/errno.h + enum EPERM = 1; + enum ENOENT = 2; + enum ESRCH = 3; + enum EINTR = 4; + enum EIO = 5; + enum ENXIO = 6; + enum E2BIG = 7; + enum ENOEXEC = 8; + enum EBADF = 9; + enum ECHILD = 10; + enum EDEADLK = 11; + /// + enum ENOMEM = 12; + enum EACCES = 13; + enum EFAULT = 14; + enum ENOTBLK = 15; + enum EBUSY = 16; + enum EEXIST = 17; + enum EXDEV = 18; + enum ENODEV = 19; + enum ENOTDIR = 20; + enum EISDIR = 21; + enum EINVAL = 22; + enum ENFILE = 23; + enum EMFILE = 24; + enum ENOTTY = 25; + enum ETXTBSY = 26; + enum EFBIG = 27; + enum ENOSPC = 28; + enum ESPIPE = 29; + enum EROFS = 30; + enum EMLINK = 31; + enum EPIPE = 32; + /// + enum EDOM = 33; + enum ERANGE = 34; + + /// + enum EAGAIN = 35; + enum EWOULDBLOCK = EAGAIN; + enum EINPROGRESS = 36; + enum EALREADY = 37; + + /// + enum ENOTSOCK = 38; + enum EDESTADDRREQ = 39; + enum EMSGSIZE = 40; + enum EPROTOTYPE = 41; + enum ENOPROTOOPT = 42; + enum EPROTONOSUPPORT = 43; + enum ESOCKTNOSUPPORT = 44; + enum EOPNOTSUPP = 45; + enum EPFNOSUPPORT = 46; + enum EAFNOSUPPORT = 47; + enum EADDRINUSE = 48; + enum EADDRNOTAVAIL = 49; + + /// + enum ENETDOWN = 50; + enum ENETUNREACH = 51; + enum ENETRESET = 52; + enum ECONNABORTED = 53; + enum ECONNRESET = 54; + enum ENOBUFS = 55; + enum EISCONN = 56; + enum ENOTCONN = 57; + enum ESHUTDOWN = 58; + enum ETOOMANYREFS = 59; + enum ETIMEDOUT = 60; + enum ECONNREFUSED = 61; + enum ELOOP = 62; + enum ENAMETOOLONG = 63; + + /// + enum EHOSTDOWN = 64; + enum EHOSTUNREACH = 65; + enum ENOTEMPTY = 66; + + /// + enum EPROCLIM = 67; + enum EUSERS = 68; + enum EDQUOT = 69; + + /// + enum ESTALE = 70; + enum EREMOTE = 71; + enum EBADRPC = 72; + enum ERPCMISMATCH = 73; + enum EPROGUNAVAIL = 74; + enum EPROGMISMATCH = 75; + enum EPROCUNAVAIL = 76; + + enum ENOLCK = 77; + enum ENOSYS = 78; + + enum EFTYPE = 79; + enum EAUTH = 80; + enum ENEEDAUTH = 81; + + /// + enum EIDRM = 82; + enum ENOMSG = 83; + enum EOVERFLOW = 84; + /// + enum EILSEQ = 85; + + /// + enum ENOTSUP = 86; + + /// + enum ECANCELED = 87; + + /// + enum EBADMSG = 88; + + /// + enum ENODATA = 89; + enum ENOSR = 90; + enum ENOSTR = 91; + enum ETIME = 92; + + /// + enum ENOATTR = 93; + + /// + enum EMULTIHOP = 94; + enum ENOLINK = 95; + enum EPROTO = 96; +} +else version (OpenBSD) +{ + enum EPERM = 1; /// Operation not permitted + enum ENOENT = 2; /// No such file or directory + enum ESRCH = 3; /// No such process + enum EINTR = 4; /// Interrupted system call + enum EIO = 5; /// Input/output error + enum ENXIO = 6; /// Device not configured + enum E2BIG = 7; /// Argument list too long + enum ENOEXEC = 8; /// Exec format error + enum EBADF = 9; /// Bad file descriptor + enum ECHILD = 10; /// No child processes + enum EDEADLK = 11; /// Resource deadlock avoided + enum ENOMEM = 12; /// Cannot allocate memory + enum EACCES = 13; /// Permission denied + enum EFAULT = 14; /// Bad address + enum ENOTBLK = 15; /// Block device required + enum EBUSY = 16; /// Device busy + enum EEXIST = 17; /// File exists + enum EXDEV = 18; /// Cross-device link + enum ENODEV = 19; /// Operation not supported by device + enum ENOTDIR = 20; /// Not a directory + enum EISDIR = 21; /// Is a directory + enum EINVAL = 22; /// Invalid argument + enum ENFILE = 23; /// Too many open files in system + enum EMFILE = 24; /// Too many open files + enum ENOTTY = 25; /// Inappropriate ioctl for device + enum ETXTBSY = 26; /// Text file busy + enum EFBIG = 27; /// File too large + enum ENOSPC = 28; /// No space left on device + enum ESPIPE = 29; /// Illegal seek + enum EROFS = 30; /// Read-only file system + enum EMLINK = 31; /// Too many links + enum EPIPE = 32; /// Broken pipe + enum EDOM = 33; /// Numerical argument out of domain + enum ERANGE = 34; /// Result too large + enum EAGAIN = 35; /// Resource temporarily unavailable + enum EWOULDBLOCK = EAGAIN; /// Operation would block + enum EINPROGRESS = 36; /// Operation now in progress + enum EALREADY = 37; /// Operation already in progress + enum ENOTSOCK = 38; /// Socket operation on non-socket + enum EDESTADDRREQ = 39; /// Destination address required + enum EMSGSIZE = 40; /// Message too long + enum EPROTOTYPE = 41; /// Protocol wrong type for socket + enum ENOPROTOOPT = 42; /// Protocol not available + enum EPROTONOSUPPORT = 43; /// Protocol not supported + enum ESOCKTNOSUPPORT = 44; /// Socket type not supported + enum EOPNOTSUPP = 45; /// Operation not supported + enum EPFNOSUPPORT = 46; /// Protocol family not supported + enum EAFNOSUPPORT = 47; /// Address family not supported by protocol family + enum EADDRINUSE = 48; /// Address already in use + enum EADDRNOTAVAIL = 49; /// Can't assign requested address + enum ENETDOWN = 50; /// Network is down + enum ENETUNREACH = 51; /// Network is unreachable + enum ENETRESET = 52; /// Network dropped connection on reset + enum ECONNABORTED = 53; /// Software caused connection abort + enum ECONNRESET = 54; /// Connection reset by peer + enum ENOBUFS = 55; /// No buffer space available + enum EISCONN = 56; /// Socket is already connected + enum ENOTCONN = 57; /// Socket is not connected + enum ESHUTDOWN = 58; /// Can't send after socket shutdown + enum ETOOMANYREFS = 59; /// Too many references: can't splice + enum ETIMEDOUT = 60; /// Operation timed out + enum ECONNREFUSED = 61; /// Connection refused + enum ELOOP = 62; /// Too many levels of symbolic links + enum ENAMETOOLONG = 63; /// File name too long + enum EHOSTDOWN = 64; /// Host is down + enum EHOSTUNREACH = 65; /// No route to host + enum ENOTEMPTY = 66; /// Directory not empty + enum EPROCLIM = 67; /// Too many processes + enum EUSERS = 68; /// Too many users + enum EDQUOT = 69; /// Disk quota exceeded + enum ESTALE = 70; /// Stale NFS file handle + enum EREMOTE = 71; /// Too many levels of remote in path + enum EBADRPC = 72; /// RPC struct is bad + enum ERPCMISMATCH = 73; /// RPC version wrong + enum EPROGUNAVAIL = 74; /// RPC program not available + enum EPROGMISMATCH = 75; /// Program version wrong + enum EPROCUNAVAIL = 76; /// Bad procedure for program + enum ENOLCK = 77; /// No locks available + enum ENOSYS = 78; /// Function not implemented + enum EFTYPE = 79; /// Inappropriate file type or format + enum EAUTH = 80; /// Authentication error + enum ENEEDAUTH = 81; /// Need authenticator + enum EIPSEC = 82; /// IPsec processing failure + enum ENOATTR = 83; /// Attribute not found + enum EILSEQ = 84; /// Illegal byte sequence + enum ENOMEDIUM = 85; /// No medium found + enum EMEDIUMTYPE = 86; /// Wrong medium type + enum EOVERFLOW = 87; /// Value too large to be stored in data type + enum ECANCELED = 88; /// Operation canceled + enum EIDRM = 89; /// Identifier removed + enum ENOMSG = 90; /// No message of desired type + enum ENOTSUP = 91; /// Not supported + enum EBADMSG = 92; /// Bad message + enum ENOTRECOVERABLE = 93; /// State not recoverable + enum EOWNERDEAD = 94; /// Previous owner died + enum EPROTO = 95; /// Protocol error + enum ELAST = 95; /// Must be equal largest errno +} +else version (DragonFlyBSD) +{ + enum EPERM = 1; + enum ENOENT = 2; + enum ESRCH = 3; + enum EINTR = 4; + enum EIO = 5; + enum ENXIO = 6; + enum E2BIG = 7; + enum ENOEXEC = 8; + enum EBADF = 9; + enum ECHILD = 10; + enum EDEADLK = 11; + enum ENOMEM = 12; + enum EACCES = 13; + enum EFAULT = 14; + enum ENOTBLK = 15; + enum EBUSY = 16; + enum EEXIST = 17; + enum EXDEV = 18; + enum ENODEV = 19; + enum ENOTDIR = 20; + enum EISDIR = 21; + enum EINVAL = 22; + enum ENFILE = 23; + enum EMFILE = 24; + enum ENOTTY = 25; + enum ETXTBSY = 26; + enum EFBIG = 27; + enum ENOSPC = 28; + enum ESPIPE = 29; + enum EROFS = 30; + enum EMLINK = 31; + enum EPIPE = 32; + enum EDOM = 33; + enum ERANGE = 34; + enum EAGAIN = 35; + enum EWOULDBLOCK = EAGAIN; + enum EINPROGRESS = 36; + enum EALREADY = 37; + enum ENOTSOCK = 38; + enum EDESTADDRREQ = 39; + enum EMSGSIZE = 40; + enum EPROTOTYPE = 41; + enum ENOPROTOOPT = 42; + enum EPROTONOSUPPORT = 43; + enum ENOTSUP = 45; + enum EOPNOTSUPP = ENOTSUP; + enum EPFNOSUPPORT = 46; + enum EAFNOSUPPORT = 47; + enum EADDRINUSE = 48; + enum EADDRNOTAVAIL = 49; + enum ENETDOWN = 50; + enum ENETUNREACH = 51; + enum ENETRESET = 52; + enum ECONNABORTED = 53; + enum ECONNRESET = 54; + enum ENOBUFS = 55; + enum EISCONN = 56; + enum ENOTCONN = 57; + enum ESHUTDOWN = 58; + enum ETOOMANYREFS = 59; + enum ETIMEDOUT = 60; + enum ECONNREFUSED = 61; + enum ELOOP = 62; + enum ENAMETOOLONG = 63; + enum EHOSTUNREACH = 65; + enum ENOTEMPTY = 66; + enum EPROCLIM = 67; + enum EUSERS = 68; + enum EDQUOT = 69; + enum ESTALE = 70; + enum EREMOTE = 71; + enum EBADRPC = 72; + enum ERPCMISMATCH = 73; + enum EPROGUNAVAIL = 74; + enum EPROGMISMATCH = 75; + enum EPROCUNAVAIL = 76; + enum ENOLCK = 77; + enum ENOSYS = 78; + enum EFTYPE = 79; + enum EAUTH = 80; + enum ENEEDAUTH = 81; + enum EIDRM = 82; + enum ENOMSG = 83; + enum EOVERFLOW = 84; + enum ECANCELED = 85; + enum EILSEQ = 86; + enum ENOATTR = 87; + enum EDOOFUS = 88; + enum EBADMSG = 89; + enum EMULTIHOP = 90; + enum ENOLINK = 91; + enum EPROTO = 92; + enum ENOMEDIUM = 93; + enum EUNUSED94 = 94; + enum EUNUSED95 = 95; + enum EUNUSED96 = 96; + enum EUNUSED97 = 97; + enum EUNUSED98 = 98; + enum EASYNC = 99; + enum ELAST = 99; +} +else version (Solaris) +{ + enum EPERM = 1; /// Not super-user + enum ENOENT = 2; /// No such file or directory + enum ESRCH = 3; /// No such process + enum EINTR = 4; /// interrupted system call + enum EIO = 5; /// I/O error + enum ENXIO = 6; /// No such device or address + enum E2BIG = 7; /// Arg list too long + enum ENOEXEC = 8; /// Exec format error + enum EBADF = 9; /// Bad file number + enum ECHILD = 10; /// No children + enum EAGAIN = 11; /// Resource temporarily unavailable + enum ENOMEM = 12; /// Not enough core + enum EACCES = 13; /// Permission denied + enum EFAULT = 14; /// Bad address + enum ENOTBLK = 15; /// Block device required + enum EBUSY = 16; /// Mount device busy + enum EEXIST = 17; /// File exists + enum EXDEV = 18; /// Cross-device link + enum ENODEV = 19; /// No such device + enum ENOTDIR = 20; /// Not a directory + enum EISDIR = 21; /// Is a directory + enum EINVAL = 22; /// Invalid argument + enum ENFILE = 23; /// File table overflow + enum EMFILE = 24; /// Too many open files + enum ENOTTY = 25; /// Inappropriate ioctl for device + enum ETXTBSY = 26; /// Text file busy + enum EFBIG = 27; /// File too large + enum ENOSPC = 28; /// No space left on device + enum ESPIPE = 29; /// Illegal seek + enum EROFS = 30; /// Read only file system + enum EMLINK = 31; /// Too many links + enum EPIPE = 32; /// Broken pipe + enum EDOM = 33; /// Math arg out of domain of func + enum ERANGE = 34; /// Math result not representable + enum ENOMSG = 35; /// No message of desired type + enum EIDRM = 36; /// Identifier removed + enum ECHRNG = 37; /// Channel number out of range + enum EL2NSYNC = 38; /// Level 2 not synchronized + enum EL3HLT = 39; /// Level 3 halted + enum EL3RST = 40; /// Level 3 reset + enum ELNRNG = 41; /// Link number out of range + enum EUNATCH = 42; /// Protocol driver not attached + enum ENOCSI = 43; /// No CSI structure available + enum EL2HLT = 44; /// Level 2 halted + enum EDEADLK = 45; /// Deadlock condition. + enum ENOLCK = 46; /// No record locks available. + enum ECANCELED = 47; /// Operation canceled + enum ENOTSUP = 48; /// Operation not supported + enum EDQUOT = 49; /// Disc quota exceeded + enum EBADE = 50; /// invalid exchange + enum EBADR = 51; /// invalid request descriptor + enum EXFULL = 52; /// exchange full + enum ENOANO = 53; /// no anode + enum EBADRQC = 54; /// invalid request code + enum EBADSLT = 55; /// invalid slot + enum EDEADLOCK = 56; /// file locking deadlock error + enum EBFONT = 57; /// bad font file fmt + enum EOWNERDEAD = 58; /// process died with the lock + enum ENOTRECOVERABLE = 59; /// lock is not recoverable + enum ENOSTR = 60; /// Device not a stream + enum ENODATA = 61; /// no data (for no delay io) + enum ETIME = 62; /// timer expired + enum ENOSR = 63; /// out of streams resources + enum ENONET = 64; /// Machine is not on the network + enum ENOPKG = 65; /// Package not installed + enum EREMOTE = 66; /// The object is remote + enum ENOLINK = 67; /// the link has been severed + enum EADV = 68; /// advertise error + enum ESRMNT = 69; /// srmount error + enum ECOMM = 70; /// Communication error on send + enum EPROTO = 71; /// Protocol error + enum ELOCKUNMAPPED = 72; /// locked lock was unmapped + enum ENOTACTIVE = 73; /// Facility is not active + enum EMULTIHOP = 74; /// multihop attempted + enum EBADMSG = 77; /// trying to read unreadable message + enum ENAMETOOLONG = 78; /// path name is too long + enum EOVERFLOW = 79; /// value too large to be stored in data type + enum ENOTUNIQ = 80; /// given log. name not unique + enum EBADFD = 81; /// f.d. invalid for this operation + enum EREMCHG = 82; /// Remote address changed + enum ELIBACC = 83; /// Can't access a needed shared lib. + enum ELIBBAD = 84; /// Accessing a corrupted shared lib. + enum ELIBSCN = 85; /// .lib section in a.out corrupted. + enum ELIBMAX = 86; /// Attempting to link in too many libs. + enum ELIBEXEC = 87; /// Attempting to exec a shared library. + enum EILSEQ = 88; /// Illegal byte sequence. + enum ENOSYS = 89; /// Unsupported file system operation + enum ELOOP = 90; /// Symbolic link loop + enum ERESTART = 91; /// Restartable system call + enum ESTRPIPE = 92; /// if pipe/FIFO, don't sleep in stream head + enum ENOTEMPTY = 93; /// directory not empty + enum EUSERS = 94; /// Too many users (for UFS) + enum ENOTSOCK = 95; /// Socket operation on non-socket + enum EDESTADDRREQ = 96; /// Destination address required + enum EMSGSIZE = 97; /// Message too long + enum EPROTOTYPE = 98; /// Protocol wrong type for socket + enum ENOPROTOOPT = 99; /// Protocol not available + enum EPROTONOSUPPORT = 120; /// Protocol not supported + enum ESOCKTNOSUPPORT = 121; /// Socket type not supported + enum EOPNOTSUPP = 122; /// Operation not supported on socket + enum EPFNOSUPPORT = 123; /// Protocol family not supported + enum EAFNOSUPPORT = 124; /// Address family not supported by the protocol family + enum EADDRINUSE = 125; /// Address already in use + enum EADDRNOTAVAIL = 126; /// Can't assign requested address + enum ENETDOWN = 127; /// Network is down + enum ENETUNREACH = 128; /// Network is unreachable + enum ENETRESET = 129; /// Network dropped connection because of reset + enum ECONNABORTED = 130; /// Software caused connection abort + enum ECONNRESET = 131; /// Connection reset by peer + enum ENOBUFS = 132; /// No buffer space available + enum EISCONN = 133; /// Socket is already connected + enum ENOTCONN = 134; /// Socket is not connected + enum ESHUTDOWN = 143; /// Can't send after socket shutdown + enum ETOOMANYREFS = 144; /// Too many references: can't splice + enum ETIMEDOUT = 145; /// Connection timed out + enum ECONNREFUSED = 146; /// Connection refused + enum EHOSTDOWN = 147; /// Host is down + enum EHOSTUNREACH = 148; /// No route to host + enum EWOULDBLOCK = EAGAIN; /// Resource temporarily unavailable + enum EALREADY = 149; /// operation already in progress + enum EINPROGRESS = 150; /// operation now in progress + enum ESTALE = 151; /// Stale NFS file handle +} +else version (Haiku) +{ + // https://github.com/haiku/haiku/blob/master/headers/os/support/Errors.h + // https://github.com/haiku/haiku/blob/master/headers/build/os/support/Errors.h + import core.stdc.limits : INT_MIN; + enum B_GENERAL_ERROR_BASE = INT_MIN; + enum B_OS_ERROR_BASE = (B_GENERAL_ERROR_BASE + 0x1000); + enum B_APP_ERROR_BASE = (B_GENERAL_ERROR_BASE + 0x2000); + enum B_INTERFACE_ERROR_BASE = (B_GENERAL_ERROR_BASE + 0x3000); + enum B_MEDIA_ERROR_BASE = (B_GENERAL_ERROR_BASE + 0x4000); + /* - 0x41ff */ + enum B_TRANSLATION_ERROR_BASE = (B_GENERAL_ERROR_BASE + 0x4800); + /* - 0x48ff */ + enum B_MIDI_ERROR_BASE = (B_GENERAL_ERROR_BASE + 0x5000); + enum B_STORAGE_ERROR_BASE = (B_GENERAL_ERROR_BASE + 0x6000); + enum B_POSIX_ERROR_BASE = (B_GENERAL_ERROR_BASE + 0x7000); + enum B_MAIL_ERROR_BASE = (B_GENERAL_ERROR_BASE + 0x8000); + enum B_PRINT_ERROR_BASE = (B_GENERAL_ERROR_BASE + 0x9000); + enum B_DEVICE_ERROR_BASE = (B_GENERAL_ERROR_BASE + 0xa000); + + /* General Errors */ + enum B_NO_MEMORY = (B_GENERAL_ERROR_BASE + 0); + enum B_IO_ERROR = (B_GENERAL_ERROR_BASE + 1); + enum B_PERMISSION_DENIED = (B_GENERAL_ERROR_BASE + 2); + enum B_BAD_INDEX = (B_GENERAL_ERROR_BASE + 3); + enum B_BAD_TYPE = (B_GENERAL_ERROR_BASE + 4); + enum B_BAD_VALUE = (B_GENERAL_ERROR_BASE + 5); + enum B_MISMATCHED_VALUES = (B_GENERAL_ERROR_BASE + 6); + enum B_NAME_NOT_FOUND = (B_GENERAL_ERROR_BASE + 7); + enum B_NAME_IN_USE = (B_GENERAL_ERROR_BASE + 8); + enum B_TIMED_OUT = (B_GENERAL_ERROR_BASE + 9); + enum B_INTERRUPTED = (B_GENERAL_ERROR_BASE + 10); + enum B_WOULD_BLOCK = (B_GENERAL_ERROR_BASE + 11); + enum B_CANCELED = (B_GENERAL_ERROR_BASE + 12); + enum B_NO_INIT = (B_GENERAL_ERROR_BASE + 13); + enum B_NOT_INITIALIZED = (B_GENERAL_ERROR_BASE + 13); + enum B_BUSY = (B_GENERAL_ERROR_BASE + 14); + enum B_NOT_ALLOWED = (B_GENERAL_ERROR_BASE + 15); + enum B_BAD_DATA = (B_GENERAL_ERROR_BASE + 16); + enum B_DONT_DO_THAT = (B_GENERAL_ERROR_BASE + 17); + + enum B_ERROR = (-1); + enum B_OK = (int(0)); + enum B_NO_ERROR = (int(0)); + + /* Kernel Kit Errors */ + enum B_BAD_SEM_ID = (B_OS_ERROR_BASE + 0); + enum B_NO_MORE_SEMS = (B_OS_ERROR_BASE + 1); + + enum B_BAD_THREAD_ID = (B_OS_ERROR_BASE + 0x100); + enum B_NO_MORE_THREADS = (B_OS_ERROR_BASE + 0x101); + enum B_BAD_THREAD_STATE = (B_OS_ERROR_BASE + 0x102); + enum B_BAD_TEAM_ID = (B_OS_ERROR_BASE + 0x103); + enum B_NO_MORE_TEAMS = (B_OS_ERROR_BASE + 0x104); + + enum B_BAD_PORT_ID = (B_OS_ERROR_BASE + 0x200); + enum B_NO_MORE_PORTS = (B_OS_ERROR_BASE + 0x201); + + enum B_BAD_IMAGE_ID = (B_OS_ERROR_BASE + 0x300); + enum B_BAD_ADDRESS = (B_OS_ERROR_BASE + 0x301); + enum B_NOT_AN_EXECUTABLE = (B_OS_ERROR_BASE + 0x302); + enum B_MISSING_LIBRARY = (B_OS_ERROR_BASE + 0x303); + enum B_MISSING_SYMBOL = (B_OS_ERROR_BASE + 0x304); + enum B_UNKNOWN_EXECUTABLE = (B_OS_ERROR_BASE + 0x305); + enum B_LEGACY_EXECUTABLE = (B_OS_ERROR_BASE + 0x306); + + enum B_DEBUGGER_ALREADY_INSTALLED = (B_OS_ERROR_BASE + 0x400); + + /* Application Kit Errors */ + enum B_BAD_REPLY = (B_APP_ERROR_BASE + 0); + enum B_DUPLICATE_REPLY = (B_APP_ERROR_BASE + 1); + enum B_MESSAGE_TO_SELF = (B_APP_ERROR_BASE + 2); + enum B_BAD_HANDLER = (B_APP_ERROR_BASE + 3); + enum B_ALREADY_RUNNING = (B_APP_ERROR_BASE + 4); + enum B_LAUNCH_FAILED = (B_APP_ERROR_BASE + 5); + enum B_AMBIGUOUS_APP_LAUNCH = (B_APP_ERROR_BASE + 6); + enum B_UNKNOWN_MIME_TYPE = (B_APP_ERROR_BASE + 7); + enum B_BAD_SCRIPT_SYNTAX = (B_APP_ERROR_BASE + 8); + enum B_LAUNCH_FAILED_NO_RESOLVE_LINK = (B_APP_ERROR_BASE + 9); + enum B_LAUNCH_FAILED_EXECUTABLE = (B_APP_ERROR_BASE + 10); + enum B_LAUNCH_FAILED_APP_NOT_FOUND = (B_APP_ERROR_BASE + 11); + enum B_LAUNCH_FAILED_APP_IN_TRASH = (B_APP_ERROR_BASE + 12); + enum B_LAUNCH_FAILED_NO_PREFERRED_APP = (B_APP_ERROR_BASE + 13); + enum B_LAUNCH_FAILED_FILES_APP_NOT_FOUND = (B_APP_ERROR_BASE + 14); + enum B_BAD_MIME_SNIFFER_RULE = (B_APP_ERROR_BASE + 15); + enum B_NOT_A_MESSAGE = (B_APP_ERROR_BASE + 16); + enum B_SHUTDOWN_CANCELLED = (B_APP_ERROR_BASE + 17); + enum B_SHUTTING_DOWN = (B_APP_ERROR_BASE + 18); + + /* Storage Kit/File System Errors */ + enum B_FILE_ERROR = (B_STORAGE_ERROR_BASE + 0); + enum B_FILE_NOT_FOUND = (B_STORAGE_ERROR_BASE + 1); + /* deprecated: use B_ENTRY_NOT_FOUND instead */ + enum B_FILE_EXISTS = (B_STORAGE_ERROR_BASE + 2); + enum B_ENTRY_NOT_FOUND = (B_STORAGE_ERROR_BASE + 3); + enum B_NAME_TOO_LONG = (B_STORAGE_ERROR_BASE + 4); + enum B_NOT_A_DIRECTORY = (B_STORAGE_ERROR_BASE + 5); + enum B_DIRECTORY_NOT_EMPTY = (B_STORAGE_ERROR_BASE + 6); + enum B_DEVICE_FULL = (B_STORAGE_ERROR_BASE + 7); + enum B_READ_ONLY_DEVICE = (B_STORAGE_ERROR_BASE + 8); + enum B_IS_A_DIRECTORY = (B_STORAGE_ERROR_BASE + 9); + enum B_NO_MORE_FDS = (B_STORAGE_ERROR_BASE + 10); + enum B_CROSS_DEVICE_LINK = (B_STORAGE_ERROR_BASE + 11); + enum B_LINK_LIMIT = (B_STORAGE_ERROR_BASE + 12); + enum B_BUSTED_PIPE = (B_STORAGE_ERROR_BASE + 13); + enum B_UNSUPPORTED = (B_STORAGE_ERROR_BASE + 14); + enum B_PARTITION_TOO_SMALL = (B_STORAGE_ERROR_BASE + 15); + enum B_PARTIAL_READ = (B_STORAGE_ERROR_BASE + 16); + enum B_PARTIAL_WRITE = (B_STORAGE_ERROR_BASE + 17); + + /* POSIX Errors */ + enum B_USE_POSITIVE_POSIX_ERRORS = false; + + static if (B_USE_POSITIVE_POSIX_ERRORS) + { + enum B_TO_POSIX_ERROR(int code) = -code; + } + else + { + enum B_TO_POSIX_ERROR(int code) = code; + } + alias B_FROM_POSIX_ERROR = B_TO_POSIX_ERROR; + + enum B_POSIX_ENOMEM = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 0); + enum E2BIG = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 1); + enum ECHILD = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 2); + enum EDEADLK = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 3); + enum EFBIG = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 4); + enum EMLINK = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 5); + enum ENFILE = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 6); + enum ENODEV = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 7); + enum ENOLCK = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 8); + enum ENOSYS = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 9); + enum ENOTTY = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 10); + enum ENXIO = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 11); + enum ESPIPE = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 12); + enum ESRCH = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 13); + enum EFPOS = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 14); + enum ESIGPARM = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 15); + enum EDOM = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 16); + enum ERANGE = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 17); + enum EPROTOTYPE = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 18); + enum EPROTONOSUPPORT = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 19); + enum EPFNOSUPPORT = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 20); + enum EAFNOSUPPORT = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 21); + enum EADDRINUSE = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 22); + enum EADDRNOTAVAIL = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 23); + enum ENETDOWN = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 24); + enum ENETUNREACH = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 25); + enum ENETRESET = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 26); + enum ECONNABORTED = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 27); + enum ECONNRESET = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 28); + enum EISCONN = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 29); + enum ENOTCONN = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 30); + enum ESHUTDOWN = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 31); + enum ECONNREFUSED = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 32); + enum EHOSTUNREACH = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 33); + enum ENOPROTOOPT = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 34); + enum ENOBUFS = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 35); + enum EINPROGRESS = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 36); + enum EALREADY = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 37); + enum EILSEQ = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 38); + enum ENOMSG = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 39); + enum ESTALE = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 40); + enum EOVERFLOW = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 41); + enum EMSGSIZE = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 42); + enum EOPNOTSUPP = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 43); + enum ENOTSOCK = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 44); + enum EHOSTDOWN = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 45); + enum EBADMSG = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 46); + enum ECANCELED = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 47); + enum EDESTADDRREQ = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 48); + enum EDQUOT = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 49); + enum EIDRM = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 50); + enum EMULTIHOP = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 51); + enum ENODATA = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 52); + enum ENOLINK = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 53); + enum ENOSR = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 54); + enum ENOSTR = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 55); + enum ENOTSUP = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 56); + enum EPROTO = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 57); + enum ETIME = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 58); + enum ETXTBSY = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 59); + enum ENOATTR = B_TO_POSIX_ERROR!(B_POSIX_ERROR_BASE + 60); + + /* B_NO_MEMORY (0x80000000) can't be negated, so it needs special handling */ + static if (B_USE_POSITIVE_POSIX_ERRORS) + enum ENOMEM = B_POSIX_ENOMEM; + else + enum ENOMEM = B_NO_MEMORY; + + /* POSIX errors that can be mapped to BeOS error codes */ + enum EACCES = B_TO_POSIX_ERROR!(B_PERMISSION_DENIED); + enum EINTR = B_TO_POSIX_ERROR!(B_INTERRUPTED); + enum EIO = B_TO_POSIX_ERROR!(B_IO_ERROR); + enum EBUSY = B_TO_POSIX_ERROR!(B_BUSY); + enum EFAULT = B_TO_POSIX_ERROR!(B_BAD_ADDRESS); + enum ETIMEDOUT = B_TO_POSIX_ERROR!(B_TIMED_OUT); + enum EAGAIN = B_TO_POSIX_ERROR!(B_WOULD_BLOCK) /* SysV compatibility */; + enum EWOULDBLOCK = B_TO_POSIX_ERROR!(B_WOULD_BLOCK) /* BSD compatibility */; + enum EBADF = B_TO_POSIX_ERROR!(B_FILE_ERROR); + enum EEXIST = B_TO_POSIX_ERROR!(B_FILE_EXISTS); + enum EINVAL = B_TO_POSIX_ERROR!(B_BAD_VALUE); + enum ENAMETOOLONG = B_TO_POSIX_ERROR!(B_NAME_TOO_LONG); + enum ENOENT = B_TO_POSIX_ERROR!(B_ENTRY_NOT_FOUND); + enum EPERM = B_TO_POSIX_ERROR!(B_NOT_ALLOWED); + enum ENOTDIR = B_TO_POSIX_ERROR!(B_NOT_A_DIRECTORY); + enum EISDIR = B_TO_POSIX_ERROR!(B_IS_A_DIRECTORY); + enum ENOTEMPTY = B_TO_POSIX_ERROR!(B_DIRECTORY_NOT_EMPTY); + enum ENOSPC = B_TO_POSIX_ERROR!(B_DEVICE_FULL); + enum EROFS = B_TO_POSIX_ERROR!(B_READ_ONLY_DEVICE); + enum EMFILE = B_TO_POSIX_ERROR!(B_NO_MORE_FDS); + enum EXDEV = B_TO_POSIX_ERROR!(B_CROSS_DEVICE_LINK); + enum ELOOP = B_TO_POSIX_ERROR!(B_LINK_LIMIT); + enum ENOEXEC = B_TO_POSIX_ERROR!(B_NOT_AN_EXECUTABLE); + enum EPIPE = B_TO_POSIX_ERROR!(B_BUSTED_PIPE); + + /* new error codes that can be mapped to POSIX errors */ + enum B_BUFFER_OVERFLOW = B_FROM_POSIX_ERROR!(EOVERFLOW); + enum B_TOO_MANY_ARGS = B_FROM_POSIX_ERROR!(E2BIG); + enum B_FILE_TOO_LARGE = B_FROM_POSIX_ERROR!(EFBIG); + enum B_RESULT_NOT_REPRESENTABLE = B_FROM_POSIX_ERROR!(ERANGE); + enum B_DEVICE_NOT_FOUND = B_FROM_POSIX_ERROR!(ENODEV); + enum B_NOT_SUPPORTED = B_FROM_POSIX_ERROR!(EOPNOTSUPP); + + /* Media Kit Errors */ + enum B_STREAM_NOT_FOUND = (B_MEDIA_ERROR_BASE + 0); + enum B_SERVER_NOT_FOUND = (B_MEDIA_ERROR_BASE + 1); + enum B_RESOURCE_NOT_FOUND = (B_MEDIA_ERROR_BASE + 2); + enum B_RESOURCE_UNAVAILABLE = (B_MEDIA_ERROR_BASE + 3); + enum B_BAD_SUBSCRIBER = (B_MEDIA_ERROR_BASE + 4); + enum B_SUBSCRIBER_NOT_ENTERED = (B_MEDIA_ERROR_BASE + 5); + enum B_BUFFER_NOT_AVAILABLE = (B_MEDIA_ERROR_BASE + 6); + enum B_LAST_BUFFER_ERROR = (B_MEDIA_ERROR_BASE + 7); + + enum B_MEDIA_SYSTEM_FAILURE = (B_MEDIA_ERROR_BASE + 100); + enum B_MEDIA_BAD_NODE = (B_MEDIA_ERROR_BASE + 101); + enum B_MEDIA_NODE_BUSY = (B_MEDIA_ERROR_BASE + 102); + enum B_MEDIA_BAD_FORMAT = (B_MEDIA_ERROR_BASE + 103); + enum B_MEDIA_BAD_BUFFER = (B_MEDIA_ERROR_BASE + 104); + enum B_MEDIA_TOO_MANY_NODES = (B_MEDIA_ERROR_BASE + 105); + enum B_MEDIA_TOO_MANY_BUFFERS = (B_MEDIA_ERROR_BASE + 106); + enum B_MEDIA_NODE_ALREADY_EXISTS = (B_MEDIA_ERROR_BASE + 107); + enum B_MEDIA_BUFFER_ALREADY_EXISTS = (B_MEDIA_ERROR_BASE + 108); + enum B_MEDIA_CANNOT_SEEK = (B_MEDIA_ERROR_BASE + 109); + enum B_MEDIA_CANNOT_CHANGE_RUN_MODE = (B_MEDIA_ERROR_BASE + 110); + enum B_MEDIA_APP_ALREADY_REGISTERED = (B_MEDIA_ERROR_BASE + 111); + enum B_MEDIA_APP_NOT_REGISTERED = (B_MEDIA_ERROR_BASE + 112); + enum B_MEDIA_CANNOT_RECLAIM_BUFFERS = (B_MEDIA_ERROR_BASE + 113); + enum B_MEDIA_BUFFERS_NOT_RECLAIMED = (B_MEDIA_ERROR_BASE + 114); + enum B_MEDIA_TIME_SOURCE_STOPPED = (B_MEDIA_ERROR_BASE + 115); + enum B_MEDIA_TIME_SOURCE_BUSY = (B_MEDIA_ERROR_BASE + 116); + enum B_MEDIA_BAD_SOURCE = (B_MEDIA_ERROR_BASE + 117); + enum B_MEDIA_BAD_DESTINATION = (B_MEDIA_ERROR_BASE + 118); + enum B_MEDIA_ALREADY_CONNECTED = (B_MEDIA_ERROR_BASE + 119); + enum B_MEDIA_NOT_CONNECTED = (B_MEDIA_ERROR_BASE + 120); + enum B_MEDIA_BAD_CLIP_FORMAT = (B_MEDIA_ERROR_BASE + 121); + enum B_MEDIA_ADDON_FAILED = (B_MEDIA_ERROR_BASE + 122); + enum B_MEDIA_ADDON_DISABLED = (B_MEDIA_ERROR_BASE + 123); + enum B_MEDIA_CHANGE_IN_PROGRESS = (B_MEDIA_ERROR_BASE + 124); + enum B_MEDIA_STALE_CHANGE_COUNT = (B_MEDIA_ERROR_BASE + 125); + enum B_MEDIA_ADDON_RESTRICTED = (B_MEDIA_ERROR_BASE + 126); + enum B_MEDIA_NO_HANDLER = (B_MEDIA_ERROR_BASE + 127); + enum B_MEDIA_DUPLICATE_FORMAT = (B_MEDIA_ERROR_BASE + 128); + enum B_MEDIA_REALTIME_DISABLED = (B_MEDIA_ERROR_BASE + 129); + enum B_MEDIA_REALTIME_UNAVAILABLE = (B_MEDIA_ERROR_BASE + 130); + + /* Mail Kit Errors */ + enum B_MAIL_NO_DAEMON = (B_MAIL_ERROR_BASE + 0); + enum B_MAIL_UNKNOWN_USER = (B_MAIL_ERROR_BASE + 1); + enum B_MAIL_WRONG_PASSWORD = (B_MAIL_ERROR_BASE + 2); + enum B_MAIL_UNKNOWN_HOST = (B_MAIL_ERROR_BASE + 3); + enum B_MAIL_ACCESS_ERROR = (B_MAIL_ERROR_BASE + 4); + enum B_MAIL_UNKNOWN_FIELD = (B_MAIL_ERROR_BASE + 5); + enum B_MAIL_NO_RECIPIENT = (B_MAIL_ERROR_BASE + 6); + enum B_MAIL_INVALID_MAIL = (B_MAIL_ERROR_BASE + 7); + + /* Printing Errors */ + enum B_NO_PRINT_SERVER = (B_PRINT_ERROR_BASE + 0); + + /* Device Kit Errors */ + enum B_DEV_INVALID_IOCTL = (B_DEVICE_ERROR_BASE + 0); + enum B_DEV_NO_MEMORY = (B_DEVICE_ERROR_BASE + 1); + enum B_DEV_BAD_DRIVE_NUM = (B_DEVICE_ERROR_BASE + 2); + enum B_DEV_NO_MEDIA = (B_DEVICE_ERROR_BASE + 3); + enum B_DEV_UNREADABLE = (B_DEVICE_ERROR_BASE + 4); + enum B_DEV_FORMAT_ERROR = (B_DEVICE_ERROR_BASE + 5); + enum B_DEV_TIMEOUT = (B_DEVICE_ERROR_BASE + 6); + enum B_DEV_RECALIBRATE_ERROR = (B_DEVICE_ERROR_BASE + 7); + enum B_DEV_SEEK_ERROR = (B_DEVICE_ERROR_BASE + 8); + enum B_DEV_ID_ERROR = (B_DEVICE_ERROR_BASE + 9); + enum B_DEV_READ_ERROR = (B_DEVICE_ERROR_BASE + 10); + enum B_DEV_WRITE_ERROR = (B_DEVICE_ERROR_BASE + 11); + enum B_DEV_NOT_READY = (B_DEVICE_ERROR_BASE + 12); + enum B_DEV_MEDIA_CHANGED = (B_DEVICE_ERROR_BASE + 13); + enum B_DEV_MEDIA_CHANGE_REQUESTED = (B_DEVICE_ERROR_BASE + 14); + enum B_DEV_RESOURCE_CONFLICT = (B_DEVICE_ERROR_BASE + 15); + enum B_DEV_CONFIGURATION_ERROR = (B_DEVICE_ERROR_BASE + 16); + enum B_DEV_DISABLED_BY_USER = (B_DEVICE_ERROR_BASE + 17); + enum B_DEV_DOOR_OPEN = (B_DEVICE_ERROR_BASE + 18); + + enum B_DEV_INVALID_PIPE = (B_DEVICE_ERROR_BASE + 19); + enum B_DEV_CRC_ERROR = (B_DEVICE_ERROR_BASE + 20); + enum B_DEV_STALLED = (B_DEVICE_ERROR_BASE + 21); + enum B_DEV_BAD_PID = (B_DEVICE_ERROR_BASE + 22); + enum B_DEV_UNEXPECTED_PID = (B_DEVICE_ERROR_BASE + 23); + enum B_DEV_DATA_OVERRUN = (B_DEVICE_ERROR_BASE + 24); + enum B_DEV_DATA_UNDERRUN = (B_DEVICE_ERROR_BASE + 25); + enum B_DEV_FIFO_OVERRUN = (B_DEVICE_ERROR_BASE + 26); + enum B_DEV_FIFO_UNDERRUN = (B_DEVICE_ERROR_BASE + 27); + enum B_DEV_PENDING = (B_DEVICE_ERROR_BASE + 28); + enum B_DEV_MULTIPLE_ERRORS = (B_DEVICE_ERROR_BASE + 29); + enum B_DEV_TOO_LATE = (B_DEVICE_ERROR_BASE + 30); + + /* Translation Kit Errors */ + enum B_TRANSLATION_BASE_ERROR = (B_TRANSLATION_ERROR_BASE + 0); + enum B_NO_TRANSLATOR = (B_TRANSLATION_ERROR_BASE + 1); + enum B_ILLEGAL_DATA = (B_TRANSLATION_ERROR_BASE + 2); +} +else version (WASI) +{ + enum EPERM = 1; + enum ENOENT = 2; + enum ESRCH = 3; + enum EINTR = 4; + enum EIO = 5; + enum ENXIO = 6; + enum E2BIG = 7; + enum ENOEXEC = 8; + enum EBADF = 9; + enum ECHILD = 10; + enum EAGAIN = 11; + enum ENOMEM = 12; + enum EACCES = 13; + enum EFAULT = 14; + enum ENOTBLK = 15; + enum EBUSY = 16; + enum EEXIST = 17; + enum EXDEV = 18; + enum ENODEV = 19; + enum ENOTDIR = 20; + enum EISDIR = 21; + enum EINVAL = 22; + enum ENFILE = 23; + enum EMFILE = 24; + enum ENOTTY = 25; + enum ETXTBSY = 26; + enum EFBIG = 27; + enum ENOSPC = 28; + enum ESPIPE = 29; + enum EROFS = 30; + enum EMLINK = 31; + enum EPIPE = 32; + enum EDOM = 33; + enum ERANGE = 34; + enum EDEADLK = 35; + enum ENAMETOOLONG = 36; + enum ENOLCK = 37; + enum ENOSYS = 38; + enum ENOTEMPTY = 39; + enum ELOOP = 40; + enum EWOULDBLOCK = EAGAIN; + enum ENOMSG = 42; + enum EIDRM = 43; + enum ECHRNG = 44; + enum EL2NSYNC = 45; + enum EL3HLT = 46; + enum EL3RST = 47; + enum ELNRNG = 48; + enum EUNATCH = 49; + enum ENOCSI = 50; + enum EL2HLT = 51; + enum EBADE = 52; + enum EBADR = 53; + enum EXFULL = 54; + enum ENOANO = 55; + enum EBADRQC = 56; + enum EBADSLT = 57; + enum EDEADLOCK = EDEADLK; + enum EBFONT = 59; + enum ENOSTR = 60; + enum ENODATA = 61; + enum ETIME = 62; + enum ENOSR = 63; + enum ENONET = 64; + enum ENOPKG = 65; + enum EREMOTE = 66; + enum ENOLINK = 67; + enum EADV = 68; + enum ESRMNT = 69; + enum ECOMM = 70; + enum EPROTO = 71; + enum EMULTIHOP = 72; + enum EDOTDOT = 73; + enum EBADMSG = 74; + enum EOVERFLOW = 75; + enum ENOTUNIQ = 76; + enum EBADFD = 77; + enum EREMCHG = 78; + enum ELIBACC = 79; + enum ELIBBAD = 80; + enum ELIBSCN = 81; + enum ELIBMAX = 82; + enum ELIBEXEC = 83; + enum EILSEQ = 84; + enum ERESTART = 85; + enum ESTRPIPE = 86; + enum EUSERS = 87; + enum ENOTSOCK = 88; + enum EDESTADDRREQ = 89; + enum EMSGSIZE = 90; + enum EPROTOTYPE = 91; + enum ENOPROTOOPT = 92; + enum EPROTONOSUPPORT = 93; + enum ESOCKTNOSUPPORT = 94; + enum EOPNOTSUPP = 95; + enum ENOTSUP = EOPNOTSUPP; + enum EPFNOSUPPORT = 96; + enum EAFNOSUPPORT = 97; + enum EADDRINUSE = 98; + enum EADDRNOTAVAIL = 99; + enum ENETDOWN = 100; + enum ENETUNREACH = 101; + enum ENETRESET = 102; + enum ECONNABORTED = 103; + enum ECONNRESET = 104; + enum ENOBUFS = 105; + enum EISCONN = 106; + enum ENOTCONN = 107; + enum ESHUTDOWN = 108; + enum ETOOMANYREFS = 109; + enum ETIMEDOUT = 110; + enum ECONNREFUSED = 111; + enum EHOSTDOWN = 112; + enum EHOSTUNREACH = 113; + enum EALREADY = 114; + enum EINPROGRESS = 115; + enum ESTALE = 116; + enum EUCLEAN = 117; + enum ENOTNAM = 118; + enum ENAVAIL = 119; + enum EISNAM = 120; + enum EREMOTEIO = 121; + enum EDQUOT = 122; + enum ENOMEDIUM = 123; + enum EMEDIUMTYPE = 124; + enum ECANCELED = 125; + enum ENOKEY = 126; + enum EKEYEXPIRED = 127; + enum EKEYREVOKED = 128; + enum EKEYREJECTED = 129; + enum EOWNERDEAD = 130; + enum ENOTRECOVERABLE = 131; + enum ERFKILL = 132; + enum EHWPOISON = 133; +} +else version (FreeStanding) +{ + // no errno on bare metal +} +else +{ + static assert(false, "Unsupported platform"); +} diff --git a/src/urt/internal/stdc/stdio.d b/src/urt/internal/stdc/stdio.d new file mode 100644 index 0000000..7aabfa3 --- /dev/null +++ b/src/urt/internal/stdc/stdio.d @@ -0,0 +1,111 @@ +// Minimal C stdio bindings - only what URT actually uses. +// FILE is opaque; we never dereference its fields. + +module urt.internal.stdc.stdio; + +struct FILE; + +extern(C) nothrow @nogc: + +size_t fread(scope void* ptr, size_t size, size_t nmemb, FILE* stream); +size_t fwrite(scope const void* ptr, size_t size, size_t nmemb, FILE* stream); +int fgetc(FILE* stream); +char* fgets(char* s, int n, FILE* stream); +int feof(FILE* stream); +int ferror(FILE* stream); +int fflush(FILE* stream); + +version (CRuntime_Microsoft) +{ + FILE* __acrt_iob_func(int hnd); + + FILE* stdin()() { return __acrt_iob_func(0); } + FILE* stdout()() { return __acrt_iob_func(1); } + FILE* stderr()() { return __acrt_iob_func(2); } +} +else version (CRuntime_Glibc) +{ + extern __gshared FILE* stdin; + extern __gshared FILE* stdout; + extern __gshared FILE* stderr; +} +else version (Darwin) +{ + private extern __gshared FILE* __stdinp; + private extern __gshared FILE* __stdoutp; + private extern __gshared FILE* __stderrp; + + alias stdin = __stdinp; + alias stdout = __stdoutp; + alias stderr = __stderrp; +} +else version (FreeBSD) +{ + private extern __gshared FILE* __stdinp; + private extern __gshared FILE* __stdoutp; + private extern __gshared FILE* __stderrp; + + alias stdin = __stdinp; + alias stdout = __stdoutp; + alias stderr = __stderrp; +} +else version (DragonFlyBSD) +{ + private extern __gshared FILE* __stdinp; + private extern __gshared FILE* __stdoutp; + private extern __gshared FILE* __stderrp; + + alias stdin = __stdinp; + alias stdout = __stdoutp; + alias stderr = __stderrp; +} +else version (CRuntime_Musl) +{ + extern __gshared FILE* stdin; + extern __gshared FILE* stdout; + extern __gshared FILE* stderr; +} +else version (CRuntime_UClibc) +{ + extern __gshared FILE* stdin; + extern __gshared FILE* stdout; + extern __gshared FILE* stderr; +} +else version (CRuntime_Bionic) +{ + private extern __gshared FILE[3] __sF; + + @property FILE* stdin()() { return &__sF[0]; } + @property FILE* stdout()() { return &__sF[1]; } + @property FILE* stderr()() { return &__sF[2]; } +} +else version (CRuntime_Newlib) +{ + private struct _reent + { + int _errno; + FILE* _stdin; + FILE* _stdout; + FILE* _stderr; + } + + private extern(C) _reent* __getreent(); + + @property FILE* stdin()() { return __getreent()._stdin; } + @property FILE* stdout()() { return __getreent()._stdout; } + @property FILE* stderr()() { return __getreent()._stderr; } +} +else version (WASI) +{ + extern __gshared FILE* stdin; + extern __gshared FILE* stdout; + extern __gshared FILE* stderr; +} +else version (FreeStanding) +{ + // no stdio streams - io.d uses UART directly +} +else +{ + static assert(false, "Unsupported platform"); +} diff --git a/src/urt/internal/stdc/stdlib.d b/src/urt/internal/stdc/stdlib.d new file mode 100644 index 0000000..960d32e --- /dev/null +++ b/src/urt/internal/stdc/stdlib.d @@ -0,0 +1,12 @@ +// Minimal C stdlib bindings - only what URT actually uses. + +module urt.internal.stdc.stdlib; + +extern(C) nothrow @nogc: + +noreturn abort() @safe; +noreturn exit(int status); + +pure char* gcvt(double value, int ndigit, char* buf); +version (Windows) + pure int _gcvt_s(const char* buffer, size_t size_in_bytes, double value, int digits); diff --git a/src/urt/internal/sys/posix/package.d b/src/urt/internal/sys/posix/package.d new file mode 100644 index 0000000..c9b2ef8 --- /dev/null +++ b/src/urt/internal/sys/posix/package.d @@ -0,0 +1,336 @@ +// Minimal POSIX bindings — only what URT actually uses. +// Replaces imports of core.sys.posix.* to avoid druntime's transitive +// core.stdc.stdio dependency (stdint → wchar_ → stdio). + +module urt.internal.sys.posix; + +version (Posix): +extern(C) nothrow @nogc: + +// ── types ── + +alias off_t = long; +alias mode_t = uint; +alias ssize_t = ptrdiff_t; +alias time_t = long; +alias clockid_t = int; +alias blkcnt_t = long; +alias dev_t = ulong; +alias ino_t = ulong; +alias uid_t = uint; +alias gid_t = uint; + +version (X86) +{ + alias blksize_t = int; + alias nlink_t = uint; +} +else version (X86_64) +{ + alias blksize_t = long; + alias nlink_t = ulong; +} +else version (AArch64) +{ + alias blksize_t = int; + alias nlink_t = uint; +} +else version (ARM) +{ + alias blksize_t = int; + alias nlink_t = uint; +} +else version (RISCV32) +{ + alias blksize_t = int; + alias nlink_t = uint; +} +else version (RISCV64) +{ + alias blksize_t = int; + alias nlink_t = uint; +} +else + static assert(false, "POSIX type aliases not defined for this arch"); + +// ── fcntl ── + +enum O_RDONLY = 0x0; +enum O_WRONLY = 0x1; +enum O_RDWR = 0x2; +enum O_CREAT = 0x40; +enum O_TRUNC = 0x200; +enum O_APPEND = 0x400; +enum O_NOCTTY = 0x100; +enum O_NDELAY = 0x800; // same as O_NONBLOCK +enum O_CLOEXEC = 0x80000; + +version (linux) + enum O_DIRECT = 0x4000; + +version (X86) +{ + // glibc x86: largefile64 redirects + int open64(scope const char* pathname, int flags, ...); + alias open = open64; +} +else + int open(scope const char* pathname, int flags, ...); +int fcntl(int fd, int cmd, ...); + +version (Darwin) +{ + enum F_NOCACHE = 48; + enum F_GETPATH = 50; +} + +enum F_GETFL = 3; +enum F_SETFL = 4; +enum O_NONBLOCK = 0x800; + +// ── unistd ── + +int close(int fd); +int unlink(scope const char* pathname); +ssize_t read(int fd, void* buf, size_t count); +ssize_t write(int fd, const void* buf, size_t count); +version (X86) +{ + off_t lseek64(int fd, off_t offset, int whence); + alias lseek = lseek64; + int ftruncate64(int fd, off_t length); + alias ftruncate = ftruncate64; +} +else +{ + off_t lseek(int fd, off_t offset, int whence); + int ftruncate(int fd, off_t length); +} +ssize_t readlink(scope const char* path, char* buf, size_t bufsiz); +version (X86) +{ + ssize_t pread64(int fd, void* buf, size_t count, off_t offset); + alias pread = pread64; + ssize_t pwrite64(int fd, const void* buf, size_t count, off_t offset); + alias pwrite = pwrite64; +} +else +{ + ssize_t pread(int fd, void* buf, size_t count, off_t offset); + ssize_t pwrite(int fd, const void* buf, size_t count, off_t offset); +} +int fsync(int fd); +int usleep(uint usec); +long sysconf(int name); +int gethostname(char* name, size_t len); + +enum _SC_PAGE_SIZE = 30; +enum _SC_PHYS_PAGES = 85; + +enum STDIN_FILENO = 0; +enum STDOUT_FILENO = 1; +enum STDERR_FILENO = 2; +int isatty(int fd); + +// ── sys/stat ── + +version (X86_64) +{ + // x86_64 is unique: nlink before mode, long blksize/blocks, long[3] tail + struct stat_t + { + dev_t st_dev; + ino_t st_ino; + nlink_t st_nlink; + mode_t st_mode; + uid_t st_uid; + gid_t st_gid; + uint __pad0; + dev_t st_rdev; + off_t st_size; + long st_blksize; + long st_blocks; + timespec st_atim; + timespec st_mtim; + timespec st_ctim; + long[3] __unused; + } + static assert(stat_t.sizeof == 144); +} +else version (X86) version = OldStatLayout; +else version (ARM) version = OldStatLayout; +else version (AArch64) version = NewStatLayout; +else version (RISCV32) version = NewStatLayout; +else version (RISCV64) version = NewStatLayout; +else + static assert(false, "stat_t not defined for this arch"); + +version (NewStatLayout) +{ + // AArch64, RISCV32, RISCV64: new kernel stat layout + struct stat_t + { + dev_t st_dev; + ino_t st_ino; + mode_t st_mode; + nlink_t st_nlink; + uid_t st_uid; + gid_t st_gid; + dev_t st_rdev; + ulong __pad1; + off_t st_size; + blksize_t st_blksize; + int __pad2; + blkcnt_t st_blocks; + timespec st_atim; + timespec st_mtim; + timespec st_ctim; + int[2] __unused; + } + static assert(stat_t.sizeof == 128); +} + +version (OldStatLayout) +{ + // X86, ARM: old glibc stat layout with __USE_FILE_OFFSET64 + private struct __timespec32 { int tv_sec; int tv_nsec; } + struct stat_t + { + dev_t st_dev; + ushort __pad1; + uint __st_ino; + mode_t st_mode; + nlink_t st_nlink; + uid_t st_uid; + gid_t st_gid; + dev_t st_rdev; + ushort __pad2; + off_t st_size; + blksize_t st_blksize; + blkcnt_t st_blocks; + version (CRuntime_Musl) + { + __timespec32 __st_atim32; + __timespec32 __st_mtim32; + __timespec32 __st_ctim32; + } + ino_t st_ino; + timespec st_atim; + timespec st_mtim; + timespec st_ctim; + } +} + +bool S_ISREG(mode_t mode) + => (mode & 0xF000) == 0x8000; + +version (X86) +{ + int stat64(scope const char* pathname, stat_t* buf); + alias stat = stat64; + int fstat64(int fd, stat_t* buf); + alias fstat = fstat64; + int mkstemp64(char* tmpl); + alias mkstemp = mkstemp64; +} +else +{ + int stat(scope const char* pathname, stat_t* buf); + int fstat(int fd, stat_t* buf); + int mkstemp(char* tmpl); +} +int mkdir(scope const char* pathname, mode_t mode); +pure int posix_memalign(void** memptr, size_t alignment, size_t size); + +// ── time ── + +struct timespec +{ + time_t tv_sec; + long tv_nsec; +} + +struct tm +{ + int tm_sec; + int tm_min; + int tm_hour; + int tm_mday; + int tm_mon; + int tm_year; + int tm_wday; + int tm_yday; + int tm_isdst; + long tm_gmtoff; + const(char)* tm_zone; +} + +enum CLOCK_REALTIME = 0; +enum CLOCK_MONOTONIC = 1; + +int clock_gettime(clockid_t clk_id, timespec* tp); +int clock_settime(clockid_t clk_id, const timespec* tp); +tm* gmtime_r(scope const time_t* timep, tm* result); +time_t mktime(tm* tp); + +// ── sys/mman ── + +enum PROT_READ = 0x1; +enum MAP_PRIVATE = 0x02; +enum MAP_FAILED = cast(void*)-1; + +void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset); +int munmap(void* addr, size_t length); + +// ── dlfcn ── + +struct Dl_info +{ + const(char)* dli_fname; + void* dli_fbase; + const(char)* dli_sname; + void* dli_saddr; +} + +int dladdr(scope const void* addr, Dl_info* info); + +// ── netinet/in ── +// in6_addr and sockaddr_in6 are provided by ImportC (urt.internal.os) +// on platforms that use it. Defined here as fallback for others. + +version (none) +{ + struct in6_addr + { + ubyte[16] s6_addr; + } + + struct sockaddr_in6 + { + ushort sin6_family; + ushort sin6_port; + uint sin6_flowinfo; + in6_addr sin6_addr; + uint sin6_scope_id; + } +} + +// ── poll ── + +struct pollfd +{ + int fd; + short events; + short revents; +} + +enum POLLIN = 0x001; +enum POLLPRI = 0x002; +enum POLLOUT = 0x004; +enum POLLERR = 0x008; +enum POLLHUP = 0x010; +enum POLLNVAL = 0x020; +enum POLLRDNORM = 0x040; +enum POLLWRNORM = 0x100; + +int poll(pollfd* fds, size_t nfds, int timeout); diff --git a/src/urt/internal/sys/posix/termios.d b/src/urt/internal/sys/posix/termios.d new file mode 100644 index 0000000..c322114 --- /dev/null +++ b/src/urt/internal/sys/posix/termios.d @@ -0,0 +1,107 @@ +// Minimal termios bindings for Linux. + +module urt.internal.sys.posix.termios; + +version (linux): +extern(C) nothrow @nogc: + +alias cc_t = ubyte; +alias speed_t = uint; +alias tcflag_t = uint; + +enum NCCS = 32; + +struct termios +{ + tcflag_t c_iflag; + tcflag_t c_oflag; + tcflag_t c_lflag; + tcflag_t c_cflag; + cc_t c_line; + cc_t[NCCS] c_cc; + speed_t c_ispeed; + speed_t c_ospeed; +} + +// c_iflag +enum IGNBRK = 0x01; +enum BRKINT = 0x02; +enum PARMRK = 0x08; +enum ISTRIP = 0x20; +enum INLCR = 0x40; +enum IGNCR = 0x80; +enum ICRNL = 0x100; +enum IXON = 0x400; +enum IXOFF = 0x1000; +enum IXANY = 0x800; + +// c_oflag +enum OPOST = 0x01; +enum ONLCR = 0x04; + +// c_cflag +enum CSIZE = 0x30; +enum CS5 = 0x00; +enum CS6 = 0x10; +enum CS7 = 0x20; +enum CS8 = 0x30; +enum CSTOPB = 0x40; +enum CREAD = 0x80; +enum PARENB = 0x100; +enum PARODD = 0x200; +enum CLOCAL = 0x800; +enum CRTSCTS = 0x80000000; + +// c_lflag +enum ISIG = 0x01; +enum ICANON = 0x02; +enum ECHO = 0x08; +enum ECHOE = 0x10; +enum ECHONL = 0x40; +enum IEXTEN = 0x8000; + +// c_cc indices +enum VMIN = 6; +enum VTIME = 5; + +// tcsetattr actions +enum TCSANOW = 0; +enum TCSAFLUSH = 2; + +// tcflush queue selectors +enum TCIFLUSH = 0; +enum TCOFLUSH = 1; +enum TCIOFLUSH = 2; + +// baud rates +enum B0 = 0x0; +enum B50 = 0x1; +enum B75 = 0x2; +enum B110 = 0x3; +enum B134 = 0x4; +enum B150 = 0x5; +enum B200 = 0x6; +enum B300 = 0x7; +enum B600 = 0x8; +enum B1200 = 0x9; +enum B1800 = 0xA; +enum B2400 = 0xB; +enum B4800 = 0xC; +enum B9600 = 0xD; +enum B19200 = 0xE; +enum B38400 = 0xF; +enum B57600 = 0x1001; +enum B115200 = 0x1002; +enum B230400 = 0x1003; +enum B460800 = 0x1004; +enum B500000 = 0x1005; +enum B576000 = 0x1006; +enum B921600 = 0x1007; + +int tcgetattr(int fd, termios* t); +int tcsetattr(int fd, int action, const termios* t); +int tcflush(int fd, int queue); +speed_t cfgetispeed(const termios* t); +speed_t cfgetospeed(const termios* t); +int cfsetispeed(termios* t, speed_t speed); +int cfsetospeed(termios* t, speed_t speed); diff --git a/src/urt/internal/sys/windows/basetsd.d b/src/urt/internal/sys/windows/basetsd.d new file mode 100644 index 0000000..b881b5a --- /dev/null +++ b/src/urt/internal/sys/windows/basetsd.d @@ -0,0 +1,141 @@ +/** + * Windows API header module + * + * Translated from MinGW API for MS-Windows 3.12 + * + * Authors: Stewart Gordon + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_basetsd.d) + */ +module urt.internal.sys.windows.basetsd; +version (Windows): + +// [SnakE 2009-02-23] Moved HANDLE definition here from winnt.d to avoid +// 'forwatd template reference' to CPtr from winnt.d caused by a circular +// import. +alias HANDLE = void*; + +alias HANDLE* PHANDLE, LPHANDLE; + +// helper for aligned structs +// alignVal 0 means the default align. +// _alignSpec as parameter does not pollute namespace. +package mixin template AlignedStr(int alignVal, string name, string memberlist, + string _alignSpec = !alignVal ? "align" : "align("~alignVal.stringof~")" ) +{ + mixin( _alignSpec ~ " struct " ~ name ~" { " ~ _alignSpec ~":"~ memberlist~" }" ); +} + +version (CoreUnittest) { + private mixin AlignedStr!(16, "_Test_Aligned_Str", q{char a; char b;}); + private mixin AlignedStr!(0, "_Test_NoAligned_Str", q{char a; char b;}); +} + +version (Win64) { + alias long __int3264; +enum ulong ADDRESS_TAG_BIT = 0x40000000000; + + alias long INT_PTR, LONG_PTR; + alias long* PINT_PTR, PLONG_PTR; + alias ulong UINT_PTR, ULONG_PTR, HANDLE_PTR; + alias ulong* PUINT_PTR, PULONG_PTR; + alias int HALF_PTR; + alias int* PHALF_PTR; + alias uint UHALF_PTR; + alias uint* PUHALF_PTR; + + uint HandleToULong()(void* h) { return(cast(uint) cast(ULONG_PTR) h); } + int HandleToLong()(void* h) { return(cast(int) cast(LONG_PTR) h); } + void* ULongToHandle()(uint h) { return(cast(void*) cast(UINT_PTR) h); } + void* LongToHandle()(int h) { return(cast(void*) cast(INT_PTR) h); } + uint PtrToUlong()(void* p) { return(cast(uint) cast(ULONG_PTR) p); } + uint PtrToUint()(void* p) { return(cast(uint) cast(UINT_PTR) p); } + ushort PtrToUshort()(void* p) { return(cast(ushort) cast(uint) cast(ULONG_PTR) p); } + int PtrToLong()(void* p) { return(cast(int) cast(LONG_PTR) p); } + int PtrToInt()(void* p) { return(cast(int) cast(INT_PTR) p); } + short PtrToShort()(void* p) { return(cast(short) cast(int) cast(LONG_PTR) p); } + void* IntToPtr()(int i) { return(cast(void*) cast(INT_PTR) i); } + void* UIntToPtr()(uint ui) { return(cast(void*) cast(UINT_PTR) ui); } + void* LongToPtr()(int l) { return(cast(void*) cast(LONG_PTR) l); } + void* ULongToPtr()(uint ul) { return(cast(void*) cast(ULONG_PTR) ul); } + +} else { + alias int __int3264; +enum uint ADDRESS_TAG_BIT = 0x80000000; + + alias int INT_PTR, LONG_PTR; + alias int* PINT_PTR, PLONG_PTR; + alias uint UINT_PTR, ULONG_PTR, HANDLE_PTR; + alias uint* PUINT_PTR, PULONG_PTR; + alias short HALF_PTR; + alias short* PHALF_PTR; + alias ushort UHALF_PTR; + alias ushort* PUHALF_PTR; + + uint HandleToUlong()(HANDLE h) { return cast(uint) h; } + int HandleToLong()(HANDLE h) { return cast(int) h; } + HANDLE LongToHandle()(LONG_PTR h) { return cast(HANDLE)h; } + uint PtrToUlong(const(void)* p) { return cast(uint) p; } + uint PtrToUint(const(void)* p) { return cast(uint) p; } + int PtrToInt(const(void)* p) { return cast(int) p; } + ushort PtrToUshort(const(void)* p) { return cast(ushort) p; } + short PtrToShort(const(void)* p) { return cast(short) p; } + void* IntToPtr()(int i) { return cast(void*) i; } + void* UIntToPtr()(uint ui) { return cast(void*) ui; } + alias IntToPtr LongToPtr; + alias UIntToPtr ULongToPtr; +} + +alias UIntToPtr UintToPtr, UlongToPtr; + +enum : UINT_PTR { + MAXUINT_PTR = UINT_PTR.max +} + +enum : INT_PTR { + MAXINT_PTR = INT_PTR.max, + MININT_PTR = INT_PTR.min +} + +enum : ULONG_PTR { + MAXULONG_PTR = ULONG_PTR.max +} + +enum : LONG_PTR { + MAXLONG_PTR = LONG_PTR.max, + MINLONG_PTR = LONG_PTR.min +} + +enum : UHALF_PTR { + MAXUHALF_PTR = UHALF_PTR.max +} + +enum : HALF_PTR { + MAXHALF_PTR = HALF_PTR.max, + MINHALF_PTR = HALF_PTR.min +} + +alias byte INT8; +alias byte* PINT8; +alias ubyte UINT8; +alias ubyte* PUINT8; + +alias short INT16; +alias short* PINT16; +alias ushort UINT16; +alias ushort* PUINT16; + +alias int LONG32, INT32; +alias int* PLONG32, PINT32; +alias uint ULONG32, DWORD32, UINT32; +alias uint* PULONG32, PDWORD32, PUINT32; + +alias ULONG_PTR SIZE_T, DWORD_PTR; +alias ULONG_PTR* PSIZE_T, PDWORD_PTR; +alias LONG_PTR SSIZE_T; +alias LONG_PTR* PSSIZE_T; + +alias long LONG64, INT64; +alias long* PLONG64, PINT64; +alias ulong ULONG64, DWORD64, UINT64; +alias ulong* PULONG64, PDWORD64, PUINT64; diff --git a/src/urt/internal/sys/windows/basetyps.d b/src/urt/internal/sys/windows/basetyps.d new file mode 100644 index 0000000..d6cee67 --- /dev/null +++ b/src/urt/internal/sys/windows/basetyps.d @@ -0,0 +1,27 @@ +/** + * Windows API header module + * + * Translated from MinGW API for MS-Windows 3.10 + * + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_basetyps.d) + */ +module urt.internal.sys.windows.basetyps; +version (Windows): + +import urt.internal.sys.windows.windef, urt.internal.sys.windows.basetsd; + +align(1) struct GUID { // size is 16 + align(1): + DWORD Data1; + WORD Data2; + WORD Data3; + BYTE[8] Data4; +} +alias GUID UUID, /*IID, CLSID, */FMTID, uuid_t; +alias IID = const(GUID); +alias CLSID = const(GUID); + +alias GUID* LPGUID, LPCLSID, LPIID; +alias const(GUID)* LPCGUID, REFGUID, REFIID, REFCLSID, REFFMTID; +alias uint error_status_t, PROPID; diff --git a/src/urt/internal/sys/windows/ntdef.d b/src/urt/internal/sys/windows/ntdef.d new file mode 100644 index 0000000..a80762d --- /dev/null +++ b/src/urt/internal/sys/windows/ntdef.d @@ -0,0 +1,85 @@ +/** + * Windows API header module + * + * Translated from MinGW Windows headers + * + * Authors: Stewart Gordon + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_ntdef.d) + */ +module urt.internal.sys.windows.ntdef; +version (Windows): + +import urt.internal.sys.windows.basetsd, urt.internal.sys.windows.subauth, urt.internal.sys.windows.windef, urt.internal.sys.windows.winnt; + +enum uint + OBJ_INHERIT = 0x0002, + OBJ_PERMANENT = 0x0010, + OBJ_EXCLUSIVE = 0x0020, + OBJ_CASE_INSENSITIVE = 0x0040, + OBJ_OPENIF = 0x0080, + OBJ_OPENLINK = 0x0100, + OBJ_VALID_ATTRIBUTES = 0x01F2; + +void InitializeObjectAttributes(OBJECT_ATTRIBUTES* p, UNICODE_STRING* n, + uint a, HANDLE r, void* s) { + with (*p) { + Length = OBJECT_ATTRIBUTES.sizeof; + RootDirectory = r; + Attributes = a; + ObjectName = n; + SecurityDescriptor = s; + SecurityQualityOfService = null; + } +} + +pragma(inline, true) @safe pure nothrow @nogc { + bool NT_SUCCESS()(NTSTATUS Status) { return Status >= 0; } + bool NT_INFORMATION()(NTSTATUS Status) { return ((cast(ULONG) Status) >> 30) == 1; } + bool NT_WARNING()(NTSTATUS Status) { return ((cast(ULONG) Status) >> 30) == 2; } + bool NT_ERROR()(NTSTATUS Status) { return ((cast(ULONG) Status) >> 30) == 3; } +} + +/* In MinGW, NTSTATUS, UNICODE_STRING, STRING and their associated pointer + * type aliases are defined in ntdef.h, ntsecapi.h and subauth.h, each of + * which checks that none of the others is already included. + */ +alias int NTSTATUS; +alias int* PNTSTATUS; + +struct UNICODE_STRING { + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; +} +alias UNICODE_STRING* PUNICODE_STRING; +alias const(UNICODE_STRING)* PCUNICODE_STRING; + +struct STRING { + USHORT Length; + USHORT MaximumLength; + PCHAR Buffer; +} +alias STRING ANSI_STRING, OEM_STRING; +alias STRING* PSTRING, PANSI_STRING, POEM_STRING; + +alias LARGE_INTEGER PHYSICAL_ADDRESS; +alias LARGE_INTEGER* PPHYSICAL_ADDRESS; + +enum SECTION_INHERIT { + ViewShare = 1, + ViewUnmap +} + +/* In MinGW, this is defined in ntdef.h and ntsecapi.h, each of which checks + * that the other isn't already included. + */ +struct OBJECT_ATTRIBUTES { + ULONG Length = OBJECT_ATTRIBUTES.sizeof; + HANDLE RootDirectory; + PUNICODE_STRING ObjectName; + ULONG Attributes; + PVOID SecurityDescriptor; + PVOID SecurityQualityOfService; +} +alias OBJECT_ATTRIBUTES* POBJECT_ATTRIBUTES; diff --git a/src/urt/internal/sys/windows/ntsecapi.d b/src/urt/internal/sys/windows/ntsecapi.d new file mode 100644 index 0000000..bf372bf --- /dev/null +++ b/src/urt/internal/sys/windows/ntsecapi.d @@ -0,0 +1,796 @@ +/** + * Windows API header module + * + * Translated from MinGW Windows headers + * + * Authors: Stewart Gordon + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_ntsecapi.d) + */ +module urt.internal.sys.windows.ntsecapi; +version (Windows): +pragma(lib, "advapi32"); + +version (ANSI) {} else version = Unicode; + +private import + urt.internal.sys.windows.basetyps, urt.internal.sys.windows.ntdef, urt.internal.sys.windows.windef, urt.internal.sys.windows.winnt, urt.internal.sys.windows.w32api; + +// FIXME: check types and grouping of constants +// FIXME: check Windows version support + +enum KERB_WRAP_NO_ENCRYPT = 0x80000001; + +enum LOGON_GUEST = 0x00000001; +enum LOGON_NOENCRYPTION = 0x00000002; +enum LOGON_CACHED_ACCOUNT = 0x00000004; +enum LOGON_USED_LM_PASSWORD = 0x00000008; +enum LOGON_EXTRA_SIDS = 0x00000020; +enum LOGON_SUBAUTH_SESSION_KEY = 0x00000040; +enum LOGON_SERVER_TRUST_ACCOUNT = 0x00000080; +enum LOGON_NTLMV2_ENABLED = 0x00000100; +enum LOGON_RESOURCE_GROUPS = 0x00000200; +enum LOGON_PROFILE_PATH_RETURNED = 0x00000400; +enum LOGON_GRACE_LOGON = 0x01000000; + +enum { + LSA_MODE_PASSWORD_PROTECTED = 1, + LSA_MODE_INDIVIDUAL_ACCOUNTS, + LSA_MODE_MANDATORY_ACCESS, + LSA_MODE_LOG_FULL +} + +bool LSA_SUCCESS()(int x) { return x >= 0; } + +/* TOTHINKABOUT: These constants don't have ANSI/Unicode versioned + * aliases. Should we merge them anyway? + */ +const char[] MICROSOFT_KERBEROS_NAME_A = "Kerberos"; +const wchar[] MICROSOFT_KERBEROS_NAME_W = "Kerberos"; +const char[] MSV1_0_PACKAGE_NAME = "MICROSOFT_AUTHENTICATION_PACKAGE_V1_0"; +const wchar[] MSV1_0_PACKAGE_NAMEW = "MICROSOFT_AUTHENTICATION_PACKAGE_V1_0"; + +enum MSV1_0_ALLOW_SERVER_TRUST_ACCOUNT = 32; +enum MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT = 2048; +enum MSV1_0_CLEARTEXT_PASSWORD_ALLOWED = 2; +enum MSV1_0_CRED_LM_PRESENT = 1; +enum MSV1_0_CRED_NT_PRESENT = 2; +enum MSV1_0_CRED_VERSION = 0; +enum MSV1_0_DONT_TRY_GUEST_ACCOUNT = 16; +enum MSV1_0_MAX_NTLM3_LIFE = 1800; +enum MSV1_0_MAX_AVL_SIZE = 64000; +enum MSV1_0_MNS_LOGON = 16777216; + +enum size_t + MSV1_0_CHALLENGE_LENGTH = 8, + MSV1_0_LANMAN_SESSION_KEY_LENGTH = 8, + MSV1_0_NTLM3_RESPONSE_LENGTH = 16, + MSV1_0_NTLM3_OWF_LENGTH = 16, + MSV1_0_NTLM3_INPUT_LENGTH = MSV1_0_NTLM3_RESPONSE.sizeof + - MSV1_0_NTLM3_RESPONSE_LENGTH, + MSV1_0_OWF_PASSWORD_LENGTH = 16, + MSV1_0_PACKAGE_NAMEW_LENGTH = MSV1_0_PACKAGE_NAMEW.sizeof + - WCHAR.sizeof; + +enum MSV1_0_RETURN_USER_PARAMETERS = 8; +enum MSV1_0_RETURN_PASSWORD_EXPIRY = 64; +enum MSV1_0_RETURN_PROFILE_PATH = 512; +enum MSV1_0_SUBAUTHENTICATION_DLL_EX = 1048576; +enum MSV1_0_SUBAUTHENTICATION_DLL = 0xff000000; +enum MSV1_0_SUBAUTHENTICATION_DLL_SHIFT = 24; +enum MSV1_0_SUBAUTHENTICATION_DLL_RAS = 2; +enum MSV1_0_SUBAUTHENTICATION_DLL_IIS = 132; +enum MSV1_0_SUBAUTHENTICATION_FLAGS = 0xff000000; +enum MSV1_0_TRY_GUEST_ACCOUNT_ONLY = 256; +enum MSV1_0_TRY_SPECIFIED_DOMAIN_ONLY = 1024; +enum MSV1_0_UPDATE_LOGON_STATISTICS = 4; +enum MSV1_0_USE_CLIENT_CHALLENGE = 128; +enum MSV1_0_USER_SESSION_KEY_LENGTH = 16; + +const char[] + MSV1_0_SUBAUTHENTICATION_KEY + = `System\CurrentControlSet\Control\Lsa\MSV1_0`, + MSV1_0_SUBAUTHENTICATION_VALUE = "Auth"; + +enum ACCESS_MASK + POLICY_VIEW_LOCAL_INFORMATION = 0x0001, + POLICY_VIEW_AUDIT_INFORMATION = 0x0002, + POLICY_GET_PRIVATE_INFORMATION = 0x0004, + POLICY_TRUST_ADMIN = 0x0008, + POLICY_CREATE_ACCOUNT = 0x0010, + POLICY_CREATE_SECRET = 0x0020, + POLICY_CREATE_PRIVILEGE = 0x0040, + POLICY_SET_DEFAULT_QUOTA_LIMITS = 0x0080, + POLICY_SET_AUDIT_REQUIREMENTS = 0x0100, + POLICY_AUDIT_LOG_ADMIN = 0x0200, + POLICY_SERVER_ADMIN = 0x0400, + POLICY_LOOKUP_NAMES = 0x0800, + + POLICY_READ = STANDARD_RIGHTS_READ | 0x0006, + POLICY_WRITE = STANDARD_RIGHTS_WRITE | 0x07F8, + POLICY_EXECUTE = STANDARD_RIGHTS_EXECUTE | 0x0801, + POLICY_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | 0x0FFF; + +enum POLICY_AUDIT_EVENT_UNCHANGED = 0; +enum POLICY_AUDIT_EVENT_SUCCESS = 1; +enum POLICY_AUDIT_EVENT_FAILURE = 2; +enum POLICY_AUDIT_EVENT_NONE = 4; +enum POLICY_AUDIT_EVENT_MASK = 7; + +enum { + POLICY_LOCATION_LOCAL = 1, + POLICY_LOCATION_DS +} + +enum : uint { + POLICY_MACHINE_POLICY_LOCAL = 0, + POLICY_MACHINE_POLICY_DEFAULTED, + POLICY_MACHINE_POLICY_EXPLICIT, + POLICY_MACHINE_POLICY_UNKNOWN = 0xFFFFFFFF +} + + +enum POLICY_QOS_SCHANEL_REQUIRED = 0x0001; +enum POLICY_QOS_OUTBOUND_INTEGRITY = 0x0002; +enum POLICY_QOS_OUTBOUND_CONFIDENTIALITY = 0x0004; +enum POLICY_QOS_INBOUND_INTEGREITY = 0x0008; +enum POLICY_QOS_INBOUND_CONFIDENTIALITY = 0x0010; +enum POLICY_QOS_ALLOW_LOCAL_ROOT_CERT_STORE = 0x0020; +enum POLICY_QOS_RAS_SERVER_ALLOWED = 0x0040; +enum POLICY_QOS_DHCP_SERVER_ALLOWD = 0x0080; + +enum POLICY_KERBEROS_FORWARDABLE = 1; +enum POLICY_KERBEROS_PROXYABLE = 2; +enum POLICY_KERBEROS_RENEWABLE = 4; +enum POLICY_KERBEROS_POSTDATEABLE = 8; + +const char[] + SAM_PASSWORD_CHANGE_NOTIFY_ROUTINE = "PasswordChangeNotify", + SAM_INIT_NOTIFICATION_ROUTINE = "InitializeChangeNotify", + SAM_PASSWORD_FILTER_ROUTINE = "PasswordFilter"; + +const TCHAR[] + SE_INTERACTIVE_LOGON_NAME = "SeInteractiveLogonRight", + SE_NETWORK_LOGON_NAME = "SeNetworkLogonRight", + SE_BATCH_LOGON_NAME = "SeBatchLogonRight", + SE_SERVICE_LOGON_NAME = "SeServiceLogonRight"; + +enum { + TRUST_ATTRIBUTE_NON_TRANSITIVE = 1, + TRUST_ATTRIBUTE_UPLEVEL_ONLY = 2, + TRUST_ATTRIBUTE_TREE_PARENT = 4194304, + TRUST_ATTRIBUTES_VALID = -16580609 +} + +enum { + TRUST_AUTH_TYPE_NONE, + TRUST_AUTH_TYPE_NT4OWF, + TRUST_AUTH_TYPE_CLEAR +} + +enum { + TRUST_DIRECTION_DISABLED, + TRUST_DIRECTION_INBOUND, + TRUST_DIRECTION_OUTBOUND, + TRUST_DIRECTION_BIDIRECTIONAL +} + +enum { + TRUST_TYPE_DOWNLEVEL = 1, + TRUST_TYPE_UPLEVEL, + TRUST_TYPE_MIT, + TRUST_TYPE_DCE +} + +alias UNICODE_STRING LSA_UNICODE_STRING; +alias UNICODE_STRING* PLSA_UNICODE_STRING; +alias STRING LSA_STRING; +alias STRING* PLSA_STRING; + +enum MSV1_0_LOGON_SUBMIT_TYPE { + MsV1_0InteractiveLogon = 2, + MsV1_0Lm20Logon, + MsV1_0NetworkLogon, + MsV1_0SubAuthLogon, + MsV1_0WorkstationUnlockLogon = 7 +} +alias MSV1_0_LOGON_SUBMIT_TYPE* PMSV1_0_LOGON_SUBMIT_TYPE; + +enum MSV1_0_PROFILE_BUFFER_TYPE { + MsV1_0InteractiveProfile = 2, + MsV1_0Lm20LogonProfile, + MsV1_0SmartCardProfile +} +alias MSV1_0_PROFILE_BUFFER_TYPE* PMSV1_0_PROFILE_BUFFER_TYPE; + + +enum MSV1_0_AVID { + MsvAvEOL, + MsvAvNbComputerName, + MsvAvNbDomainName, + MsvAvDnsComputerName, + MsvAvDnsDomainName +} + +enum MSV1_0_PROTOCOL_MESSAGE_TYPE { + MsV1_0Lm20ChallengeRequest = 0, + MsV1_0Lm20GetChallengeResponse, + MsV1_0EnumerateUsers, + MsV1_0GetUserInfo, + MsV1_0ReLogonUsers, + MsV1_0ChangePassword, + MsV1_0ChangeCachedPassword, + MsV1_0GenericPassthrough, + MsV1_0CacheLogon, + MsV1_0SubAuth, + MsV1_0DeriveCredential, + MsV1_0CacheLookup +} +alias MSV1_0_PROTOCOL_MESSAGE_TYPE* PMSV1_0_PROTOCOL_MESSAGE_TYPE; + +enum POLICY_LSA_SERVER_ROLE { + PolicyServerRoleBackup = 2, + PolicyServerRolePrimary +} +alias POLICY_LSA_SERVER_ROLE* PPOLICY_LSA_SERVER_ROLE; + +enum POLICY_SERVER_ENABLE_STATE { + PolicyServerEnabled = 2, + PolicyServerDisabled +} +alias POLICY_SERVER_ENABLE_STATE* PPOLICY_SERVER_ENABLE_STATE; + +enum POLICY_INFORMATION_CLASS { + PolicyAuditLogInformation = 1, + PolicyAuditEventsInformation, + PolicyPrimaryDomainInformation, + PolicyPdAccountInformation, + PolicyAccountDomainInformation, + PolicyLsaServerRoleInformation, + PolicyReplicaSourceInformation, + PolicyDefaultQuotaInformation, + PolicyModificationInformation, + PolicyAuditFullSetInformation, + PolicyAuditFullQueryInformation, + PolicyDnsDomainInformation, + PolicyEfsInformation +} +alias POLICY_INFORMATION_CLASS* PPOLICY_INFORMATION_CLASS; + +enum POLICY_AUDIT_EVENT_TYPE { + AuditCategorySystem, + AuditCategoryLogon, + AuditCategoryObjectAccess, + AuditCategoryPrivilegeUse, + AuditCategoryDetailedTracking, + AuditCategoryPolicyChange, + AuditCategoryAccountManagement, + AuditCategoryDirectoryServiceAccess, + AuditCategoryAccountLogon +} +alias POLICY_AUDIT_EVENT_TYPE* PPOLICY_AUDIT_EVENT_TYPE; + +enum POLICY_LOCAL_INFORMATION_CLASS { + PolicyLocalAuditEventsInformation = 1, + PolicyLocalPdAccountInformation, + PolicyLocalAccountDomainInformation, + PolicyLocalLsaServerRoleInformation, + PolicyLocalReplicaSourceInformation, + PolicyLocalModificationInformation, + PolicyLocalAuditFullSetInformation, + PolicyLocalAuditFullQueryInformation, + PolicyLocalDnsDomainInformation, + PolicyLocalIPSecReferenceInformation, + PolicyLocalMachinePasswordInformation, + PolicyLocalQualityOfServiceInformation, + PolicyLocalPolicyLocationInformation +} +alias POLICY_LOCAL_INFORMATION_CLASS* PPOLICY_LOCAL_INFORMATION_CLASS; + +enum POLICY_DOMAIN_INFORMATION_CLASS { + PolicyDomainIPSecReferenceInformation = 1, + PolicyDomainQualityOfServiceInformation, + PolicyDomainEfsInformation, + PolicyDomainPublicKeyInformation, + PolicyDomainPasswordPolicyInformation, + PolicyDomainLockoutInformation, + PolicyDomainKerberosTicketInformation +} +alias POLICY_DOMAIN_INFORMATION_CLASS* PPOLICY_DOMAIN_INFORMATION_CLASS; + +enum SECURITY_LOGON_TYPE { + Interactive = 2, + Network, + Batch, + Service, + Proxy, + Unlock +} +alias SECURITY_LOGON_TYPE* PSECURITY_LOGON_TYPE; + +enum TRUSTED_INFORMATION_CLASS { + TrustedDomainNameInformation = 1, + TrustedControllersInformation, + TrustedPosixOffsetInformation, + TrustedPasswordInformation, + TrustedDomainInformationBasic, + TrustedDomainInformationEx, + TrustedDomainAuthInformation, + TrustedDomainFullInformation +} +alias TRUSTED_INFORMATION_CLASS* PTRUSTED_INFORMATION_CLASS; + +struct DOMAIN_PASSWORD_INFORMATION { + USHORT MinPasswordLength; + USHORT PasswordHistoryLength; + ULONG PasswordProperties; + LARGE_INTEGER MaxPasswordAge; + LARGE_INTEGER MinPasswordAge; +} +alias DOMAIN_PASSWORD_INFORMATION* PDOMAIN_PASSWORD_INFORMATION; + +struct LSA_ENUMERATION_INFORMATION { + PSID Sid; +} +alias LSA_ENUMERATION_INFORMATION* PLSA_ENUMERATION_INFORMATION; + +alias OBJECT_ATTRIBUTES LSA_OBJECT_ATTRIBUTES; +alias OBJECT_ATTRIBUTES* PLSA_OBJECT_ATTRIBUTES; + +struct LSA_TRUST_INFORMATION { + LSA_UNICODE_STRING Name; + PSID Sid; +} +alias LSA_TRUST_INFORMATION TRUSTED_DOMAIN_INFORMATION_BASIC; +alias LSA_TRUST_INFORMATION* PLSA_TRUST_INFORMATION; +/* in MinGW (further down the code): + * typedef PLSA_TRUST_INFORMATION *PTRUSTED_DOMAIN_INFORMATION_BASIC; + * but it doesn't look right.... + */ +alias LSA_TRUST_INFORMATION** PTRUSTED_DOMAIN_INFORMATION_BASIC; + +struct LSA_REFERENCED_DOMAIN_LIST { + ULONG Entries; + PLSA_TRUST_INFORMATION Domains; +} +alias LSA_REFERENCED_DOMAIN_LIST* PLSA_REFERENCED_DOMAIN_LIST; + +struct LSA_TRANSLATED_SID { + SID_NAME_USE Use; + ULONG RelativeId; + LONG DomainIndex; +} +alias LSA_TRANSLATED_SID* PLSA_TRANSLATED_SID; + +struct LSA_TRANSLATED_NAME { + SID_NAME_USE Use; + LSA_UNICODE_STRING Name; + LONG DomainIndex; +} +alias LSA_TRANSLATED_NAME* PLSA_TRANSLATED_NAME; + +struct MSV1_0_INTERACTIVE_LOGON { + MSV1_0_LOGON_SUBMIT_TYPE MessageType; + UNICODE_STRING LogonDomainName; + UNICODE_STRING UserName; + UNICODE_STRING Password; +} +alias MSV1_0_INTERACTIVE_LOGON* PMSV1_0_INTERACTIVE_LOGON; + +struct MSV1_0_INTERACTIVE_PROFILE { + MSV1_0_PROFILE_BUFFER_TYPE MessageType; + USHORT LogonCount; + USHORT BadPasswordCount; + LARGE_INTEGER LogonTime; + LARGE_INTEGER LogoffTime; + LARGE_INTEGER KickOffTime; + LARGE_INTEGER PasswordLastSet; + LARGE_INTEGER PasswordCanChange; + LARGE_INTEGER PasswordMustChange; + UNICODE_STRING LogonScript; + UNICODE_STRING HomeDirectory; + UNICODE_STRING FullName; + UNICODE_STRING ProfilePath; + UNICODE_STRING HomeDirectoryDrive; + UNICODE_STRING LogonServer; + ULONG UserFlags; +} +alias MSV1_0_INTERACTIVE_PROFILE* PMSV1_0_INTERACTIVE_PROFILE; + +struct MSV1_0_LM20_LOGON { + MSV1_0_LOGON_SUBMIT_TYPE MessageType; + UNICODE_STRING LogonDomainName; + UNICODE_STRING UserName; + UNICODE_STRING Workstation; + UCHAR[MSV1_0_CHALLENGE_LENGTH] ChallengeToClient; + STRING CaseSensitiveChallengeResponse; + STRING CaseInsensitiveChallengeResponse; + ULONG ParameterControl; +} +alias MSV1_0_LM20_LOGON* PMSV1_0_LM20_LOGON; + +//static if (_WIN32_WINNT >= 0x500) { + struct MSV1_0_SUBAUTH_LOGON { + MSV1_0_LOGON_SUBMIT_TYPE MessageType; + UNICODE_STRING LogonDomainName; + UNICODE_STRING UserName; + UNICODE_STRING Workstation; + UCHAR[MSV1_0_CHALLENGE_LENGTH] ChallengeToClient; + STRING AuthenticationInfo1; + STRING AuthenticationInfo2; + ULONG ParameterControl; + ULONG SubAuthPackageId; + } + alias MSV1_0_SUBAUTH_LOGON* PMSV1_0_SUBAUTH_LOGON; +//} + +struct MSV1_0_LM20_LOGON_PROFILE { + MSV1_0_PROFILE_BUFFER_TYPE MessageType; + LARGE_INTEGER KickOffTime; + LARGE_INTEGER LogoffTime; + ULONG UserFlags; + UCHAR[MSV1_0_USER_SESSION_KEY_LENGTH] UserSessionKey; + UNICODE_STRING LogonDomainName; + UCHAR[MSV1_0_LANMAN_SESSION_KEY_LENGTH] LanmanSessionKey; + UNICODE_STRING LogonServer; + UNICODE_STRING UserParameters; +} +alias MSV1_0_LM20_LOGON_PROFILE* PMSV1_0_LM20_LOGON_PROFILE; + +struct MSV1_0_SUPPLEMENTAL_CREDENTIAL { + ULONG Version; + ULONG Flags; + UCHAR[MSV1_0_OWF_PASSWORD_LENGTH] LmPassword; + UCHAR[MSV1_0_OWF_PASSWORD_LENGTH] NtPassword; +} +alias MSV1_0_SUPPLEMENTAL_CREDENTIAL* PMSV1_0_SUPPLEMENTAL_CREDENTIAL; + +struct MSV1_0_NTLM3_RESPONSE { + UCHAR[MSV1_0_NTLM3_RESPONSE_LENGTH] Response; + UCHAR RespType; + UCHAR HiRespType; + USHORT Flags; + ULONG MsgWord; + ULONGLONG TimeStamp; + UCHAR[MSV1_0_CHALLENGE_LENGTH] ChallengeFromClient; + ULONG AvPairsOff; + UCHAR _Buffer; + UCHAR* Buffer() return { return &_Buffer; } +} +alias MSV1_0_NTLM3_RESPONSE* PMSV1_0_NTLM3_RESPONSE; + +struct MSV1_0_AV_PAIR { + USHORT AvId; + USHORT AvLen; +} +alias MSV1_0_AV_PAIR* PMSV1_0_AV_PAIR; + +struct MSV1_0_CHANGEPASSWORD_REQUEST { + MSV1_0_PROTOCOL_MESSAGE_TYPE MessageType; + UNICODE_STRING DomainName; + UNICODE_STRING AccountName; + UNICODE_STRING OldPassword; + UNICODE_STRING NewPassword; + BOOLEAN Impersonating; +} +alias MSV1_0_CHANGEPASSWORD_REQUEST* PMSV1_0_CHANGEPASSWORD_REQUEST; + +struct MSV1_0_CHANGEPASSWORD_RESPONSE { + MSV1_0_PROTOCOL_MESSAGE_TYPE MessageType; + BOOLEAN PasswordInfoValid; + DOMAIN_PASSWORD_INFORMATION DomainPasswordInfo; +} +alias MSV1_0_CHANGEPASSWORD_RESPONSE* PMSV1_0_CHANGEPASSWORD_RESPONSE; + +struct MSV1_0_SUBAUTH_REQUEST { + MSV1_0_PROTOCOL_MESSAGE_TYPE MessageType; + ULONG SubAuthPackageId; + ULONG SubAuthInfoLength; + PUCHAR SubAuthSubmitBuffer; +} +alias MSV1_0_SUBAUTH_REQUEST* PMSV1_0_SUBAUTH_REQUEST; + +struct MSV1_0_SUBAUTH_RESPONSE { + MSV1_0_PROTOCOL_MESSAGE_TYPE MessageType; + ULONG SubAuthInfoLength; + PUCHAR SubAuthReturnBuffer; +} +alias MSV1_0_SUBAUTH_RESPONSE* PMSV1_0_SUBAUTH_RESPONSE; + +enum MSV1_0_DERIVECRED_TYPE_SHA1 = 0; + +struct MSV1_0_DERIVECRED_REQUEST { + MSV1_0_PROTOCOL_MESSAGE_TYPE MessageType; + LUID LogonId; + ULONG DeriveCredType; + ULONG DeriveCredInfoLength; + UCHAR _DeriveCredSubmitBuffer; + UCHAR* DeriveCredSubmitBuffer() return { return &_DeriveCredSubmitBuffer; } +} +alias MSV1_0_DERIVECRED_REQUEST* PMSV1_0_DERIVECRED_REQUEST; + +struct MSV1_0_DERIVECRED_RESPONSE { + MSV1_0_PROTOCOL_MESSAGE_TYPE MessageType; + ULONG DeriveCredInfoLength; + UCHAR _DeriveCredReturnBuffer; + UCHAR* DeriveCredReturnBuffer() return { return &_DeriveCredReturnBuffer; } +} +alias MSV1_0_DERIVECRED_RESPONSE* PMSV1_0_DERIVECRED_RESPONSE; + +alias uint LSA_ENUMERATION_HANDLE, LSA_OPERATIONAL_MODE, + POLICY_AUDIT_EVENT_OPTIONS; +alias uint* PLSA_ENUMERATION_HANDLE, PLSA_OPERATIONAL_MODE, + PPOLICY_AUDIT_EVENT_OPTIONS; + +struct POLICY_PRIVILEGE_DEFINITION { + LSA_UNICODE_STRING Name; + LUID LocalValue; +} +alias POLICY_PRIVILEGE_DEFINITION* PPOLICY_PRIVILEGE_DEFINITION; + +struct POLICY_AUDIT_LOG_INFO { + ULONG AuditLogPercentFull; + ULONG MaximumLogSize; + LARGE_INTEGER AuditRetentionPeriod; + BOOLEAN AuditLogFullShutdownInProgress; + LARGE_INTEGER TimeToShutdown; + ULONG NextAuditRecordId; +} +alias POLICY_AUDIT_LOG_INFO* PPOLICY_AUDIT_LOG_INFO; + +struct POLICY_AUDIT_EVENTS_INFO { + BOOLEAN AuditingMode; + PPOLICY_AUDIT_EVENT_OPTIONS EventAuditingOptions; + ULONG MaximumAuditEventCount; +} +alias POLICY_AUDIT_EVENTS_INFO* PPOLICY_AUDIT_EVENTS_INFO; + +struct POLICY_ACCOUNT_DOMAIN_INFO { + LSA_UNICODE_STRING DomainName; + PSID DomainSid; +} +alias POLICY_ACCOUNT_DOMAIN_INFO* PPOLICY_ACCOUNT_DOMAIN_INFO; + +struct POLICY_PRIMARY_DOMAIN_INFO { + LSA_UNICODE_STRING Name; + PSID Sid; +} +alias POLICY_PRIMARY_DOMAIN_INFO* PPOLICY_PRIMARY_DOMAIN_INFO; + +struct POLICY_DNS_DOMAIN_INFO { + LSA_UNICODE_STRING Name; + LSA_UNICODE_STRING DnsDomainName; + LSA_UNICODE_STRING DnsTreeName; + GUID DomainGuid; + PSID Sid; +} +alias POLICY_DNS_DOMAIN_INFO* PPOLICY_DNS_DOMAIN_INFO; + +struct POLICY_PD_ACCOUNT_INFO { + LSA_UNICODE_STRING Name; +} +alias POLICY_PD_ACCOUNT_INFO* PPOLICY_PD_ACCOUNT_INFO; + +struct POLICY_LSA_SERVER_ROLE_INFO { + POLICY_LSA_SERVER_ROLE LsaServerRole; +} +alias POLICY_LSA_SERVER_ROLE_INFO* PPOLICY_LSA_SERVER_ROLE_INFO; + +struct POLICY_REPLICA_SOURCE_INFO { + LSA_UNICODE_STRING ReplicaSource; + LSA_UNICODE_STRING ReplicaAccountName; +} +alias POLICY_REPLICA_SOURCE_INFO* PPOLICY_REPLICA_SOURCE_INFO; + +struct POLICY_DEFAULT_QUOTA_INFO { + QUOTA_LIMITS QuotaLimits; +} +alias POLICY_DEFAULT_QUOTA_INFO* PPOLICY_DEFAULT_QUOTA_INFO; + +struct POLICY_MODIFICATION_INFO { + LARGE_INTEGER ModifiedId; + LARGE_INTEGER DatabaseCreationTime; +} +alias POLICY_MODIFICATION_INFO* PPOLICY_MODIFICATION_INFO; + +struct POLICY_AUDIT_FULL_SET_INFO { + BOOLEAN ShutDownOnFull; +} +alias POLICY_AUDIT_FULL_SET_INFO* PPOLICY_AUDIT_FULL_SET_INFO; + +struct POLICY_AUDIT_FULL_QUERY_INFO { + BOOLEAN ShutDownOnFull; + BOOLEAN LogIsFull; +} +alias POLICY_AUDIT_FULL_QUERY_INFO* PPOLICY_AUDIT_FULL_QUERY_INFO; + +struct POLICY_EFS_INFO { + ULONG InfoLength; + PUCHAR EfsBlob; +} +alias POLICY_EFS_INFO* PPOLICY_EFS_INFO; + +struct POLICY_LOCAL_IPSEC_REFERENCE_INFO { + LSA_UNICODE_STRING ObjectPath; +} +alias POLICY_LOCAL_IPSEC_REFERENCE_INFO* PPOLICY_LOCAL_IPSEC_REFERENCE_INFO; + +struct POLICY_LOCAL_MACHINE_PASSWORD_INFO { + LARGE_INTEGER PasswordChangeInterval; +} +alias POLICY_LOCAL_MACHINE_PASSWORD_INFO* PPOLICY_LOCAL_MACHINE_PASSWORD_INFO; + +struct POLICY_LOCAL_POLICY_LOCATION_INFO { + ULONG PolicyLocation; +} +alias POLICY_LOCAL_POLICY_LOCATION_INFO* PPOLICY_LOCAL_POLICY_LOCATION_INFO; + +struct POLICY_LOCAL_QUALITY_OF_SERVICE_INFO{ + ULONG QualityOfService; +} +alias POLICY_LOCAL_QUALITY_OF_SERVICE_INFO + POLICY_DOMAIN_QUALITY_OF_SERVICE_INFO; +alias POLICY_LOCAL_QUALITY_OF_SERVICE_INFO* + PPOLICY_LOCAL_QUALITY_OF_SERVICE_INFO, + PPOLICY_DOMAIN_QUALITY_OF_SERVICE_INFO; + +struct POLICY_DOMAIN_PUBLIC_KEY_INFO { + ULONG InfoLength; + PUCHAR PublicKeyInfo; +} +alias POLICY_DOMAIN_PUBLIC_KEY_INFO* PPOLICY_DOMAIN_PUBLIC_KEY_INFO; + +struct POLICY_DOMAIN_LOCKOUT_INFO { + LARGE_INTEGER LockoutDuration; + LARGE_INTEGER LockoutObservationWindow; + USHORT LockoutThreshold; +} +alias POLICY_DOMAIN_LOCKOUT_INFO* PPOLICY_DOMAIN_LOCKOUT_INFO; + +struct POLICY_DOMAIN_PASSWORD_INFO { + USHORT MinPasswordLength; + USHORT PasswordHistoryLength; + ULONG PasswordProperties; + LARGE_INTEGER MaxPasswordAge; + LARGE_INTEGER MinPasswordAge; +} +alias POLICY_DOMAIN_PASSWORD_INFO* PPOLICY_DOMAIN_PASSWORD_INFO; + +struct POLICY_DOMAIN_KERBEROS_TICKET_INFO { + ULONG AuthenticationOptions; + LARGE_INTEGER MinTicketAge; + LARGE_INTEGER MaxTicketAge; + LARGE_INTEGER MaxRenewAge; + LARGE_INTEGER ProxyLifetime; + LARGE_INTEGER ForceLogoff; +} +alias POLICY_DOMAIN_KERBEROS_TICKET_INFO* PPOLICY_DOMAIN_KERBEROS_TICKET_INFO; + +alias LSA_HANDLE = HANDLE; +alias LSA_HANDLE* PLSA_HANDLE; + +struct TRUSTED_DOMAIN_NAME_INFO { + LSA_UNICODE_STRING Name; +} +alias TRUSTED_DOMAIN_NAME_INFO* PTRUSTED_DOMAIN_NAME_INFO; + +struct TRUSTED_CONTROLLERS_INFO { + ULONG Entries; + PLSA_UNICODE_STRING Names; +} +alias TRUSTED_CONTROLLERS_INFO* PTRUSTED_CONTROLLERS_INFO; + +struct TRUSTED_POSIX_OFFSET_INFO { + ULONG Offset; +} +alias TRUSTED_POSIX_OFFSET_INFO* PTRUSTED_POSIX_OFFSET_INFO; + +struct TRUSTED_PASSWORD_INFO { + LSA_UNICODE_STRING Password; + LSA_UNICODE_STRING OldPassword; +} +alias TRUSTED_PASSWORD_INFO* PTRUSTED_PASSWORD_INFO; + +struct TRUSTED_DOMAIN_INFORMATION_EX { + LSA_UNICODE_STRING Name; + LSA_UNICODE_STRING FlatName; + PSID Sid; + ULONG TrustDirection; + ULONG TrustType; + ULONG TrustAttributes; +} +alias TRUSTED_DOMAIN_INFORMATION_EX* PTRUSTED_DOMAIN_INFORMATION_EX; + +struct LSA_AUTH_INFORMATION { + LARGE_INTEGER LastUpdateTime; + ULONG AuthType; + ULONG AuthInfoLength; + PUCHAR AuthInfo; +} +alias LSA_AUTH_INFORMATION* PLSA_AUTH_INFORMATION; + +struct TRUSTED_DOMAIN_AUTH_INFORMATION { + ULONG IncomingAuthInfos; + PLSA_AUTH_INFORMATION IncomingAuthenticationInformation; + PLSA_AUTH_INFORMATION IncomingPreviousAuthenticationInformation; + ULONG OutgoingAuthInfos; + PLSA_AUTH_INFORMATION OutgoingAuthenticationInformation; + PLSA_AUTH_INFORMATION OutgoingPreviousAuthenticationInformation; +} +alias TRUSTED_DOMAIN_AUTH_INFORMATION* PTRUSTED_DOMAIN_AUTH_INFORMATION; + +struct TRUSTED_DOMAIN_FULL_INFORMATION { + TRUSTED_DOMAIN_INFORMATION_EX Information; + TRUSTED_POSIX_OFFSET_INFO PosixOffset; + TRUSTED_DOMAIN_AUTH_INFORMATION AuthInformation; +} +alias TRUSTED_DOMAIN_FULL_INFORMATION* PTRUSTED_DOMAIN_FULL_INFORMATION; + +extern (Windows) nothrow @nogc { + NTSTATUS LsaAddAccountRights(LSA_HANDLE, PSID, PLSA_UNICODE_STRING, + ULONG); + NTSTATUS LsaCallAuthenticationPackage(HANDLE, ULONG, PVOID, ULONG, + PVOID*, PULONG, PNTSTATUS); + NTSTATUS LsaClose(LSA_HANDLE); + NTSTATUS LsaConnectUntrusted(PHANDLE); + NTSTATUS LsaCreateTrustedDomainEx(LSA_HANDLE, + PTRUSTED_DOMAIN_INFORMATION_EX, PTRUSTED_DOMAIN_AUTH_INFORMATION, + ACCESS_MASK, PLSA_HANDLE); + NTSTATUS LsaDeleteTrustedDomain(LSA_HANDLE, PSID); + NTSTATUS LsaDeregisterLogonProcess(HANDLE); + NTSTATUS LsaEnumerateAccountRights(LSA_HANDLE, PSID, PLSA_UNICODE_STRING*, + PULONG); + NTSTATUS LsaEnumerateAccountsWithUserRight(LSA_HANDLE, + PLSA_UNICODE_STRING, PVOID*, PULONG); + NTSTATUS LsaEnumerateTrustedDomains(LSA_HANDLE, PLSA_ENUMERATION_HANDLE, + PVOID*, ULONG, PULONG); + NTSTATUS LsaEnumerateTrustedDomainsEx(LSA_HANDLE, PLSA_ENUMERATION_HANDLE, + TRUSTED_INFORMATION_CLASS, PVOID*, ULONG, PULONG); + NTSTATUS LsaFreeMemory(PVOID); + NTSTATUS LsaFreeReturnBuffer(PVOID); + NTSTATUS LsaLogonUser(HANDLE, PLSA_STRING, SECURITY_LOGON_TYPE, ULONG, + PVOID, ULONG, PTOKEN_GROUPS, PTOKEN_SOURCE, PVOID*, PULONG, PLUID, + PHANDLE, PQUOTA_LIMITS, PNTSTATUS); + NTSTATUS LsaLookupAuthenticationPackage(HANDLE, PLSA_STRING, PULONG); + NTSTATUS LsaLookupNames(LSA_HANDLE, ULONG, PLSA_UNICODE_STRING, + PLSA_REFERENCED_DOMAIN_LIST*, PLSA_TRANSLATED_SID*); + NTSTATUS LsaLookupSids(LSA_HANDLE, ULONG, PSID*, + PLSA_REFERENCED_DOMAIN_LIST*, PLSA_TRANSLATED_NAME*); + ULONG LsaNtStatusToWinError(NTSTATUS); + NTSTATUS LsaOpenPolicy(PLSA_UNICODE_STRING, PLSA_OBJECT_ATTRIBUTES, + ACCESS_MASK, PLSA_HANDLE); + NTSTATUS LsaQueryDomainInformationPolicy(LSA_HANDLE, + POLICY_DOMAIN_INFORMATION_CLASS, PVOID*); + NTSTATUS LsaQueryInformationPolicy(LSA_HANDLE, POLICY_INFORMATION_CLASS, + PVOID*); + NTSTATUS LsaQueryLocalInformationPolicy(LSA_HANDLE, + POLICY_LOCAL_INFORMATION_CLASS, PVOID*); + NTSTATUS LsaQueryTrustedDomainInfo(LSA_HANDLE, PSID, + TRUSTED_INFORMATION_CLASS, PVOID*); + NTSTATUS LsaQueryTrustedDomainInfoByName(LSA_HANDLE, PLSA_UNICODE_STRING, + TRUSTED_INFORMATION_CLASS, PVOID*); + NTSTATUS LsaRegisterLogonProcess(PLSA_STRING, PHANDLE, + PLSA_OPERATIONAL_MODE); + NTSTATUS LsaRemoveAccountRights(LSA_HANDLE, PSID, BOOLEAN, + PLSA_UNICODE_STRING, ULONG); + NTSTATUS LsaRetrievePrivateData(LSA_HANDLE, PLSA_UNICODE_STRING, + PLSA_UNICODE_STRING*); + NTSTATUS LsaSetDomainInformationPolicy(LSA_HANDLE, + POLICY_DOMAIN_INFORMATION_CLASS, PVOID); + NTSTATUS LsaSetInformationPolicy(LSA_HANDLE, POLICY_INFORMATION_CLASS, + PVOID); + NTSTATUS LsaSetLocalInformationPolicy(LSA_HANDLE, + POLICY_LOCAL_INFORMATION_CLASS, PVOID); + NTSTATUS LsaSetTrustedDomainInformation(LSA_HANDLE, PSID, + TRUSTED_INFORMATION_CLASS, PVOID); + NTSTATUS LsaSetTrustedDomainInfoByName(LSA_HANDLE, PLSA_UNICODE_STRING, + TRUSTED_INFORMATION_CLASS, PVOID); + NTSTATUS LsaStorePrivateData(LSA_HANDLE, PLSA_UNICODE_STRING, + PLSA_UNICODE_STRING); +} + +alias NTSTATUS function(PUNICODE_STRING, ULONG, PUNICODE_STRING) + PSAM_PASSWORD_NOTIFICATION_ROUTINE; +alias BOOLEAN function() PSAM_INIT_NOTIFICATION_ROUTINE; +alias BOOLEAN function(PUNICODE_STRING, PUNICODE_STRING, + PUNICODE_STRING, BOOLEAN) PSAM_PASSWORD_FILTER_ROUTINE; diff --git a/src/urt/internal/sys/windows/ntsecpkg.d b/src/urt/internal/sys/windows/ntsecpkg.d new file mode 100644 index 0000000..6a2dc69 --- /dev/null +++ b/src/urt/internal/sys/windows/ntsecpkg.d @@ -0,0 +1,445 @@ +/** + * Windows API header module + * + * Translated from MinGW Windows headers + * + * Authors: Ellery Newcomer + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_ntsecpkg.d) + */ +module urt.internal.sys.windows.ntsecpkg; +version (Windows): + +import urt.internal.sys.windows.windef, urt.internal.sys.windows.ntsecapi, urt.internal.sys.windows.security, urt.internal.sys.windows.ntdef, urt.internal.sys.windows.sspi; +import urt.internal.sys.windows.basetyps : GUID; +import urt.internal.sys.windows.winbase; + +extern(Windows): + +enum :ULONG{ + ISC_REQ_DELEGATE = 1, + ISC_REQ_MUTUAL_AUTH = 2, + ISC_REQ_REPLAY_DETECT = 4, + ISC_REQ_SEQUENCE_DETECT = 8, + ISC_REQ_CONFIDENTIALITY = 16, + ISC_REQ_USE_SESSION_KEY = 32, + ISC_REQ_PROMPT_FOR_CREDS = 64, + ISC_REQ_USE_SUPPLIED_CREDS = 128, + ISC_REQ_ALLOCATE_MEMORY = 256, + ISC_REQ_USE_DCE_STYLE = 512, + ISC_REQ_DATAGRAM = 1024, + ISC_REQ_CONNECTION = 2048, + ISC_REQ_EXTENDED_ERROR = 16384, + ISC_REQ_STREAM = 32768, + ISC_REQ_INTEGRITY = 65536, + ISC_REQ_MANUAL_CRED_VALIDATION = 524288, + ISC_REQ_HTTP = 268435456, +} + +enum ISC_RET_EXTENDED_ERROR = 16384; + +enum :ULONG{ + ASC_REQ_DELEGATE = 1, + ASC_REQ_MUTUAL_AUTH = 2, + ASC_REQ_REPLAY_DETECT = 4, + ASC_REQ_SEQUENCE_DETECT = 8, + ASC_REQ_CONFIDENTIALITY = 16, + ASC_REQ_USE_SESSION_KEY = 32, + ASC_REQ_ALLOCATE_MEMORY = 256, + ASC_REQ_USE_DCE_STYLE = 512, + ASC_REQ_DATAGRAM = 1024, + ASC_REQ_CONNECTION = 2048, + ASC_REQ_EXTENDED_ERROR = 32768, + ASC_REQ_STREAM = 65536, + ASC_REQ_INTEGRITY = 131072, +} + +enum SECURITY_NATIVE_DREP = 16; +enum SECURITY_NETWORK_DREP = 0; + +enum :ULONG{ + SECPKG_STATE_ENCRYPTION_PERMITTED = 0x01, + SECPKG_STATE_STRONG_ENCRYPTION_PERMITTED = 0x02, + SECPKG_STATE_DOMAIN_CONTROLLER = 0x04, + SECPKG_STATE_WORKSTATION = 0x08, + SECPKG_STATE_STANDALONE = 0x10, +} + +/* enum definitions for Secure Service Provider/Authentication Packages */ +enum LSA_TOKEN_INFORMATION_TYPE { + LsaTokenInformationNull, + LsaTokenInformationV1 +} +alias LSA_TOKEN_INFORMATION_TYPE* PLSA_TOKEN_INFORMATION_TYPE; +enum SECPKG_EXTENDED_INFORMATION_CLASS +{ + SecpkgGssInfo = 1, + SecpkgContextThunks, + SecpkgMutualAuthLevel, + SecpkgMaxInfo +} +enum SECPKG_NAME_TYPE { + SecNameSamCompatible, + SecNameAlternateId, + SecNameFlat, + SecNameDN +} + +/* struct definitions for SSP/AP */ +struct SECPKG_PRIMARY_CRED { + LUID LogonId; + UNICODE_STRING DownlevelName; + UNICODE_STRING DomainName; + UNICODE_STRING Password; + UNICODE_STRING OldPassword; + PSID UserSid; + ULONG Flags; + UNICODE_STRING DnsDomainName; + UNICODE_STRING Upn; + UNICODE_STRING LogonServer; + UNICODE_STRING Spare1; + UNICODE_STRING Spare2; + UNICODE_STRING Spare3; + UNICODE_STRING Spare4; +} +alias SECPKG_PRIMARY_CRED* PSECPKG_PRIMARY_CRED; +struct SECPKG_SUPPLEMENTAL_CRED { + UNICODE_STRING PackageName; + ULONG CredentialSize; + PUCHAR Credentials; +} +alias SECPKG_SUPPLEMENTAL_CRED* PSECPKG_SUPPLEMENTAL_CRED; +struct SECPKG_SUPPLEMENTAL_CRED_ARRAY { + ULONG CredentialCount; + SECPKG_SUPPLEMENTAL_CRED[1] Credentials; +} +alias SECPKG_SUPPLEMENTAL_CRED_ARRAY* PSECPKG_SUPPLEMENTAL_CRED_ARRAY; +struct SECPKG_PARAMETERS { + ULONG Version; + ULONG MachineState; + ULONG SetupMode; + PSID DomainSid; + UNICODE_STRING DomainName; + UNICODE_STRING DnsDomainName; + GUID DomainGuid; +} +alias SECPKG_PARAMETERS* PSECPKG_PARAMETERS,PSECPKG_EVENT_DOMAIN_CHANGE; +alias SECPKG_PARAMETERS SECPKG_EVENT_DOMAIN_CHANGE; +struct SECPKG_CLIENT_INFO { + LUID LogonId; + ULONG ProcessID; + ULONG ThreadID; + BOOLEAN HasTcbPrivilege; + BOOLEAN Impersonating; + BOOLEAN Restricted; +} +alias SECPKG_CLIENT_INFO* PSECPKG_CLIENT_INFO; +struct SECURITY_USER_DATA { + SECURITY_STRING UserName; + SECURITY_STRING LogonDomainName; + SECURITY_STRING LogonServer; + PSID pSid; +} +alias SECURITY_USER_DATA* PSECURITY_USER_DATA,PSecurityUserData; +alias SECURITY_USER_DATA SecurityUserData; +struct SECPKG_GSS_INFO { + ULONG EncodedIdLength; + UCHAR[4] EncodedId; +} +alias SECPKG_GSS_INFO* PSECPKG_GSS_INFO; +struct SECPKG_CONTEXT_THUNKS { + ULONG InfoLevelCount; + ULONG[1] Levels; +} +alias SECPKG_CONTEXT_THUNKS* PSECPKG_CONTEXT_THUNKS; +struct SECPKG_MUTUAL_AUTH_LEVEL { + ULONG MutualAuthLevel; +} +alias SECPKG_MUTUAL_AUTH_LEVEL* PSECPKG_MUTUAL_AUTH_LEVEL; +struct SECPKG_CALL_INFO { + ULONG ProcessId; + ULONG ThreadId; + ULONG Attributes; + ULONG CallCount; +} +alias SECPKG_CALL_INFO* PSECPKG_CALL_INFO; +struct SECPKG_EXTENDED_INFORMATION { + SECPKG_EXTENDED_INFORMATION_CLASS Class; + union _Info{ + SECPKG_GSS_INFO GssInfo; + SECPKG_CONTEXT_THUNKS ContextThunks; + SECPKG_MUTUAL_AUTH_LEVEL MutualAuthLevel; + } + _Info Info; +} +alias SECPKG_EXTENDED_INFORMATION* PSECPKG_EXTENDED_INFORMATION; + +/* callbacks implemented by SSP/AP dlls and called by the LSA */ +alias void function(ULONG_PTR, ULONG_PTR, PSecBuffer, + PSecBuffer) PLSA_CALLBACK_FUNCTION; + +/* misc typedefs used in the below prototypes */ +alias PVOID* PLSA_CLIENT_REQUEST; +alias ULONG_PTR LSA_SEC_HANDLE; +alias LSA_SEC_HANDLE* PLSA_SEC_HANDLE; +alias LPTHREAD_START_ROUTINE SEC_THREAD_START; +alias PSECURITY_ATTRIBUTES SEC_ATTRS; + +/* functions used by SSP/AP obtainable by dispatch tables */ +alias NTSTATUS function(ULONG, PLSA_CALLBACK_FUNCTION) PLSA_REGISTER_CALLBACK; +alias NTSTATUS function(PLUID) PLSA_CREATE_LOGON_SESSION; +alias NTSTATUS function(PLUID) PLSA_DELETE_LOGON_SESSION; +alias NTSTATUS function(PLUID, ULONG, PLSA_STRING, + PLSA_STRING) PLSA_ADD_CREDENTIAL; +alias NTSTATUS function(PLUID, ULONG, PULONG, BOOLEAN, + PLSA_STRING, PULONG, PLSA_STRING) PLSA_GET_CREDENTIALS; +alias NTSTATUS function(PLUID, ULONG, PLSA_STRING) PLSA_DELETE_CREDENTIAL; +alias PVOID function(ULONG) PLSA_ALLOCATE_LSA_HEAP; +alias void function(PVOID) PLSA_FREE_LSA_HEAP; +alias NTSTATUS function(PLSA_CLIENT_REQUEST, + ULONG, PVOID*) PLSA_ALLOCATE_CLIENT_BUFFER; +alias NTSTATUS function(PLSA_CLIENT_REQUEST, PVOID) PLSA_FREE_CLIENT_BUFFER; +alias NTSTATUS function(PLSA_CLIENT_REQUEST, ULONG, + PVOID, PVOID) PLSA_COPY_TO_CLIENT_BUFFER; +alias NTSTATUS function(PLSA_CLIENT_REQUEST, + ULONG, PVOID, PVOID) PLSA_COPY_FROM_CLIENT_BUFFER; +alias NTSTATUS function() PLSA_IMPERSONATE_CLIENT; +alias NTSTATUS function() PLSA_UNLOAD_PACKAGE; +alias NTSTATUS function(HANDLE, PHANDLE) PLSA_DUPLICATE_HANDLE; +alias NTSTATUS function(PLUID, ULONG, + PVOID, BOOLEAN) PLSA_SAVE_SUPPLEMENTAL_CREDENTIALS; +alias HANDLE function(SEC_ATTRS, ULONG, SEC_THREAD_START, + PVOID, ULONG, PULONG) PLSA_CREATE_THREAD; +alias NTSTATUS function(PSECPKG_CLIENT_INFO) PLSA_GET_CLIENT_INFO; +alias HANDLE function(SEC_THREAD_START, PVOID, + ULONG, ULONG, ULONG, ULONG, HANDLE) PLSA_REGISTER_NOTIFICATION; +alias NTSTATUS function(HANDLE) PLSA_CANCEL_NOTIFICATION; +alias NTSTATUS function(PSecBuffer, PSecBuffer) PLSA_MAP_BUFFER; +alias NTSTATUS function(PLUID, PTOKEN_SOURCE, + SECURITY_LOGON_TYPE, SECURITY_IMPERSONATION_LEVEL, LSA_TOKEN_INFORMATION_TYPE, + PVOID, PTOKEN_GROUPS, PUNICODE_STRING, PUNICODE_STRING, PUNICODE_STRING, + PUNICODE_STRING, PHANDLE, PNTSTATUS) PLSA_CREATE_TOKEN; +alias void function(NTSTATUS, NTSTATUS, PUNICODE_STRING, + PUNICODE_STRING, PUNICODE_STRING, PSID, SECURITY_LOGON_TYPE, + PTOKEN_SOURCE, PLUID) PLSA_AUDIT_LOGON; +alias NTSTATUS function(PUNICODE_STRING, PVOID, ULONG, + PVOID*, PULONG, PNTSTATUS) PLSA_CALL_PACKAGE; +alias BOOLEAN function(PSECPKG_CALL_INFO) PLSA_GET_CALL_INFO; +alias NTSTATUS function(PUNICODE_STRING, PVOID, PVOID, + ULONG, PVOID*, PULONG, PNTSTATUS) PLSA_CALL_PACKAGEEX; +alias PVOID function(ULONG, ULONG) PLSA_CREATE_SHARED_MEMORY; +alias PVOID function(PVOID, ULONG) PLSA_ALLOCATE_SHARED_MEMORY; +alias void function(PVOID, PVOID) PLSA_FREE_SHARED_MEMORY; +alias BOOLEAN function(PVOID) PLSA_DELETE_SHARED_MEMORY; +alias NTSTATUS function(PSECURITY_STRING, SECPKG_NAME_TYPE, + PSECURITY_STRING, BOOLEAN, ULONG, PVOID*) PLSA_OPEN_SAM_USER; +alias NTSTATUS function(PVOID, PVOID *, PULONG, + PVOID *, PULONG) PLSA_GET_USER_CREDENTIALS; +alias NTSTATUS function(PVOID, PUCHAR *, PULONG) PLSA_GET_USER_AUTH_DATA; +alias NTSTATUS function(PVOID) PLSA_CLOSE_SAM_USER; +alias NTSTATUS function(PVOID, ULONG, + SECURITY_IMPERSONATION_LEVEL, PTOKEN_SOURCE, SECURITY_LOGON_TYPE, + PUNICODE_STRING, PHANDLE, PLUID, PUNICODE_STRING, PNTSTATUS) PLSA_CONVERT_AUTH_DATA_TO_TOKEN; +alias NTSTATUS function(PCHAR, ULONG_PTR, ULONG_PTR, + PSecBuffer, PSecBuffer) PLSA_CLIENT_CALLBACK; +alias NTSTATUS function(PSECPKG_PRIMARY_CRED, PSECPKG_SUPPLEMENTAL_CRED_ARRAY) PLSA_UPDATE_PRIMARY_CREDENTIALS; +alias NTSTATUS function(PSECURITY_STRING, + SECPKG_NAME_TYPE, PSECURITY_STRING, PUCHAR *, PULONG, PUNICODE_STRING) PLSA_GET_AUTH_DATA_FOR_USER; +alias NTSTATUS function(ULONG, BOOLEAN, + PUNICODE_STRING, PUNICODE_STRING, ULONG, PUNICODE_STRING, PUNICODE_STRING, + PULONG) PLSA_CRACK_SINGLE_NAME; +alias NTSTATUS function(ULONG, BOOLEAN, + PUNICODE_STRING, PUNICODE_STRING, PUNICODE_STRING, NTSTATUS) PLSA_AUDIT_ACCOUNT_LOGON; +alias NTSTATUS function(PUNICODE_STRING, PVOID, + PVOID, ULONG, PVOID*, PULONG, PNTSTATUS) PLSA_CALL_PACKAGE_PASSTHROUGH; + +/* Dispatch tables of functions used by SSP/AP */ +struct SECPKG_DLL_FUNCTIONS { + PLSA_ALLOCATE_LSA_HEAP AllocateHeap; + PLSA_FREE_LSA_HEAP FreeHeap; + PLSA_REGISTER_CALLBACK RegisterCallback; +} +alias SECPKG_DLL_FUNCTIONS* PSECPKG_DLL_FUNCTIONS; +struct LSA_DISPATCH_TABLE { + PLSA_CREATE_LOGON_SESSION CreateLogonSession; + PLSA_DELETE_LOGON_SESSION DeleteLogonSession; + PLSA_ADD_CREDENTIAL AddCredential; + PLSA_GET_CREDENTIALS GetCredentials; + PLSA_DELETE_CREDENTIAL DeleteCredential; + PLSA_ALLOCATE_LSA_HEAP AllocateLsaHeap; + PLSA_FREE_LSA_HEAP FreeLsaHeap; + PLSA_ALLOCATE_CLIENT_BUFFER AllocateClientBuffer; + PLSA_FREE_CLIENT_BUFFER FreeClientBuffer; + PLSA_COPY_TO_CLIENT_BUFFER CopyToClientBuffer; + PLSA_COPY_FROM_CLIENT_BUFFER CopyFromClientBuffer; +} +alias LSA_DISPATCH_TABLE* PLSA_DISPATCH_TABLE; +struct LSA_SECPKG_FUNCTION_TABLE { + PLSA_CREATE_LOGON_SESSION CreateLogonSession; + PLSA_DELETE_LOGON_SESSION DeleteLogonSession; + PLSA_ADD_CREDENTIAL AddCredential; + PLSA_GET_CREDENTIALS GetCredentials; + PLSA_DELETE_CREDENTIAL DeleteCredential; + PLSA_ALLOCATE_LSA_HEAP AllocateLsaHeap; + PLSA_FREE_LSA_HEAP FreeLsaHeap; + PLSA_ALLOCATE_CLIENT_BUFFER AllocateClientBuffer; + PLSA_FREE_CLIENT_BUFFER FreeClientBuffer; + PLSA_COPY_TO_CLIENT_BUFFER CopyToClientBuffer; + PLSA_COPY_FROM_CLIENT_BUFFER CopyFromClientBuffer; + PLSA_IMPERSONATE_CLIENT ImpersonateClient; + PLSA_UNLOAD_PACKAGE UnloadPackage; + PLSA_DUPLICATE_HANDLE DuplicateHandle; + PLSA_SAVE_SUPPLEMENTAL_CREDENTIALS SaveSupplementalCredentials; + PLSA_CREATE_THREAD CreateThread; + PLSA_GET_CLIENT_INFO GetClientInfo; + PLSA_REGISTER_NOTIFICATION RegisterNotification; + PLSA_CANCEL_NOTIFICATION CancelNotification; + PLSA_MAP_BUFFER MapBuffer; + PLSA_CREATE_TOKEN CreateToken; + PLSA_AUDIT_LOGON AuditLogon; + PLSA_CALL_PACKAGE CallPackage; + PLSA_FREE_LSA_HEAP FreeReturnBuffer; + PLSA_GET_CALL_INFO GetCallInfo; + PLSA_CALL_PACKAGEEX CallPackageEx; + PLSA_CREATE_SHARED_MEMORY CreateSharedMemory; + PLSA_ALLOCATE_SHARED_MEMORY AllocateSharedMemory; + PLSA_FREE_SHARED_MEMORY FreeSharedMemory; + PLSA_DELETE_SHARED_MEMORY DeleteSharedMemory; + PLSA_OPEN_SAM_USER OpenSamUser; + PLSA_GET_USER_CREDENTIALS GetUserCredentials; + PLSA_GET_USER_AUTH_DATA GetUserAuthData; + PLSA_CLOSE_SAM_USER CloseSamUser; + PLSA_CONVERT_AUTH_DATA_TO_TOKEN ConvertAuthDataToToken; + PLSA_CLIENT_CALLBACK ClientCallback; + PLSA_UPDATE_PRIMARY_CREDENTIALS UpdateCredentials; + PLSA_GET_AUTH_DATA_FOR_USER GetAuthDataForUser; + PLSA_CRACK_SINGLE_NAME CrackSingleName; + PLSA_AUDIT_ACCOUNT_LOGON AuditAccountLogon; + PLSA_CALL_PACKAGE_PASSTHROUGH CallPackagePassthrough; +} +alias LSA_SECPKG_FUNCTION_TABLE* PLSA_SECPKG_FUNCTION_TABLE; + +/* functions implemented by SSP/AP obtainable by dispatch tables */ +alias NTSTATUS function(ULONG, PLSA_DISPATCH_TABLE, + PLSA_STRING, PLSA_STRING, PLSA_STRING *) PLSA_AP_INITIALIZE_PACKAGE; +alias NTSTATUS function(LPWSTR, LPWSTR, LPWSTR, LPWSTR, + DWORD, DWORD, PHANDLE) PLSA_AP_LOGON_USER; +alias NTSTATUS function(PUNICODE_STRING, PVOID, ULONG, + PVOID *, PULONG, PNTSTATUS) PLSA_AP_CALL_PACKAGE; +alias void function(PLUID) PLSA_AP_LOGON_TERMINATED; +alias NTSTATUS function(PLSA_CLIENT_REQUEST, + PVOID, PVOID, ULONG, PVOID *, PULONG, PNTSTATUS) PLSA_AP_CALL_PACKAGE_UNTRUSTED; +alias NTSTATUS function(PUNICODE_STRING, + PVOID, PVOID, ULONG, PVOID *, PULONG, PNTSTATUS) PLSA_AP_CALL_PACKAGE_PASSTHROUGH; +alias NTSTATUS function(PLSA_CLIENT_REQUEST, + SECURITY_LOGON_TYPE, PVOID, PVOID, ULONG, PVOID *, PULONG, PLUID, PNTSTATUS, + PLSA_TOKEN_INFORMATION_TYPE, PVOID *, PUNICODE_STRING *, PUNICODE_STRING *, + PUNICODE_STRING *) PLSA_AP_LOGON_USER_EX; +alias NTSTATUS function(PLSA_CLIENT_REQUEST, + SECURITY_LOGON_TYPE, PVOID, PVOID, ULONG, PVOID *, PULONG, PLUID, PNTSTATUS, + PLSA_TOKEN_INFORMATION_TYPE, PVOID *, PUNICODE_STRING *, PUNICODE_STRING *, + PUNICODE_STRING *, PSECPKG_PRIMARY_CRED, PSECPKG_SUPPLEMENTAL_CRED_ARRAY *) PLSA_AP_LOGON_USER_EX2; +alias NTSTATUS function(ULONG_PTR, PSECPKG_PARAMETERS, + PLSA_SECPKG_FUNCTION_TABLE) SpInitializeFn; +alias NTSTATUS function() SpShutDownFn; +alias NTSTATUS function(PSecPkgInfoW) SpGetInfoFn; +alias NTSTATUS function(SECURITY_LOGON_TYPE, + PUNICODE_STRING, PSECPKG_PRIMARY_CRED, PSECPKG_SUPPLEMENTAL_CRED) SpAcceptCredentialsFn; +alias NTSTATUS function(PUNICODE_STRING, ULONG, + PLUID, PVOID, PVOID, PVOID, PLSA_SEC_HANDLE, PTimeStamp) SpAcquireCredentialsHandleFn; +alias NTSTATUS function(LSA_SEC_HANDLE, ULONG, PVOID) SpQueryCredentialsAttributesFn; +alias NTSTATUS function(LSA_SEC_HANDLE) SpFreeCredentialsHandleFn; +alias NTSTATUS function(LSA_SEC_HANDLE, PSecBuffer) SpSaveCredentialsFn; +alias NTSTATUS function(LSA_SEC_HANDLE, PSecBuffer) SpGetCredentialsFn; +alias NTSTATUS function(LSA_SEC_HANDLE, PSecBuffer) SpDeleteCredentialsFn; +alias NTSTATUS function(LSA_SEC_HANDLE, LSA_SEC_HANDLE, + PUNICODE_STRING, ULONG, ULONG, PSecBufferDesc, PLSA_SEC_HANDLE, PSecBufferDesc, + PULONG, PTimeStamp, PBOOLEAN, PSecBuffer) SpInitLsaModeContextFn; +alias NTSTATUS function(LSA_SEC_HANDLE, + LSA_SEC_HANDLE, PSecBufferDesc, ULONG, ULONG, PLSA_SEC_HANDLE, PSecBufferDesc, + PULONG, PTimeStamp, PBOOLEAN, PSecBuffer) SpAcceptLsaModeContextFn; +alias NTSTATUS function(LSA_SEC_HANDLE) SpDeleteContextFn; +alias NTSTATUS function(LSA_SEC_HANDLE, PSecBufferDesc) SpApplyControlTokenFn; +alias NTSTATUS function(PLUID, ULONG, PSecurityUserData *) SpGetUserInfoFn; +alias NTSTATUS function(SECPKG_EXTENDED_INFORMATION_CLASS, PSECPKG_EXTENDED_INFORMATION *) SpGetExtendedInformationFn; +alias NTSTATUS function(LSA_SEC_HANDLE, ULONG, PVOID) SpQueryContextAttributesFn; +alias NTSTATUS function(LSA_SEC_HANDLE, PUNICODE_STRING, + PUNICODE_STRING, ULONG, PVOID, PVOID, PVOID, PTimeStamp) SpAddCredentialsFn; +alias NTSTATUS function( + SECPKG_EXTENDED_INFORMATION_CLASS, PSECPKG_EXTENDED_INFORMATION) SpSetExtendedInformationFn; +alias NTSTATUS function(ULONG, PSECPKG_DLL_FUNCTIONS, + PVOID *) SpInstanceInitFn; +alias NTSTATUS function(LSA_SEC_HANDLE, PSecBuffer) SpInitUserModeContextFn; +alias NTSTATUS function(LSA_SEC_HANDLE, ULONG, + PSecBufferDesc, ULONG) SpMakeSignatureFn; +alias NTSTATUS function(LSA_SEC_HANDLE, PSecBufferDesc, + ULONG, PULONG) SpVerifySignatureFn; +alias NTSTATUS function(LSA_SEC_HANDLE, ULONG, PSecBufferDesc, + ULONG) SpSealMessageFn; +alias NTSTATUS function(LSA_SEC_HANDLE, PSecBufferDesc, + ULONG, PULONG) SpUnsealMessageFn; +alias NTSTATUS function(LSA_SEC_HANDLE, PHANDLE) SpGetContextTokenFn; +alias NTSTATUS function(LSA_SEC_HANDLE, PSecBufferDesc) SpCompleteAuthTokenFn; +alias NTSTATUS function(PSecBuffer, PSecBuffer) SpFormatCredentialsFn; +alias NTSTATUS function(ULONG, PUCHAR, PULONG, + PVOID *) SpMarshallSupplementalCredsFn; +alias NTSTATUS function(LSA_SEC_HANDLE, ULONG, + PSecBuffer, PHANDLE) SpExportSecurityContextFn; +alias NTSTATUS function(PSecBuffer, HANDLE, + PLSA_SEC_HANDLE) SpImportSecurityContextFn; + +/* Dispatch tables of functions implemented by SSP/AP */ +struct SECPKG_FUNCTION_TABLE { + PLSA_AP_INITIALIZE_PACKAGE InitializePackage; + PLSA_AP_LOGON_USER LogonUser; + PLSA_AP_CALL_PACKAGE CallPackage; + PLSA_AP_LOGON_TERMINATED LogonTerminated; + PLSA_AP_CALL_PACKAGE_UNTRUSTED CallPackageUntrusted; + PLSA_AP_CALL_PACKAGE_PASSTHROUGH CallPackagePassthrough; + PLSA_AP_LOGON_USER_EX LogonUserEx; + PLSA_AP_LOGON_USER_EX2 LogonUserEx2; + SpInitializeFn *Initialize; + SpShutDownFn *Shutdown; + SpGetInfoFn *GetInfo; + SpAcceptCredentialsFn *AcceptCredentials; + SpAcquireCredentialsHandleFn *AcquireCredentialsHandle; + SpQueryCredentialsAttributesFn *QueryCredentialsAttributes; + SpFreeCredentialsHandleFn *FreeCredentialsHandle; + SpSaveCredentialsFn *SaveCredentials; + SpGetCredentialsFn *GetCredentials; + SpDeleteCredentialsFn *DeleteCredentials; + SpInitLsaModeContextFn *InitLsaModeContext; + SpAcceptLsaModeContextFn *AcceptLsaModeContext; + SpDeleteContextFn *DeleteContext; + SpApplyControlTokenFn *ApplyControlToken; + SpGetUserInfoFn *GetUserInfo; + SpGetExtendedInformationFn *GetExtendedInformation; + SpQueryContextAttributesFn *QueryContextAttributes; + SpAddCredentialsFn *AddCredentials; + SpSetExtendedInformationFn *SetExtendedInformation; +} +alias SECPKG_FUNCTION_TABLE* PSECPKG_FUNCTION_TABLE; + +struct SECPKG_USER_FUNCTION_TABLE { + SpInstanceInitFn *InstanceInit; + SpInitUserModeContextFn *InitUserModeContext; + SpMakeSignatureFn *MakeSignature; + SpVerifySignatureFn *VerifySignature; + SpSealMessageFn *SealMessage; + SpUnsealMessageFn *UnsealMessage; + SpGetContextTokenFn *GetContextToken; + SpQueryContextAttributesFn *QueryContextAttributes; + SpCompleteAuthTokenFn *CompleteAuthToken; + SpDeleteContextFn *DeleteUserModeContext; + SpFormatCredentialsFn *FormatCredentials; + SpMarshallSupplementalCredsFn *MarshallSupplementalCreds; + SpExportSecurityContextFn *ExportContext; + SpImportSecurityContextFn *ImportContext; +} +alias SECPKG_USER_FUNCTION_TABLE* PSECPKG_USER_FUNCTION_TABLE; + +/* Entry points to SSP/AP */ +alias NTSTATUS function(ULONG, PULONG, + PSECPKG_FUNCTION_TABLE *, PULONG) SpLsaModeInitializeFn; +alias NTSTATUS function(ULONG, PULONG, + PSECPKG_USER_FUNCTION_TABLE *, PULONG) SpUserModeInitializeFn; diff --git a/src/urt/internal/sys/windows/package.d b/src/urt/internal/sys/windows/package.d new file mode 100644 index 0000000..91631b0 --- /dev/null +++ b/src/urt/internal/sys/windows/package.d @@ -0,0 +1,13 @@ +/// Slim umbrella - re-exports only the vendored Windows modules. +module urt.internal.sys.windows; + +public import urt.internal.sys.windows.w32api; +public import urt.internal.sys.windows.basetsd; +public import urt.internal.sys.windows.basetyps; +public import urt.internal.sys.windows.windef; +public import urt.internal.sys.windows.winnt; +public import urt.internal.sys.windows.winbase; +public import urt.internal.sys.windows.wincon; +public import urt.internal.sys.windows.winerror; +public import urt.internal.sys.windows.winsock2; +public import urt.internal.sys.windows.winuser; diff --git a/src/urt/internal/sys/windows/schannel.d b/src/urt/internal/sys/windows/schannel.d new file mode 100644 index 0000000..10a83e9 --- /dev/null +++ b/src/urt/internal/sys/windows/schannel.d @@ -0,0 +1,106 @@ +/** + * Windows API header module + * + * Translated from MinGW Windows headers + * + * Authors: Stewart Gordon + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_schannel.d) + */ +module urt.internal.sys.windows.schannel; +version (Windows): + +import urt.internal.sys.windows.wincrypt; +import urt.internal.sys.windows.windef; + +enum DWORD SCHANNEL_CRED_VERSION = 4; +enum SCHANNEL_SHUTDOWN = 1; +/* Comment from MinGW + ? Do these belong here or in wincrypt.h + */ +enum : DWORD { + AUTHTYPE_CLIENT = 1, + AUTHTYPE_SERVER = 2 +} + +enum DWORD + SP_PROT_PCT1_SERVER = 0x01, + SP_PROT_PCT1_CLIENT = 0x02, + SP_PROT_SSL2_SERVER = 0x04, + SP_PROT_SSL2_CLIENT = 0x08, + SP_PROT_SSL3_SERVER = 0x10, + SP_PROT_SSL3_CLIENT = 0x20, + SP_PROT_TLS1_SERVER = 0x40, + SP_PROT_TLS1_CLIENT = 0x80, + SP_PROT_PCT1 = SP_PROT_PCT1_CLIENT | SP_PROT_PCT1_SERVER, + SP_PROT_TLS1 = SP_PROT_TLS1_CLIENT | SP_PROT_TLS1_SERVER, + SP_PROT_SSL2 = SP_PROT_SSL2_CLIENT | SP_PROT_SSL2_SERVER, + SP_PROT_SSL3 = SP_PROT_SSL3_CLIENT | SP_PROT_SSL3_SERVER; + +enum DWORD + SCH_CRED_NO_SYSTEM_MAPPER = 0x0002, + SCH_CRED_NO_SERVERNAME_CHECK = 0x0004, + SCH_CRED_MANUAL_CRED_VALIDATION = 0x0008, + SCH_CRED_NO_DEFAULT_CREDS = 0x0010, + SCH_CRED_AUTO_CRED_VALIDATION = 0x0020, + SCH_CRED_USE_DEFAULT_CREDS = 0x0040, + SCH_CRED_REVOCATION_CHECK_END_CERT = 0x0100, + SCH_CRED_REVOCATION_CHECK_CHAIN = 0x0200, + SCH_CRED_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT = 0x0400, + SCH_CRED_IGNORE_NO_REVOCATION_CHECK = 0x0800, + SCH_CRED_IGNORE_REVOCATION_OFFLINE = 0x1000; + +// No definition - presumably an opaque structure +struct _HMAPPER; + +struct SCHANNEL_CRED { + DWORD dwVersion = SCHANNEL_CRED_VERSION; + DWORD cCreds; + PCCERT_CONTEXT* paCred; + HCERTSTORE hRootStore; + DWORD cMappers; + _HMAPPER** aphMappers; + DWORD cSupportedAlgs; + ALG_ID* palgSupportedAlgs; + DWORD grbitEnabledProtocols; + DWORD dwMinimumCypherStrength; + DWORD dwMaximumCypherStrength; + DWORD dwSessionLifespan; + DWORD dwFlags; + DWORD reserved; +} +alias SCHANNEL_CRED* PSCHANNEL_CRED; + +struct SecPkgCred_SupportedAlgs { + DWORD cSupportedAlgs; + ALG_ID* palgSupportedAlgs; +} +alias SecPkgCred_SupportedAlgs* PSecPkgCred_SupportedAlgs; + +struct SecPkgCred_CypherStrengths { + DWORD dwMinimumCypherStrength; + DWORD dwMaximumCypherStrength; +} +alias SecPkgCred_CypherStrengths* PSecPkgCred_CypherStrengths; + +struct SecPkgCred_SupportedProtocols { + DWORD grbitProtocol; +} +alias SecPkgCred_SupportedProtocols* PSecPkgCred_SupportedProtocols; + +struct SecPkgContext_IssuerListInfoEx { + PCERT_NAME_BLOB aIssuers; + DWORD cIssuers; +} +alias SecPkgContext_IssuerListInfoEx* PSecPkgContext_IssuerListInfoEx; + +struct SecPkgContext_ConnectionInfo { + DWORD dwProtocol; + ALG_ID aiCipher; + DWORD dwCipherStrength; + ALG_ID aiHash; + DWORD dwHashStrength; + ALG_ID aiExch; + DWORD dwExchStrength; +} +alias SecPkgContext_ConnectionInfo* PSecPkgContext_ConnectionInfo; diff --git a/src/urt/internal/sys/windows/sdkddkver.d b/src/urt/internal/sys/windows/sdkddkver.d new file mode 100644 index 0000000..7cb696a --- /dev/null +++ b/src/urt/internal/sys/windows/sdkddkver.d @@ -0,0 +1,155 @@ +/** + * Windows API header module + * + * Translated from Windows SDK API + * + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/sdkddkver.d) + */ +module urt.internal.sys.windows.sdkddkver; +version (Windows): + +import urt.internal.sys.windows.w32api; + +enum _WIN32_WINNT_NT4 = 0x0400; +enum _WIN32_WINNT_WIN2K = 0x0500; +enum _WIN32_WINNT_WINXP = 0x0501; +enum _WIN32_WINNT_WS03 = 0x0502; +enum _WIN32_WINNT_WIN6 = 0x0600; +enum _WIN32_WINNT_VISTA = 0x0600; +enum _WIN32_WINNT_WS08 = 0x0600; +enum _WIN32_WINNT_LONGHORN = 0x0600; +enum _WIN32_WINNT_WIN7 = 0x0601; +enum _WIN32_WINNT_WIN8 = 0x0602; +enum _WIN32_WINNT_WINBLUE = 0x0603; +enum _WIN32_WINNT_WINTHRESHOLD = 0x0A00; +enum _WIN32_WINNT_WIN10 = 0x0A00; + +enum _WIN32_IE_IE20 = 0x0200; +enum _WIN32_IE_IE30 = 0x0300; +enum _WIN32_IE_IE302 = 0x0302; +enum _WIN32_IE_IE40 = 0x0400; +enum _WIN32_IE_IE401 = 0x0401; +enum _WIN32_IE_IE50 = 0x0500; +enum _WIN32_IE_IE501 = 0x0501; +enum _WIN32_IE_IE55 = 0x0550; +enum _WIN32_IE_IE60 = 0x0600; +enum _WIN32_IE_IE60SP1 = 0x0601; +enum _WIN32_IE_IE60SP2 = 0x0603; +enum _WIN32_IE_IE70 = 0x0700; +enum _WIN32_IE_IE80 = 0x0800; +enum _WIN32_IE_IE90 = 0x0900; +enum _WIN32_IE_IE100 = 0x0A00; +enum _WIN32_IE_IE110 = 0x0A00; + +enum _WIN32_IE_NT4 = _WIN32_IE_IE20; +enum _WIN32_IE_NT4SP1 = _WIN32_IE_IE20; +enum _WIN32_IE_NT4SP2 = _WIN32_IE_IE20; +enum _WIN32_IE_NT4SP3 = _WIN32_IE_IE302; +enum _WIN32_IE_NT4SP4 = _WIN32_IE_IE401; +enum _WIN32_IE_NT4SP5 = _WIN32_IE_IE401; +enum _WIN32_IE_NT4SP6 = _WIN32_IE_IE50; +enum _WIN32_IE_WIN98 = _WIN32_IE_IE401; +enum _WIN32_IE_WIN98SE = _WIN32_IE_IE50; +enum _WIN32_IE_WINME = _WIN32_IE_IE55; +enum _WIN32_IE_WIN2K = _WIN32_IE_IE501; +enum _WIN32_IE_WIN2KSP1 = _WIN32_IE_IE501; +enum _WIN32_IE_WIN2KSP2 = _WIN32_IE_IE501; +enum _WIN32_IE_WIN2KSP3 = _WIN32_IE_IE501; +enum _WIN32_IE_WIN2KSP4 = _WIN32_IE_IE501; +enum _WIN32_IE_XP = _WIN32_IE_IE60; +enum _WIN32_IE_XPSP1 = _WIN32_IE_IE60SP1; +enum _WIN32_IE_XPSP2 = _WIN32_IE_IE60SP2; +enum _WIN32_IE_WS03 = 0x0602; +enum _WIN32_IE_WS03SP1 = _WIN32_IE_IE60SP2; +enum _WIN32_IE_WIN6 = _WIN32_IE_IE70; +enum _WIN32_IE_LONGHORN = _WIN32_IE_IE70; +enum _WIN32_IE_WIN7 = _WIN32_IE_IE80; +enum _WIN32_IE_WIN8 = _WIN32_IE_IE100; +enum _WIN32_IE_WINBLUE = _WIN32_IE_IE100; +enum _WIN32_IE_WINTHRESHOLD = _WIN32_IE_IE110; +enum _WIN32_IE_WIN10 = _WIN32_IE_IE110; + + +enum NTDDI_WIN2K = 0x05000000; +enum NTDDI_WIN2KSP1 = 0x05000100; +enum NTDDI_WIN2KSP2 = 0x05000200; +enum NTDDI_WIN2KSP3 = 0x05000300; +enum NTDDI_WIN2KSP4 = 0x05000400; + +enum NTDDI_WINXP = 0x05010000; +enum NTDDI_WINXPSP1 = 0x05010100; +enum NTDDI_WINXPSP2 = 0x05010200; +enum NTDDI_WINXPSP3 = 0x05010300; +enum NTDDI_WINXPSP4 = 0x05010400; + +enum NTDDI_WS03 = 0x05020000; +enum NTDDI_WS03SP1 = 0x05020100; +enum NTDDI_WS03SP2 = 0x05020200; +enum NTDDI_WS03SP3 = 0x05020300; +enum NTDDI_WS03SP4 = 0x05020400; + +enum NTDDI_WIN6 = 0x06000000; +enum NTDDI_WIN6SP1 = 0x06000100; +enum NTDDI_WIN6SP2 = 0x06000200; +enum NTDDI_WIN6SP3 = 0x06000300; +enum NTDDI_WIN6SP4 = 0x06000400; + +enum NTDDI_VISTA = NTDDI_WIN6; +enum NTDDI_VISTASP1 = NTDDI_WIN6SP1; +enum NTDDI_VISTASP2 = NTDDI_WIN6SP2; +enum NTDDI_VISTASP3 = NTDDI_WIN6SP3; +enum NTDDI_VISTASP4 = NTDDI_WIN6SP4; + +enum NTDDI_LONGHORN = NTDDI_VISTA; + +enum NTDDI_WS08 = NTDDI_WIN6SP1; +enum NTDDI_WS08SP2 = NTDDI_WIN6SP2; +enum NTDDI_WS08SP3 = NTDDI_WIN6SP3; +enum NTDDI_WS08SP4 = NTDDI_WIN6SP4; + +enum NTDDI_WIN7 = 0x06010000; +enum NTDDI_WIN8 = 0x06020000; +enum NTDDI_WINBLUE = 0x06030000; +enum NTDDI_WINTHRESHOLD = 0x0A000000; +enum NTDDI_WIN10 = 0x0A000000; +enum NTDDI_WIN10_TH2 = 0x0A000001; +enum NTDDI_WIN10_RS1 = 0x0A000002; +enum NTDDI_WIN10_RS2 = 0x0A000003; +enum NTDDI_WIN10_RS3 = 0x0A000004; +enum NTDDI_WIN10_RS4 = 0x0A000005; +enum NTDDI_WIN10_RS5 = 0x0A000006; +enum NTDDI_WIN10_19H1 = 0x0A000007; +enum NTDDI_WIN10_VB = 0x0A000008; +enum NTDDI_WIN10_MN = 0x0A000009; +enum NTDDI_WIN10_FE = 0x0A00000A; +enum NTDDI_WIN10_CO = 0x0A00000B; +enum NTDDI_WIN10_NI = 0x0A00000C; +enum NTDDI_WIN10_CU = 0x0A00000D; +enum NTDDI_WIN11_ZN = 0x0A00000E; +enum NTDDI_WIN11_GA = 0x0A00000F; +enum NTDDI_WIN11_GE = 0x0A000010; + +enum WDK_NTDDI_VERSION = NTDDI_WIN11_GE; + +enum OSVERSION_MASK = 0xFFFF0000U; +enum SPVERSION_MASK = 0x0000FF00; +enum SUBVERSION_MASK = 0x000000FF; + +pragma(inline, true) nothrow @nogc pure @safe { + uint OSVER(uint Version) => Version & OSVERSION_MASK; + uint SPVER(uint Version) => (Version & SPVERSION_MASK) >> 8; + uint SUBVER(uint Version) => Version & SUBVERSION_MASK; + + uint NTDDI_VERSION_FROM_WIN32_WINNT2(uint Version) => Version * 0x10000; + alias NTDDI_VERSION_FROM_WIN32_WINNT = NTDDI_VERSION_FROM_WIN32_WINNT2; +} + + +static if (_WIN32_WINNT < _WIN32_WINNT_WIN10) { + enum NTDDI_VERSION = NTDDI_VERSION_FROM_WIN32_WINNT(_WIN32_WINNT); +} else { + enum NTDDI_VERSION = WDK_NTDDI_VERSION; +} + +enum WINVER = _WIN32_WINNT; diff --git a/src/urt/internal/sys/windows/security.d b/src/urt/internal/sys/windows/security.d new file mode 100644 index 0000000..5efa509 --- /dev/null +++ b/src/urt/internal/sys/windows/security.d @@ -0,0 +1,119 @@ +/** + * Windows API header module + * + * Translated from MinGW Windows headers + * + * Authors: Ellery Newcomer, John Colvin + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_security.d) + */ +module urt.internal.sys.windows.security; +version (Windows): + +enum : SECURITY_STATUS +{ + SEC_E_OK = 0x00000000, + SEC_E_INSUFFICIENT_MEMORY = 0x80090300, + SEC_E_INVALID_HANDLE = 0x80090301, + SEC_E_UNSUPPORTED_FUNCTION = 0x80090302, + SEC_E_TARGET_UNKNOWN = 0x80090303, + SEC_E_INTERNAL_ERROR = 0x80090304, + SEC_E_SECPKG_NOT_FOUND = 0x80090305, + SEC_E_NOT_OWNER = 0x80090306, + SEC_E_CANNOT_INSTALL = 0x80090307, + SEC_E_INVALID_TOKEN = 0x80090308, + SEC_E_CANNOT_PACK = 0x80090309, + SEC_E_QOP_NOT_SUPPORTED = 0x8009030A, + SEC_E_NO_IMPERSONATION = 0x8009030B, + SEC_E_LOGON_DENIED = 0x8009030C, + SEC_E_UNKNOWN_CREDENTIALS = 0x8009030D, + SEC_E_NO_CREDENTIALS = 0x8009030E, + SEC_E_MESSAGE_ALTERED = 0x8009030F, + SEC_E_OUT_OF_SEQUENCE = 0x80090310, + SEC_E_NO_AUTHENTICATING_AUTHORITY = 0x80090311, + SEC_E_BAD_PKGID = 0x80090316, + SEC_E_CONTEXT_EXPIRED = 0x80090317, + SEC_E_INCOMPLETE_MESSAGE = 0x80090318, + SEC_E_INCOMPLETE_CREDENTIALS = 0x80090320, + SEC_E_BUFFER_TOO_SMALL = 0x80090321, + SEC_E_WRONG_PRINCIPAL = 0x80090322, + SEC_E_TIME_SKEW = 0x80090324, + SEC_E_UNTRUSTED_ROOT = 0x80090325, + SEC_E_ILLEGAL_MESSAGE = 0x80090326, + SEC_E_CERT_UNKNOWN = 0x80090327, + SEC_E_CERT_EXPIRED = 0x80090328, + SEC_E_ENCRYPT_FAILURE = 0x80090329, + SEC_E_DECRYPT_FAILURE = 0x80090330, + SEC_E_ALGORITHM_MISMATCH = 0x80090331, + SEC_E_SECURITY_QOS_FAILED = 0x80090332, + SEC_E_UNFINISHED_CONTEXT_DELETED = 0x80090333, + SEC_E_NO_TGT_REPLY = 0x80090334, + SEC_E_NO_IP_ADDRESSES = 0x80090335, + SEC_E_WRONG_CREDENTIAL_HANDLE = 0x80090336, + SEC_E_CRYPTO_SYSTEM_INVALID = 0x80090337, + SEC_E_MAX_REFERRALS_EXCEEDED = 0x80090338, + SEC_E_MUST_BE_KDC = 0x80090339, + SEC_E_STRONG_CRYPTO_NOT_SUPPORTED = 0x8009033A, + SEC_E_TOO_MANY_PRINCIPALS = 0x8009033B, + SEC_E_NO_PA_DATA = 0x8009033C, + SEC_E_PKINIT_NAME_MISMATCH = 0x8009033D, + SEC_E_SMARTCARD_LOGON_REQUIRED = 0x8009033E, + SEC_E_SHUTDOWN_IN_PROGRESS = 0x8009033F, + SEC_E_KDC_INVALID_REQUEST = 0x80090340, + SEC_E_KDC_UNABLE_TO_REFER = 0x80090341, + SEC_E_KDC_UNKNOWN_ETYPE = 0x80090342, + SEC_E_UNSUPPORTED_PREAUTH = 0x80090343, + SEC_E_DELEGATION_REQUIRED = 0x80090345, + SEC_E_BAD_BINDINGS = 0x80090346, + SEC_E_MULTIPLE_ACCOUNTS = 0x80090347, + SEC_E_NO_KERB_KEY = 0x80090348, + SEC_E_CERT_WRONG_USAGE = 0x80090349, + SEC_E_DOWNGRADE_DETECTED = 0x80090350, + SEC_E_SMARTCARD_CERT_REVOKED = 0x80090351, + SEC_E_ISSUING_CA_UNTRUSTED = 0x80090352, + SEC_E_REVOCATION_OFFLINE_C = 0x80090353, + SEC_E_PKINIT_CLIENT_FAILURE = 0x80090354, + SEC_E_SMARTCARD_CERT_EXPIRED = 0x80090355, + SEC_E_NO_S4U_PROT_SUPPORT = 0x80090356, + SEC_E_CROSSREALM_DELEGATION_FAILURE = 0x80090357, + SEC_E_REVOCATION_OFFLINE_KDC = 0x80090358, + SEC_E_ISSUING_CA_UNTRUSTED_KDC = 0x80090359, + SEC_E_KDC_CERT_EXPIRED = 0x8009035A, + SEC_E_KDC_CERT_REVOKED = 0x8009035B, + SEC_E_INVALID_PARAMETER = 0x8009035D, + SEC_E_DELEGATION_POLICY = 0x8009035E, + SEC_E_POLICY_NLTM_ONLY = 0x8009035F, + SEC_E_NO_CONTEXT = 0x80090361, + SEC_E_PKU2U_CERT_FAILURE = 0x80090362, + SEC_E_MUTUAL_AUTH_FAILED = 0x80090363, + SEC_E_ONLY_HTTPS_ALLOWED = 0x80090365, + SEC_E_APPLICATION_PROTOCOL_MISMATCH = 0x80090367, + SEC_E_INVALID_UPN_NAME = 0x80090369, + SEC_E_EXT_BUFFER_TOO_SMALL = 0x8009036A, + SEC_E_INSUFFICIENT_BUFFERS = 0x8009036B, + SEC_E_NO_SPM = SEC_E_INTERNAL_ERROR, + SEC_E_NOT_SUPPORTED = SEC_E_UNSUPPORTED_FUNCTION +} +enum : SECURITY_STATUS +{ + SEC_I_CONTINUE_NEEDED = 0x00090312, + SEC_I_COMPLETE_NEEDED = 0x00090313, + SEC_I_COMPLETE_AND_CONTINUE = 0x00090314, + SEC_I_LOCAL_LOGON = 0x00090315, + SEC_I_GENERIC_EXTENSION_RECEIVED = 0x00090316, + SEC_I_CONTEXT_EXPIRED = 0x00090317, + SEC_I_INCOMPLETE_CREDENTIALS = 0x00090320, + SEC_I_RENEGOTIATE = 0x00090321, + SEC_I_NO_LSA_CONTEXT = 0x00090323, + SEC_I_SIGNATURE_NEEDED = 0x0009035C, + SEC_I_NO_RENEGOTIATION = 0x00090360, + SEC_I_MESSAGE_FRAGMENT = 0x00090364, + SEC_I_CONTINUE_NEEDED_MESSAGE_OK = 0x00090366, + SEC_I_ASYNC_CALL_PENDING = 0x00090368, +} + +/* always a char */ +alias SEC_CHAR = char; +alias SEC_WCHAR = wchar; + +alias SECURITY_STATUS = int; diff --git a/src/urt/internal/sys/windows/sspi.d b/src/urt/internal/sys/windows/sspi.d new file mode 100644 index 0000000..d085c4a --- /dev/null +++ b/src/urt/internal/sys/windows/sspi.d @@ -0,0 +1,381 @@ +/** + * Windows API header module + * + * Translated from MinGW Windows headers + * + * Authors: Ellery Newcomer + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_sspi.d) + */ +module urt.internal.sys.windows.sspi; +version (Windows): + +version (ANSI) {} else version = Unicode; + +import urt.internal.sys.windows.windef; +import urt.internal.sys.windows.ntdef; +import urt.internal.sys.windows.w32api; +import urt.internal.sys.windows.security; +import urt.internal.sys.windows.ntsecapi; +import urt.internal.sys.windows.subauth; + +enum :ULONG{ + SECPKG_CRED_INBOUND = 1, + SECPKG_CRED_OUTBOUND = 2, + SECPKG_CRED_BOTH = (SECPKG_CRED_OUTBOUND|SECPKG_CRED_INBOUND), + SECPKG_CRED_ATTR_NAMES = 1, +} + +enum :ULONG{ + SECPKG_FLAG_INTEGRITY = 1, + SECPKG_FLAG_PRIVACY = 2, + SECPKG_FLAG_TOKEN_ONLY = 4, + SECPKG_FLAG_DATAGRAM = 8, + SECPKG_FLAG_CONNECTION = 16, + SECPKG_FLAG_MULTI_REQUIRED = 32, + SECPKG_FLAG_CLIENT_ONLY = 64, + SECPKG_FLAG_EXTENDED_ERROR = 128, + SECPKG_FLAG_IMPERSONATION = 256, + SECPKG_FLAG_ACCEPT_WIN32_NAME = 512, + SECPKG_FLAG_STREAM = 1024, +} + +enum :ULONG{ + SECPKG_ATTR_AUTHORITY = 6, + SECPKG_ATTR_CONNECTION_INFO = 90, + SECPKG_ATTR_ISSUER_LIST = 80, + SECPKG_ATTR_ISSUER_LIST_EX = 89, + SECPKG_ATTR_KEY_INFO = 5, + SECPKG_ATTR_LIFESPAN = 2, + SECPKG_ATTR_LOCAL_CERT_CONTEXT = 84, + SECPKG_ATTR_LOCAL_CRED = 82, + SECPKG_ATTR_NAMES = 1, + SECPKG_ATTR_PROTO_INFO = 7, + SECPKG_ATTR_REMOTE_CERT_CONTEXT = 83, + SECPKG_ATTR_REMOTE_CRED = 81, + SECPKG_ATTR_SIZES = 0, + SECPKG_ATTR_STREAM_SIZES = 4, +} + +enum :ULONG{ + SECBUFFER_EMPTY = 0, + SECBUFFER_DATA = 1, + SECBUFFER_TOKEN = 2, + SECBUFFER_PKG_PARAMS = 3, + SECBUFFER_MISSING = 4, + SECBUFFER_EXTRA = 5, + SECBUFFER_STREAM_TRAILER = 6, + SECBUFFER_STREAM_HEADER = 7, + SECBUFFER_PADDING = 9, + SECBUFFER_STREAM = 10, + SECBUFFER_READONLY = 0x80000000, + SECBUFFER_ATTRMASK = 0xf0000000, +} + +enum UNISP_NAME_A = "Microsoft Unified Security Protocol Provider"; +enum UNISP_NAME_W = "Microsoft Unified Security Protocol Provider"w; +enum SECBUFFER_VERSION = 0; + +alias UNICODE_STRING SECURITY_STRING; +alias UNICODE_STRING* PSECURITY_STRING; + +extern(Windows): + +struct SecHandle { + ULONG_PTR dwLower; + ULONG_PTR dwUpper; +} +alias SecHandle* PSecHandle; +struct SecBuffer { + ULONG cbBuffer; + ULONG BufferType; + PVOID pvBuffer; +} +alias SecBuffer* PSecBuffer; +alias SecHandle CredHandle; +alias PSecHandle PCredHandle; +alias SecHandle CtxtHandle; +alias PSecHandle PCtxtHandle; +struct SECURITY_INTEGER { + uint LowPart; + int HighPart; +} +alias SECURITY_INTEGER TimeStamp; +alias SECURITY_INTEGER* PTimeStamp; +struct SecBufferDesc { + ULONG ulVersion; + ULONG cBuffers; + PSecBuffer pBuffers; +} +alias SecBufferDesc* PSecBufferDesc; +struct SecPkgContext_StreamSizes { + ULONG cbHeader; + ULONG cbTrailer; + ULONG cbMaximumMessage; + ULONG cBuffers; + ULONG cbBlockSize; +} +alias SecPkgContext_StreamSizes* PSecPkgContext_StreamSizes; +struct SecPkgContext_Sizes { + ULONG cbMaxToken; + ULONG cbMaxSignature; + ULONG cbBlockSize; + ULONG cbSecurityTrailer; +} +alias SecPkgContext_Sizes* PSecPkgContext_Sizes; +struct SecPkgContext_AuthorityW { + SEC_WCHAR* sAuthorityName; +} +alias SecPkgContext_AuthorityW* PSecPkgContext_AuthorityW; +struct SecPkgContext_AuthorityA { + SEC_CHAR* sAuthorityName; +} +alias SecPkgContext_AuthorityA* PSecPkgContext_AuthorityA; +struct SecPkgContext_KeyInfoW { + SEC_WCHAR* sSignatureAlgorithmName; + SEC_WCHAR* sEncryptAlgorithmName; + ULONG KeySize; + ULONG SignatureAlgorithm; + ULONG EncryptAlgorithm; +} +alias SecPkgContext_KeyInfoW* PSecPkgContext_KeyInfoW; +struct SecPkgContext_KeyInfoA { + SEC_CHAR* sSignatureAlgorithmName; + SEC_CHAR* sEncryptAlgorithmName; + ULONG KeySize; + ULONG SignatureAlgorithm; + ULONG EncryptAlgorithm; +} +alias SecPkgContext_KeyInfoA* PSecPkgContext_KeyInfoA; +struct SecPkgContext_LifeSpan { + TimeStamp tsStart; + TimeStamp tsExpiry; +} +alias SecPkgContext_LifeSpan* PSecPkgContext_LifeSpan; +struct SecPkgContext_NamesW { + SEC_WCHAR* sUserName; +} +alias SecPkgContext_NamesW* PSecPkgContext_NamesW; +struct SecPkgContext_NamesA { + SEC_CHAR* sUserName; +} +alias SecPkgContext_NamesA* PSecPkgContext_NamesA; +struct SecPkgInfoW { + ULONG fCapabilities; + USHORT wVersion; + USHORT wRPCID; + ULONG cbMaxToken; + SEC_WCHAR* Name; + SEC_WCHAR* Comment; +} +alias SecPkgInfoW* PSecPkgInfoW; +struct SecPkgInfoA { + ULONG fCapabilities; + USHORT wVersion; + USHORT wRPCID; + ULONG cbMaxToken; + SEC_CHAR* Name; + SEC_CHAR* Comment; +} +alias SecPkgInfoA* PSecPkgInfoA; +/* supported only in win2k+, so it should be a PSecPkgInfoW */ +/* PSDK does not say it has ANSI/Unicode versions */ +struct SecPkgContext_PackageInfo { + PSecPkgInfoW PackageInfo; +} +alias SecPkgContext_PackageInfo* PSecPkgContext_PackageInfo; +struct SecPkgCredentials_NamesW { + SEC_WCHAR* sUserName; +} +alias SecPkgCredentials_NamesW* PSecPkgCredentials_NamesW; +struct SecPkgCredentials_NamesA { + SEC_CHAR* sUserName; +} +alias SecPkgCredentials_NamesA* PSecPkgCredentials_NamesA; + +/* TODO: missing type in SDK */ +alias void function() SEC_GET_KEY_FN; + +alias SECURITY_STATUS function(PULONG,PSecPkgInfoW*) ENUMERATE_SECURITY_PACKAGES_FN_W; +alias SECURITY_STATUS function(PULONG,PSecPkgInfoA*) ENUMERATE_SECURITY_PACKAGES_FN_A; +alias SECURITY_STATUS function(PCredHandle,ULONG,PVOID) QUERY_CREDENTIALS_ATTRIBUTES_FN_W; +alias SECURITY_STATUS function(PCredHandle,ULONG,PVOID) QUERY_CREDENTIALS_ATTRIBUTES_FN_A; +alias SECURITY_STATUS function(SEC_WCHAR*,SEC_WCHAR*,ULONG,PLUID,PVOID,SEC_GET_KEY_FN,PVOID,PCredHandle,PTimeStamp) ACQUIRE_CREDENTIALS_HANDLE_FN_W; +alias SECURITY_STATUS function(SEC_CHAR*,SEC_CHAR*,ULONG,PLUID,PVOID,SEC_GET_KEY_FN,PVOID,PCredHandle,PTimeStamp) ACQUIRE_CREDENTIALS_HANDLE_FN_A; +alias SECURITY_STATUS function(PCredHandle) FREE_CREDENTIALS_HANDLE_FN; +alias SECURITY_STATUS function(PCredHandle,PCtxtHandle,SEC_WCHAR*,ULONG,ULONG,ULONG,PSecBufferDesc,ULONG,PCtxtHandle,PSecBufferDesc,PULONG,PTimeStamp) INITIALIZE_SECURITY_CONTEXT_FN_W; +alias SECURITY_STATUS function(PCredHandle,PCtxtHandle,SEC_CHAR*,ULONG,ULONG,ULONG,PSecBufferDesc,ULONG,PCtxtHandle,PSecBufferDesc,PULONG,PTimeStamp) INITIALIZE_SECURITY_CONTEXT_FN_A; +alias SECURITY_STATUS function(PCredHandle,PCtxtHandle,PSecBufferDesc,ULONG,ULONG,PCtxtHandle,PSecBufferDesc,PULONG,PTimeStamp) ACCEPT_SECURITY_CONTEXT_FN; +alias SECURITY_STATUS function(PCtxtHandle,PSecBufferDesc) COMPLETE_AUTH_TOKEN_FN; +alias SECURITY_STATUS function(PCtxtHandle) DELETE_SECURITY_CONTEXT_FN; +alias SECURITY_STATUS function(PCtxtHandle,PSecBufferDesc) APPLY_CONTROL_TOKEN_FN_W; +alias SECURITY_STATUS function(PCtxtHandle,PSecBufferDesc) APPLY_CONTROL_TOKEN_FN_A; +alias SECURITY_STATUS function(PCtxtHandle,ULONG,PVOID) QUERY_CONTEXT_ATTRIBUTES_FN_A; +alias SECURITY_STATUS function(PCtxtHandle,ULONG,PVOID) QUERY_CONTEXT_ATTRIBUTES_FN_W; +alias SECURITY_STATUS function(PCtxtHandle) IMPERSONATE_SECURITY_CONTEXT_FN; +alias SECURITY_STATUS function(PCtxtHandle) REVERT_SECURITY_CONTEXT_FN; +alias SECURITY_STATUS function(PCtxtHandle,ULONG,PSecBufferDesc,ULONG) MAKE_SIGNATURE_FN; +alias SECURITY_STATUS function(PCtxtHandle,PSecBufferDesc,ULONG,PULONG) VERIFY_SIGNATURE_FN; +alias SECURITY_STATUS function(PVOID) FREE_CONTEXT_BUFFER_FN; +alias SECURITY_STATUS function(SEC_CHAR*,PSecPkgInfoA*) QUERY_SECURITY_PACKAGE_INFO_FN_A; +alias SECURITY_STATUS function(PCtxtHandle,HANDLE*) QUERY_SECURITY_CONTEXT_TOKEN_FN; +alias SECURITY_STATUS function(SEC_WCHAR*,PSecPkgInfoW*) QUERY_SECURITY_PACKAGE_INFO_FN_W; +alias SECURITY_STATUS function(PCtxtHandle,ULONG,PSecBufferDesc,ULONG) ENCRYPT_MESSAGE_FN; +alias SECURITY_STATUS function(PCtxtHandle,PSecBufferDesc,ULONG,PULONG) DECRYPT_MESSAGE_FN; + +/* No, it really is FreeCredentialsHandle, see the thread beginning + * http://sourceforge.net/mailarchive/message.php?msg_id=4321080 for a + * discovery discussion. */ +struct SecurityFunctionTableW{ + uint dwVersion; + ENUMERATE_SECURITY_PACKAGES_FN_W EnumerateSecurityPackagesW; + QUERY_CREDENTIALS_ATTRIBUTES_FN_W QueryCredentialsAttributesW; + ACQUIRE_CREDENTIALS_HANDLE_FN_W AcquireCredentialsHandleW; + FREE_CREDENTIALS_HANDLE_FN FreeCredentialsHandle; + void* Reserved2; + INITIALIZE_SECURITY_CONTEXT_FN_W InitializeSecurityContextW; + ACCEPT_SECURITY_CONTEXT_FN AcceptSecurityContext; + COMPLETE_AUTH_TOKEN_FN CompleteAuthToken; + DELETE_SECURITY_CONTEXT_FN DeleteSecurityContext; + APPLY_CONTROL_TOKEN_FN_W ApplyControlTokenW; + QUERY_CONTEXT_ATTRIBUTES_FN_W QueryContextAttributesW; + IMPERSONATE_SECURITY_CONTEXT_FN ImpersonateSecurityContext; + REVERT_SECURITY_CONTEXT_FN RevertSecurityContext; + MAKE_SIGNATURE_FN MakeSignature; + VERIFY_SIGNATURE_FN VerifySignature; + FREE_CONTEXT_BUFFER_FN FreeContextBuffer; + QUERY_SECURITY_PACKAGE_INFO_FN_W QuerySecurityPackageInfoW; + void* Reserved3; + void* Reserved4; + void* Reserved5; + void* Reserved6; + void* Reserved7; + void* Reserved8; + QUERY_SECURITY_CONTEXT_TOKEN_FN QuerySecurityContextToken; + ENCRYPT_MESSAGE_FN EncryptMessage; + DECRYPT_MESSAGE_FN DecryptMessage; +} +alias SecurityFunctionTableW* PSecurityFunctionTableW; +struct SecurityFunctionTableA{ + uint dwVersion; + ENUMERATE_SECURITY_PACKAGES_FN_A EnumerateSecurityPackagesA; + QUERY_CREDENTIALS_ATTRIBUTES_FN_A QueryCredentialsAttributesA; + ACQUIRE_CREDENTIALS_HANDLE_FN_A AcquireCredentialsHandleA; + FREE_CREDENTIALS_HANDLE_FN FreeCredentialsHandle; + void* Reserved2; + INITIALIZE_SECURITY_CONTEXT_FN_A InitializeSecurityContextA; + ACCEPT_SECURITY_CONTEXT_FN AcceptSecurityContext; + COMPLETE_AUTH_TOKEN_FN CompleteAuthToken; + DELETE_SECURITY_CONTEXT_FN DeleteSecurityContext; + APPLY_CONTROL_TOKEN_FN_A ApplyControlTokenA; + QUERY_CONTEXT_ATTRIBUTES_FN_A QueryContextAttributesA; + IMPERSONATE_SECURITY_CONTEXT_FN ImpersonateSecurityContext; + REVERT_SECURITY_CONTEXT_FN RevertSecurityContext; + MAKE_SIGNATURE_FN MakeSignature; + VERIFY_SIGNATURE_FN VerifySignature; + FREE_CONTEXT_BUFFER_FN FreeContextBuffer; + QUERY_SECURITY_PACKAGE_INFO_FN_A QuerySecurityPackageInfoA; + void* Reserved3; + void* Reserved4; + void* Unknown1; + void* Unknown2; + void* Unknown3; + void* Unknown4; + void* Unknown5; + ENCRYPT_MESSAGE_FN EncryptMessage; + DECRYPT_MESSAGE_FN DecryptMessage; +} +alias SecurityFunctionTableA* PSecurityFunctionTableA; +alias PSecurityFunctionTableA function() INIT_SECURITY_INTERFACE_A; +alias PSecurityFunctionTableW function() INIT_SECURITY_INTERFACE_W; + +SECURITY_STATUS FreeCredentialsHandle(PCredHandle); +SECURITY_STATUS EnumerateSecurityPackagesA(PULONG,PSecPkgInfoA*); +SECURITY_STATUS EnumerateSecurityPackagesW(PULONG,PSecPkgInfoW*); +SECURITY_STATUS AcquireCredentialsHandleA(SEC_CHAR*,SEC_CHAR*,ULONG,PLUID,PVOID,SEC_GET_KEY_FN,PVOID,PCredHandle,PTimeStamp); +SECURITY_STATUS AcquireCredentialsHandleW(SEC_WCHAR*,SEC_WCHAR*,ULONG,PLUID,PVOID,SEC_GET_KEY_FN,PVOID,PCredHandle,PTimeStamp); +SECURITY_STATUS AcceptSecurityContext(PCredHandle,PCtxtHandle,PSecBufferDesc,ULONG,ULONG,PCtxtHandle,PSecBufferDesc,PULONG,PTimeStamp); +SECURITY_STATUS InitializeSecurityContextA(PCredHandle,PCtxtHandle,SEC_CHAR*,ULONG,ULONG,ULONG,PSecBufferDesc,ULONG,PCtxtHandle,PSecBufferDesc,PULONG,PTimeStamp); +SECURITY_STATUS InitializeSecurityContextW(PCredHandle,PCtxtHandle,SEC_WCHAR*,ULONG,ULONG,ULONG,PSecBufferDesc,ULONG,PCtxtHandle,PSecBufferDesc,PULONG,PTimeStamp); +SECURITY_STATUS FreeContextBuffer(PVOID); +SECURITY_STATUS QueryContextAttributesA(PCtxtHandle,ULONG,PVOID); +SECURITY_STATUS QueryContextAttributesW(PCtxtHandle,ULONG,PVOID); +SECURITY_STATUS QueryCredentialsAttributesA(PCredHandle,ULONG,PVOID); +SECURITY_STATUS QueryCredentialsAttributesW(PCredHandle,ULONG,PVOID); +static if (_WIN32_WINNT >= 0x500){ + SECURITY_STATUS QuerySecurityContextToken(PCtxtHandle,HANDLE*); +} +SECURITY_STATUS DecryptMessage(PCtxtHandle,PSecBufferDesc,ULONG,PULONG); +SECURITY_STATUS EncryptMessage(PCtxtHandle,ULONG,PSecBufferDesc,ULONG); +SECURITY_STATUS DeleteSecurityContext(PCtxtHandle); +SECURITY_STATUS CompleteAuthToken(PCtxtHandle,PSecBufferDesc); +SECURITY_STATUS ApplyControlTokenA(PCtxtHandle,PSecBufferDesc); +SECURITY_STATUS ApplyControlTokenW(PCtxtHandle,PSecBufferDesc); +SECURITY_STATUS ImpersonateSecurityContext(PCtxtHandle); +SECURITY_STATUS RevertSecurityContext(PCtxtHandle); +SECURITY_STATUS MakeSignature(PCtxtHandle,ULONG,PSecBufferDesc,ULONG); +SECURITY_STATUS VerifySignature(PCtxtHandle,PSecBufferDesc,ULONG,PULONG); +SECURITY_STATUS QuerySecurityPackageInfoA(SEC_CHAR*,PSecPkgInfoA*); +SECURITY_STATUS QuerySecurityPackageInfoW(SEC_WCHAR*,PSecPkgInfoW*); +PSecurityFunctionTableA InitSecurityInterfaceA(); +PSecurityFunctionTableW InitSecurityInterfaceW(); + +version (Unicode) { + alias UNISP_NAME_W UNISP_NAME; + alias SecPkgInfoW SecPkgInfo; + alias PSecPkgInfoW PSecPkgInfo; + alias SecPkgCredentials_NamesW SecPkgCredentials_Names; + alias PSecPkgCredentials_NamesW PSecPkgCredentials_Names; + alias SecPkgContext_AuthorityW SecPkgContext_Authority; + alias PSecPkgContext_AuthorityW PSecPkgContext_Authority; + alias SecPkgContext_KeyInfoW SecPkgContext_KeyInfo; + alias PSecPkgContext_KeyInfoW PSecPkgContext_KeyInfo; + alias SecPkgContext_NamesW SecPkgContext_Names; + alias PSecPkgContext_NamesW PSecPkgContext_Names; + alias SecurityFunctionTableW SecurityFunctionTable; + alias PSecurityFunctionTableW PSecurityFunctionTable; + alias AcquireCredentialsHandleW AcquireCredentialsHandle; + alias EnumerateSecurityPackagesW EnumerateSecurityPackages; + alias InitializeSecurityContextW InitializeSecurityContext; + alias QueryContextAttributesW QueryContextAttributes; + alias QueryCredentialsAttributesW QueryCredentialsAttributes; + alias QuerySecurityPackageInfoW QuerySecurityPackageInfo; + alias ApplyControlTokenW ApplyControlToken; + alias ENUMERATE_SECURITY_PACKAGES_FN_W ENUMERATE_SECURITY_PACKAGES_FN; + alias QUERY_CREDENTIALS_ATTRIBUTES_FN_W QUERY_CREDENTIALS_ATTRIBUTES_FN; + alias ACQUIRE_CREDENTIALS_HANDLE_FN_W ACQUIRE_CREDENTIALS_HANDLE_FN; + alias INITIALIZE_SECURITY_CONTEXT_FN_W INITIALIZE_SECURITY_CONTEXT_FN; + alias APPLY_CONTROL_TOKEN_FN_W APPLY_CONTROL_TOKEN_FN; + alias QUERY_CONTEXT_ATTRIBUTES_FN_W QUERY_CONTEXT_ATTRIBUTES_FN; + alias QUERY_SECURITY_PACKAGE_INFO_FN_W QUERY_SECURITY_PACKAGE_INFO_FN; + alias INIT_SECURITY_INTERFACE_W INIT_SECURITY_INTERFACE; +}else{ + alias UNISP_NAME_A UNISP_NAME; + alias SecPkgInfoA SecPkgInfo; + alias PSecPkgInfoA PSecPkgInfo; + alias SecPkgCredentials_NamesA SecPkgCredentials_Names; + alias PSecPkgCredentials_NamesA PSecPkgCredentials_Names; + alias SecPkgContext_AuthorityA SecPkgContext_Authority; + alias PSecPkgContext_AuthorityA PSecPkgContext_Authority; + alias SecPkgContext_KeyInfoA SecPkgContext_KeyInfo; + alias PSecPkgContext_KeyInfoA PSecPkgContext_KeyInfo; + alias SecPkgContext_NamesA SecPkgContext_Names; + alias PSecPkgContext_NamesA PSecPkgContext_Names; + alias SecurityFunctionTableA SecurityFunctionTable; + alias PSecurityFunctionTableA PSecurityFunctionTable; + alias AcquireCredentialsHandleA AcquireCredentialsHandle; + alias EnumerateSecurityPackagesA EnumerateSecurityPackages; + alias InitializeSecurityContextA InitializeSecurityContext; + alias QueryContextAttributesA QueryContextAttributes; + alias QueryCredentialsAttributesA QueryCredentialsAttributes; + alias QuerySecurityPackageInfoA QuerySecurityPackageInfo; + alias ApplyControlTokenA ApplyControlToken; + alias ENUMERATE_SECURITY_PACKAGES_FN_A ENUMERATE_SECURITY_PACKAGES_FN; + alias QUERY_CREDENTIALS_ATTRIBUTES_FN_A QUERY_CREDENTIALS_ATTRIBUTES_FN; + alias ACQUIRE_CREDENTIALS_HANDLE_FN_A ACQUIRE_CREDENTIALS_HANDLE_FN; + alias INITIALIZE_SECURITY_CONTEXT_FN_A INITIALIZE_SECURITY_CONTEXT_FN; + alias APPLY_CONTROL_TOKEN_FN_A APPLY_CONTROL_TOKEN_FN; + alias QUERY_CONTEXT_ATTRIBUTES_FN_A QUERY_CONTEXT_ATTRIBUTES_FN; + alias QUERY_SECURITY_PACKAGE_INFO_FN_A QUERY_SECURITY_PACKAGE_INFO_FN; + alias INIT_SECURITY_INTERFACE_A INIT_SECURITY_INTERFACE; +} diff --git a/src/urt/internal/sys/windows/subauth.d b/src/urt/internal/sys/windows/subauth.d new file mode 100644 index 0000000..fc43416 --- /dev/null +++ b/src/urt/internal/sys/windows/subauth.d @@ -0,0 +1,275 @@ +/** + * Windows API header module + * + * Translated from MinGW Windows headers + * + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_subauth.d) + */ +module urt.internal.sys.windows.subauth; +version (Windows): + +import urt.internal.sys.windows.ntdef, urt.internal.sys.windows.windef; + +/+ +alias LONG NTSTATUS; +alias NTSTATUS* PNTSTATUS; ++/ + +enum : ULONG { + MSV1_0_PASSTHRU = 1, + MSV1_0_GUEST_LOGON = 2 +} + +// USER_ALL_INFORMATION.WhichFields (Undocumented) +enum ULONG + MSV1_0_VALIDATION_LOGOFF_TIME = 1, + MSV1_0_VALIDATION_KICKOFF_TIME = 2, + MSV1_0_VALIDATION_LOGON_SERVER = 4, + MSV1_0_VALIDATION_LOGON_DOMAIN = 8, + MSV1_0_VALIDATION_SESSION_KEY = 16, + MSV1_0_VALIDATION_USER_FLAGS = 32, + MSV1_0_VALIDATION_USER_ID = 64; + +// ?ActionsPerformed? (Undocumented) +enum MSV1_0_SUBAUTH_ACCOUNT_DISABLED = 1; +enum MSV1_0_SUBAUTH_PASSWORD = 2; +enum MSV1_0_SUBAUTH_WORKSTATIONS = 4; +enum MSV1_0_SUBAUTH_LOGON_HOURS = 8; +enum MSV1_0_SUBAUTH_ACCOUNT_EXPIRY = 16; +enum MSV1_0_SUBAUTH_PASSWORD_EXPIRY = 32; +enum MSV1_0_SUBAUTH_ACCOUNT_TYPE = 64; +enum MSV1_0_SUBAUTH_LOCKOUT = 128; + +enum NEXT_FREE_ACCOUNT_CONTROL_BIT = 131072; + +enum SAM_DAYS_PER_WEEK = 7; +enum SAM_HOURS_PER_WEEK = 168; +enum SAM_MINUTES_PER_WEEK = 10080; + +enum : NTSTATUS { + STATUS_SUCCESS = 0, + STATUS_INVALID_INFO_CLASS = 0xC0000003, + STATUS_NO_SUCH_USER = 0xC0000064, + STATUS_WRONG_PASSWORD = 0xC000006A, + STATUS_PASSWORD_RESTRICTION = 0xC000006C, + STATUS_LOGON_FAILURE = 0xC000006D, + STATUS_ACCOUNT_RESTRICTION = 0xC000006E, + STATUS_INVALID_LOGON_HOURS = 0xC000006F, + STATUS_INVALID_WORKSTATION = 0xC0000070, + STATUS_PASSWORD_EXPIRED = 0xC0000071, + STATUS_ACCOUNT_DISABLED = 0xC0000072, + STATUS_INSUFFICIENT_RESOURCES = 0xC000009A, + STATUS_ACCOUNT_EXPIRED = 0xC0000193, + STATUS_PASSWORD_MUST_CHANGE = 0xC0000224, + STATUS_ACCOUNT_LOCKED_OUT = 0xC0000234 +} + +// Note: undocumented in MSDN +// USER_ALL_INFORMATION.UserAccountControl +enum ULONG + USER_ACCOUNT_DISABLED = 1, + USER_HOME_DIRECTORY_REQUIRED = 2, + USER_PASSWORD_NOT_REQUIRED = 4, + USER_TEMP_DUPLICATE_ACCOUNT = 8, + USER_NORMAL_ACCOUNT = 16, + USER_MNS_LOGON_ACCOUNT = 32, + USER_INTERDOMAIN_TRUST_ACCOUNT = 64, + USER_WORKSTATION_TRUST_ACCOUNT = 128, + USER_SERVER_TRUST_ACCOUNT = 256, + USER_DONT_EXPIRE_PASSWORD = 512, + USER_ACCOUNT_AUTO_LOCKED = 1024, + USER_ENCRYPTED_TEXT_PASSWORD_ALLOWED = 2048, + USER_SMARTCARD_REQUIRED = 4096, + USER_TRUSTED_FOR_DELEGATION = 8192, + USER_NOT_DELEGATED = 16384, + USER_USE_DES_KEY_ONLY = 32768, + USER_DONT_REQUIRE_PREAUTH = 65536, + + USER_MACHINE_ACCOUNT_MASK = 448, + USER_ACCOUNT_TYPE_MASK = 472, + USER_ALL_PARAMETERS = 2097152; + +/+ +struct UNICODE_STRING { + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; +} +alias UNICODE_STRING* PUNICODE_STRING; + +struct STRING { + USHORT Length; + USHORT MaximumLength; + PCHAR Buffer; +} +alias STRING* PSTRING; ++/ + +alias SAM_HANDLE = HANDLE; +alias SAM_HANDLE* PSAM_HANDLE; + +struct OLD_LARGE_INTEGER { + ULONG LowPart; + LONG HighPart; +} +alias OLD_LARGE_INTEGER* POLD_LARGE_INTEGER; + +enum NETLOGON_LOGON_INFO_CLASS { + NetlogonInteractiveInformation = 1, + NetlogonNetworkInformation, + NetlogonServiceInformation, + NetlogonGenericInformation, + NetlogonInteractiveTransitiveInformation, + NetlogonNetworkTransitiveInformation, + NetlogonServiceTransitiveInformation +} + + +enum CYPHER_BLOCK_LENGTH = 8; +enum USER_SESSION_KEY_LENGTH = CYPHER_BLOCK_LENGTH * 2; +enum CLEAR_BLOCK_LENGTH = 8; + +struct CYPHER_BLOCK { + CHAR[CYPHER_BLOCK_LENGTH] data = 0; +} +alias CYPHER_BLOCK* PCYPHER_BLOCK; + +struct CLEAR_BLOCK { + CHAR[CLEAR_BLOCK_LENGTH] data = 0; +} +alias CLEAR_BLOCK* PCLEAR_BLOCK; + +struct LM_OWF_PASSWORD { + CYPHER_BLOCK[2] data; +} +alias LM_OWF_PASSWORD* PLM_OWF_PASSWORD; + +struct USER_SESSION_KEY { + CYPHER_BLOCK[2] data; +} +alias USER_SESSION_KEY* PUSER_SESSION_KEY; + +alias CLEAR_BLOCK LM_CHALLENGE; +alias LM_CHALLENGE* PLM_CHALLENGE; + +alias LM_OWF_PASSWORD NT_OWF_PASSWORD; +alias NT_OWF_PASSWORD* PNT_OWF_PASSWORD; +alias LM_CHALLENGE NT_CHALLENGE; +alias NT_CHALLENGE* PNT_CHALLENGE; + +struct LOGON_HOURS { + USHORT UnitsPerWeek; + PUCHAR LogonHours; +} +alias LOGON_HOURS* PLOGON_HOURS; + +struct SR_SECURITY_DESCRIPTOR { + ULONG Length; + PUCHAR SecurityDescriptor; +} +alias SR_SECURITY_DESCRIPTOR* PSR_SECURITY_DESCRIPTOR; + +align(4): +struct USER_ALL_INFORMATION { + LARGE_INTEGER LastLogon; + LARGE_INTEGER LastLogoff; + LARGE_INTEGER PasswordLastSet; + LARGE_INTEGER AccountExpires; + LARGE_INTEGER PasswordCanChange; + LARGE_INTEGER PasswordMustChange; + UNICODE_STRING UserName; + UNICODE_STRING FullName; + UNICODE_STRING HomeDirectory; + UNICODE_STRING HomeDirectoryDrive; + UNICODE_STRING ScriptPath; + UNICODE_STRING ProfilePath; + UNICODE_STRING AdminComment; + UNICODE_STRING WorkStations; + UNICODE_STRING UserComment; + UNICODE_STRING Parameters; + UNICODE_STRING LmPassword; + UNICODE_STRING NtPassword; + UNICODE_STRING PrivateData; + SR_SECURITY_DESCRIPTOR SecurityDescriptor; + ULONG UserId; + ULONG PrimaryGroupId; + ULONG UserAccountControl; + ULONG WhichFields; + LOGON_HOURS LogonHours; + USHORT BadPasswordCount; + USHORT LogonCount; + USHORT CountryCode; + USHORT CodePage; + BOOLEAN LmPasswordPresent; + BOOLEAN NtPasswordPresent; + BOOLEAN PasswordExpired; + BOOLEAN PrivateDataSensitive; +} +alias USER_ALL_INFORMATION* PUSER_ALL_INFORMATION; +align: + +struct MSV1_0_VALIDATION_INFO { + LARGE_INTEGER LogoffTime; + LARGE_INTEGER KickoffTime; + UNICODE_STRING LogonServer; + UNICODE_STRING LogonDomainName; + USER_SESSION_KEY SessionKey; + BOOLEAN Authoritative; + ULONG UserFlags; + ULONG WhichFields; + ULONG UserId; +} +alias MSV1_0_VALIDATION_INFO* PMSV1_0_VALIDATION_INFO; + +struct NETLOGON_LOGON_IDENTITY_INFO { + UNICODE_STRING LogonDomainName; + ULONG ParameterControl; + OLD_LARGE_INTEGER LogonId; + UNICODE_STRING UserName; + UNICODE_STRING Workstation; +} +alias NETLOGON_LOGON_IDENTITY_INFO* PNETLOGON_LOGON_IDENTITY_INFO; + +struct NETLOGON_INTERACTIVE_INFO { + NETLOGON_LOGON_IDENTITY_INFO Identity; + LM_OWF_PASSWORD LmOwfPassword; + NT_OWF_PASSWORD NtOwfPassword; +} +alias NETLOGON_INTERACTIVE_INFO* PNETLOGON_INTERACTIVE_INFO; + +struct NETLOGON_GENERIC_INFO { + NETLOGON_LOGON_IDENTITY_INFO Identity; + UNICODE_STRING PackageName; + ULONG DataLength; + PUCHAR LogonData; +} +alias NETLOGON_GENERIC_INFO* PNETLOGON_GENERIC_INFO; + +struct NETLOGON_NETWORK_INFO { + NETLOGON_LOGON_IDENTITY_INFO Identity; + LM_CHALLENGE LmChallenge; + STRING NtChallengeResponse; + STRING LmChallengeResponse; +} +alias NETLOGON_NETWORK_INFO* PNETLOGON_NETWORK_INFO; + +struct NETLOGON_SERVICE_INFO { + NETLOGON_LOGON_IDENTITY_INFO Identity; + LM_OWF_PASSWORD LmOwfPassword; + NT_OWF_PASSWORD NtOwfPassword; +} +alias NETLOGON_SERVICE_INFO* PNETLOGON_SERVICE_INFO; + +extern (Windows) { +NTSTATUS Msv1_0SubAuthenticationRoutine(NETLOGON_LOGON_INFO_CLASS,PVOID, + ULONG,PUSER_ALL_INFORMATION,PULONG,PULONG, + PBOOLEAN,PLARGE_INTEGER,PLARGE_INTEGER); +NTSTATUS Msv1_0SubAuthenticationFilter(NETLOGON_LOGON_INFO_CLASS,PVOID, + ULONG,PUSER_ALL_INFORMATION,PULONG,PULONG, + PBOOLEAN,PLARGE_INTEGER,PLARGE_INTEGER); +NTSTATUS Msv1_0SubAuthenticationRoutineGeneric(PVOID,ULONG,PULONG,PVOID*); +NTSTATUS Msv1_0SubAuthenticationRoutineEx(NETLOGON_LOGON_INFO_CLASS,PVOID, + ULONG,PUSER_ALL_INFORMATION,SAM_HANDLE, + PMSV1_0_VALIDATION_INFO,PULONG); +} diff --git a/src/urt/internal/sys/windows/w32api.d b/src/urt/internal/sys/windows/w32api.d new file mode 100644 index 0000000..94ed9ac --- /dev/null +++ b/src/urt/internal/sys/windows/w32api.d @@ -0,0 +1,138 @@ +/** + * Windows API header module + * + * Translated from MinGW API for MS-Windows 4.0 + * + * Authors: Stewart Gordon + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_w32api.d) + */ +module urt.internal.sys.windows.w32api; +version (Windows): + +import urt.internal.sys.windows.sdkddkver; + +version (ANSI) {} else version = Unicode; + +enum __W32API_VERSION = 3.17; +enum __W32API_MAJOR_VERSION = 3; +enum __W32API_MINOR_VERSION = 17; + +enum Windows95 = 0x0400; +enum Windows98 = 0x0410; +enum WindowsME = 0x0500; + +enum WindowsNT4 = 0x0400; +enum Windows2000 = 0x0500; +enum WindowsXP = 0x0501; +enum Windows2003 = 0x0502; +enum WindowsVista = 0x0600; +enum Windows7 = 0x0601; +enum Windows8 = 0x0602; + +enum IE3 = 0x0300; +enum IE301 = 0x0300; +enum IE302 = 0x0300; +enum IE4 = 0x0400; +enum IE401 = 0x0401; +enum IE5 = 0x0500; +enum IE5a = 0x0500; +enum IE5b = 0x0500; +enum IE501 = 0x0501; +enum IE55 = 0x0501; +enum IE56 = 0x0560; +enum IE6 = 0x0600; +enum IE601 = 0x0601; +enum IE602 = 0x0603; +enum IE7 = 0x0700; +enum IE8 = 0x0800; +enum IE9 = 0x0900; +enum IE10 = 0x0A00; + +/* These version identifiers are used to specify the minimum version of Windows that an + * application will support. + * + * Previously the minimum Windows 9x and Windows NT versions could be specified. However, since + * Windows 9x is no longer supported, either by Microsoft or by DMD, this distinction has been + * removed in order to simplify the bindings. + */ +version (Windows11) { + enum uint _WIN32_WINNT = _WIN32_WINNT_WIN10; +} else version (Windows10) { + enum uint _WIN32_WINNT = _WIN32_WINNT_WIN10; +} else version (Windows8_1) { // also Windows2012R2 + enum uint _WIN32_WINNT = _WIN32_WINNT_WINBLUE; +} else version (Windows8) { // also Windows2012 + enum uint _WIN32_WINNT = _WIN32_WINNT_WIN8; +} else version (Windows7) { // also Windows2008R2 + enum uint _WIN32_WINNT = _WIN32_WINNT_WIN7; +} else version (WindowsVista) { // also Windows2008 + enum uint _WIN32_WINNT = _WIN32_WINNT_VISTA; +} else version (Windows2003) { // also WindowsHomeServer, WindowsXP64 + enum uint _WIN32_WINNT = _WIN32_WINNT_WS03; +} else version (WindowsXP) { + enum uint _WIN32_WINNT = _WIN32_WINNT_WINXP; +} else version (Windows2000) { + // Current DMD doesn't support any version of Windows older than XP, + // but third-party compilers could use this + enum uint _WIN32_WINNT = _WIN32_WINNT_WIN2K; +} else { + enum uint _WIN32_WINNT = _WIN32_WINNT_WIN7; +} + +version (IE11) { + enum uint _WIN32_IE = _WIN32_IE_IE110; +} else version (IE10) { + enum uint _WIN32_IE = _WIN32_IE_IE100; +} else version (IE9) { + enum uint _WIN32_IE = _WIN32_IE_IE90; +} else version (IE8) { + enum uint _WIN32_IE = _WIN32_IE_IE80; +} else version (IE7) { + enum uint _WIN32_IE = _WIN32_IE_IE70; +} else version (IE602) { + enum uint _WIN32_IE = _WIN32_IE_IE60SP2; +} else version (IE601) { + enum uint _WIN32_IE = _WIN32_IE_IE60SP1; +} else version (IE6) { + enum uint _WIN32_IE = _WIN32_IE_IE60; +} else version (IE56) { + enum uint _WIN32_IE = _WIN32_IE_IE60; +} else version (IE55) { + enum uint _WIN32_IE = _WIN32_IE_IE55; +} else version (IE501) { + enum uint _WIN32_IE = _WIN32_IE_IE501; +} else version (IE5) { + enum uint _WIN32_IE = _WIN32_IE_IE50; +} else version (IE401) { + enum uint _WIN32_IE = _WIN32_IE_IE401; +} else version (IE4) { + enum uint _WIN32_IE = _WIN32_IE_IE40; +} else version (IE3) { + enum uint _WIN32_IE = _WIN32_IE_IE30; +} else static if (_WIN32_WINNT >= _WIN32_WINNT_WIN2K) { + enum uint _WIN32_IE = _WIN32_IE_IE60; +} else static if (_WIN32_WINNT >= Windows98) { //NOTE: _WIN32_WINNT will never be set this low + enum uint _WIN32_IE = _WIN32_IE_IE40; +} else { + enum uint _WIN32_IE = 0; +} + +debug (WindowsUnitTest) { + unittest { + printf("Windows NT version: %03x\n", _WIN32_WINNT); + printf("IE version: %03x\n", _WIN32_IE); + } +} + +version (Unicode) { + enum bool _WIN32_UNICODE = true; + package template DECLARE_AW(string name) { + mixin("alias " ~ name ~ "W " ~ name ~ ";"); + } +} else { + enum bool _WIN32_UNICODE = false; + package template DECLARE_AW(string name) { + mixin("alias " ~ name ~ "A " ~ name ~ ";"); + } +} diff --git a/src/urt/internal/sys/windows/winbase.d b/src/urt/internal/sys/windows/winbase.d new file mode 100644 index 0000000..59d208c --- /dev/null +++ b/src/urt/internal/sys/windows/winbase.d @@ -0,0 +1,2941 @@ +/** + * Windows API header module + * + * Translated from MinGW API for MS-Windows 3.10 + * + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_winbase.d) + */ +module urt.internal.sys.windows.winbase; +version (Windows): + +version (ANSI) {} else version = Unicode; +pragma(lib, "kernel32"); + +/** +Translation Notes: +The following macros are obsolete, and have no effect. + +LockSegment(w), MakeProcInstance(p, i), UnlockResource(h), UnlockSegment(w) +FreeModule(m), FreeProcInstance(p), GetFreeSpace(w), DefineHandleTable(w) +SetSwapAreaSize(w), LimitEmsPages(n), Yield() + +// These are not required for DMD. + +//FIXME: +// #ifndef UNDER_CE + int WinMain(HINSTANCE, HINSTANCE, LPSTR, int); +#else + int WinMain(HINSTANCE, HINSTANCE, LPWSTR, int); +#endif +int wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int); + +*/ + +import urt.internal.sys.windows.windef; +import urt.internal.sys.windows.basetyps, urt.internal.sys.windows.w32api, urt.internal.sys.windows.winnt; + +// FIXME: +//alias void va_list; +//import urt.internal.stdc : va_list; +import urt.mem : memset, memcpy, memmove; + + +// COMMPROP structure, used by GetCommProperties() +// ----------------------------------------------- + +// Communications provider type +enum : DWORD { + PST_UNSPECIFIED, + PST_RS232, + PST_PARALLELPORT, + PST_RS422, + PST_RS423, + PST_RS449, + PST_MODEM, // = 6 + PST_FAX = 0x0021, + PST_SCANNER = 0x0022, + PST_NETWORK_BRIDGE = 0x0100, + PST_LAT = 0x0101, + PST_TCPIP_TELNET = 0x0102, + PST_X25 = 0x0103 +} + +// Max baud rate +enum : DWORD { + BAUD_075 = 0x00000001, + BAUD_110 = 0x00000002, + BAUD_134_5 = 0x00000004, + BAUD_150 = 0x00000008, + BAUD_300 = 0x00000010, + BAUD_600 = 0x00000020, + BAUD_1200 = 0x00000040, + BAUD_1800 = 0x00000080, + BAUD_2400 = 0x00000100, + BAUD_4800 = 0x00000200, + BAUD_7200 = 0x00000400, + BAUD_9600 = 0x00000800, + BAUD_14400 = 0x00001000, + BAUD_19200 = 0x00002000, + BAUD_38400 = 0x00004000, + BAUD_56K = 0x00008000, + BAUD_128K = 0x00010000, + BAUD_115200 = 0x00020000, + BAUD_57600 = 0x00040000, + BAUD_USER = 0x10000000 +} + +// Comm capabilities +enum : DWORD { + PCF_DTRDSR = 0x0001, + PCF_RTSCTS = 0x0002, + PCF_RLSD = 0x0004, + PCF_PARITY_CHECK = 0x0008, + PCF_XONXOFF = 0x0010, + PCF_SETXCHAR = 0x0020, + PCF_TOTALTIMEOUTS = 0x0040, + PCF_INTTIMEOUTS = 0x0080, + PCF_SPECIALCHARS = 0x0100, + PCF_16BITMODE = 0x0200 +} + +enum : DWORD { + SP_PARITY = 1, + SP_BAUD = 2, + SP_DATABITS = 4, + SP_STOPBITS = 8, + SP_HANDSHAKING = 16, + SP_PARITY_CHECK = 32, + SP_RLSD = 64 +} + +enum : DWORD { + DATABITS_5 = 1, + DATABITS_6 = 2, + DATABITS_7 = 4, + DATABITS_8 = 8, + DATABITS_16 = 16, + DATABITS_16X = 32 +} + +enum : WORD { + STOPBITS_10 = 0x0001, + STOPBITS_15 = 0x0002, + STOPBITS_20 = 0x0004, + PARITY_NONE = 0x0100, + PARITY_ODD = 0x0200, + PARITY_EVEN = 0x0400, + PARITY_MARK = 0x0800, + PARITY_SPACE = 0x1000 +} + +// used by dwServiceMask +enum SP_SERIALCOMM = 1; + +struct COMMPROP { + WORD wPacketLength; + WORD wPacketVersion; + DWORD dwServiceMask; + DWORD dwReserved1; + DWORD dwMaxTxQueue; + DWORD dwMaxRxQueue; + DWORD dwMaxBaud; + DWORD dwProvSubType; + DWORD dwProvCapabilities; + DWORD dwSettableParams; + DWORD dwSettableBaud; + WORD wSettableData; + WORD wSettableStopParity; + DWORD dwCurrentTxQueue; + DWORD dwCurrentRxQueue; + DWORD dwProvSpec1; + DWORD dwProvSpec2; + WCHAR _wcProvChar = 0; + + WCHAR* wcProvChar() return { return &_wcProvChar; } +} +alias COMMPROP* LPCOMMPROP; + +// ---------- + +// for DEBUG_EVENT +enum : DWORD { + EXCEPTION_DEBUG_EVENT = 1, + CREATE_THREAD_DEBUG_EVENT, + CREATE_PROCESS_DEBUG_EVENT, + EXIT_THREAD_DEBUG_EVENT, + EXIT_PROCESS_DEBUG_EVENT, + LOAD_DLL_DEBUG_EVENT, + UNLOAD_DLL_DEBUG_EVENT, + OUTPUT_DEBUG_STRING_EVENT, + RIP_EVENT +} + +enum HFILE HFILE_ERROR = cast(HFILE) (-1); + +// for SetFilePointer() +enum : DWORD { + FILE_BEGIN = 0, + FILE_CURRENT = 1, + FILE_END = 2 +} +enum DWORD INVALID_SET_FILE_POINTER = -1; + + +// for OpenFile() +deprecated enum : UINT { + OF_READ = 0, + OF_WRITE = 0x0001, + OF_READWRITE = 0x0002, + OF_SHARE_COMPAT = 0, + OF_SHARE_EXCLUSIVE = 0x0010, + OF_SHARE_DENY_WRITE = 0x0020, + OF_SHARE_DENY_READ = 0x0030, + OF_SHARE_DENY_NONE = 0x0040, + OF_PARSE = 0x0100, + OF_DELETE = 0x0200, + OF_VERIFY = 0x0400, + OF_CANCEL = 0x0800, + OF_CREATE = 0x1000, + OF_PROMPT = 0x2000, + OF_EXIST = 0x4000, + OF_REOPEN = 0x8000 +} + +enum : DWORD { + NMPWAIT_NOWAIT = 1, + NMPWAIT_WAIT_FOREVER = -1, + NMPWAIT_USE_DEFAULT_WAIT = 0 +} + +// for ClearCommError() +enum DWORD + CE_RXOVER = 0x0001, + CE_OVERRUN = 0x0002, + CE_RXPARITY = 0x0004, + CE_FRAME = 0x0008, + CE_BREAK = 0x0010, + CE_TXFULL = 0x0100, + CE_PTO = 0x0200, + CE_IOE = 0x0400, + CE_DNS = 0x0800, + CE_OOP = 0x1000, + CE_MODE = 0x8000; + +// for CopyProgressRoutine callback. +enum : DWORD { + PROGRESS_CONTINUE = 0, + PROGRESS_CANCEL = 1, + PROGRESS_STOP = 2, + PROGRESS_QUIET = 3 +} + +enum : DWORD { + CALLBACK_CHUNK_FINISHED = 0, + CALLBACK_STREAM_SWITCH = 1 +} + +// CopyFileEx() +enum : DWORD { + COPY_FILE_FAIL_IF_EXISTS = 1, + COPY_FILE_RESTARTABLE = 2 +} + +enum : DWORD { + FILE_MAP_COPY = 1, + FILE_MAP_WRITE = 2, + FILE_MAP_READ = 4, + FILE_MAP_ALL_ACCESS = 0x000F001F +} + +enum : DWORD { + MUTEX_ALL_ACCESS = 0x001f0001, + MUTEX_MODIFY_STATE = 0x00000001, + SEMAPHORE_ALL_ACCESS = 0x001f0003, + SEMAPHORE_MODIFY_STATE = 0x00000002, + EVENT_ALL_ACCESS = 0x001f0003, + EVENT_MODIFY_STATE = 0x00000002 +} + +// CreateNamedPipe() +enum : DWORD { + PIPE_ACCESS_INBOUND = 1, + PIPE_ACCESS_OUTBOUND = 2, + PIPE_ACCESS_DUPLEX = 3 +} + +enum DWORD + PIPE_TYPE_BYTE = 0, + PIPE_TYPE_MESSAGE = 4, + PIPE_READMODE_BYTE = 0, + PIPE_READMODE_MESSAGE = 2, + PIPE_WAIT = 0, + PIPE_NOWAIT = 1; + +// GetNamedPipeInfo() +enum DWORD + PIPE_CLIENT_END = 0, + PIPE_SERVER_END = 1; + +enum DWORD PIPE_UNLIMITED_INSTANCES = 255; + +// dwCreationFlags for CreateProcess() and CreateProcessAsUser() +enum : DWORD { + DEBUG_PROCESS = 0x00000001, + DEBUG_ONLY_THIS_PROCESS = 0x00000002, + CREATE_SUSPENDED = 0x00000004, + DETACHED_PROCESS = 0x00000008, + CREATE_NEW_CONSOLE = 0x00000010, + NORMAL_PRIORITY_CLASS = 0x00000020, + IDLE_PRIORITY_CLASS = 0x00000040, + HIGH_PRIORITY_CLASS = 0x00000080, + REALTIME_PRIORITY_CLASS = 0x00000100, + CREATE_NEW_PROCESS_GROUP = 0x00000200, + CREATE_UNICODE_ENVIRONMENT = 0x00000400, + CREATE_SEPARATE_WOW_VDM = 0x00000800, + CREATE_SHARED_WOW_VDM = 0x00001000, + CREATE_FORCEDOS = 0x00002000, + BELOW_NORMAL_PRIORITY_CLASS = 0x00004000, + ABOVE_NORMAL_PRIORITY_CLASS = 0x00008000, + CREATE_BREAKAWAY_FROM_JOB = 0x01000000, + CREATE_WITH_USERPROFILE = 0x02000000, + CREATE_DEFAULT_ERROR_MODE = 0x04000000, + CREATE_NO_WINDOW = 0x08000000, + PROFILE_USER = 0x10000000, + PROFILE_KERNEL = 0x20000000, + PROFILE_SERVER = 0x40000000 +} + +enum DWORD CONSOLE_TEXTMODE_BUFFER = 1; + +// CreateFile() +enum : DWORD { + CREATE_NEW = 1, + CREATE_ALWAYS, + OPEN_EXISTING, + OPEN_ALWAYS, + TRUNCATE_EXISTING +} + +// CreateFile() +enum DWORD + FILE_FLAG_WRITE_THROUGH = 0x80000000, + FILE_FLAG_OVERLAPPED = 0x40000000, + FILE_FLAG_NO_BUFFERING = 0x20000000, + FILE_FLAG_RANDOM_ACCESS = 0x10000000, + FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000, + FILE_FLAG_DELETE_ON_CLOSE = 0x04000000, + FILE_FLAG_BACKUP_SEMANTICS = 0x02000000, + FILE_FLAG_POSIX_SEMANTICS = 0x01000000, + FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000, + FILE_FLAG_OPEN_NO_RECALL = 0x00100000; + +static if (_WIN32_WINNT >= 0x500) { +enum DWORD FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000; +} + +// for CreateFile() +enum DWORD + SECURITY_ANONYMOUS = SECURITY_IMPERSONATION_LEVEL.SecurityAnonymous<<16, + SECURITY_IDENTIFICATION = SECURITY_IMPERSONATION_LEVEL.SecurityIdentification<<16, + SECURITY_IMPERSONATION = SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation<<16, + SECURITY_DELEGATION = SECURITY_IMPERSONATION_LEVEL.SecurityDelegation<<16, + SECURITY_CONTEXT_TRACKING = 0x00040000, + SECURITY_EFFECTIVE_ONLY = 0x00080000, + SECURITY_SQOS_PRESENT = 0x00100000, + SECURITY_VALID_SQOS_FLAGS = 0x001F0000; + +// for GetFinalPathNameByHandle() +enum DWORD + VOLUME_NAME_DOS = 0x0, + VOLUME_NAME_GUID = 0x1, + VOLUME_NAME_NT = 0x2, + VOLUME_NAME_NONE = 0x4, + FILE_NAME_NORMALIZED = 0x0, + FILE_NAME_OPENED = 0x8; + +// Thread exit code +enum DWORD STILL_ACTIVE = 0x103; + +/* ??? The only documentation of this seems to be about Windows CE and to + * state what _doesn't_ support it. + */ +enum DWORD FIND_FIRST_EX_CASE_SENSITIVE = 1; + +// GetBinaryType() +enum : DWORD { + SCS_32BIT_BINARY = 0, + SCS_DOS_BINARY, + SCS_WOW_BINARY, + SCS_PIF_BINARY, + SCS_POSIX_BINARY, + SCS_OS216_BINARY +} + +enum size_t + MAX_COMPUTERNAME_LENGTH = 15, + HW_PROFILE_GUIDLEN = 39, + MAX_PROFILE_LEN = 80; + +// HW_PROFILE_INFO +enum DWORD + DOCKINFO_UNDOCKED = 1, + DOCKINFO_DOCKED = 2, + DOCKINFO_USER_SUPPLIED = 4, + DOCKINFO_USER_UNDOCKED = DOCKINFO_USER_SUPPLIED | DOCKINFO_UNDOCKED, + DOCKINFO_USER_DOCKED = DOCKINFO_USER_SUPPLIED | DOCKINFO_DOCKED; + +// DriveType(), RealDriveType() +enum : int { + DRIVE_UNKNOWN = 0, + DRIVE_NO_ROOT_DIR, + DRIVE_REMOVABLE, + DRIVE_FIXED, + DRIVE_REMOTE, + DRIVE_CDROM, + DRIVE_RAMDISK +} + +// GetFileType() +enum : DWORD { + FILE_TYPE_UNKNOWN = 0, + FILE_TYPE_DISK, + FILE_TYPE_CHAR, + FILE_TYPE_PIPE, + FILE_TYPE_REMOTE = 0x8000 +} + +// Get/SetHandleInformation() +enum DWORD + HANDLE_FLAG_INHERIT = 0x01, + HANDLE_FLAG_PROTECT_FROM_CLOSE = 0x02; + +enum : DWORD { + STD_INPUT_HANDLE = 0xFFFFFFF6, + STD_OUTPUT_HANDLE = 0xFFFFFFF5, + STD_ERROR_HANDLE = 0xFFFFFFF4 +} + +@trusted enum HANDLE INVALID_HANDLE_VALUE = cast(HANDLE) (-1); + +enum : DWORD { + GET_TAPE_MEDIA_INFORMATION = 0, + GET_TAPE_DRIVE_INFORMATION = 1 +} + +enum : DWORD { + SET_TAPE_MEDIA_INFORMATION = 0, + SET_TAPE_DRIVE_INFORMATION = 1 +} + +// SetThreadPriority()/GetThreadPriority() +enum : int { + THREAD_PRIORITY_IDLE = -15, + THREAD_PRIORITY_LOWEST = -2, + THREAD_PRIORITY_BELOW_NORMAL = -1, + THREAD_PRIORITY_NORMAL = 0, + THREAD_PRIORITY_ABOVE_NORMAL = 1, + THREAD_PRIORITY_HIGHEST = 2, + THREAD_PRIORITY_TIME_CRITICAL = 15, + THREAD_PRIORITY_ERROR_RETURN = 2147483647 +} + +enum : DWORD { + TIME_ZONE_ID_UNKNOWN, + TIME_ZONE_ID_STANDARD, + TIME_ZONE_ID_DAYLIGHT, + TIME_ZONE_ID_INVALID = 0xFFFFFFFF +} + +enum DWORD + FS_CASE_SENSITIVE = 1, + FS_CASE_IS_PRESERVED = 2, + FS_UNICODE_STORED_ON_DISK = 4, + FS_PERSISTENT_ACLS = 8, + FS_FILE_COMPRESSION = 16, + FS_VOL_IS_COMPRESSED = 32768; + +// Flags for GlobalAlloc +enum UINT + GMEM_FIXED = 0, + GMEM_MOVEABLE = 0x0002, + GMEM_ZEROINIT = 0x0040, + GPTR = 0x0040, + GHND = 0x0042, + GMEM_MODIFY = 0x0080, // used only for GlobalRealloc + GMEM_VALID_FLAGS = 0x7F72; + +/+ // Obselete flags (Win16 only) + GMEM_NOCOMPACT=16; + GMEM_NODISCARD=32; + GMEM_DISCARDABLE=256; + GMEM_NOT_BANKED=4096; + GMEM_LOWER=4096; + GMEM_SHARE=8192; + GMEM_DDESHARE=8192; + + GMEM_LOCKCOUNT=255; + +// for GlobalFlags() + GMEM_DISCARDED = 16384; + GMEM_INVALID_HANDLE = 32768; + + GMEM_NOTIFY = 16384; ++/ + +enum UINT + LMEM_FIXED = 0, + LMEM_MOVEABLE = 0x0002, + LMEM_NONZEROLPTR = 0, + NONZEROLPTR = 0, + LMEM_NONZEROLHND = 0x0002, + NONZEROLHND = 0x0002, + LMEM_DISCARDABLE = 0x0F00, + LMEM_NOCOMPACT = 0x0010, + LMEM_NODISCARD = 0x0020, + LMEM_ZEROINIT = 0x0040, + LPTR = 0x0040, + LHND = 0x0042, + LMEM_MODIFY = 0x0080, + LMEM_LOCKCOUNT = 0x00FF, + LMEM_DISCARDED = 0x4000, + LMEM_INVALID_HANDLE = 0x8000; + + + +// used in EXCEPTION_RECORD +enum : DWORD { + STATUS_WAIT_0 = 0, + STATUS_ABANDONED_WAIT_0 = 0x00000080, + STATUS_USER_APC = 0x000000C0, + STATUS_TIMEOUT = 0x00000102, + STATUS_PENDING = 0x00000103, + + STATUS_SEGMENT_NOTIFICATION = 0x40000005, + STATUS_GUARD_PAGE_VIOLATION = 0x80000001, + STATUS_DATATYPE_MISALIGNMENT = 0x80000002, + STATUS_BREAKPOINT = 0x80000003, + STATUS_SINGLE_STEP = 0x80000004, + + STATUS_ACCESS_VIOLATION = 0xC0000005, + STATUS_IN_PAGE_ERROR = 0xC0000006, + STATUS_INVALID_HANDLE = 0xC0000008, + + STATUS_NO_MEMORY = 0xC0000017, + STATUS_ILLEGAL_INSTRUCTION = 0xC000001D, + STATUS_NONCONTINUABLE_EXCEPTION = 0xC0000025, + STATUS_INVALID_DISPOSITION = 0xC0000026, + STATUS_ARRAY_BOUNDS_EXCEEDED = 0xC000008C, + STATUS_FLOAT_DENORMAL_OPERAND = 0xC000008D, + STATUS_FLOAT_DIVIDE_BY_ZERO = 0xC000008E, + STATUS_FLOAT_INEXACT_RESULT = 0xC000008F, + STATUS_FLOAT_INVALID_OPERATION = 0xC0000090, + STATUS_FLOAT_OVERFLOW = 0xC0000091, + STATUS_FLOAT_STACK_CHECK = 0xC0000092, + STATUS_FLOAT_UNDERFLOW = 0xC0000093, + STATUS_INTEGER_DIVIDE_BY_ZERO = 0xC0000094, + STATUS_INTEGER_OVERFLOW = 0xC0000095, + STATUS_PRIVILEGED_INSTRUCTION = 0xC0000096, + STATUS_STACK_OVERFLOW = 0xC00000FD, + STATUS_CONTROL_C_EXIT = 0xC000013A, + STATUS_DLL_INIT_FAILED = 0xC0000142, + STATUS_DLL_INIT_FAILED_LOGOFF = 0xC000026B, + + CONTROL_C_EXIT = STATUS_CONTROL_C_EXIT, + + EXCEPTION_ACCESS_VIOLATION = STATUS_ACCESS_VIOLATION, + EXCEPTION_DATATYPE_MISALIGNMENT = STATUS_DATATYPE_MISALIGNMENT, + EXCEPTION_BREAKPOINT = STATUS_BREAKPOINT, + EXCEPTION_SINGLE_STEP = STATUS_SINGLE_STEP, + EXCEPTION_ARRAY_BOUNDS_EXCEEDED = STATUS_ARRAY_BOUNDS_EXCEEDED, + EXCEPTION_FLT_DENORMAL_OPERAND = STATUS_FLOAT_DENORMAL_OPERAND, + EXCEPTION_FLT_DIVIDE_BY_ZERO = STATUS_FLOAT_DIVIDE_BY_ZERO, + EXCEPTION_FLT_INEXACT_RESULT = STATUS_FLOAT_INEXACT_RESULT, + EXCEPTION_FLT_INVALID_OPERATION = STATUS_FLOAT_INVALID_OPERATION, + EXCEPTION_FLT_OVERFLOW = STATUS_FLOAT_OVERFLOW, + EXCEPTION_FLT_STACK_CHECK = STATUS_FLOAT_STACK_CHECK, + EXCEPTION_FLT_UNDERFLOW = STATUS_FLOAT_UNDERFLOW, + EXCEPTION_INT_DIVIDE_BY_ZERO = STATUS_INTEGER_DIVIDE_BY_ZERO, + EXCEPTION_INT_OVERFLOW = STATUS_INTEGER_OVERFLOW, + EXCEPTION_PRIV_INSTRUCTION = STATUS_PRIVILEGED_INSTRUCTION, + EXCEPTION_IN_PAGE_ERROR = STATUS_IN_PAGE_ERROR, + EXCEPTION_ILLEGAL_INSTRUCTION = STATUS_ILLEGAL_INSTRUCTION, + EXCEPTION_NONCONTINUABLE_EXCEPTION = STATUS_NONCONTINUABLE_EXCEPTION, + EXCEPTION_STACK_OVERFLOW = STATUS_STACK_OVERFLOW, + EXCEPTION_INVALID_DISPOSITION = STATUS_INVALID_DISPOSITION, + EXCEPTION_GUARD_PAGE = STATUS_GUARD_PAGE_VIOLATION, + EXCEPTION_INVALID_HANDLE = STATUS_INVALID_HANDLE +} + +// for PROCESS_HEAP_ENTRY +enum WORD + PROCESS_HEAP_REGION = 1, + PROCESS_HEAP_UNCOMMITTED_RANGE = 2, + PROCESS_HEAP_ENTRY_BUSY = 4, + PROCESS_HEAP_ENTRY_MOVEABLE = 16, + PROCESS_HEAP_ENTRY_DDESHARE = 32; + +// for LoadLibraryEx() +enum DWORD + DONT_RESOLVE_DLL_REFERENCES = 0x01, // not for WinME and earlier + LOAD_LIBRARY_AS_DATAFILE = 0x02, + LOAD_WITH_ALTERED_SEARCH_PATH = 0x08, + LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x10; // only for XP and later + +// for LockFile() +enum DWORD + LOCKFILE_FAIL_IMMEDIATELY = 1, + LOCKFILE_EXCLUSIVE_LOCK = 2; + +enum MAXIMUM_WAIT_OBJECTS = 64; +enum MAXIMUM_SUSPEND_COUNT = 0x7F; + +enum WAIT_OBJECT_0 = 0; +enum WAIT_ABANDONED_0 = 128; + +//const WAIT_TIMEOUT=258; // also in winerror.h + +enum : DWORD { + WAIT_IO_COMPLETION = 0x000000C0, + WAIT_ABANDONED = 0x00000080, + WAIT_FAILED = 0xFFFFFFFF +} + +// PurgeComm() +enum DWORD + PURGE_TXABORT = 1, + PURGE_RXABORT = 2, + PURGE_TXCLEAR = 4, + PURGE_RXCLEAR = 8; + +// ReadEventLog() +enum DWORD + EVENTLOG_SEQUENTIAL_READ = 1, + EVENTLOG_SEEK_READ = 2, + EVENTLOG_FORWARDS_READ = 4, + EVENTLOG_BACKWARDS_READ = 8; + +// ReportEvent() +enum : WORD { + EVENTLOG_SUCCESS = 0, + EVENTLOG_ERROR_TYPE = 1, + EVENTLOG_WARNING_TYPE = 2, + EVENTLOG_INFORMATION_TYPE = 4, + EVENTLOG_AUDIT_SUCCESS = 8, + EVENTLOG_AUDIT_FAILURE = 16 +} + +// FormatMessage() +enum DWORD + FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x0100, + FORMAT_MESSAGE_IGNORE_INSERTS = 0x0200, + FORMAT_MESSAGE_FROM_STRING = 0x0400, + FORMAT_MESSAGE_FROM_HMODULE = 0x0800, + FORMAT_MESSAGE_FROM_SYSTEM = 0x1000, + FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x2000; + +enum DWORD FORMAT_MESSAGE_MAX_WIDTH_MASK = 255; + +// also in ddk/ntapi.h +// To restore default error mode, call SetErrorMode(0) +enum { + SEM_FAILCRITICALERRORS = 0x0001, + SEM_NOGPFAULTERRORBOX = 0x0002, + SEM_NOALIGNMENTFAULTEXCEPT = 0x0004, + SEM_NOOPENFILEERRORBOX = 0x8000 +} +// end ntapi.h + +enum { + SLE_ERROR = 1, + SLE_MINORERROR, + SLE_WARNING +} + +enum SHUTDOWN_NORETRY = 1; + +// Return type for exception filters. +enum : LONG { + EXCEPTION_EXECUTE_HANDLER = 1, + EXCEPTION_CONTINUE_EXECUTION = -1, + EXCEPTION_CONTINUE_SEARCH = 0 +} + +enum : ATOM { + MAXINTATOM = 0xC000, + INVALID_ATOM = 0 +} + +enum IGNORE = 0; +enum INFINITE = 0xFFFFFFFF; + +// EscapeCommFunction() +enum { + SETXOFF = 1, + SETXON, + SETRTS, + CLRRTS, + SETDTR, + CLRDTR, // = 6 + SETBREAK = 8, + CLRBREAK = 9 +} + + +// for SetCommMask() +enum DWORD + EV_RXCHAR = 0x0001, + EV_RXFLAG = 0x0002, + EV_TXEMPTY = 0x0004, + EV_CTS = 0x0008, + EV_DSR = 0x0010, + EV_RLSD = 0x0020, + EV_BREAK = 0x0040, + EV_ERR = 0x0080, + EV_RING = 0x0100, + EV_PERR = 0x0200, + EV_RX80FULL = 0x0400, + EV_EVENT1 = 0x0800, + EV_EVENT2 = 0x1000; + +// GetCommModemStatus() +enum DWORD + MS_CTS_ON = 0x0010, + MS_DSR_ON = 0x0020, + MS_RING_ON = 0x0040, + MS_RLSD_ON = 0x0080; + + +// DCB +enum : BYTE { + NOPARITY = 0, + ODDPARITY, + EVENPARITY, + MARKPARITY, + SPACEPARITY +} +// DCB +enum : BYTE { + ONESTOPBIT = 0, + ONE5STOPBITS, + TWOSTOPBITS +} +// DCB +enum : DWORD { + CBR_110 = 110, + CBR_300 = 300, + CBR_600 = 600, + CBR_1200 = 1200, + CBR_2400 = 2400, + CBR_4800 = 4800, + CBR_9600 = 9600, + CBR_14400 = 14400, + CBR_19200 = 19200, + CBR_38400 = 38400, + CBR_56000 = 56000, + CBR_57600 = 57600, + CBR_115200 = 115200, + CBR_128000 = 128000, + CBR_256000 = 256000 +} +// DCB, 2-bit bitfield +enum { + DTR_CONTROL_DISABLE = 0, + DTR_CONTROL_ENABLE, + DTR_CONTROL_HANDSHAKE +} + +// DCB, 2-bit bitfield +enum { + RTS_CONTROL_DISABLE = 0, + RTS_CONTROL_ENABLE, + RTS_CONTROL_HANDSHAKE, + RTS_CONTROL_TOGGLE, +} + +// WIN32_STREAM_ID +enum : DWORD { + BACKUP_INVALID = 0, + BACKUP_DATA, + BACKUP_EA_DATA, + BACKUP_SECURITY_DATA, + BACKUP_ALTERNATE_DATA, + BACKUP_LINK, + BACKUP_PROPERTY_DATA, + BACKUP_OBJECT_ID, + BACKUP_REPARSE_DATA, + BACKUP_SPARSE_BLOCK +} + +// WIN32_STREAM_ID +enum : DWORD { + STREAM_NORMAL_ATTRIBUTE = 0, + STREAM_MODIFIED_WHEN_READ = 1, + STREAM_CONTAINS_SECURITY = 2, + STREAM_CONTAINS_PROPERTIES = 4 +} + +// STARTUPINFO +enum DWORD + STARTF_USESHOWWINDOW = 0x0001, + STARTF_USESIZE = 0x0002, + STARTF_USEPOSITION = 0x0004, + STARTF_USECOUNTCHARS = 0x0008, + STARTF_USEFILLATTRIBUTE = 0x0010, + STARTF_RUNFULLSCREEN = 0x0020, + STARTF_FORCEONFEEDBACK = 0x0040, + STARTF_FORCEOFFFEEDBACK = 0x0080, + STARTF_USESTDHANDLES = 0x0100, + STARTF_USEHOTKEY = 0x0200; + +// ??? +enum { + TC_NORMAL = 0, + TC_HARDERR = 1, + TC_GP_TRAP = 2, + TC_SIGNAL = 3 +} + +/+ These seem to be Windows CE-specific +enum { + AC_LINE_OFFLINE = 0, + AC_LINE_ONLINE = 1, + AC_LINE_BACKUP_POWER = 2, + AC_LINE_UNKNOWN = 255 +} + +enum { + BATTERY_FLAG_HIGH = 1, + BATTERY_FLAG_LOW = 2, + BATTERY_FLAG_CRITICAL = 4, + BATTERY_FLAG_CHARGING = 8, + BATTERY_FLAG_NO_BATTERY = 128, + BATTERY_FLAG_UNKNOWN = 255, + BATTERY_PERCENTAGE_UNKNOWN = 255, + BATTERY_LIFE_UNKNOWN = 0xFFFFFFFF +} ++/ + +// ??? +enum HINSTANCE_ERROR = 32; + +// returned from GetFileSize() +enum DWORD INVALID_FILE_SIZE = 0xFFFFFFFF; + +enum DWORD TLS_OUT_OF_INDEXES = 0xFFFFFFFF; + +// GetWriteWatch() +enum DWORD WRITE_WATCH_FLAG_RESET = 1; + +// for LogonUser() +enum : DWORD { + LOGON32_LOGON_INTERACTIVE = 2, + LOGON32_LOGON_NETWORK = 3, + LOGON32_LOGON_BATCH = 4, + LOGON32_LOGON_SERVICE = 5, + LOGON32_LOGON_UNLOCK = 7 +} + +// for LogonUser() +enum : DWORD { + LOGON32_PROVIDER_DEFAULT, + LOGON32_PROVIDER_WINNT35, + LOGON32_PROVIDER_WINNT40, + LOGON32_PROVIDER_WINNT50 +} + +// for MoveFileEx() +enum DWORD + MOVEFILE_REPLACE_EXISTING = 1, + MOVEFILE_COPY_ALLOWED = 2, + MOVEFILE_DELAY_UNTIL_REBOOT = 4, + MOVEFILE_WRITE_THROUGH = 8; + +// DefineDosDevice() +enum DWORD + DDD_RAW_TARGET_PATH = 1, + DDD_REMOVE_DEFINITION = 2, + DDD_EXACT_MATCH_ON_REMOVE = 4; + +static if (_WIN32_WINNT >= 0x500) { + enum : DWORD { + LOGON32_LOGON_NETWORK_CLEARTEXT = 8, + LOGON32_LOGON_NEW_CREDENTIALS = 9 + } + + // ReplaceFile() +enum DWORD + REPLACEFILE_WRITE_THROUGH = 1, + REPLACEFILE_IGNORE_MERGE_ERRORS = 2; +} + +static if (_WIN32_WINNT >= 0x501) { +enum DWORD + GET_MODULE_HANDLE_EX_FLAG_PIN = 1, + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT = 2, + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS = 4; + + // for ACTCTX +enum DWORD + ACTCTX_FLAG_PROCESSOR_ARCHITECTURE_VALID = 0x01, + ACTCTX_FLAG_LANGID_VALID = 0x02, + ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID = 0x04, + ACTCTX_FLAG_RESOURCE_NAME_VALID = 0x08, + ACTCTX_FLAG_SET_PROCESS_DEFAULT = 0x10, + ACTCTX_FLAG_APPLICATION_NAME_VALID = 0x20, + ACTCTX_FLAG_HMODULE_VALID = 0x80; + + // DeactivateActCtx() +enum DWORD DEACTIVATE_ACTCTX_FLAG_FORCE_EARLY_DEACTIVATION = 1; + // FindActCtxSectionString() +enum DWORD FIND_ACTCTX_SECTION_KEY_RETURN_HACTCTX = 1; + // QueryActCtxW() +enum DWORD + QUERY_ACTCTX_FLAG_USE_ACTIVE_ACTCTX = 0x04, + QUERY_ACTCTX_FLAG_ACTCTX_IS_HMODULE = 0x08, + QUERY_ACTCTX_FLAG_ACTCTX_IS_ADDRESS = 0x10; + + enum { + LOGON_WITH_PROFILE = 1, + LOGON_NETCREDENTIALS_ONLY + } +} + +// ---- + +struct FILETIME { + DWORD dwLowDateTime; + DWORD dwHighDateTime; +} +alias FILETIME* PFILETIME, LPFILETIME; + +struct BY_HANDLE_FILE_INFORMATION { + DWORD dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + DWORD dwVolumeSerialNumber; + DWORD nFileSizeHigh; + DWORD nFileSizeLow; + DWORD nNumberOfLinks; + DWORD nFileIndexHigh; + DWORD nFileIndexLow; +} +alias BY_HANDLE_FILE_INFORMATION* LPBY_HANDLE_FILE_INFORMATION; + +struct DCB { + DWORD DCBlength = DCB.sizeof; + DWORD BaudRate; +/+ + DWORD fBinary:1; // Binary Mode (skip EOF check) + DWORD fParity:1; // Enable parity checking + DWORD fOutxCtsFlow:1; // CTS handshaking on output + DWORD fOutxDsrFlow:1; // DSR handshaking on output + DWORD fDtrControl:2; // DTR Flow control + DWORD fDsrSensitivity:1; // DSR Sensitivity + DWORD fTXContinueOnXoff:1; // Continue TX when Xoff sent + DWORD fOutX:1; // Enable output X-ON/X-OFF + DWORD fInX:1; // Enable input X-ON/X-OFF + DWORD fErrorChar:1; // Enable Err Replacement + DWORD fNull:1; // Enable Null stripping + DWORD fRtsControl:2; // Rts Flow control + DWORD fAbortOnError:1; // Abort all reads and writes on Error + DWORD fDummy2:17; // Reserved ++/ + uint _bf; + bool fBinary(bool f) { _bf = (_bf & ~0x0001) | f; return f; } + bool fParity(bool f) { _bf = (_bf & ~0x0002) | (f<<1); return f; } + bool fOutxCtsFlow(bool f) { _bf = (_bf & ~0x0004) | (f<<2); return f; } + bool fOutxDsrFlow(bool f) { _bf = (_bf & ~0x0008) | (f<<3); return f; } + byte fDtrControl(byte x) { _bf = (_bf & ~0x0030) | (x<<4); return cast(byte)(x & 3); } + bool fDsrSensitivity(bool f) { _bf = (_bf & ~0x0040) | (f<<6); return f; } + bool fTXContinueOnXoff(bool f) { _bf = (_bf & ~0x0080) | (f<<7); return f; } + bool fOutX(bool f) { _bf = (_bf & ~0x0100) | (f<<8); return f; } + bool fInX(bool f) { _bf = (_bf & ~0x0200) | (f<<9); return f; } + bool fErrorChar(bool f) { _bf = (_bf & ~0x0400) | (f<<10); return f; } + bool fNull(bool f) { _bf = (_bf & ~0x0800) | (f<<11); return f; } + byte fRtsControl(byte x) { _bf = (_bf & ~0x3000) | (x<<12); return cast(byte)(x & 3); } + bool fAbortOnError(bool f) { _bf = (_bf & ~0x4000) | (f<<14); return f; } + + bool fBinary() { return cast(bool) (_bf & 1); } + bool fParity() { return cast(bool) (_bf & 2); } + bool fOutxCtsFlow() { return cast(bool) (_bf & 4); } + bool fOutxDsrFlow() { return cast(bool) (_bf & 8); } + byte fDtrControl() { return cast(byte) ((_bf & (32+16))>>4); } + bool fDsrSensitivity() { return cast(bool) (_bf & 64); } + bool fTXContinueOnXoff()() { return cast(bool) (_bf & 128); } + bool fOutX() { return cast(bool) (_bf & 256); } + bool fInX() { return cast(bool) (_bf & 512); } + bool fErrorChar() { return cast(bool) (_bf & 1024); } + bool fNull() { return cast(bool) (_bf & 2048); } + byte fRtsControl() { return cast(byte) ((_bf & (4096+8192))>>12); } + bool fAbortOnError() { return cast(bool) (_bf & 16384); } + + WORD wReserved; + WORD XonLim; + WORD XoffLim; + BYTE ByteSize; + BYTE Parity; + BYTE StopBits; + char XonChar = 0; + char XoffChar = 0; + char ErrorChar = 0; + char EofChar = 0; + char EvtChar = 0; + WORD wReserved1; +} +alias DCB* LPDCB; + +struct COMMCONFIG { + DWORD dwSize = COMMCONFIG.sizeof; + WORD wVersion; + WORD wReserved; + DCB dcb; + DWORD dwProviderSubType; + DWORD dwProviderOffset; + DWORD dwProviderSize; + WCHAR _wcProviderData = 0; + + WCHAR* wcProviderData() return { return &_wcProviderData; } +} +alias COMMCONFIG* LPCOMMCONFIG; + +struct COMMTIMEOUTS { + DWORD ReadIntervalTimeout; + DWORD ReadTotalTimeoutMultiplier; + DWORD ReadTotalTimeoutConstant; + DWORD WriteTotalTimeoutMultiplier; + DWORD WriteTotalTimeoutConstant; +} +alias COMMTIMEOUTS* LPCOMMTIMEOUTS; + +struct COMSTAT { +/+ + DWORD fCtsHold:1; + DWORD fDsrHold:1; + DWORD fRlsdHold:1; + DWORD fXoffHold:1; + DWORD fXoffSent:1; + DWORD fEof:1; + DWORD fTxim:1; + DWORD fReserved:25; ++/ + DWORD _bf; + bool fCtsHold(bool f) { _bf = (_bf & ~1) | f; return f; } + bool fDsrHold(bool f) { _bf = (_bf & ~2) | (f<<1); return f; } + bool fRlsdHold(bool f) { _bf = (_bf & ~4) | (f<<2); return f; } + bool fXoffHold(bool f) { _bf = (_bf & ~8) | (f<<3); return f; } + bool fXoffSent(bool f) { _bf = (_bf & ~16) | (f<<4); return f; } + bool fEof(bool f) { _bf = (_bf & ~32) | (f<<5); return f; } + bool fTxim(bool f) { _bf = (_bf & ~64) | (f<<6); return f; } + + bool fCtsHold() { return cast(bool) (_bf & 1); } + bool fDsrHold() { return cast(bool) (_bf & 2); } + bool fRlsdHold()() { return cast(bool) (_bf & 4); } + bool fXoffHold()() { return cast(bool) (_bf & 8); } + bool fXoffSent()() { return cast(bool) (_bf & 16); } + bool fEof() { return cast(bool) (_bf & 32); } + bool fTxim() { return cast(bool) (_bf & 64); } + + DWORD cbInQue; + DWORD cbOutQue; +} +alias COMSTAT* LPCOMSTAT; + +struct CREATE_PROCESS_DEBUG_INFO { + HANDLE hFile; + HANDLE hProcess; + HANDLE hThread; + LPVOID lpBaseOfImage; + DWORD dwDebugInfoFileOffset; + DWORD nDebugInfoSize; + LPVOID lpThreadLocalBase; + LPTHREAD_START_ROUTINE lpStartAddress; + LPVOID lpImageName; + WORD fUnicode; +} +alias CREATE_PROCESS_DEBUG_INFO* LPCREATE_PROCESS_DEBUG_INFO; + +struct CREATE_THREAD_DEBUG_INFO { + HANDLE hThread; + LPVOID lpThreadLocalBase; + LPTHREAD_START_ROUTINE lpStartAddress; +} +alias CREATE_THREAD_DEBUG_INFO* LPCREATE_THREAD_DEBUG_INFO; + +struct EXCEPTION_DEBUG_INFO { + EXCEPTION_RECORD ExceptionRecord; + DWORD dwFirstChance; +} +alias EXCEPTION_DEBUG_INFO* LPEXCEPTION_DEBUG_INFO; + +struct EXIT_THREAD_DEBUG_INFO { + DWORD dwExitCode; +} +alias EXIT_THREAD_DEBUG_INFO* LPEXIT_THREAD_DEBUG_INFO; + +struct EXIT_PROCESS_DEBUG_INFO { + DWORD dwExitCode; +} +alias EXIT_PROCESS_DEBUG_INFO* LPEXIT_PROCESS_DEBUG_INFO; + +struct LOAD_DLL_DEBUG_INFO { + HANDLE hFile; + LPVOID lpBaseOfDll; + DWORD dwDebugInfoFileOffset; + DWORD nDebugInfoSize; + LPVOID lpImageName; + WORD fUnicode; +} +alias LOAD_DLL_DEBUG_INFO* LPLOAD_DLL_DEBUG_INFO; + +struct UNLOAD_DLL_DEBUG_INFO { + LPVOID lpBaseOfDll; +} +alias UNLOAD_DLL_DEBUG_INFO* LPUNLOAD_DLL_DEBUG_INFO; + +struct OUTPUT_DEBUG_STRING_INFO { + LPSTR lpDebugStringData; + WORD fUnicode; + WORD nDebugStringLength; +} +alias OUTPUT_DEBUG_STRING_INFO* LPOUTPUT_DEBUG_STRING_INFO; + +struct RIP_INFO { + DWORD dwError; + DWORD dwType; +} +alias RIP_INFO* LPRIP_INFO; + +struct DEBUG_EVENT { + DWORD dwDebugEventCode; + DWORD dwProcessId; + DWORD dwThreadId; + union { + EXCEPTION_DEBUG_INFO Exception; + CREATE_THREAD_DEBUG_INFO CreateThread; + CREATE_PROCESS_DEBUG_INFO CreateProcessInfo; + EXIT_THREAD_DEBUG_INFO ExitThread; + EXIT_PROCESS_DEBUG_INFO ExitProcess; + LOAD_DLL_DEBUG_INFO LoadDll; + UNLOAD_DLL_DEBUG_INFO UnloadDll; + OUTPUT_DEBUG_STRING_INFO DebugString; + RIP_INFO RipInfo; + } +} +alias DEBUG_EVENT* LPDEBUG_EVENT; + +struct OVERLAPPED { + ULONG_PTR Internal; + ULONG_PTR InternalHigh; + union { + struct { + DWORD Offset; + DWORD OffsetHigh; + } + PVOID Pointer; + } + HANDLE hEvent; +} +alias OVERLAPPED* POVERLAPPED, LPOVERLAPPED; + +struct STARTUPINFOA { + DWORD cb = STARTUPINFOA.sizeof; + LPSTR lpReserved; + LPSTR lpDesktop; + LPSTR lpTitle; + DWORD dwX; + DWORD dwY; + DWORD dwXSize; + DWORD dwYSize; + DWORD dwXCountChars; + DWORD dwYCountChars; + DWORD dwFillAttribute; + DWORD dwFlags; + WORD wShowWindow; + WORD cbReserved2; + PBYTE lpReserved2; + HANDLE hStdInput; + HANDLE hStdOutput; + HANDLE hStdError; +} +alias STARTUPINFOA* LPSTARTUPINFOA; + +struct STARTUPINFOW { + DWORD cb = STARTUPINFOW.sizeof; + LPWSTR lpReserved; + LPWSTR lpDesktop; + LPWSTR lpTitle; + DWORD dwX; + DWORD dwY; + DWORD dwXSize; + DWORD dwYSize; + DWORD dwXCountChars; + DWORD dwYCountChars; + DWORD dwFillAttribute; + DWORD dwFlags; + WORD wShowWindow; + WORD cbReserved2; + PBYTE lpReserved2; + HANDLE hStdInput; + HANDLE hStdOutput; + HANDLE hStdError; +} +alias STARTUPINFOW STARTUPINFO_W; +alias STARTUPINFOW* LPSTARTUPINFOW, LPSTARTUPINFO_W; + +struct PROCESS_INFORMATION { + HANDLE hProcess; + HANDLE hThread; + DWORD dwProcessId; + DWORD dwThreadId; +} +alias PROCESS_INFORMATION* PPROCESS_INFORMATION, LPPROCESS_INFORMATION; + +/* +struct CRITICAL_SECTION_DEBUG { + WORD Type; + WORD CreatorBackTraceIndex; + CRITICAL_SECTION* CriticalSection; + LIST_ENTRY ProcessLocksList; + DWORD EntryCount; + DWORD ContentionCount; + DWORD[2] Spare; +} +alias CRITICAL_SECTION_DEBUG* PCRITICAL_SECTION_DEBUG; + +struct CRITICAL_SECTION { + PCRITICAL_SECTION_DEBUG DebugInfo; + LONG LockCount; + LONG RecursionCount; + HANDLE OwningThread; + HANDLE LockSemaphore; + DWORD SpinCount; +} +alias CRITICAL_SECTION* PCRITICAL_SECTION, LPCRITICAL_SECTION; +*/ + +alias CRITICAL_SECTION_DEBUG = RTL_CRITICAL_SECTION_DEBUG; +alias CRITICAL_SECTION_DEBUG* PCRITICAL_SECTION_DEBUG; + +alias CRITICAL_SECTION = RTL_CRITICAL_SECTION; +alias CRITICAL_SECTION* PCRITICAL_SECTION, LPCRITICAL_SECTION; + +struct SYSTEMTIME { + WORD wYear; + WORD wMonth; + WORD wDayOfWeek; + WORD wDay; + WORD wHour; + WORD wMinute; + WORD wSecond; + WORD wMilliseconds; +} +alias SYSTEMTIME* LPSYSTEMTIME; + +struct WIN32_FILE_ATTRIBUTE_DATA { + DWORD dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + DWORD nFileSizeHigh; + DWORD nFileSizeLow; +} +alias WIN32_FILE_ATTRIBUTE_DATA* LPWIN32_FILE_ATTRIBUTE_DATA; + +struct WIN32_FIND_DATAA { + DWORD dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + DWORD nFileSizeHigh; + DWORD nFileSizeLow; +// #ifdef _WIN32_WCE +// DWORD dwOID; +// #else + DWORD dwReserved0; + DWORD dwReserved1; +// #endif + CHAR[MAX_PATH] cFileName = 0; +// #ifndef _WIN32_WCE + CHAR[14] cAlternateFileName = 0; +// #endif +} +alias WIN32_FIND_DATAA* PWIN32_FIND_DATAA, LPWIN32_FIND_DATAA; + +struct WIN32_FIND_DATAW { + DWORD dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + DWORD nFileSizeHigh; + DWORD nFileSizeLow; +// #ifdef _WIN32_WCE +// DWORD dwOID; +// #else + DWORD dwReserved0; + DWORD dwReserved1; +// #endif + WCHAR[MAX_PATH] cFileName = 0; +// #ifndef _WIN32_WCE + WCHAR[14] cAlternateFileName = 0; +// #endif +} +alias WIN32_FIND_DATAW* PWIN32_FIND_DATAW, LPWIN32_FIND_DATAW; + +struct WIN32_STREAM_ID { + DWORD dwStreamId; + DWORD dwStreamAttributes; + LARGE_INTEGER Size; + DWORD dwStreamNameSize; + WCHAR _cStreamName = 0; + + WCHAR* cStreamName() return { return &_cStreamName; } +} +alias WIN32_STREAM_ID* LPWIN32_STREAM_ID; + +static if (_WIN32_WINNT >= 0x601) { + enum FINDEX_INFO_LEVELS { + FindExInfoStandard, + FindExInfoBasic, + FindExInfoMaxInfoLevel, + } +} else { + enum FINDEX_INFO_LEVELS { + FindExInfoStandard, + FindExInfoMaxInfoLevel, + } +} + +enum FINDEX_SEARCH_OPS { + FindExSearchNameMatch, + FindExSearchLimitToDirectories, + FindExSearchLimitToDevices, + FindExSearchMaxSearchOp +} + +enum ACL_INFORMATION_CLASS { + AclRevisionInformation = 1, + AclSizeInformation +} + +struct HW_PROFILE_INFOA { + DWORD dwDockInfo; + CHAR[HW_PROFILE_GUIDLEN] szHwProfileGuid = 0; + CHAR[MAX_PROFILE_LEN] szHwProfileName = 0; +} +alias HW_PROFILE_INFOA* LPHW_PROFILE_INFOA; + +struct HW_PROFILE_INFOW { + DWORD dwDockInfo; + WCHAR[HW_PROFILE_GUIDLEN] szHwProfileGuid = 0; + WCHAR[MAX_PROFILE_LEN] szHwProfileName = 0; +} +alias HW_PROFILE_INFOW* LPHW_PROFILE_INFOW; + +/* ??? MSDN documents this only for Windows CE/Mobile, but it's used by + * GetFileAttributesEx, which is in desktop Windows. + */ +enum GET_FILEEX_INFO_LEVELS { + GetFileExInfoStandard, + GetFileExMaxInfoLevel +} + +import urt.internal.sys.windows.sdkddkver : NTDDI_VERSION, NTDDI_LONGHORN; + +static if (NTDDI_VERSION >= NTDDI_LONGHORN) +{ + enum FILE_INFO_BY_HANDLE_CLASS + { + FileBasicInfo, + FileStandardInfo, + FileNameInfo, + FileRenameInfo, + FileDispositionInfo, + FileAllocationInfo, + FileEndOfFileInfo, + FileStreamInfo, + FileCompressionInfo, + FileAttributeTagInfo, + FileIdBothDirectoryInfo, + FileIdBothDirectoryRestartInfo, + FileIoPriorityHintInfo, + FileRemoteProtocolInfo, + FileFullDirectoryInfo, + FileFullDirectoryRestartInfo, +// static if (NTDDI_VERSION >= NTDDI_WIN8) +// { + FileStorageInfo, + FileAlignmentInfo, + FileIdInfo, + FileIdExtdDirectoryInfo, + FileIdExtdDirectoryRestartInfo, +// } +// static if (NTDDI_VERSION >= NTDDI_WIN10_RS1) +// { + FileDispositionInfoEx, + FileRenameInfoEx, +// } +// static if (NTDDI_VERSION >= NTDDI_WIN10_19H1) +// { + FileCaseSensitiveInfo, + FileNormalizedNameInfo, +// } + MaximumFileInfoByHandleClass + } + alias PFILE_INFO_BY_HANDLE_CLASS = FILE_INFO_BY_HANDLE_CLASS*; +} + +struct SYSTEM_INFO { + union { + DWORD dwOemId; + struct { + WORD wProcessorArchitecture; + WORD wReserved; + } + } + DWORD dwPageSize; + PVOID lpMinimumApplicationAddress; + PVOID lpMaximumApplicationAddress; + DWORD_PTR dwActiveProcessorMask; + DWORD dwNumberOfProcessors; + DWORD dwProcessorType; + DWORD dwAllocationGranularity; + WORD wProcessorLevel; + WORD wProcessorRevision; +} +alias SYSTEM_INFO* LPSYSTEM_INFO; + +static if (_WIN32_WINNT >= 0x500) { + struct SYSTEM_POWER_STATUS { + BYTE ACLineStatus; + BYTE BatteryFlag; + BYTE BatteryLifePercent; + BYTE Reserved1; + DWORD BatteryLifeTime; + DWORD BatteryFullLifeTime; + } + alias SYSTEM_POWER_STATUS* LPSYSTEM_POWER_STATUS; +} + +struct TIME_ZONE_INFORMATION { + LONG Bias; + WCHAR[32] StandardName = 0; + SYSTEMTIME StandardDate; + LONG StandardBias; + WCHAR[32] DaylightName = 0; + SYSTEMTIME DaylightDate; + LONG DaylightBias; +} +alias TIME_ZONE_INFORMATION* LPTIME_ZONE_INFORMATION; + +// Does not exist in Windows headers, only MSDN +// documentation (for TIME_ZONE_INFORMATION). +// Provided solely for compatibility with the old +// urt.internal.sys.windows.windows +struct REG_TZI_FORMAT { + LONG Bias; + LONG StandardBias; + LONG DaylightBias; + SYSTEMTIME StandardDate; + SYSTEMTIME DaylightDate; +} + +// MSDN documents this, possibly erroneously, as Win2000+. +struct MEMORYSTATUS { + DWORD dwLength; + DWORD dwMemoryLoad; + SIZE_T dwTotalPhys; + SIZE_T dwAvailPhys; + SIZE_T dwTotalPageFile; + SIZE_T dwAvailPageFile; + SIZE_T dwTotalVirtual; + SIZE_T dwAvailVirtual; +} +alias MEMORYSTATUS* LPMEMORYSTATUS; + +static if (_WIN32_WINNT >= 0x500) { + struct MEMORYSTATUSEX { + DWORD dwLength; + DWORD dwMemoryLoad; + DWORDLONG ullTotalPhys; + DWORDLONG ullAvailPhys; + DWORDLONG ullTotalPageFile; + DWORDLONG ullAvailPageFile; + DWORDLONG ullTotalVirtual; + DWORDLONG ullAvailVirtual; + DWORDLONG ullAvailExtendedVirtual; + } + alias MEMORYSTATUSEX* LPMEMORYSTATUSEX; +} + +struct LDT_ENTRY { + WORD LimitLow; + WORD BaseLow; + struct { + BYTE BaseMid; + BYTE Flags1; + BYTE Flags2; + BYTE BaseHi; + + byte Type(byte f) { Flags1 = cast(BYTE) ((Flags1 & 0xE0) | f); return cast(byte)(f & 0x1F); } + byte Dpl(byte f) { Flags1 = cast(BYTE) ((Flags1 & 0x9F) | (f<<5)); return cast(byte)(f & 3); } + bool Pres(bool f) { Flags1 = cast(BYTE) ((Flags1 & 0x7F) | (f<<7)); return f; } + + byte LimitHi(byte f) { Flags2 = cast(BYTE) ((Flags2 & 0xF0) | (f&0x0F)); return cast(byte)(f & 0x0F); } + bool Sys(bool f) { Flags2 = cast(BYTE) ((Flags2 & 0xEF) | (f<<4)); return f; } + // Next bit is reserved + bool Default_Big(bool f) { Flags2 = cast(BYTE) ((Flags2 & 0xBF) | (f<<6)); return f; } + bool Granularity(bool f) { Flags2 = cast(BYTE) ((Flags2 & 0x7F) | (f<<7)); return f; } + + byte Type() { return cast(byte) (Flags1 & 0x1F); } + byte Dpl() { return cast(byte) ((Flags1 & 0x60)>>5); } + bool Pres() { return cast(bool) (Flags1 & 0x80); } + + byte LimitHi() { return cast(byte) (Flags2 & 0x0F); } + bool Sys() { return cast(bool) (Flags2 & 0x10); } + bool Default_Big()() { return cast(bool) (Flags2 & 0x40); } + bool Granularity()() { return cast(bool) (Flags2 & 0x80); } + } +/+ + union HighWord { + struct Bytes { + BYTE BaseMid; + BYTE Flags1; + BYTE Flags2; + BYTE BaseHi; + } + struct Bits { + DWORD BaseMid:8; + DWORD Type:5; + DWORD Dpl:2; + DWORD Pres:1; + DWORD LimitHi:4; + DWORD Sys:1; + DWORD Reserved_0:1; + DWORD Default_Big:1; + DWORD Granularity:1; + DWORD BaseHi:8; + } + } ++/ +} +alias LDT_ENTRY* PLDT_ENTRY, LPLDT_ENTRY; + +/* As with the other memory management functions and structures, MSDN's + * Windows version info shall be taken with a cup of salt. + */ +struct PROCESS_HEAP_ENTRY { + PVOID lpData; + DWORD cbData; + BYTE cbOverhead; + BYTE iRegionIndex; + WORD wFlags; + union { + struct _Block { + HANDLE hMem; + DWORD[3] dwReserved; + } + _Block Block; + struct _Region { + DWORD dwCommittedSize; + DWORD dwUnCommittedSize; + LPVOID lpFirstBlock; + LPVOID lpLastBlock; + } + _Region Region; + } +} +alias PROCESS_HEAP_ENTRY* LPPROCESS_HEAP_ENTRY; + +struct OFSTRUCT { + BYTE cBytes = OFSTRUCT.sizeof; + BYTE fFixedDisk; + WORD nErrCode; + WORD Reserved1; + WORD Reserved2; + CHAR[128] szPathName = 0; // const OFS_MAXPATHNAME = 128; +} +alias OFSTRUCT* LPOFSTRUCT, POFSTRUCT; + +/* ??? MSDN documents this only for Windows CE, but it's used by + * ImageGetCertificateData, which is in desktop Windows. + */ +struct WIN_CERTIFICATE { + DWORD dwLength; + WORD wRevision; + WORD wCertificateType; + BYTE _bCertificate; + + BYTE* bCertificate() return { return &_bCertificate; } +} +alias WIN_CERTIFICATE* LPWIN_CERTIFICATE; + +static if (_WIN32_WINNT >= 0x500) { + enum COMPUTER_NAME_FORMAT { + ComputerNameNetBIOS, + ComputerNameDnsHostname, + ComputerNameDnsDomain, + ComputerNameDnsFullyQualified, + ComputerNamePhysicalNetBIOS, + ComputerNamePhysicalDnsHostname, + ComputerNamePhysicalDnsDomain, + ComputerNamePhysicalDnsFullyQualified, + ComputerNameMax + } +} + +static if (_WIN32_WINNT >= 0x501) { + struct ACTCTXA { + ULONG cbSize = this.sizeof; + DWORD dwFlags; + LPCSTR lpSource; + USHORT wProcessorArchitecture; + LANGID wLangId; + LPCSTR lpAssemblyDirectory; + LPCSTR lpResourceName; + LPCSTR lpApplicationName; + HMODULE hModule; + } + alias ACTCTXA* PACTCTXA; + alias const(ACTCTXA)* PCACTCTXA; + + struct ACTCTXW { + ULONG cbSize = this.sizeof; + DWORD dwFlags; + LPCWSTR lpSource; + USHORT wProcessorArchitecture; + LANGID wLangId; + LPCWSTR lpAssemblyDirectory; + LPCWSTR lpResourceName; + LPCWSTR lpApplicationName; + HMODULE hModule; + } + alias ACTCTXW* PACTCTXW; + alias const(ACTCTXW)* PCACTCTXW; + + struct ACTCTX_SECTION_KEYED_DATA { + ULONG cbSize = this.sizeof; + ULONG ulDataFormatVersion; + PVOID lpData; + ULONG ulLength; + PVOID lpSectionGlobalData; + ULONG ulSectionGlobalDataLength; + PVOID lpSectionBase; + ULONG ulSectionTotalLength; + HANDLE hActCtx; + HANDLE ulAssemblyRosterIndex; + } + alias ACTCTX_SECTION_KEYED_DATA* PACTCTX_SECTION_KEYED_DATA; + alias const(ACTCTX_SECTION_KEYED_DATA)* PCACTCTX_SECTION_KEYED_DATA; + + enum MEMORY_RESOURCE_NOTIFICATION_TYPE { + LowMemoryResourceNotification, + HighMemoryResourceNotification + } + +} // (_WIN32_WINNT >= 0x501) + +static if (_WIN32_WINNT >= 0x410) { + /* apparently used only by SetThreadExecutionState (Win2000+) + * and DDK functions (version compatibility not established) + */ + alias DWORD EXECUTION_STATE; +} + +// CreateSymbolicLink, GetFileInformationByHandleEx +static if (_WIN32_WINNT >= 0x600) { + enum { + SYMBOLIC_LINK_FLAG_DIRECTORY = 0x1, + SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 0x2 + } + + struct FILE_BASIC_INFO + { + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + DWORD FileAttributes; + } + alias PFILE_BASIC_INFO = FILE_BASIC_INFO*; + + struct FILE_STANDARD_INFO + { + LARGE_INTEGER AllocationSize; + LARGE_INTEGER EndOfFile; + DWORD NumberOfLinks; + BOOLEAN DeletePending; + BOOLEAN Directory; + } + alias PFILE_STANDARD_INFO = FILE_STANDARD_INFO*; +} + +// Callbacks +extern (Windows) { + alias DWORD function(LPVOID) LPTHREAD_START_ROUTINE; + alias DWORD function(LARGE_INTEGER, LARGE_INTEGER, LARGE_INTEGER, LARGE_INTEGER, + DWORD, DWORD, HANDLE, HANDLE, LPVOID) LPPROGRESS_ROUTINE; + alias void function(PVOID) LPFIBER_START_ROUTINE; + + alias BOOL function(HMODULE, LPCSTR, LPCSTR, WORD, LONG_PTR) ENUMRESLANGPROCA; + alias BOOL function(HMODULE, LPCWSTR, LPCWSTR, WORD, LONG_PTR) ENUMRESLANGPROCW; + alias BOOL function(HMODULE, LPCSTR, LPSTR, LONG_PTR) ENUMRESNAMEPROCA; + alias BOOL function(HMODULE, LPCWSTR, LPWSTR, LONG_PTR) ENUMRESNAMEPROCW; + alias BOOL function(HMODULE, LPSTR, LONG_PTR) ENUMRESTYPEPROCA; + alias BOOL function(HMODULE, LPWSTR, LONG_PTR) ENUMRESTYPEPROCW; + alias void function(DWORD, DWORD, LPOVERLAPPED) LPOVERLAPPED_COMPLETION_ROUTINE; + alias LONG function(LPEXCEPTION_POINTERS) PTOP_LEVEL_EXCEPTION_FILTER; + alias PTOP_LEVEL_EXCEPTION_FILTER LPTOP_LEVEL_EXCEPTION_FILTER; + + alias void function(ULONG_PTR) PAPCFUNC; + alias void function(PVOID, DWORD, DWORD) PTIMERAPCROUTINE; + + static if (_WIN32_WINNT >= 0x500) { + alias void function(PVOID, BOOLEAN) WAITORTIMERCALLBACK; + } +} + +LPTSTR MAKEINTATOM()(ushort i) { + return cast(LPTSTR) cast(size_t) i; +} + +extern (Windows) nothrow @nogc { + // The following Win16 functions are obselete in Win32. + int _hread(HFILE, LPVOID, int); + int _hwrite(HFILE, LPCSTR, int); + HFILE _lclose(HFILE); + HFILE _lcreat(LPCSTR, int); + LONG _llseek(HFILE, LONG, int); + HFILE _lopen(LPCSTR, int); + UINT _lread(HFILE, LPVOID, UINT); + UINT _lwrite(HFILE, LPCSTR, UINT); + SIZE_T GlobalCompact(DWORD); + VOID GlobalFix(HGLOBAL); + + // MSDN contradicts itself on GlobalFlags: + // "This function is provided only for compatibility with 16-bit versions of Windows." + // but also requires Windows 2000 or above + UINT GlobalFlags(HGLOBAL); + VOID GlobalUnfix(HGLOBAL); + BOOL GlobalUnWire(HGLOBAL); + PVOID GlobalWire(HGLOBAL); + SIZE_T LocalCompact(UINT); + UINT LocalFlags(HLOCAL); + SIZE_T LocalShrink(HLOCAL, UINT); + + /+ + //-------------------------------------- + // These functions are problematic + + version (UseNtoSKernel) {}else { + /* CAREFUL: These are exported from ntoskrnl.exe and declared in winddk.h + as __fastcall functions, but are exported from kernel32.dll as __stdcall */ + static if (_WIN32_WINNT >= 0x501) { + VOID InitializeSListHead(PSLIST_HEADER); + } + LONG InterlockedCompareExchange(LPLONG, LONG, LONG); + // PVOID WINAPI InterlockedCompareExchangePointer(PVOID*, PVOID, PVOID); + (PVOID)InterlockedCompareExchange((LPLONG)(d) (PVOID)InterlockedCompareExchange((LPLONG)(d), (LONG)(e), (LONG)(c)) + LONG InterlockedDecrement(LPLONG); + LONG InterlockedExchange(LPLONG, LONG); + // PVOID WINAPI InterlockedExchangePointer(PVOID*, PVOID); + (PVOID)InterlockedExchange((LPLONG)((PVOID)InterlockedExchange((LPLONG)(t), (LONG)(v)) + LONG InterlockedExchangeAdd(LPLONG, LONG); + + static if (_WIN32_WINNT >= 0x501) { + PSLIST_ENTRY InterlockedFlushSList(PSLIST_HEADER); + } + LONG InterlockedIncrement(LPLONG); + static if (_WIN32_WINNT >= 0x501) { + PSLIST_ENTRY InterlockedPopEntrySList(PSLIST_HEADER); + PSLIST_ENTRY InterlockedPushEntrySList(PSLIST_HEADER, PSLIST_ENTRY); + } + } // #endif // __USE_NTOSKRNL__ + //-------------------------------------- + +/ + + LONG InterlockedIncrement(LPLONG lpAddend); + LONG InterlockedDecrement(LPLONG lpAddend); + LONG InterlockedExchange(LPLONG Target, LONG Value); + LONG InterlockedExchangeAdd(LPLONG Addend, LONG Value); + LONG InterlockedCompareExchange(LONG *Destination, LONG Exchange, LONG Comperand); + + ATOM AddAtomA(LPCSTR); + ATOM AddAtomW(LPCWSTR); + BOOL AreFileApisANSI(); + BOOL Beep(DWORD, DWORD); + HANDLE BeginUpdateResourceA(LPCSTR, BOOL); + HANDLE BeginUpdateResourceW(LPCWSTR, BOOL); + BOOL BuildCommDCBA(LPCSTR, LPDCB); + BOOL BuildCommDCBW(LPCWSTR, LPDCB); + BOOL BuildCommDCBAndTimeoutsA(LPCSTR, LPDCB, LPCOMMTIMEOUTS); + BOOL BuildCommDCBAndTimeoutsW(LPCWSTR, LPDCB, LPCOMMTIMEOUTS); + BOOL CallNamedPipeA(LPCSTR, PVOID, DWORD, PVOID, DWORD, PDWORD, DWORD); + BOOL CallNamedPipeW(LPCWSTR, PVOID, DWORD, PVOID, DWORD, PDWORD, DWORD); + BOOL CancelDeviceWakeupRequest(HANDLE); + BOOL CheckTokenMembership(HANDLE, PSID, PBOOL); + BOOL ClearCommBreak(HANDLE); + BOOL ClearCommError(HANDLE, PDWORD, LPCOMSTAT); + BOOL CloseHandle(HANDLE) @trusted; + BOOL CommConfigDialogA(LPCSTR, HWND, LPCOMMCONFIG); + BOOL CommConfigDialogW(LPCWSTR, HWND, LPCOMMCONFIG); + LONG CompareFileTime(const(FILETIME)*, const(FILETIME)*); + BOOL ContinueDebugEvent(DWORD, DWORD, DWORD); + BOOL CopyFileA(LPCSTR, LPCSTR, BOOL); + BOOL CopyFileW(LPCWSTR, LPCWSTR, BOOL); + BOOL CopyFileExA(LPCSTR, LPCSTR, LPPROGRESS_ROUTINE, LPVOID, LPBOOL, DWORD); + BOOL CopyFileExW(LPCWSTR, LPCWSTR, LPPROGRESS_ROUTINE, LPVOID, LPBOOL, DWORD); + + alias RtlMoveMemory = memmove; + alias RtlCopyMemory = memcpy; + pragma(inline, true) void RtlFillMemory(PVOID Destination, SIZE_T Length, BYTE Fill) pure { memset(Destination, Fill, Length); } + pragma(inline, true) void RtlZeroMemory(PVOID Destination, SIZE_T Length) pure { memset(Destination, 0, Length); } + alias MoveMemory = RtlMoveMemory; + alias CopyMemory = RtlCopyMemory; + alias FillMemory = RtlFillMemory; + alias ZeroMemory = RtlZeroMemory; + + BOOL CreateDirectoryA(LPCSTR, LPSECURITY_ATTRIBUTES); + BOOL CreateDirectoryW(LPCWSTR, LPSECURITY_ATTRIBUTES); + BOOL CreateDirectoryExA(LPCSTR, LPCSTR, LPSECURITY_ATTRIBUTES); + BOOL CreateDirectoryExW(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES); + HANDLE CreateEventA(LPSECURITY_ATTRIBUTES, BOOL, BOOL, LPCSTR); + HANDLE CreateEventW(LPSECURITY_ATTRIBUTES, BOOL, BOOL, LPCWSTR); + HANDLE CreateFileA(LPCSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE); + HANDLE CreateFileW(LPCWSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE); + HANDLE CreateIoCompletionPort(HANDLE, HANDLE, ULONG_PTR, DWORD); + HANDLE CreateMailslotA(LPCSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES); + HANDLE CreateMailslotW(LPCWSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES); + HANDLE CreateMutexA(LPSECURITY_ATTRIBUTES, BOOL, LPCSTR); + HANDLE CreateMutexW(LPSECURITY_ATTRIBUTES, BOOL, LPCWSTR); + BOOL CreatePipe(PHANDLE, PHANDLE, LPSECURITY_ATTRIBUTES, DWORD); + BOOL CreateProcessA(LPCSTR, LPSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, BOOL, DWORD, PVOID, LPCSTR, LPSTARTUPINFOA, LPPROCESS_INFORMATION); + BOOL CreateProcessW(LPCWSTR, LPWSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, BOOL, DWORD, PVOID, LPCWSTR, LPSTARTUPINFOW, LPPROCESS_INFORMATION); + HANDLE CreateSemaphoreA(LPSECURITY_ATTRIBUTES, LONG, LONG, LPCSTR) @trusted; + HANDLE CreateSemaphoreW(LPSECURITY_ATTRIBUTES, LONG, LONG, LPCWSTR) @trusted; + HANDLE CreateThread(LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, PVOID, DWORD, PDWORD); + BOOL DebugActiveProcess(DWORD); + void DebugBreak(); + ATOM DeleteAtom(ATOM); + void DeleteCriticalSection(PCRITICAL_SECTION); + BOOL DeleteFileA(LPCSTR); + BOOL DeleteFileW(LPCWSTR); + BOOL DisableThreadLibraryCalls(HMODULE); + BOOL DosDateTimeToFileTime(WORD, WORD, LPFILETIME); + BOOL DuplicateHandle(HANDLE, HANDLE, HANDLE, PHANDLE, DWORD, BOOL, DWORD); + BOOL EndUpdateResourceA(HANDLE, BOOL); + BOOL EndUpdateResourceW(HANDLE, BOOL); + void EnterCriticalSection(LPCRITICAL_SECTION); + void EnterCriticalSection(shared(CRITICAL_SECTION)*); + BOOL EnumResourceLanguagesA(HMODULE, LPCSTR, LPCSTR, ENUMRESLANGPROC, LONG_PTR); + BOOL EnumResourceLanguagesW(HMODULE, LPCWSTR, LPCWSTR, ENUMRESLANGPROC, LONG_PTR); + BOOL EnumResourceNamesA(HMODULE, LPCSTR, ENUMRESNAMEPROC, LONG_PTR); + BOOL EnumResourceNamesW(HMODULE, LPCWSTR, ENUMRESNAMEPROC, LONG_PTR); + BOOL EnumResourceTypesA(HMODULE, ENUMRESTYPEPROC, LONG_PTR); + BOOL EnumResourceTypesW(HMODULE, ENUMRESTYPEPROC, LONG_PTR); + BOOL EscapeCommFunction(HANDLE, DWORD); + void ExitProcess(UINT); // Never returns + void ExitThread(DWORD); // Never returns + DWORD ExpandEnvironmentStringsA(LPCSTR, LPSTR, DWORD); + DWORD ExpandEnvironmentStringsW(LPCWSTR, LPWSTR, DWORD); + void FatalAppExitA(UINT, LPCSTR); + void FatalAppExitW(UINT, LPCWSTR); + void FatalExit(int); + BOOL FileTimeToDosDateTime(const(FILETIME)*, LPWORD, LPWORD); + BOOL FileTimeToLocalFileTime(const(FILETIME)*, LPFILETIME); + BOOL FileTimeToSystemTime(const(FILETIME)*, LPSYSTEMTIME); + ATOM FindAtomA(LPCSTR); + ATOM FindAtomW(LPCWSTR); + BOOL FindClose(HANDLE); + BOOL FindCloseChangeNotification(HANDLE); + HANDLE FindFirstChangeNotificationA(LPCSTR, BOOL, DWORD); + HANDLE FindFirstChangeNotificationW(LPCWSTR, BOOL, DWORD); + HANDLE FindFirstFileA(LPCSTR, LPWIN32_FIND_DATAA); + HANDLE FindFirstFileW(LPCWSTR, LPWIN32_FIND_DATAW); + BOOL FindNextChangeNotification(HANDLE); + BOOL FindNextFileA(HANDLE, LPWIN32_FIND_DATAA); + BOOL FindNextFileW(HANDLE, LPWIN32_FIND_DATAW); + HRSRC FindResourceA(HMODULE, LPCSTR, LPCSTR); + HRSRC FindResourceW(HINSTANCE, LPCWSTR, LPCWSTR); + HRSRC FindResourceExA(HINSTANCE, LPCSTR, LPCSTR, WORD); + HRSRC FindResourceExW(HINSTANCE, LPCWSTR, LPCWSTR, WORD); + BOOL FlushFileBuffers(HANDLE); + BOOL FlushInstructionCache(HANDLE, PCVOID, SIZE_T); +// DWORD FormatMessageA(DWORD, PCVOID, DWORD, DWORD, LPSTR, DWORD, va_list*); +// DWORD FormatMessageW(DWORD, PCVOID, DWORD, DWORD, LPWSTR, DWORD, va_list*); + BOOL FreeEnvironmentStringsA(LPSTR); + BOOL FreeEnvironmentStringsW(LPWSTR); + BOOL FreeLibrary(HMODULE); + void FreeLibraryAndExitThread(HMODULE, DWORD); // never returns + BOOL FreeResource(HGLOBAL); + UINT GetAtomNameA(ATOM, LPSTR, int); + UINT GetAtomNameW(ATOM, LPWSTR, int); + LPSTR GetCommandLineA(); + LPWSTR GetCommandLineW(); + BOOL GetCommConfig(HANDLE, LPCOMMCONFIG, PDWORD); + BOOL GetCommMask(HANDLE, PDWORD); + BOOL GetCommModemStatus(HANDLE, PDWORD); + BOOL GetCommProperties(HANDLE, LPCOMMPROP); + BOOL GetCommState(HANDLE, LPDCB); + BOOL GetCommTimeouts(HANDLE, LPCOMMTIMEOUTS); + BOOL GetComputerNameA(LPSTR, PDWORD); + BOOL GetComputerNameW(LPWSTR, PDWORD); + DWORD GetCurrentDirectoryA(DWORD, LPSTR); + DWORD GetCurrentDirectoryW(DWORD, LPWSTR); + HANDLE GetCurrentProcess(); + DWORD GetCurrentProcessId(); + HANDLE GetCurrentThread(); +/* In MinGW: +#ifdef _WIN32_WCE +extern DWORD GetCurrentThreadId(void); +#else +WINBASEAPI DWORD WINAPI GetCurrentThreadId(void); +#endif +*/ + DWORD GetCurrentThreadId(); + + alias GetTickCount GetCurrentTime; + + BOOL GetDefaultCommConfigA(LPCSTR, LPCOMMCONFIG, PDWORD); + BOOL GetDefaultCommConfigW(LPCWSTR, LPCOMMCONFIG, PDWORD); + BOOL GetDiskFreeSpaceA(LPCSTR, PDWORD, PDWORD, PDWORD, PDWORD); + BOOL GetDiskFreeSpaceW(LPCWSTR, PDWORD, PDWORD, PDWORD, PDWORD); + BOOL GetDiskFreeSpaceExA(LPCSTR, PULARGE_INTEGER, PULARGE_INTEGER, PULARGE_INTEGER); + BOOL GetDiskFreeSpaceExW(LPCWSTR, PULARGE_INTEGER, PULARGE_INTEGER, PULARGE_INTEGER); + UINT GetDriveTypeA(LPCSTR); + UINT GetDriveTypeW(LPCWSTR); + LPSTR GetEnvironmentStringsA(); + LPWSTR GetEnvironmentStringsW(); + DWORD GetEnvironmentVariableA(LPCSTR, LPSTR, DWORD); + DWORD GetEnvironmentVariableW(LPCWSTR, LPWSTR, DWORD); + BOOL GetExitCodeProcess(HANDLE, PDWORD); + BOOL GetExitCodeThread(HANDLE, PDWORD); + DWORD GetFileAttributesA(LPCSTR); + DWORD GetFileAttributesW(LPCWSTR); + BOOL GetFileInformationByHandle(HANDLE, LPBY_HANDLE_FILE_INFORMATION); + DWORD GetFileSize(HANDLE, PDWORD); + BOOL GetFileTime(HANDLE, LPFILETIME, LPFILETIME, LPFILETIME); + DWORD GetFileType(HANDLE); + DWORD GetFinalPathNameByHandleA(HANDLE, LPSTR, DWORD, DWORD); + DWORD GetFinalPathNameByHandleW(HANDLE, LPWSTR, DWORD, DWORD); + DWORD GetFullPathNameA(LPCSTR, DWORD, LPSTR, LPSTR*); + DWORD GetFullPathNameW(LPCWSTR, DWORD, LPWSTR, LPWSTR*); + DWORD GetLastError() @trusted; + void GetLocalTime(LPSYSTEMTIME); + DWORD GetLogicalDrives(); + DWORD GetLogicalDriveStringsA(DWORD, LPSTR); + DWORD GetLogicalDriveStringsW(DWORD, LPWSTR); + BOOL GetMailslotInfo(HANDLE, PDWORD, PDWORD, PDWORD, PDWORD); + DWORD GetModuleFileNameA(HINSTANCE, LPSTR, DWORD); + DWORD GetModuleFileNameW(HINSTANCE, LPWSTR, DWORD); + HMODULE GetModuleHandleA(LPCSTR); + HMODULE GetModuleHandleW(LPCWSTR); + BOOL GetNamedPipeHandleStateA(HANDLE, PDWORD, PDWORD, PDWORD, PDWORD, LPSTR, DWORD); + BOOL GetNamedPipeHandleStateW(HANDLE, PDWORD, PDWORD, PDWORD, PDWORD, LPWSTR, DWORD); + BOOL GetNamedPipeInfo(HANDLE, PDWORD, PDWORD, PDWORD, PDWORD); + BOOL GetOverlappedResult(HANDLE, LPOVERLAPPED, PDWORD, BOOL); + DWORD GetPriorityClass(HANDLE); + UINT GetPrivateProfileIntA(LPCSTR, LPCSTR, INT, LPCSTR); + UINT GetPrivateProfileIntW(LPCWSTR, LPCWSTR, INT, LPCWSTR); + DWORD GetPrivateProfileSectionA(LPCSTR, LPSTR, DWORD, LPCSTR); + DWORD GetPrivateProfileSectionW(LPCWSTR, LPWSTR, DWORD, LPCWSTR); + DWORD GetPrivateProfileSectionNamesA(LPSTR, DWORD, LPCSTR); + DWORD GetPrivateProfileSectionNamesW(LPWSTR, DWORD, LPCWSTR); + DWORD GetPrivateProfileStringA(LPCSTR, LPCSTR, LPCSTR, LPSTR, DWORD, LPCSTR); + DWORD GetPrivateProfileStringW(LPCWSTR, LPCWSTR, LPCWSTR, LPWSTR, DWORD, LPCWSTR); + BOOL GetPrivateProfileStructA(LPCSTR, LPCSTR, LPVOID, UINT, LPCSTR); + BOOL GetPrivateProfileStructW(LPCWSTR, LPCWSTR, LPVOID, UINT, LPCWSTR); + FARPROC GetProcAddress(HMODULE, LPCSTR); // 1st param wrongly HINSTANCE in MinGW + BOOL GetProcessAffinityMask(HANDLE, PDWORD_PTR, PDWORD_PTR); + DWORD GetProcessVersion(DWORD); + UINT GetProfileIntA(LPCSTR, LPCSTR, INT); + UINT GetProfileIntW(LPCWSTR, LPCWSTR, INT); + DWORD GetProfileSectionA(LPCSTR, LPSTR, DWORD); + DWORD GetProfileSectionW(LPCWSTR, LPWSTR, DWORD); + DWORD GetProfileStringA(LPCSTR, LPCSTR, LPCSTR, LPSTR, DWORD); + DWORD GetProfileStringW(LPCWSTR, LPCWSTR, LPCWSTR, LPWSTR, DWORD); + DWORD GetShortPathNameA(LPCSTR, LPSTR, DWORD); + DWORD GetShortPathNameW(LPCWSTR, LPWSTR, DWORD); + VOID GetStartupInfoA(LPSTARTUPINFOA); + VOID GetStartupInfoW(LPSTARTUPINFOW); + HANDLE GetStdHandle(DWORD); + UINT GetSystemDirectoryA(LPSTR, UINT); + UINT GetSystemDirectoryW(LPWSTR, UINT); + VOID GetSystemInfo(LPSYSTEM_INFO); + VOID GetSystemTime(LPSYSTEMTIME); + BOOL GetSystemTimeAdjustment(PDWORD, PDWORD, PBOOL); + void GetSystemTimeAsFileTime(LPFILETIME); + UINT GetTempFileNameA(LPCSTR, LPCSTR, UINT, LPSTR); + UINT GetTempFileNameW(LPCWSTR, LPCWSTR, UINT, LPWSTR); + DWORD GetTempPathA(DWORD, LPSTR); + DWORD GetTempPathW(DWORD, LPWSTR); + BOOL GetThreadContext(HANDLE, LPCONTEXT); + int GetThreadPriority(HANDLE); + BOOL GetThreadSelectorEntry(HANDLE, DWORD, LPLDT_ENTRY); + DWORD GetTickCount(); + DWORD GetTimeZoneInformation(LPTIME_ZONE_INFORMATION); + BOOL GetUserNameA (LPSTR, PDWORD); + BOOL GetUserNameW(LPWSTR, PDWORD); + DWORD GetVersion(); + BOOL GetVersionExA(LPOSVERSIONINFOA); + BOOL GetVersionExW(LPOSVERSIONINFOW); + BOOL GetVolumeInformationA(LPCSTR, LPSTR, DWORD, PDWORD, PDWORD, PDWORD, LPSTR, DWORD); + BOOL GetVolumeInformationW(LPCWSTR, LPWSTR, DWORD, PDWORD, PDWORD, PDWORD, LPWSTR, DWORD); + UINT GetWindowsDirectoryA(LPSTR, UINT); + UINT GetWindowsDirectoryW(LPWSTR, UINT); + DWORD GetWindowThreadProcessId(HWND, PDWORD); + ATOM GlobalAddAtomA(LPCSTR); + ATOM GlobalAddAtomW(LPCWSTR); + ATOM GlobalDeleteAtom(ATOM); + ATOM GlobalFindAtomA(LPCSTR); + ATOM GlobalFindAtomW(LPCWSTR); + UINT GlobalGetAtomNameA(ATOM, LPSTR, int); + UINT GlobalGetAtomNameW(ATOM, LPWSTR, int); + + bool HasOverlappedIoCompleted(LPOVERLAPPED lpOverlapped) { + return lpOverlapped.Internal != STATUS_PENDING; + } + + BOOL InitAtomTable(DWORD); + VOID InitializeCriticalSection(LPCRITICAL_SECTION) @trusted; + /* ??? The next two are allegedly obsolete and "supported only for + * backward compatibility with the 16-bit Windows API". Yet the + * replacements IsBadReadPtr and IsBadWritePtr are apparently Win2000+ + * only. Where's the mistake? + */ + BOOL IsBadHugeReadPtr(PCVOID, UINT_PTR); + BOOL IsBadHugeWritePtr(PVOID, UINT_PTR); + BOOL IsBadReadPtr(PCVOID, UINT_PTR); + BOOL IsBadStringPtrA(LPCSTR, UINT_PTR); + BOOL IsBadStringPtrW(LPCWSTR, UINT_PTR); + BOOL IsBadWritePtr(PVOID, UINT_PTR); + void LeaveCriticalSection(LPCRITICAL_SECTION); + void LeaveCriticalSection(shared(CRITICAL_SECTION)*); + HINSTANCE LoadLibraryA(LPCSTR); + HINSTANCE LoadLibraryW(LPCWSTR); + HINSTANCE LoadLibraryExA(LPCSTR, HANDLE, DWORD); + HINSTANCE LoadLibraryExW(LPCWSTR, HANDLE, DWORD); + DWORD LoadModule(LPCSTR, PVOID); + HGLOBAL LoadResource(HINSTANCE, HRSRC); + BOOL LocalFileTimeToFileTime(const(FILETIME)*, LPFILETIME); + BOOL LockFile(HANDLE, DWORD, DWORD, DWORD, DWORD); + PVOID LockResource(HGLOBAL); + + LPSTR lstrcatA(LPSTR, LPCSTR); + LPWSTR lstrcatW(LPWSTR, LPCWSTR); + int lstrcmpA(LPCSTR, LPCSTR); + int lstrcmpiA(LPCSTR, LPCSTR); + int lstrcmpiW(LPCWSTR, LPCWSTR); + int lstrcmpW(LPCWSTR, LPCWSTR); + LPSTR lstrcpyA(LPSTR, LPCSTR); + LPSTR lstrcpynA(LPSTR, LPCSTR, int); + LPWSTR lstrcpynW(LPWSTR, LPCWSTR, int); + LPWSTR lstrcpyW(LPWSTR, LPCWSTR); + int lstrlenA(LPCSTR); + int lstrlenW(LPCWSTR); + + BOOL MoveFileA(LPCSTR, LPCSTR); + BOOL MoveFileW(LPCWSTR, LPCWSTR); + int MulDiv(int, int, int); + HANDLE OpenEventA(DWORD, BOOL, LPCSTR); + HANDLE OpenEventW(DWORD, BOOL, LPCWSTR); + deprecated HFILE OpenFile(LPCSTR, LPOFSTRUCT, UINT); + HANDLE OpenMutexA(DWORD, BOOL, LPCSTR); + HANDLE OpenMutexW(DWORD, BOOL, LPCWSTR); + HANDLE OpenProcess(DWORD, BOOL, DWORD); + HANDLE OpenSemaphoreA(DWORD, BOOL, LPCSTR); + HANDLE OpenSemaphoreW(DWORD, BOOL, LPCWSTR); + void OutputDebugStringA(LPCSTR); + void OutputDebugStringW(LPCWSTR); + BOOL PeekNamedPipe(HANDLE, PVOID, DWORD, PDWORD, PDWORD, PDWORD); + BOOL PulseEvent(HANDLE); + BOOL PurgeComm(HANDLE, DWORD); + BOOL QueryPerformanceCounter(PLARGE_INTEGER); + BOOL QueryPerformanceFrequency(PLARGE_INTEGER); + DWORD QueueUserAPC(PAPCFUNC, HANDLE, ULONG_PTR); + void RaiseException(DWORD, DWORD, DWORD, const(ULONG_PTR)*); + BOOL ReadFile(HANDLE, PVOID, DWORD, PDWORD, LPOVERLAPPED); + BOOL ReadFileEx(HANDLE, PVOID, DWORD, LPOVERLAPPED, LPOVERLAPPED_COMPLETION_ROUTINE); + BOOL ReadProcessMemory(HANDLE, PCVOID, PVOID, SIZE_T, SIZE_T*); + BOOL ReleaseMutex(HANDLE); + BOOL ReleaseSemaphore(HANDLE, LONG, LPLONG); + BOOL RemoveDirectoryA(LPCSTR); + BOOL RemoveDirectoryW(LPCWSTR); +/* In MinGW: +#ifdef _WIN32_WCE +extern BOOL ResetEvent(HANDLE); +#else +WINBASEAPI BOOL WINAPI ResetEvent(HANDLE); +#endif +*/ + BOOL ResetEvent(HANDLE); + DWORD ResumeThread(HANDLE); + DWORD SearchPathA(LPCSTR, LPCSTR, LPCSTR, DWORD, LPSTR, LPSTR*); + DWORD SearchPathW(LPCWSTR, LPCWSTR, LPCWSTR, DWORD, LPWSTR, LPWSTR*); + BOOL SetCommBreak(HANDLE); + BOOL SetCommConfig(HANDLE, LPCOMMCONFIG, DWORD); + BOOL SetCommMask(HANDLE, DWORD); + BOOL SetCommState(HANDLE, LPDCB); + BOOL SetCommTimeouts(HANDLE, LPCOMMTIMEOUTS); + BOOL SetComputerNameA(LPCSTR); + BOOL SetComputerNameW(LPCWSTR); + BOOL SetCurrentDirectoryA(LPCSTR); + BOOL SetCurrentDirectoryW(LPCWSTR); + BOOL SetDefaultCommConfigA(LPCSTR, LPCOMMCONFIG, DWORD); + BOOL SetDefaultCommConfigW(LPCWSTR, LPCOMMCONFIG, DWORD); + BOOL SetEndOfFile(HANDLE); + BOOL SetEnvironmentVariableA(LPCSTR, LPCSTR); + BOOL SetEnvironmentVariableW(LPCWSTR, LPCWSTR); + UINT SetErrorMode(UINT); +/* In MinGW: +#ifdef _WIN32_WCE +extern BOOL SetEvent(HANDLE); +#else +WINBASEAPI BOOL WINAPI SetEvent(HANDLE); +#endif +*/ + BOOL SetEvent(HANDLE); + VOID SetFileApisToANSI(); + VOID SetFileApisToOEM(); + BOOL SetFileAttributesA(LPCSTR, DWORD); + BOOL SetFileAttributesW(LPCWSTR, DWORD); + DWORD SetFilePointer(HANDLE, LONG, PLONG, DWORD); + BOOL SetFileTime(HANDLE, const(FILETIME)*, const(FILETIME)*, const(FILETIME)*); + deprecated UINT SetHandleCount(UINT); + void SetLastError(DWORD); + void SetLastErrorEx(DWORD, DWORD); + BOOL SetLocalTime(const(SYSTEMTIME)*); + BOOL SetMailslotInfo(HANDLE, DWORD); + BOOL SetNamedPipeHandleState(HANDLE, PDWORD, PDWORD, PDWORD); + BOOL SetPriorityClass(HANDLE, DWORD); + BOOL SetStdHandle(DWORD, HANDLE); + BOOL SetSystemTime(const(SYSTEMTIME)*); + DWORD_PTR SetThreadAffinityMask(HANDLE, DWORD_PTR); + BOOL SetThreadContext(HANDLE, const(CONTEXT)*); + BOOL SetThreadPriority(HANDLE, int); + BOOL SetTimeZoneInformation(const(TIME_ZONE_INFORMATION)*); + LPTOP_LEVEL_EXCEPTION_FILTER SetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER); + BOOL SetupComm(HANDLE, DWORD, DWORD); + BOOL SetVolumeLabelA(LPCSTR, LPCSTR); + BOOL SetVolumeLabelW(LPCWSTR, LPCWSTR); + + DWORD SizeofResource(HINSTANCE, HRSRC); + void Sleep(DWORD); + DWORD SleepEx(DWORD, BOOL); + DWORD SuspendThread(HANDLE); + BOOL SystemTimeToFileTime(const(SYSTEMTIME)*, LPFILETIME); + BOOL TerminateProcess(HANDLE, UINT); + BOOL TerminateThread(HANDLE, DWORD); + DWORD TlsAlloc(); + BOOL TlsFree(DWORD); + PVOID TlsGetValue(DWORD); + BOOL TlsSetValue(DWORD, PVOID); + BOOL TransactNamedPipe(HANDLE, PVOID, DWORD, PVOID, DWORD, PDWORD, LPOVERLAPPED); + BOOL TransmitCommChar(HANDLE, char); + LONG UnhandledExceptionFilter(LPEXCEPTION_POINTERS); + BOOL UnlockFile(HANDLE, DWORD, DWORD, DWORD, DWORD); + BOOL WaitCommEvent(HANDLE, PDWORD, LPOVERLAPPED); + BOOL WaitForDebugEvent(LPDEBUG_EVENT, DWORD); + DWORD WaitForMultipleObjects(DWORD, const(HANDLE)*, BOOL, DWORD); + DWORD WaitForMultipleObjectsEx(DWORD, const(HANDLE)*, BOOL, DWORD, BOOL); + DWORD WaitForSingleObject(HANDLE, DWORD); + DWORD WaitForSingleObjectEx(HANDLE, DWORD, BOOL); + BOOL WaitNamedPipeA(LPCSTR, DWORD); + BOOL WaitNamedPipeW(LPCWSTR, DWORD); + // undocumented on MSDN + BOOL WinLoadTrustProvider(GUID*); + BOOL WriteFile(HANDLE, PCVOID, DWORD, PDWORD, LPOVERLAPPED); + BOOL WriteFileEx(HANDLE, PCVOID, DWORD, LPOVERLAPPED, LPOVERLAPPED_COMPLETION_ROUTINE); + BOOL WritePrivateProfileSectionA(LPCSTR, LPCSTR, LPCSTR); + BOOL WritePrivateProfileSectionW(LPCWSTR, LPCWSTR, LPCWSTR); + BOOL WritePrivateProfileStringA(LPCSTR, LPCSTR, LPCSTR, LPCSTR); + BOOL WritePrivateProfileStringW(LPCWSTR, LPCWSTR, LPCWSTR, LPCWSTR); + BOOL WritePrivateProfileStructA(LPCSTR, LPCSTR, LPVOID, UINT, LPCSTR); + BOOL WritePrivateProfileStructW(LPCWSTR, LPCWSTR, LPVOID, UINT, LPCWSTR); + BOOL WriteProcessMemory(HANDLE, LPVOID, LPCVOID, SIZE_T, SIZE_T*); + BOOL WriteProfileSectionA(LPCSTR, LPCSTR); + BOOL WriteProfileSectionW(LPCWSTR, LPCWSTR); + BOOL WriteProfileStringA(LPCSTR, LPCSTR, LPCSTR); + BOOL WriteProfileStringW(LPCWSTR, LPCWSTR, LPCWSTR); + + /* Memory allocation functions. + * MSDN documents these erroneously as Win2000+; thus it is uncertain what + * version compatibility they really have. + */ + HGLOBAL GlobalAlloc(UINT, SIZE_T); + HGLOBAL GlobalDiscard(HGLOBAL); + HGLOBAL GlobalFree(HGLOBAL); + HGLOBAL GlobalHandle(PCVOID); + LPVOID GlobalLock(HGLOBAL); + VOID GlobalMemoryStatus(LPMEMORYSTATUS); + HGLOBAL GlobalReAlloc(HGLOBAL, SIZE_T, UINT); + SIZE_T GlobalSize(HGLOBAL); + BOOL GlobalUnlock(HGLOBAL); + PVOID HeapAlloc(HANDLE, DWORD, SIZE_T); + SIZE_T HeapCompact(HANDLE, DWORD); + HANDLE HeapCreate(DWORD, SIZE_T, SIZE_T); + BOOL HeapDestroy(HANDLE); + BOOL HeapFree(HANDLE, DWORD, PVOID); + BOOL HeapLock(HANDLE); + PVOID HeapReAlloc(HANDLE, DWORD, PVOID, SIZE_T); + SIZE_T HeapSize(HANDLE, DWORD, PCVOID); + BOOL HeapUnlock(HANDLE); + BOOL HeapValidate(HANDLE, DWORD, PCVOID); + BOOL HeapWalk(HANDLE, LPPROCESS_HEAP_ENTRY); + HLOCAL LocalAlloc(UINT, SIZE_T); + HLOCAL LocalDiscard(HLOCAL); + HLOCAL LocalFree(HLOCAL); + HLOCAL LocalHandle(LPCVOID); + PVOID LocalLock(HLOCAL); + HLOCAL LocalReAlloc(HLOCAL, SIZE_T, UINT); + SIZE_T LocalSize(HLOCAL); + BOOL LocalUnlock(HLOCAL); + PVOID VirtualAlloc(PVOID, SIZE_T, DWORD, DWORD); + PVOID VirtualAllocEx(HANDLE, PVOID, SIZE_T, DWORD, DWORD); + BOOL VirtualFree(PVOID, SIZE_T, DWORD); + BOOL VirtualFreeEx(HANDLE, PVOID, SIZE_T, DWORD); + BOOL VirtualLock(PVOID, SIZE_T); + BOOL VirtualProtect(PVOID, SIZE_T, DWORD, PDWORD); + BOOL VirtualProtectEx(HANDLE, PVOID, SIZE_T, DWORD, PDWORD); + SIZE_T VirtualQuery(LPCVOID, PMEMORY_BASIC_INFORMATION, SIZE_T); + SIZE_T VirtualQueryEx(HANDLE, LPCVOID, PMEMORY_BASIC_INFORMATION, SIZE_T); + BOOL VirtualUnlock(PVOID, SIZE_T); +// not in MinGW 4.0 - ??? + static if (_WIN32_WINNT >= 0x600) { + BOOL CancelIoEx(HANDLE, LPOVERLAPPED); + } + + BOOL CancelIo(HANDLE); + BOOL CancelWaitableTimer(HANDLE); + PVOID ConvertThreadToFiber(PVOID); + LPVOID CreateFiber(SIZE_T, LPFIBER_START_ROUTINE, LPVOID); + HANDLE CreateWaitableTimerA(LPSECURITY_ATTRIBUTES, BOOL, LPCSTR); + HANDLE CreateWaitableTimerW(LPSECURITY_ATTRIBUTES, BOOL, LPCWSTR); + void DeleteFiber(PVOID); + BOOL GetFileAttributesExA(LPCSTR, GET_FILEEX_INFO_LEVELS, PVOID); + BOOL GetFileAttributesExW(LPCWSTR, GET_FILEEX_INFO_LEVELS, PVOID); + DWORD GetLongPathNameA(LPCSTR, LPSTR, DWORD); + DWORD GetLongPathNameW(LPCWSTR, LPWSTR, DWORD); + BOOL InitializeCriticalSectionAndSpinCount(LPCRITICAL_SECTION, DWORD); + BOOL IsDebuggerPresent(); + HANDLE OpenWaitableTimerA(DWORD, BOOL, LPCSTR); + HANDLE OpenWaitableTimerW(DWORD, BOOL, LPCWSTR); + DWORD QueryDosDeviceA(LPCSTR, LPSTR, DWORD); + DWORD QueryDosDeviceW(LPCWSTR, LPWSTR, DWORD); + BOOL SetWaitableTimer(HANDLE, const(LARGE_INTEGER)*, LONG, PTIMERAPCROUTINE, PVOID, BOOL); + void SwitchToFiber(PVOID); + + static if (_WIN32_WINNT >= 0x500) { + HANDLE OpenThread(DWORD, BOOL, DWORD); + } + + BOOL AccessCheck(PSECURITY_DESCRIPTOR, HANDLE, DWORD, PGENERIC_MAPPING, PPRIVILEGE_SET, PDWORD, PDWORD, PBOOL); + BOOL AccessCheckAndAuditAlarmA(LPCSTR, LPVOID, LPSTR, LPSTR, PSECURITY_DESCRIPTOR, DWORD, PGENERIC_MAPPING, BOOL, PDWORD, PBOOL, PBOOL); + BOOL AccessCheckAndAuditAlarmW(LPCWSTR, LPVOID, LPWSTR, LPWSTR, PSECURITY_DESCRIPTOR, DWORD, PGENERIC_MAPPING, BOOL, PDWORD, PBOOL, PBOOL); + BOOL AddAccessAllowedAce(PACL, DWORD, DWORD, PSID); + BOOL AddAccessDeniedAce(PACL, DWORD, DWORD, PSID); + BOOL AddAce(PACL, DWORD, DWORD, PVOID, DWORD); + BOOL AddAuditAccessAce(PACL, DWORD, DWORD, PSID, BOOL, BOOL); + BOOL AdjustTokenGroups(HANDLE, BOOL, PTOKEN_GROUPS, DWORD, PTOKEN_GROUPS, PDWORD); + BOOL AdjustTokenPrivileges(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD); + BOOL AllocateAndInitializeSid(PSID_IDENTIFIER_AUTHORITY, BYTE, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, PSID*); + BOOL AllocateLocallyUniqueId(PLUID); + BOOL AreAllAccessesGranted(DWORD, DWORD); + BOOL AreAnyAccessesGranted(DWORD, DWORD); + BOOL BackupEventLogA(HANDLE, LPCSTR); + BOOL BackupEventLogW(HANDLE, LPCWSTR); + BOOL BackupRead(HANDLE, LPBYTE, DWORD, LPDWORD, BOOL, BOOL, LPVOID*); + BOOL BackupSeek(HANDLE, DWORD, DWORD, LPDWORD, LPDWORD, LPVOID*); + BOOL BackupWrite(HANDLE, LPBYTE, DWORD, LPDWORD, BOOL, BOOL, LPVOID*); + BOOL ClearEventLogA(HANDLE, LPCSTR); + BOOL ClearEventLogW(HANDLE, LPCWSTR); + BOOL CloseEventLog(HANDLE); + BOOL ConnectNamedPipe(HANDLE, LPOVERLAPPED); + BOOL CopySid(DWORD, PSID, PSID); + HANDLE CreateNamedPipeA(LPCSTR, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, LPSECURITY_ATTRIBUTES); + HANDLE CreateNamedPipeW(LPCWSTR, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, LPSECURITY_ATTRIBUTES); + BOOL CreatePrivateObjectSecurity(PSECURITY_DESCRIPTOR, PSECURITY_DESCRIPTOR, PSECURITY_DESCRIPTOR*, BOOL, HANDLE, PGENERIC_MAPPING); + BOOL CreateProcessAsUserA(HANDLE, LPCSTR, LPSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, BOOL, DWORD, PVOID, LPCSTR, LPSTARTUPINFOA, LPPROCESS_INFORMATION); + BOOL CreateProcessAsUserW(HANDLE, LPCWSTR, LPWSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, BOOL, DWORD, PVOID, LPCWSTR, LPSTARTUPINFOW, LPPROCESS_INFORMATION); + HANDLE CreateRemoteThread(HANDLE, LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD); + DWORD CreateTapePartition(HANDLE, DWORD, DWORD, DWORD); + BOOL DefineDosDeviceA(DWORD, LPCSTR, LPCSTR); + BOOL DefineDosDeviceW(DWORD, LPCWSTR, LPCWSTR); + BOOL DeleteAce(PACL, DWORD); + BOOL DeregisterEventSource(HANDLE); + BOOL DestroyPrivateObjectSecurity(PSECURITY_DESCRIPTOR*); + BOOL DeviceIoControl(HANDLE, DWORD, PVOID, DWORD, PVOID, DWORD, PDWORD, POVERLAPPED); + BOOL DisconnectNamedPipe(HANDLE); + BOOL DuplicateToken(HANDLE, SECURITY_IMPERSONATION_LEVEL, PHANDLE); + BOOL DuplicateTokenEx(HANDLE, DWORD, LPSECURITY_ATTRIBUTES, SECURITY_IMPERSONATION_LEVEL, TOKEN_TYPE, PHANDLE); + BOOL EqualPrefixSid(PSID, PSID); + BOOL EqualSid(PSID, PSID); + DWORD EraseTape(HANDLE, DWORD, BOOL); + HANDLE FindFirstFileExA(LPCSTR, FINDEX_INFO_LEVELS, PVOID, FINDEX_SEARCH_OPS, PVOID, DWORD); + HANDLE FindFirstFileExW(LPCWSTR, FINDEX_INFO_LEVELS, PVOID, FINDEX_SEARCH_OPS, PVOID, DWORD); + BOOL FindFirstFreeAce(PACL, PVOID*); + PVOID FreeSid(PSID); + BOOL GetAce(PACL, DWORD, LPVOID*); + BOOL GetAclInformation(PACL, PVOID, DWORD, ACL_INFORMATION_CLASS); + BOOL GetBinaryTypeA(LPCSTR, PDWORD); + BOOL GetBinaryTypeW(LPCWSTR, PDWORD); + DWORD GetCompressedFileSizeA(LPCSTR, PDWORD); + DWORD GetCompressedFileSizeW(LPCWSTR, PDWORD); + BOOL GetCurrentHwProfileA(LPHW_PROFILE_INFOA); + BOOL GetCurrentHwProfileW(LPHW_PROFILE_INFOW); + BOOL GetFileSecurityA(LPCSTR, SECURITY_INFORMATION, PSECURITY_DESCRIPTOR, DWORD, PDWORD); + BOOL GetFileSecurityW(LPCWSTR, SECURITY_INFORMATION, PSECURITY_DESCRIPTOR, DWORD, PDWORD); + BOOL GetHandleInformation(HANDLE, PDWORD); + BOOL GetKernelObjectSecurity(HANDLE, SECURITY_INFORMATION, PSECURITY_DESCRIPTOR, DWORD, PDWORD); + DWORD GetLengthSid(PSID); + BOOL GetNumberOfEventLogRecords(HANDLE, PDWORD); + BOOL GetOldestEventLogRecord(HANDLE, PDWORD); + BOOL GetPrivateObjectSecurity(PSECURITY_DESCRIPTOR, SECURITY_INFORMATION, PSECURITY_DESCRIPTOR, DWORD, PDWORD); + BOOL GetProcessPriorityBoost(HANDLE, PBOOL); + BOOL GetProcessShutdownParameters(PDWORD, PDWORD); + BOOL GetProcessTimes(HANDLE, LPFILETIME, LPFILETIME, LPFILETIME, LPFILETIME); + HWINSTA GetProcessWindowStation(); + BOOL GetProcessWorkingSetSize(HANDLE, PSIZE_T, PSIZE_T); + BOOL GetQueuedCompletionStatus(HANDLE, PDWORD, PULONG_PTR, LPOVERLAPPED*, DWORD); + BOOL GetSecurityDescriptorControl(PSECURITY_DESCRIPTOR, PSECURITY_DESCRIPTOR_CONTROL, PDWORD); + BOOL GetSecurityDescriptorDacl(PSECURITY_DESCRIPTOR, LPBOOL, PACL*, LPBOOL); + BOOL GetSecurityDescriptorGroup(PSECURITY_DESCRIPTOR, PSID*, LPBOOL); + DWORD GetSecurityDescriptorLength(PSECURITY_DESCRIPTOR); + BOOL GetSecurityDescriptorOwner(PSECURITY_DESCRIPTOR, PSID*, LPBOOL); + BOOL GetSecurityDescriptorSacl(PSECURITY_DESCRIPTOR, LPBOOL, PACL*, LPBOOL); + PSID_IDENTIFIER_AUTHORITY GetSidIdentifierAuthority(PSID); + DWORD GetSidLengthRequired(UCHAR); + PDWORD GetSidSubAuthority(PSID, DWORD); + PUCHAR GetSidSubAuthorityCount(PSID); + DWORD GetTapeParameters(HANDLE, DWORD, PDWORD, PVOID); + DWORD GetTapePosition(HANDLE, DWORD, PDWORD, PDWORD, PDWORD); + DWORD GetTapeStatus(HANDLE); + BOOL GetThreadPriorityBoost(HANDLE, PBOOL); + BOOL GetThreadTimes(HANDLE, LPFILETIME, LPFILETIME, LPFILETIME, LPFILETIME); + BOOL GetTokenInformation(HANDLE, TOKEN_INFORMATION_CLASS, PVOID, DWORD, PDWORD); + BOOL ImpersonateLoggedOnUser(HANDLE); + BOOL ImpersonateNamedPipeClient(HANDLE); + BOOL ImpersonateSelf(SECURITY_IMPERSONATION_LEVEL); + BOOL InitializeAcl(PACL, DWORD, DWORD); + DWORD SetCriticalSectionSpinCount(LPCRITICAL_SECTION, DWORD); + BOOL InitializeSecurityDescriptor(PSECURITY_DESCRIPTOR, DWORD); + BOOL InitializeSid(PSID, PSID_IDENTIFIER_AUTHORITY, BYTE); + BOOL IsProcessorFeaturePresent(DWORD); + BOOL IsTextUnicode(PCVOID, int, LPINT); + BOOL IsValidAcl(PACL); + BOOL IsValidSecurityDescriptor(PSECURITY_DESCRIPTOR); + BOOL IsValidSid(PSID); + BOOL CreateWellKnownSid(WELL_KNOWN_SID_TYPE, PSID, PSID, PDWORD); + BOOL LockFileEx(HANDLE, DWORD, DWORD, DWORD, DWORD, LPOVERLAPPED); + BOOL LogonUserA(LPSTR, LPSTR, LPSTR, DWORD, DWORD, PHANDLE); + BOOL LogonUserW(LPWSTR, LPWSTR, LPWSTR, DWORD, DWORD, PHANDLE); + BOOL LookupAccountNameA(LPCSTR, LPCSTR, PSID, PDWORD, LPSTR, PDWORD, PSID_NAME_USE); + BOOL LookupAccountNameW(LPCWSTR, LPCWSTR, PSID, PDWORD, LPWSTR, PDWORD, PSID_NAME_USE); + BOOL LookupAccountSidA(LPCSTR, PSID, LPSTR, PDWORD, LPSTR, PDWORD, PSID_NAME_USE); + BOOL LookupAccountSidW(LPCWSTR, PSID, LPWSTR, PDWORD, LPWSTR, PDWORD, PSID_NAME_USE); + BOOL LookupPrivilegeDisplayNameA(LPCSTR, LPCSTR, LPSTR, PDWORD, PDWORD); + BOOL LookupPrivilegeDisplayNameW(LPCWSTR, LPCWSTR, LPWSTR, PDWORD, PDWORD); + BOOL LookupPrivilegeNameA(LPCSTR, PLUID, LPSTR, PDWORD); + BOOL LookupPrivilegeNameW(LPCWSTR, PLUID, LPWSTR, PDWORD); + BOOL LookupPrivilegeValueA(LPCSTR, LPCSTR, PLUID); + BOOL LookupPrivilegeValueW(LPCWSTR, LPCWSTR, PLUID); + BOOL MakeAbsoluteSD(PSECURITY_DESCRIPTOR, PSECURITY_DESCRIPTOR, PDWORD, PACL, PDWORD, PACL, PDWORD, PSID, PDWORD, PSID, PDWORD); + BOOL MakeSelfRelativeSD(PSECURITY_DESCRIPTOR, PSECURITY_DESCRIPTOR, PDWORD); + VOID MapGenericMask(PDWORD, PGENERIC_MAPPING); + BOOL MoveFileExA(LPCSTR, LPCSTR, DWORD); + BOOL MoveFileExW(LPCWSTR, LPCWSTR, DWORD); + BOOL NotifyChangeEventLog(HANDLE, HANDLE); + BOOL ObjectCloseAuditAlarmA(LPCSTR, PVOID, BOOL); + BOOL ObjectCloseAuditAlarmW(LPCWSTR, PVOID, BOOL); + BOOL ObjectDeleteAuditAlarmA(LPCSTR, PVOID, BOOL); + BOOL ObjectDeleteAuditAlarmW(LPCWSTR, PVOID, BOOL); + BOOL ObjectOpenAuditAlarmA(LPCSTR, PVOID, LPSTR, LPSTR, PSECURITY_DESCRIPTOR, HANDLE, DWORD, DWORD, PPRIVILEGE_SET, BOOL, BOOL, PBOOL); + BOOL ObjectOpenAuditAlarmW(LPCWSTR, PVOID, LPWSTR, LPWSTR, PSECURITY_DESCRIPTOR, HANDLE, DWORD, DWORD, PPRIVILEGE_SET, BOOL, BOOL, PBOOL); + BOOL ObjectPrivilegeAuditAlarmA(LPCSTR, PVOID, HANDLE, DWORD, PPRIVILEGE_SET, BOOL); + BOOL ObjectPrivilegeAuditAlarmW(LPCWSTR, PVOID, HANDLE, DWORD, PPRIVILEGE_SET, BOOL); + HANDLE OpenBackupEventLogA(LPCSTR, LPCSTR); + HANDLE OpenBackupEventLogW(LPCWSTR, LPCWSTR); + HANDLE OpenEventLogA(LPCSTR, LPCSTR); + HANDLE OpenEventLogW(LPCWSTR, LPCWSTR); + BOOL OpenProcessToken(HANDLE, DWORD, PHANDLE); + BOOL OpenThreadToken(HANDLE, DWORD, BOOL, PHANDLE); + BOOL PostQueuedCompletionStatus(HANDLE, DWORD, ULONG_PTR, LPOVERLAPPED); + DWORD PrepareTape(HANDLE, DWORD, BOOL); + BOOL PrivilegeCheck(HANDLE, PPRIVILEGE_SET, PBOOL); + BOOL PrivilegedServiceAuditAlarmA(LPCSTR, LPCSTR, HANDLE, PPRIVILEGE_SET, BOOL); + BOOL PrivilegedServiceAuditAlarmW(LPCWSTR, LPCWSTR, HANDLE, PPRIVILEGE_SET, BOOL); + BOOL ReadDirectoryChangesW(HANDLE, PVOID, DWORD, BOOL, DWORD, PDWORD, LPOVERLAPPED, LPOVERLAPPED_COMPLETION_ROUTINE); + BOOL ReadEventLogA(HANDLE, DWORD, DWORD, PVOID, DWORD, DWORD*, DWORD*); + BOOL ReadEventLogW(HANDLE, DWORD, DWORD, PVOID, DWORD, DWORD*, DWORD*); + BOOL ReadFileScatter(HANDLE, FILE_SEGMENT_ELEMENT*, DWORD, LPDWORD, LPOVERLAPPED); + HANDLE RegisterEventSourceA (LPCSTR, LPCSTR); + HANDLE RegisterEventSourceW(LPCWSTR, LPCWSTR); + BOOL ReportEventA(HANDLE, WORD, WORD, DWORD, PSID, WORD, DWORD, LPCSTR*, PVOID); + BOOL ReportEventW(HANDLE, WORD, WORD, DWORD, PSID, WORD, DWORD, LPCWSTR*, PVOID); + BOOL RevertToSelf(); + BOOL SetAclInformation(PACL, PVOID, DWORD, ACL_INFORMATION_CLASS); + BOOL SetFileSecurityA(LPCSTR, SECURITY_INFORMATION, PSECURITY_DESCRIPTOR); + BOOL SetFileSecurityW(LPCWSTR, SECURITY_INFORMATION, PSECURITY_DESCRIPTOR); + BOOL SetHandleInformation(HANDLE, DWORD, DWORD); + BOOL SetKernelObjectSecurity(HANDLE, SECURITY_INFORMATION, PSECURITY_DESCRIPTOR); + BOOL SetPrivateObjectSecurity(SECURITY_INFORMATION, PSECURITY_DESCRIPTOR, PSECURITY_DESCRIPTOR*, PGENERIC_MAPPING, HANDLE); + BOOL SetProcessAffinityMask(HANDLE, DWORD_PTR); + BOOL SetProcessPriorityBoost(HANDLE, BOOL); + BOOL SetProcessShutdownParameters(DWORD, DWORD); + BOOL SetProcessWorkingSetSize(HANDLE, SIZE_T, SIZE_T); + BOOL SetSecurityDescriptorDacl(PSECURITY_DESCRIPTOR, BOOL, PACL, BOOL); + BOOL SetSecurityDescriptorGroup(PSECURITY_DESCRIPTOR, PSID, BOOL); + BOOL SetSecurityDescriptorOwner(PSECURITY_DESCRIPTOR, PSID, BOOL); + BOOL SetSecurityDescriptorSacl(PSECURITY_DESCRIPTOR, BOOL, PACL, BOOL); + BOOL SetSystemTimeAdjustment(DWORD, BOOL); + DWORD SetTapeParameters(HANDLE, DWORD, PVOID); + DWORD SetTapePosition(HANDLE, DWORD, DWORD, DWORD, DWORD, BOOL); + BOOL SetThreadPriorityBoost(HANDLE, BOOL); + BOOL SetThreadToken(PHANDLE, HANDLE); + BOOL SetTokenInformation(HANDLE, TOKEN_INFORMATION_CLASS, PVOID, DWORD); + DWORD SignalObjectAndWait(HANDLE, HANDLE, DWORD, BOOL); + BOOL SwitchToThread(); + BOOL SystemTimeToTzSpecificLocalTime(LPTIME_ZONE_INFORMATION, LPSYSTEMTIME, LPSYSTEMTIME); + BOOL TzSpecificLocalTimeToSystemTime(LPTIME_ZONE_INFORMATION, LPSYSTEMTIME, LPSYSTEMTIME); + BOOL TryEnterCriticalSection(LPCRITICAL_SECTION); + BOOL TryEnterCriticalSection(shared(CRITICAL_SECTION)*); + BOOL UnlockFileEx(HANDLE, DWORD, DWORD, DWORD, LPOVERLAPPED); + BOOL UpdateResourceA(HANDLE, LPCSTR, LPCSTR, WORD, PVOID, DWORD); + BOOL UpdateResourceW(HANDLE, LPCWSTR, LPCWSTR, WORD, PVOID, DWORD); + BOOL WriteFileGather(HANDLE, FILE_SEGMENT_ELEMENT*, DWORD, LPDWORD, LPOVERLAPPED); + DWORD WriteTapemark(HANDLE, DWORD, DWORD, BOOL); + + static if (_WIN32_WINNT >= 0x500) { + BOOL AddAccessAllowedAceEx(PACL, DWORD, DWORD, DWORD, PSID); + BOOL AddAccessDeniedAceEx(PACL, DWORD, DWORD, DWORD, PSID); + PVOID AddVectoredExceptionHandler(ULONG, PVECTORED_EXCEPTION_HANDLER); + BOOL AllocateUserPhysicalPages(HANDLE, PULONG_PTR, PULONG_PTR); + BOOL AssignProcessToJobObject(HANDLE, HANDLE); + BOOL ChangeTimerQueueTimer(HANDLE,HANDLE,ULONG,ULONG); + LPVOID CreateFiberEx(SIZE_T, SIZE_T, DWORD, LPFIBER_START_ROUTINE, LPVOID); + HANDLE CreateFileMappingA(HANDLE, LPSECURITY_ATTRIBUTES, DWORD, DWORD, DWORD, LPCSTR); + HANDLE CreateFileMappingW(HANDLE, LPSECURITY_ATTRIBUTES, DWORD, DWORD, DWORD, LPCWSTR); + BOOL CreateHardLinkA(LPCSTR, LPCSTR, LPSECURITY_ATTRIBUTES); + BOOL CreateHardLinkW(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES); + HANDLE CreateJobObjectA(LPSECURITY_ATTRIBUTES, LPCSTR); + HANDLE CreateJobObjectW(LPSECURITY_ATTRIBUTES, LPCWSTR); + BOOL CreateProcessWithLogonW(LPCWSTR, LPCWSTR, LPCWSTR, DWORD, LPCWSTR, LPWSTR, DWORD, LPVOID, LPCWSTR, LPSTARTUPINFOW, LPPROCESS_INFORMATION); + HANDLE CreateTimerQueue(); + BOOL CreateTimerQueueTimer(PHANDLE, HANDLE, WAITORTIMERCALLBACK, PVOID, DWORD, DWORD, ULONG); + BOOL DeleteTimerQueue(HANDLE); + BOOL DeleteTimerQueueEx(HANDLE, HANDLE); + BOOL DeleteTimerQueueTimer(HANDLE, HANDLE, HANDLE); + BOOL DeleteVolumeMountPointA(LPCSTR); + BOOL DeleteVolumeMountPointW(LPCWSTR); + BOOL DnsHostnameToComputerNameA(LPCSTR, LPSTR, LPDWORD); + BOOL DnsHostnameToComputerNameW(LPCWSTR, LPWSTR, LPDWORD); + BOOL EncryptFileA(LPCSTR); + BOOL EncryptFileW(LPCWSTR); + BOOL FileEncryptionStatusA(LPCSTR, LPDWORD); + BOOL FileEncryptionStatusW(LPCWSTR, LPDWORD); + HANDLE FindFirstVolumeA(LPCSTR, DWORD); + HANDLE FindFirstVolumeMountPointA(LPSTR, LPSTR, DWORD); + HANDLE FindFirstVolumeMountPointW(LPWSTR, LPWSTR, DWORD); + HANDLE FindFirstVolumeW(LPCWSTR, DWORD); + BOOL FindNextVolumeA(HANDLE, LPCSTR, DWORD); + BOOL FindNextVolumeW(HANDLE, LPWSTR, DWORD); + BOOL FindNextVolumeMountPointA(HANDLE, LPSTR, DWORD); + BOOL FindNextVolumeMountPointW(HANDLE, LPWSTR, DWORD); + BOOL FindVolumeClose(HANDLE); + BOOL FindVolumeMountPointClose(HANDLE); + BOOL FlushViewOfFile(PCVOID, SIZE_T); + BOOL FreeUserPhysicalPages(HANDLE, PULONG_PTR, PULONG_PTR); + BOOL GetComputerNameExA(COMPUTER_NAME_FORMAT, LPSTR, LPDWORD); + BOOL GetComputerNameExW(COMPUTER_NAME_FORMAT, LPWSTR, LPDWORD); + BOOL GetFileSizeEx(HANDLE, PLARGE_INTEGER); + BOOL GetModuleHandleExA(DWORD, LPCSTR, HMODULE*); + BOOL GetModuleHandleExW(DWORD, LPCWSTR, HMODULE*); + HANDLE GetProcessHeap(); + DWORD GetProcessHeaps(DWORD, PHANDLE); + BOOL GetProcessIoCounters(HANDLE, PIO_COUNTERS); + BOOL GetSystemPowerStatus(LPSYSTEM_POWER_STATUS); + UINT GetSystemWindowsDirectoryA(LPSTR, UINT); + UINT GetSystemWindowsDirectoryW(LPWSTR, UINT); + BOOL GetVolumeNameForVolumeMountPointA(LPCSTR, LPSTR, DWORD); + BOOL GetVolumeNameForVolumeMountPointW(LPCWSTR, LPWSTR, DWORD); + BOOL GetVolumePathNameA(LPCSTR, LPSTR, DWORD); + BOOL GetVolumePathNameW(LPCWSTR, LPWSTR, DWORD); + BOOL GlobalMemoryStatusEx(LPMEMORYSTATUSEX); + BOOL IsBadCodePtr(FARPROC); + BOOL IsSystemResumeAutomatic(); + BOOL MapUserPhysicalPages(PVOID, ULONG_PTR, PULONG_PTR); + BOOL MapUserPhysicalPagesScatter(PVOID*, ULONG_PTR, PULONG_PTR); + PVOID MapViewOfFile(HANDLE, DWORD, DWORD, DWORD, SIZE_T); + PVOID MapViewOfFileEx(HANDLE, DWORD, DWORD, DWORD, SIZE_T, PVOID); + HANDLE OpenFileMappingA(DWORD, BOOL, LPCSTR); + HANDLE OpenFileMappingW(DWORD, BOOL, LPCWSTR); + BOOL ProcessIdToSessionId(DWORD, DWORD*); + BOOL QueryInformationJobObject(HANDLE, JOBOBJECTINFOCLASS, LPVOID, DWORD, LPDWORD); + ULONG RemoveVectoredExceptionHandler(PVOID); + BOOL ReplaceFileA(LPCSTR, LPCSTR, LPCSTR, DWORD, LPVOID, LPVOID); + BOOL ReplaceFileW(LPCWSTR, LPCWSTR, LPCWSTR, DWORD, LPVOID, LPVOID); + BOOL SetComputerNameExA(COMPUTER_NAME_FORMAT, LPCSTR); + BOOL SetComputerNameExW(COMPUTER_NAME_FORMAT, LPCWSTR); + BOOL SetFilePointerEx(HANDLE, LARGE_INTEGER, PLARGE_INTEGER, DWORD); + BOOL SetInformationJobObject(HANDLE, JOBOBJECTINFOCLASS, LPVOID, DWORD); + BOOL SetSecurityDescriptorControl(PSECURITY_DESCRIPTOR, SECURITY_DESCRIPTOR_CONTROL, SECURITY_DESCRIPTOR_CONTROL); + BOOL SetSystemPowerState(BOOL, BOOL); + EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE); + DWORD SetThreadIdealProcessor(HANDLE, DWORD); + BOOL SetVolumeMountPointA(LPCSTR, LPCSTR); + BOOL SetVolumeMountPointW(LPCWSTR, LPCWSTR); + BOOL TerminateJobObject(HANDLE, UINT); + BOOL UnmapViewOfFile(PCVOID); + BOOL UnregisterWait(HANDLE); + BOOL UnregisterWaitEx(HANDLE, HANDLE); + BOOL VerifyVersionInfoA(LPOSVERSIONINFOEXA, DWORD, DWORDLONG); + BOOL VerifyVersionInfoW(LPOSVERSIONINFOEXW, DWORD, DWORDLONG); + } + + static if (_WIN32_WINNT >= 0x501) { + BOOL ActivateActCtx(HANDLE, ULONG_PTR*); + void AddRefActCtx(HANDLE); + BOOL CheckNameLegalDOS8Dot3A(LPCSTR, LPSTR, DWORD, PBOOL, PBOOL); + BOOL CheckNameLegalDOS8Dot3W(LPCWSTR, LPSTR, DWORD, PBOOL, PBOOL); + BOOL CheckRemoteDebuggerPresent(HANDLE, PBOOL); + BOOL ConvertFiberToThread(); + HANDLE CreateActCtxA(PCACTCTXA); + HANDLE CreateActCtxW(PCACTCTXW); + HANDLE CreateMemoryResourceNotification(MEMORY_RESOURCE_NOTIFICATION_TYPE); + BOOL DeactivateActCtx(DWORD, ULONG_PTR); + BOOL DebugActiveProcessStop(DWORD); + BOOL DebugBreakProcess(HANDLE); + BOOL DebugSetProcessKillOnExit(BOOL); + BOOL FindActCtxSectionGuid(DWORD, const(GUID)*, ULONG, const(GUID)*, + PACTCTX_SECTION_KEYED_DATA); + BOOL FindActCtxSectionStringA(DWORD, const(GUID)*, ULONG, LPCSTR, + PACTCTX_SECTION_KEYED_DATA); + BOOL FindActCtxSectionStringW(DWORD, const(GUID)*, ULONG, LPCWSTR, + PACTCTX_SECTION_KEYED_DATA); + BOOL GetCurrentActCtx(HANDLE*); + VOID GetNativeSystemInfo(LPSYSTEM_INFO); + BOOL GetProcessHandleCount(HANDLE, PDWORD); + BOOL GetSystemRegistryQuota(PDWORD, PDWORD); + BOOL GetSystemTimes(LPFILETIME, LPFILETIME, LPFILETIME); + UINT GetSystemWow64DirectoryA(LPSTR, UINT); + UINT GetSystemWow64DirectoryW(LPWSTR, UINT); + BOOL GetThreadIOPendingFlag(HANDLE, PBOOL); + BOOL GetVolumePathNamesForVolumeNameA(LPCSTR, LPSTR, DWORD, PDWORD); + BOOL GetVolumePathNamesForVolumeNameW(LPCWSTR, LPWSTR, DWORD, PDWORD); + UINT GetWriteWatch(DWORD, PVOID, SIZE_T, PVOID*, PULONG_PTR, PULONG); + BOOL HeapQueryInformation(HANDLE, HEAP_INFORMATION_CLASS, PVOID, SIZE_T, PSIZE_T); + BOOL HeapSetInformation(HANDLE, HEAP_INFORMATION_CLASS, PVOID, SIZE_T); + BOOL IsProcessInJob(HANDLE, HANDLE, PBOOL); + BOOL IsWow64Process(HANDLE, PBOOL); + BOOL QueryActCtxW(DWORD, HANDLE, PVOID, ULONG, PVOID, SIZE_T, SIZE_T*); + BOOL QueryMemoryResourceNotification(HANDLE, PBOOL); + void ReleaseActCtx(HANDLE); + UINT ResetWriteWatch(LPVOID, SIZE_T); + BOOL SetFileShortNameA(HANDLE, LPCSTR); + BOOL SetFileShortNameW(HANDLE, LPCWSTR); + BOOL SetFileValidData(HANDLE, LONGLONG); + BOOL ZombifyActCtx(HANDLE); + } + + static if (_WIN32_WINNT >= 0x502) { + DWORD GetFirmwareEnvironmentVariableA(LPCSTR, LPCSTR, PVOID, DWORD); + DWORD GetFirmwareEnvironmentVariableW(LPCWSTR, LPCWSTR, PVOID, DWORD); + DWORD GetDllDirectoryA(DWORD, LPSTR); + DWORD GetDllDirectoryW(DWORD, LPWSTR); + DWORD GetThreadId(HANDLE); + DWORD GetProcessId(HANDLE); + HANDLE ReOpenFile(HANDLE, DWORD, DWORD, DWORD); + BOOL SetDllDirectoryA(LPCSTR); + BOOL SetDllDirectoryW(LPCWSTR); + BOOL SetFirmwareEnvironmentVariableA(LPCSTR, LPCSTR, PVOID, DWORD); + BOOL SetFirmwareEnvironmentVariableW(LPCWSTR, LPCWSTR, PVOID, DWORD); + } + + // ??? + static if (_WIN32_WINNT >= 0x510) { + VOID RestoreLastError(DWORD); + } + + static if (_WIN32_WINNT >= 0x600) { + BOOL CreateSymbolicLinkA(LPCSTR, LPCSTR, DWORD); + BOOL CreateSymbolicLinkW(LPCWSTR, LPCWSTR, DWORD); + BOOL GetFileInformationByHandleEx(HANDLE, FILE_INFO_BY_HANDLE_CLASS, LPVOID, DWORD); + } +} + +// For compatibility with old urt.internal.sys.windows.windows: +version (LittleEndian) nothrow @nogc +{ + BOOL QueryPerformanceCounter()(long* lpPerformanceCount) { return QueryPerformanceCounter(cast(PLARGE_INTEGER)lpPerformanceCount); } + BOOL QueryPerformanceFrequency()(long* lpFrequency) { return QueryPerformanceFrequency(cast(PLARGE_INTEGER)lpFrequency); } +} + +mixin DECLARE_AW!("STARTUPINFO"); +version (Unicode) { + //alias STARTUPINFOW STARTUPINFO; + alias WIN32_FIND_DATAW WIN32_FIND_DATA; + alias ENUMRESLANGPROCW ENUMRESLANGPROC; + alias ENUMRESNAMEPROCW ENUMRESNAMEPROC; + alias ENUMRESTYPEPROCW ENUMRESTYPEPROC; + alias AddAtomW AddAtom; + alias BeginUpdateResourceW BeginUpdateResource; + alias BuildCommDCBW BuildCommDCB; + alias BuildCommDCBAndTimeoutsW BuildCommDCBAndTimeouts; + alias CallNamedPipeW CallNamedPipe; + alias CommConfigDialogW CommConfigDialog; + alias CopyFileW CopyFile; + alias CopyFileExW CopyFileEx; + alias CreateDirectoryW CreateDirectory; + alias CreateDirectoryExW CreateDirectoryEx; + alias CreateEventW CreateEvent; + alias CreateFileW CreateFile; + alias CreateMailslotW CreateMailslot; + alias CreateMutexW CreateMutex; + alias CreateProcessW CreateProcess; + alias CreateSemaphoreW CreateSemaphore; + alias DeleteFileW DeleteFile; + alias EndUpdateResourceW EndUpdateResource; + alias EnumResourceLanguagesW EnumResourceLanguages; + alias EnumResourceNamesW EnumResourceNames; + alias EnumResourceTypesW EnumResourceTypes; + alias ExpandEnvironmentStringsW ExpandEnvironmentStrings; + alias FatalAppExitW FatalAppExit; + alias FindAtomW FindAtom; + alias FindFirstChangeNotificationW FindFirstChangeNotification; + alias FindFirstFileW FindFirstFile; + alias FindNextFileW FindNextFile; + alias FindResourceW FindResource; + alias FindResourceExW FindResourceEx; +// alias FormatMessageW FormatMessage; + alias FreeEnvironmentStringsW FreeEnvironmentStrings; + alias GetAtomNameW GetAtomName; + alias GetCommandLineW GetCommandLine; + alias GetComputerNameW GetComputerName; + alias GetCurrentDirectoryW GetCurrentDirectory; + alias GetDefaultCommConfigW GetDefaultCommConfig; + alias GetDiskFreeSpaceW GetDiskFreeSpace; + alias GetDiskFreeSpaceExW GetDiskFreeSpaceEx; + alias GetDriveTypeW GetDriveType; + alias GetEnvironmentStringsW GetEnvironmentStrings; + alias GetEnvironmentVariableW GetEnvironmentVariable; + alias GetFileAttributesW GetFileAttributes; + alias GetFullPathNameW GetFullPathName; + alias GetLogicalDriveStringsW GetLogicalDriveStrings; + alias GetModuleFileNameW GetModuleFileName; + alias GetModuleHandleW GetModuleHandle; + alias GetNamedPipeHandleStateW GetNamedPipeHandleState; + alias GetPrivateProfileIntW GetPrivateProfileInt; + alias GetPrivateProfileSectionW GetPrivateProfileSection; + alias GetPrivateProfileSectionNamesW GetPrivateProfileSectionNames; + alias GetPrivateProfileStringW GetPrivateProfileString; + alias GetPrivateProfileStructW GetPrivateProfileStruct; + alias GetProfileIntW GetProfileInt; + alias GetProfileSectionW GetProfileSection; + alias GetProfileStringW GetProfileString; + alias GetShortPathNameW GetShortPathName; + alias GetStartupInfoW GetStartupInfo; + alias GetSystemDirectoryW GetSystemDirectory; + alias GetTempFileNameW GetTempFileName; + alias GetTempPathW GetTempPath; + alias GetUserNameW GetUserName; + alias GetVersionExW GetVersionEx; + alias GetVolumeInformationW GetVolumeInformation; + alias GetWindowsDirectoryW GetWindowsDirectory; + alias GlobalAddAtomW GlobalAddAtom; + alias GlobalFindAtomW GlobalFindAtom; + alias GlobalGetAtomNameW GlobalGetAtomName; + alias IsBadStringPtrW IsBadStringPtr; + alias LoadLibraryW LoadLibrary; + alias LoadLibraryExW LoadLibraryEx; + alias lstrcatW lstrcat; + alias lstrcmpW lstrcmp; + alias lstrcmpiW lstrcmpi; + alias lstrcpyW lstrcpy; + alias lstrcpynW lstrcpyn; + alias lstrlenW lstrlen; + alias MoveFileW MoveFile; + alias OpenEventW OpenEvent; + alias OpenMutexW OpenMutex; + alias OpenSemaphoreW OpenSemaphore; + alias OutputDebugStringW OutputDebugString; + alias RemoveDirectoryW RemoveDirectory; + alias SearchPathW SearchPath; + alias SetComputerNameW SetComputerName; + alias SetCurrentDirectoryW SetCurrentDirectory; + alias SetDefaultCommConfigW SetDefaultCommConfig; + alias SetEnvironmentVariableW SetEnvironmentVariable; + alias SetFileAttributesW SetFileAttributes; + alias SetVolumeLabelW SetVolumeLabel; + alias WaitNamedPipeW WaitNamedPipe; + alias WritePrivateProfileSectionW WritePrivateProfileSection; + alias WritePrivateProfileStringW WritePrivateProfileString; + alias WritePrivateProfileStructW WritePrivateProfileStruct; + alias WriteProfileSectionW WriteProfileSection; + alias WriteProfileStringW WriteProfileString; + alias CreateWaitableTimerW CreateWaitableTimer; + alias GetFileAttributesExW GetFileAttributesEx; + alias GetLongPathNameW GetLongPathName; + alias QueryDosDeviceW QueryDosDevice; + + alias HW_PROFILE_INFOW HW_PROFILE_INFO; + alias AccessCheckAndAuditAlarmW AccessCheckAndAuditAlarm; + alias BackupEventLogW BackupEventLog; + alias ClearEventLogW ClearEventLog; + alias CreateNamedPipeW CreateNamedPipe; + alias CreateProcessAsUserW CreateProcessAsUser; + alias DefineDosDeviceW DefineDosDevice; + alias FindFirstFileExW FindFirstFileEx; + alias GetBinaryTypeW GetBinaryType; + alias GetCompressedFileSizeW GetCompressedFileSize; + alias GetFileSecurityW GetFileSecurity; + alias LogonUserW LogonUser; + alias LookupAccountNameW LookupAccountName; + alias LookupAccountSidW LookupAccountSid; + alias LookupPrivilegeDisplayNameW LookupPrivilegeDisplayName; + alias LookupPrivilegeNameW LookupPrivilegeName; + alias LookupPrivilegeValueW LookupPrivilegeValue; + alias MoveFileExW MoveFileEx; + alias ObjectCloseAuditAlarmW ObjectCloseAuditAlarm; + alias ObjectDeleteAuditAlarmW ObjectDeleteAuditAlarm; + alias ObjectOpenAuditAlarmW ObjectOpenAuditAlarm; + alias ObjectPrivilegeAuditAlarmW ObjectPrivilegeAuditAlarm; + alias OpenBackupEventLogW OpenBackupEventLog; + alias OpenEventLogW OpenEventLog; + alias PrivilegedServiceAuditAlarmW PrivilegedServiceAuditAlarm; + alias ReadEventLogW ReadEventLog; + alias RegisterEventSourceW RegisterEventSource; + alias ReportEventW ReportEvent; + alias SetFileSecurityW SetFileSecurity; + alias UpdateResourceW UpdateResource; + + static if (_WIN32_WINNT >= 0x500) { + alias CreateFileMappingW CreateFileMapping; + alias CreateHardLinkW CreateHardLink; + alias CreateJobObjectW CreateJobObject; + alias DeleteVolumeMountPointW DeleteVolumeMountPoint; + alias DnsHostnameToComputerNameW DnsHostnameToComputerName; + alias EncryptFileW EncryptFile; + alias FileEncryptionStatusW FileEncryptionStatus; + alias FindFirstVolumeW FindFirstVolume; + alias FindFirstVolumeMountPointW FindFirstVolumeMountPoint; + alias FindNextVolumeW FindNextVolume; + alias FindNextVolumeMountPointW FindNextVolumeMountPoint; + alias GetModuleHandleExW GetModuleHandleEx; + alias GetSystemWindowsDirectoryW GetSystemWindowsDirectory; + alias GetVolumeNameForVolumeMountPointW GetVolumeNameForVolumeMountPoint; + alias GetVolumePathNameW GetVolumePathName; + alias OpenFileMappingW OpenFileMapping; + alias ReplaceFileW ReplaceFile; + alias SetVolumeMountPointW SetVolumeMountPoint; + alias VerifyVersionInfoW VerifyVersionInfo; + } + + static if (_WIN32_WINNT >= 0x501) { + alias ACTCTXW ACTCTX; + alias CheckNameLegalDOS8Dot3W CheckNameLegalDOS8Dot3; + alias CreateActCtxW CreateActCtx; + alias FindActCtxSectionStringW FindActCtxSectionString; + alias GetSystemWow64DirectoryW GetSystemWow64Directory; + alias GetVolumePathNamesForVolumeNameW GetVolumePathNamesForVolumeName; + alias SetFileShortNameW SetFileShortName; + } + + static if (_WIN32_WINNT >= 0x502) { + alias SetFirmwareEnvironmentVariableW SetFirmwareEnvironmentVariable; + alias SetDllDirectoryW SetDllDirectory; + alias GetDllDirectoryW GetDllDirectory; + } + + static if (_WIN32_WINNT >= 0x600) { + alias CreateSymbolicLinkW CreateSymbolicLink; + } + +} else { + //alias STARTUPINFOA STARTUPINFO; + alias WIN32_FIND_DATAA WIN32_FIND_DATA; + alias ENUMRESLANGPROCW ENUMRESLANGPROC; + alias ENUMRESNAMEPROCW ENUMRESNAMEPROC; + alias ENUMRESTYPEPROCW ENUMRESTYPEPROC; + alias AddAtomA AddAtom; + alias BeginUpdateResourceA BeginUpdateResource; + alias BuildCommDCBA BuildCommDCB; + alias BuildCommDCBAndTimeoutsA BuildCommDCBAndTimeouts; + alias CallNamedPipeA CallNamedPipe; + alias CommConfigDialogA CommConfigDialog; + alias CopyFileA CopyFile; + alias CopyFileExA CopyFileEx; + alias CreateDirectoryA CreateDirectory; + alias CreateDirectoryExA CreateDirectoryEx; + alias CreateEventA CreateEvent; + alias CreateFileA CreateFile; + alias CreateMailslotA CreateMailslot; + alias CreateMutexA CreateMutex; + alias CreateProcessA CreateProcess; + alias CreateSemaphoreA CreateSemaphore; + alias DeleteFileA DeleteFile; + alias EndUpdateResourceA EndUpdateResource; + alias EnumResourceLanguagesA EnumResourceLanguages; + alias EnumResourceNamesA EnumResourceNames; + alias EnumResourceTypesA EnumResourceTypes; + alias ExpandEnvironmentStringsA ExpandEnvironmentStrings; + alias FatalAppExitA FatalAppExit; + alias FindAtomA FindAtom; + alias FindFirstChangeNotificationA FindFirstChangeNotification; + alias FindFirstFileA FindFirstFile; + alias FindNextFileA FindNextFile; + alias FindResourceA FindResource; + alias FindResourceExA FindResourceEx; +// alias FormatMessageA FormatMessage; + alias FreeEnvironmentStringsA FreeEnvironmentStrings; + alias GetAtomNameA GetAtomName; + alias GetCommandLineA GetCommandLine; + alias GetComputerNameA GetComputerName; + alias GetCurrentDirectoryA GetCurrentDirectory; + alias GetDefaultCommConfigA GetDefaultCommConfig; + alias GetDiskFreeSpaceA GetDiskFreeSpace; + alias GetDiskFreeSpaceExA GetDiskFreeSpaceEx; + alias GetDriveTypeA GetDriveType; + alias GetEnvironmentStringsA GetEnvironmentStrings; + alias GetEnvironmentVariableA GetEnvironmentVariable; + alias GetFileAttributesA GetFileAttributes; + alias GetFullPathNameA GetFullPathName; + alias GetLogicalDriveStringsA GetLogicalDriveStrings; + alias GetNamedPipeHandleStateA GetNamedPipeHandleState; + alias GetModuleHandleA GetModuleHandle; + alias GetModuleFileNameA GetModuleFileName; + alias GetPrivateProfileIntA GetPrivateProfileInt; + alias GetPrivateProfileSectionA GetPrivateProfileSection; + alias GetPrivateProfileSectionNamesA GetPrivateProfileSectionNames; + alias GetPrivateProfileStringA GetPrivateProfileString; + alias GetPrivateProfileStructA GetPrivateProfileStruct; + alias GetProfileIntA GetProfileInt; + alias GetProfileSectionA GetProfileSection; + alias GetProfileStringA GetProfileString; + alias GetShortPathNameA GetShortPathName; + alias GetStartupInfoA GetStartupInfo; + alias GetSystemDirectoryA GetSystemDirectory; + alias GetTempFileNameA GetTempFileName; + alias GetTempPathA GetTempPath; + alias GetUserNameA GetUserName; + alias GetVersionExA GetVersionEx; + alias GetVolumeInformationA GetVolumeInformation; + alias GetWindowsDirectoryA GetWindowsDirectory; + alias GlobalAddAtomA GlobalAddAtom; + alias GlobalFindAtomA GlobalFindAtom; + alias GlobalGetAtomNameA GlobalGetAtomName; + alias IsBadStringPtrA IsBadStringPtr; + alias LoadLibraryA LoadLibrary; + alias LoadLibraryExA LoadLibraryEx; + alias lstrcatA lstrcat; + alias lstrcmpA lstrcmp; + alias lstrcmpiA lstrcmpi; + alias lstrcpyA lstrcpy; + alias lstrcpynA lstrcpyn; + alias lstrlenA lstrlen; + alias MoveFileA MoveFile; + alias OpenEventA OpenEvent; + alias OpenMutexA OpenMutex; + alias OpenSemaphoreA OpenSemaphore; + alias OutputDebugStringA OutputDebugString; + alias RemoveDirectoryA RemoveDirectory; + alias SearchPathA SearchPath; + alias SetComputerNameA SetComputerName; + alias SetCurrentDirectoryA SetCurrentDirectory; + alias SetDefaultCommConfigA SetDefaultCommConfig; + alias SetEnvironmentVariableA SetEnvironmentVariable; + alias SetFileAttributesA SetFileAttributes; + alias SetVolumeLabelA SetVolumeLabel; + alias WaitNamedPipeA WaitNamedPipe; + alias WritePrivateProfileSectionA WritePrivateProfileSection; + alias WritePrivateProfileStringA WritePrivateProfileString; + alias WritePrivateProfileStructA WritePrivateProfileStruct; + alias WriteProfileSectionA WriteProfileSection; + alias WriteProfileStringA WriteProfileString; + alias CreateWaitableTimerA CreateWaitableTimer; + alias GetFileAttributesExA GetFileAttributesEx; + alias GetLongPathNameA GetLongPathName; + alias QueryDosDeviceA QueryDosDevice; + + alias HW_PROFILE_INFOA HW_PROFILE_INFO; + alias AccessCheckAndAuditAlarmA AccessCheckAndAuditAlarm; + alias BackupEventLogA BackupEventLog; + alias ClearEventLogA ClearEventLog; + alias CreateNamedPipeA CreateNamedPipe; + alias CreateProcessAsUserA CreateProcessAsUser; + alias DefineDosDeviceA DefineDosDevice; + alias FindFirstFileExA FindFirstFileEx; + alias GetBinaryTypeA GetBinaryType; + alias GetCompressedFileSizeA GetCompressedFileSize; + alias GetFileSecurityA GetFileSecurity; + alias LogonUserA LogonUser; + alias LookupAccountNameA LookupAccountName; + alias LookupAccountSidA LookupAccountSid; + alias LookupPrivilegeDisplayNameA LookupPrivilegeDisplayName; + alias LookupPrivilegeNameA LookupPrivilegeName; + alias LookupPrivilegeValueA LookupPrivilegeValue; + alias MoveFileExA MoveFileEx; + alias ObjectCloseAuditAlarmA ObjectCloseAuditAlarm; + alias ObjectDeleteAuditAlarmA ObjectDeleteAuditAlarm; + alias ObjectOpenAuditAlarmA ObjectOpenAuditAlarm; + alias ObjectPrivilegeAuditAlarmA ObjectPrivilegeAuditAlarm; + alias OpenBackupEventLogA OpenBackupEventLog; + alias OpenEventLogA OpenEventLog; + alias PrivilegedServiceAuditAlarmA PrivilegedServiceAuditAlarm; + alias ReadEventLogA ReadEventLog; + alias RegisterEventSourceA RegisterEventSource; + alias ReportEventA ReportEvent; + alias SetFileSecurityA SetFileSecurity; + alias UpdateResourceA UpdateResource; + + static if (_WIN32_WINNT >= 0x500) { + alias CreateFileMappingA CreateFileMapping; + alias CreateHardLinkA CreateHardLink; + alias CreateJobObjectA CreateJobObject; + alias DeleteVolumeMountPointA DeleteVolumeMountPoint; + alias DnsHostnameToComputerNameA DnsHostnameToComputerName; + alias EncryptFileA EncryptFile; + alias FileEncryptionStatusA FileEncryptionStatus; + alias FindFirstVolumeA FindFirstVolume; + alias FindFirstVolumeMountPointA FindFirstVolumeMountPoint; + alias FindNextVolumeA FindNextVolume; + alias FindNextVolumeMountPointA FindNextVolumeMountPoint; + alias GetModuleHandleExA GetModuleHandleEx; + alias GetSystemWindowsDirectoryA GetSystemWindowsDirectory; + alias GetVolumeNameForVolumeMountPointA GetVolumeNameForVolumeMountPoint; + alias GetVolumePathNameA GetVolumePathName; + alias OpenFileMappingA OpenFileMapping; + alias ReplaceFileA ReplaceFile; + alias SetVolumeMountPointA SetVolumeMountPoint; + alias VerifyVersionInfoA VerifyVersionInfo; + } + + static if (_WIN32_WINNT >= 0x501) { + alias ACTCTXA ACTCTX; + alias CheckNameLegalDOS8Dot3A CheckNameLegalDOS8Dot3; + alias CreateActCtxA CreateActCtx; + alias FindActCtxSectionStringA FindActCtxSectionString; + alias GetSystemWow64DirectoryA GetSystemWow64Directory; + alias GetVolumePathNamesForVolumeNameA GetVolumePathNamesForVolumeName; + alias SetFileShortNameA SetFileShortName; + } + + static if (_WIN32_WINNT >= 0x502) { + alias GetDllDirectoryA GetDllDirectory; + alias SetDllDirectoryA SetDllDirectory; + alias SetFirmwareEnvironmentVariableA SetFirmwareEnvironmentVariable; + } + + static if (_WIN32_WINNT >= 0x600) { + alias CreateSymbolicLinkA CreateSymbolicLink; + } +} + +alias STARTUPINFO* LPSTARTUPINFO; +alias WIN32_FIND_DATA* LPWIN32_FIND_DATA; + +alias HW_PROFILE_INFO* LPHW_PROFILE_INFO; + +static if (_WIN32_WINNT >= 0x501) { + alias ACTCTX* PACTCTX, PCACTCTX; +} diff --git a/src/urt/internal/sys/windows/wincon.d b/src/urt/internal/sys/windows/wincon.d new file mode 100644 index 0000000..d378237 --- /dev/null +++ b/src/urt/internal/sys/windows/wincon.d @@ -0,0 +1,316 @@ +/** + * Windows API header module + * + * Translated from MinGW Windows headers + * + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_wincon.d) + */ +module urt.internal.sys.windows.wincon; +version (Windows): + +version (ANSI) {} else version = Unicode; +pragma(lib, "kernel32"); + +import urt.internal.sys.windows.w32api, urt.internal.sys.windows.windef; + +// FIXME: clean up Windows version support + +enum { + FOREGROUND_BLUE = 0x0001, + FOREGROUND_GREEN = 0x0002, + FOREGROUND_RED = 0x0004, + FOREGROUND_INTENSITY = 0x0008, + BACKGROUND_BLUE = 0x0010, + BACKGROUND_GREEN = 0x0020, + BACKGROUND_RED = 0x0040, + BACKGROUND_INTENSITY = 0x0080, + + COMMON_LVB_LEADING_BYTE = 0x0100, + COMMON_LVB_TRAILING_BYTE = 0x0200, + COMMON_LVB_GRID_HORIZONTAL = 0x0400, + COMMON_LVB_GRID_LVERTICAL = 0x0800, + COMMON_LVB_GRID_RVERTICAL = 0x1000, + COMMON_LVB_REVERSE_VIDEO = 0x4000, + COMMON_LVB_UNDERSCORE = 0x8000, + + COMMON_LVB_SBCSDBCS = 0x0300, +} + +static if (_WIN32_WINNT >= 0x501) { + enum { + CONSOLE_FULLSCREEN_MODE = 1, + CONSOLE_WINDOWED_MODE = 0 + } +} + +enum { + CTRL_C_EVENT = 0, + CTRL_BREAK_EVENT = 1, + CTRL_CLOSE_EVENT = 2, + CTRL_LOGOFF_EVENT = 5, + CTRL_SHUTDOWN_EVENT = 6 +} + +enum { + ENABLE_PROCESSED_INPUT = 1, + ENABLE_LINE_INPUT = 2, + ENABLE_ECHO_INPUT = 4, + ENABLE_WINDOW_INPUT = 8, + ENABLE_MOUSE_INPUT = 16, + ENABLE_INSERT_MODE = 32, + ENABLE_QUICK_EDIT_MODE = 64, + ENABLE_EXTENDED_FLAGS = 128, + ENABLE_AUTO_POSITION = 256, + ENABLE_VIRTUAL_TERMINAL_INPUT = 512 +} + +enum { + ENABLE_PROCESSED_OUTPUT = 1, + ENABLE_WRAP_AT_EOL_OUTPUT = 2, + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4, + DISABLE_NEWLINE_AUTO_RETURN = 8, + ENABLE_LVB_GRID_WORLDWIDE = 16 +} + +enum { + KEY_EVENT = 1, + MOUSE_EVENT = 2, + WINDOW_BUFFER_SIZE_EVENT = 4, + MENU_EVENT = 8, + FOCUS_EVENT = 16 +} +enum { + RIGHT_ALT_PRESSED = 1, + LEFT_ALT_PRESSED = 2, + RIGHT_CTRL_PRESSED = 4, + LEFT_CTRL_PRESSED = 8, + SHIFT_PRESSED = 16, + NUMLOCK_ON = 32, + SCROLLLOCK_ON = 64, + CAPSLOCK_ON = 128, + ENHANCED_KEY = 256 +} +enum { + FROM_LEFT_1ST_BUTTON_PRESSED = 1, + RIGHTMOST_BUTTON_PRESSED = 2, + FROM_LEFT_2ND_BUTTON_PRESSED = 4, + FROM_LEFT_3RD_BUTTON_PRESSED = 8, + FROM_LEFT_4TH_BUTTON_PRESSED = 16 +} + +enum { + MOUSE_MOVED = 1, + DOUBLE_CLICK = 2, + MOUSE_WHEELED = 4 +} + +struct CHAR_INFO { + union _Char { + WCHAR UnicodeChar = 0; + CHAR AsciiChar; + } + union { + _Char Char; + WCHAR UnicodeChar; + CHAR AsciiChar; + } + WORD Attributes; +} +alias CHAR_INFO* PCHAR_INFO; + +struct SMALL_RECT { + SHORT Left; + SHORT Top; + SHORT Right; + SHORT Bottom; +} +alias SMALL_RECT* PSMALL_RECT; + +struct CONSOLE_CURSOR_INFO { + DWORD dwSize; + BOOL bVisible; +} +alias CONSOLE_CURSOR_INFO* PCONSOLE_CURSOR_INFO; + +struct COORD { + SHORT X; + SHORT Y; +} +alias COORD* PCOORD; + +struct CONSOLE_FONT_INFO { + DWORD nFont; + COORD dwFontSize; +} +alias CONSOLE_FONT_INFO* PCONSOLE_FONT_INFO; + +struct CONSOLE_SCREEN_BUFFER_INFO { + COORD dwSize; + COORD dwCursorPosition; + WORD wAttributes; + SMALL_RECT srWindow; + COORD dwMaximumWindowSize; +} +alias CONSOLE_SCREEN_BUFFER_INFO* PCONSOLE_SCREEN_BUFFER_INFO; + +alias extern(Windows) BOOL function(DWORD) nothrow PHANDLER_ROUTINE; + +struct KEY_EVENT_RECORD { + BOOL bKeyDown; + WORD wRepeatCount; + WORD wVirtualKeyCode; + WORD wVirtualScanCode; + union _uChar { + WCHAR UnicodeChar = 0; + CHAR AsciiChar; + } + union { + WCHAR UnicodeChar = 0; + CHAR AsciiChar; + _uChar uChar; + } + DWORD dwControlKeyState; +} +alias KEY_EVENT_RECORD* PKEY_EVENT_RECORD; + +struct MOUSE_EVENT_RECORD { + COORD dwMousePosition; + DWORD dwButtonState; + DWORD dwControlKeyState; + DWORD dwEventFlags; +} +alias MOUSE_EVENT_RECORD* PMOUSE_EVENT_RECORD; + +struct WINDOW_BUFFER_SIZE_RECORD { + COORD dwSize; +} +alias WINDOW_BUFFER_SIZE_RECORD* PWINDOW_BUFFER_SIZE_RECORD; + +struct MENU_EVENT_RECORD { + UINT dwCommandId; +} +alias MENU_EVENT_RECORD* PMENU_EVENT_RECORD; + +struct FOCUS_EVENT_RECORD { + BOOL bSetFocus; +} +alias FOCUS_EVENT_RECORD* PFOCUS_EVENT_RECORD; + +struct INPUT_RECORD { + WORD EventType; + union _Event { + KEY_EVENT_RECORD KeyEvent; + MOUSE_EVENT_RECORD MouseEvent; + WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent; + MENU_EVENT_RECORD MenuEvent; + FOCUS_EVENT_RECORD FocusEvent; + } + union { + KEY_EVENT_RECORD KeyEvent; + MOUSE_EVENT_RECORD MouseEvent; + WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent; + MENU_EVENT_RECORD MenuEvent; + FOCUS_EVENT_RECORD FocusEvent; + _Event Event; + } +} +alias INPUT_RECORD* PINPUT_RECORD; + +extern (Windows) nothrow @nogc: + +BOOL AllocConsole(); +HANDLE CreateConsoleScreenBuffer(DWORD, DWORD, const(SECURITY_ATTRIBUTES)*, DWORD, LPVOID); +BOOL FillConsoleOutputAttribute(HANDLE, WORD, DWORD, COORD, PDWORD); +BOOL FillConsoleOutputCharacterA(HANDLE, CHAR, DWORD, COORD, PDWORD); +BOOL FillConsoleOutputCharacterW(HANDLE, WCHAR, DWORD, COORD, PDWORD); +BOOL FlushConsoleInputBuffer(HANDLE); +BOOL FreeConsole(); +BOOL GenerateConsoleCtrlEvent(DWORD, DWORD); +UINT GetConsoleCP(); +BOOL GetConsoleCursorInfo(HANDLE, PCONSOLE_CURSOR_INFO); +BOOL GetConsoleMode(HANDLE,PDWORD); +UINT GetConsoleOutputCP(); +BOOL GetConsoleScreenBufferInfo(HANDLE, PCONSOLE_SCREEN_BUFFER_INFO); +DWORD GetConsoleTitleA(LPSTR, DWORD); +DWORD GetConsoleTitleW(LPWSTR, DWORD); +COORD GetLargestConsoleWindowSize(HANDLE); +BOOL GetNumberOfConsoleInputEvents(HANDLE, PDWORD); +BOOL GetNumberOfConsoleMouseButtons(PDWORD); +BOOL PeekConsoleInputA(HANDLE, PINPUT_RECORD, DWORD, PDWORD); +BOOL PeekConsoleInputW(HANDLE, PINPUT_RECORD, DWORD, PDWORD); +BOOL ReadConsoleA(HANDLE, PVOID, DWORD, PDWORD, PVOID); +BOOL ReadConsoleW(HANDLE, PVOID, DWORD, PDWORD, PVOID); +BOOL ReadConsoleInputA(HANDLE, PINPUT_RECORD, DWORD, PDWORD); +BOOL ReadConsoleInputW(HANDLE, PINPUT_RECORD, DWORD, PDWORD); +BOOL ReadConsoleOutputAttribute(HANDLE, LPWORD, DWORD, COORD, LPDWORD); +BOOL ReadConsoleOutputCharacterA(HANDLE, LPSTR, DWORD, COORD, PDWORD); +BOOL ReadConsoleOutputCharacterW(HANDLE, LPWSTR, DWORD, COORD, PDWORD); +BOOL ReadConsoleOutputA(HANDLE, PCHAR_INFO, COORD, COORD, PSMALL_RECT); +BOOL ReadConsoleOutputW(HANDLE, PCHAR_INFO, COORD, COORD, PSMALL_RECT); +BOOL ScrollConsoleScreenBufferA(HANDLE, const(SMALL_RECT)*, const(SMALL_RECT)*, COORD, const(CHAR_INFO)*); +BOOL ScrollConsoleScreenBufferW(HANDLE, const(SMALL_RECT)*, const(SMALL_RECT)*, COORD, const(CHAR_INFO)*); +BOOL SetConsoleActiveScreenBuffer(HANDLE); +BOOL SetConsoleCP(UINT); +BOOL SetConsoleCtrlHandler(PHANDLER_ROUTINE, BOOL); +BOOL SetConsoleCursorInfo(HANDLE, const(CONSOLE_CURSOR_INFO)*); +BOOL SetConsoleCursorPosition(HANDLE, COORD); + + +static if (_WIN32_WINNT >= 0x500) { +BOOL GetConsoleDisplayMode(LPDWORD); +HWND GetConsoleWindow(); +} + +static if (_WIN32_WINNT >= 0x501) { +BOOL AttachConsole(DWORD); +BOOL SetConsoleDisplayMode(HANDLE, DWORD, PCOORD); +enum DWORD ATTACH_PARENT_PROCESS = cast(DWORD)-1; +} + +BOOL SetConsoleMode(HANDLE, DWORD); +BOOL SetConsoleOutputCP(UINT); +BOOL SetConsoleScreenBufferSize(HANDLE, COORD); +BOOL SetConsoleTextAttribute(HANDLE, WORD); +BOOL SetConsoleTitleA(LPCSTR); +BOOL SetConsoleTitleW(LPCWSTR); +BOOL SetConsoleWindowInfo(HANDLE, BOOL, const(SMALL_RECT)*); +BOOL WriteConsoleA(HANDLE, PCVOID, DWORD, PDWORD, PVOID); +BOOL WriteConsoleW(HANDLE, PCVOID, DWORD, PDWORD, PVOID); +BOOL WriteConsoleInputA(HANDLE, const(INPUT_RECORD)*, DWORD, PDWORD); +BOOL WriteConsoleInputW(HANDLE, const(INPUT_RECORD)*, DWORD, PDWORD); +BOOL WriteConsoleOutputA(HANDLE, const(CHAR_INFO)*, COORD, COORD, PSMALL_RECT); +BOOL WriteConsoleOutputW(HANDLE, const(CHAR_INFO)*, COORD, COORD, PSMALL_RECT); +BOOL WriteConsoleOutputAttribute(HANDLE, const(WORD)*, DWORD, COORD, PDWORD); +BOOL WriteConsoleOutputCharacterA(HANDLE, LPCSTR, DWORD, COORD, PDWORD); +BOOL WriteConsoleOutputCharacterW(HANDLE, LPCWSTR, DWORD, COORD, PDWORD); + +version (Unicode) { + alias FillConsoleOutputCharacterW FillConsoleOutputCharacter; + alias GetConsoleTitleW GetConsoleTitle; + alias PeekConsoleInputW PeekConsoleInput; + alias ReadConsoleW ReadConsole; + alias ReadConsoleInputW ReadConsoleInput; + alias ReadConsoleOutputW ReadConsoleOutput; + alias ReadConsoleOutputCharacterW ReadConsoleOutputCharacter; + alias ScrollConsoleScreenBufferW ScrollConsoleScreenBuffer; + alias SetConsoleTitleW SetConsoleTitle; + alias WriteConsoleW WriteConsole; + alias WriteConsoleInputW WriteConsoleInput; + alias WriteConsoleOutputW WriteConsoleOutput; + alias WriteConsoleOutputCharacterW WriteConsoleOutputCharacter; +} else { + alias FillConsoleOutputCharacterA FillConsoleOutputCharacter; + alias GetConsoleTitleA GetConsoleTitle; + alias PeekConsoleInputA PeekConsoleInput; + alias ReadConsoleA ReadConsole; + alias ReadConsoleInputA ReadConsoleInput; + alias ReadConsoleOutputA ReadConsoleOutput; + alias ReadConsoleOutputCharacterA ReadConsoleOutputCharacter; + alias ScrollConsoleScreenBufferA ScrollConsoleScreenBuffer; + alias SetConsoleTitleA SetConsoleTitle; + alias WriteConsoleA WriteConsole; + alias WriteConsoleInputA WriteConsoleInput; + alias WriteConsoleOutputA WriteConsoleOutput; + alias WriteConsoleOutputCharacterA WriteConsoleOutputCharacter; +} diff --git a/src/urt/internal/sys/windows/wincrypt.d b/src/urt/internal/sys/windows/wincrypt.d new file mode 100644 index 0000000..38e00b5 --- /dev/null +++ b/src/urt/internal/sys/windows/wincrypt.d @@ -0,0 +1,902 @@ +/** + * Windows API header module + * + * Translated from MinGW Windows headers + * + * Authors: Stewart Gordon + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_wincrypt.d) + */ +module urt.internal.sys.windows.wincrypt; +version (Windows): +pragma(lib, "advapi32"); + +version (ANSI) {} else version = Unicode; + +import urt.internal.sys.windows.w32api, urt.internal.sys.windows.winbase, urt.internal.sys.windows.windef; + +/* FIXME: + * Types of some constants + * Types of macros + * Inits of various "size" and "version" members + * Why are some #ifdefs commented out? + */ + +const TCHAR[] + MS_DEF_PROV = "Microsoft Base Cryptographic Provider v1.0", + MS_ENHANCED_PROV = "Microsoft Enhanced Cryptographic Provider v1.0", + MS_STRONG_PROV = "Microsoft Strong Cryptographic Provider", + MS_DEF_RSA_SIG_PROV = "Microsoft RSA Signature Cryptographic Provider", + MS_DEF_RSA_SCHANNEL_PROV = "Microsoft RSA SChannel Cryptographic Provider", + MS_DEF_DSS_PROV = "Microsoft Base DSS Cryptographic Provider", + MS_DEF_DSS_DH_PROV + = "Microsoft Base DSS and Diffie-Hellman Cryptographic Provider", + MS_ENH_DSS_DH_PROV + = "Microsoft Enhanced DSS and Diffie-Hellman Cryptographic Provider", + MS_DEF_DH_SCHANNEL_PROV = "Microsoft DH SChannel Cryptographic Provider", + MS_SCARD_PROV = "Microsoft Base Smart Card Crypto Provider"; + +static if (_WIN32_WINNT > 0x501) { +const TCHAR[] MS_ENH_RSA_AES_PROV + = "Microsoft Enhanced RSA and AES Cryptographic Provider"; +} else static if (_WIN32_WINNT == 0x501) { +const TCHAR[] MS_ENH_RSA_AES_PROV + = "Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)"; +} + +ALG_ID GET_ALG_CLASS()(ALG_ID x) { return x & 0xE000; } +ALG_ID GET_ALG_TYPE ()(ALG_ID x) { return x & 0x1E00; } +ALG_ID GET_ALG_SID ()(ALG_ID x) { return x & 0x01FF; } + +enum : ALG_ID { + ALG_CLASS_ANY = 0, + ALG_CLASS_SIGNATURE = 0x2000, + ALG_CLASS_MSG_ENCRYPT = 0x4000, + ALG_CLASS_DATA_ENCRYPT = 0x6000, + ALG_CLASS_HASH = 0x8000, + ALG_CLASS_KEY_EXCHANGE = 0xA000, + ALG_CLASS_ALL = 0xE000 +} + +enum : ALG_ID { + ALG_TYPE_ANY = 0, + ALG_TYPE_DSS = 0x0200, + ALG_TYPE_RSA = 0x0400, + ALG_TYPE_BLOCK = 0x0600, + ALG_TYPE_STREAM = 0x0800, + ALG_TYPE_DH = 0x0A00, + ALG_TYPE_SECURECHANNEL = 0x0C00 +} + +enum : ALG_ID { + ALG_SID_ANY = 0, + ALG_SID_RSA_ANY = 0, + ALG_SID_RSA_PKCS, + ALG_SID_RSA_MSATWORK, + ALG_SID_RSA_ENTRUST, + ALG_SID_RSA_PGP, // = 4 + ALG_SID_DSS_ANY = 0, + ALG_SID_DSS_PKCS, + ALG_SID_DSS_DMS, // = 2 + ALG_SID_DES = 1, + ALG_SID_3DES = 3, + ALG_SID_DESX, + ALG_SID_IDEA, + ALG_SID_CAST, + ALG_SID_SAFERSK64, + ALG_SID_SAFERSK128, + ALG_SID_3DES_112, + ALG_SID_SKIPJACK, + ALG_SID_TEK, + ALG_SID_CYLINK_MEK, + ALG_SID_RC5, // = 13 + ALG_SID_RC2 = 2, + ALG_SID_RC4 = 1, + ALG_SID_SEAL = 2, + ALG_SID_MD2 = 1, + ALG_SID_MD4, + ALG_SID_MD5, + ALG_SID_SHA, + ALG_SID_MAC, + ALG_SID_RIPEMD, + ALG_SID_RIPEMD160, + ALG_SID_SSL3SHAMD5, + ALG_SID_HMAC, + ALG_SID_TLS1PRF, // = 10 + ALG_SID_AES_128 = 14, + ALG_SID_AES_192, + ALG_SID_AES_256, + ALG_SID_AES, // = 17 + ALG_SID_EXAMPLE = 80 +} + +enum : ALG_ID { + CALG_MD2 = ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_MD2, + CALG_MD4 = ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_MD4, + CALG_MD5 = ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_MD5, + CALG_SHA = ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA, + CALG_SHA1 = CALG_SHA, + CALG_MAC = ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_MAC, + CALG_3DES = ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | 3, + CALG_CYLINK_MEK = ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | 12, + CALG_SKIPJACK = ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | 10, + CALG_KEA_KEYX = ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_STREAM | ALG_TYPE_DSS | 4, + CALG_RSA_SIGN = ALG_CLASS_SIGNATURE | ALG_TYPE_RSA | ALG_SID_RSA_ANY, + CALG_DSS_SIGN = ALG_CLASS_SIGNATURE | ALG_TYPE_DSS | ALG_SID_DSS_ANY, + CALG_RSA_KEYX = ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_RSA | ALG_SID_RSA_ANY, + CALG_DES = ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_DES, + CALG_RC2 = ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_RC2, + CALG_RC4 = ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_STREAM | ALG_SID_RC4, + CALG_SEAL = ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_STREAM | ALG_SID_SEAL, + CALG_DH_EPHEM = ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_STREAM | ALG_TYPE_DSS + | ALG_SID_DSS_DMS, + CALG_DESX = ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_DESX, +// is undefined ALG_CLASS_DHASH in MinGW - presuming typo + CALG_TLS1PRF = ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_TLS1PRF, + CALG_AES_128 = ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_AES_128, + CALG_AES_192 = ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_AES_192, + CALG_AES_256 = ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_AES_256, + CALG_AES = ALG_CLASS_DATA_ENCRYPT | ALG_TYPE_BLOCK | ALG_SID_AES, +} + +enum { + CRYPT_VERIFYCONTEXT = 0xF0000000, +} + +enum { + CRYPT_NEWKEYSET = 8, + CRYPT_DELETEKEYSET = 16, + CRYPT_MACHINE_KEYSET = 32, + CRYPT_SILENT = 64, +} + +enum { + CRYPT_EXPORTABLE = 1, + CRYPT_USER_PROTECTED = 2, + CRYPT_CREATE_SALT = 4, + CRYPT_UPDATE_KEY = 8, +} + +enum { + SIMPLEBLOB = 1, + PUBLICKEYBLOB = 6, + PRIVATEKEYBLOB = 7, + PLAINTEXTKEYBLOB = 8, + OPAQUEKEYBLOB = 9, + PUBLICKEYBLOBEX = 10, + SYMMETRICWRAPKEYBLOB = 11, +} + +enum { + AT_KEYEXCHANGE = 1, + AT_SIGNATURE = 2, +} + +enum { + CRYPT_USERDATA = 1, +} + +enum { + PKCS5_PADDING = 1, +} + +enum { + CRYPT_MODE_CBC = 1, + CRYPT_MODE_ECB = 2, + CRYPT_MODE_OFB = 3, + CRYPT_MODE_CFB = 4, + CRYPT_MODE_CTS = 5, + CRYPT_MODE_CBCI = 6, + CRYPT_MODE_CFBP = 7, + CRYPT_MODE_OFBP = 8, + CRYPT_MODE_CBCOFM = 9, + CRYPT_MODE_CBCOFMI = 10, +} + +enum { + CRYPT_ENCRYPT = 1, + CRYPT_DECRYPT = 2, + CRYPT_EXPORT = 4, + CRYPT_READ = 8, + CRYPT_WRITE = 16, + CRYPT_MAC = 32, +} + +enum { + HP_ALGID = 1, + HP_HASHVAL = 2, + HP_HASHSIZE = 4, + HP_HMAC_INFO = 5, +} + +enum { + CRYPT_FAILED = FALSE, + CRYPT_SUCCEED = TRUE, +} + +bool RCRYPT_SUCCEEDED()(BOOL r) { return r==CRYPT_SUCCEED; } +bool RCRYPT_FAILED()(BOOL r) { return r==CRYPT_FAILED; } + +enum { + PP_ENUMALGS = 1, + PP_ENUMCONTAINERS = 2, + PP_IMPTYPE = 3, + PP_NAME = 4, + PP_VERSION = 5, + PP_CONTAINER = 6, + PP_CHANGE_PASSWORD = 7, + PP_KEYSET_SEC_DESCR = 8, + PP_CERTCHAIN = 9, + PP_KEY_TYPE_SUBTYPE = 10, + PP_PROVTYPE = 16, + PP_KEYSTORAGE = 17, + PP_APPLI_CERT = 18, + PP_SYM_KEYSIZE = 19, + PP_SESSION_KEYSIZE = 20, + PP_UI_PROMPT = 21, + PP_ENUMALGS_EX = 22, + PP_ENUMMANDROOTS = 25, + PP_ENUMELECTROOTS = 26, + PP_KEYSET_TYPE = 27, + PP_ADMIN_PIN = 31, + PP_KEYEXCHANGE_PIN = 32, + PP_SIGNATURE_PIN = 33, + PP_SIG_KEYSIZE_INC = 34, + PP_KEYX_KEYSIZE_INC = 35, + PP_UNIQUE_CONTAINER = 36, + PP_SGC_INFO = 37, + PP_USE_HARDWARE_RNG = 38, + PP_KEYSPEC = 39, + PP_ENUMEX_SIGNING_PROT = 40, +} + +enum { + CRYPT_FIRST = 1, + CRYPT_NEXT = 2, +} + +enum { + CRYPT_IMPL_HARDWARE = 1, + CRYPT_IMPL_SOFTWARE = 2, + CRYPT_IMPL_MIXED = 3, + CRYPT_IMPL_UNKNOWN = 4, +} + +enum { + PROV_RSA_FULL = 1, + PROV_RSA_SIG = 2, + PROV_DSS = 3, + PROV_FORTEZZA = 4, + PROV_MS_MAIL = 5, + PROV_SSL = 6, + PROV_STT_MER = 7, + PROV_STT_ACQ = 8, + PROV_STT_BRND = 9, + PROV_STT_ROOT = 10, + PROV_STT_ISS = 11, + PROV_RSA_SCHANNEL = 12, + PROV_DSS_DH = 13, + PROV_EC_ECDSA_SIG = 14, + PROV_EC_ECNRA_SIG = 15, + PROV_EC_ECDSA_FULL = 16, + PROV_EC_ECNRA_FULL = 17, + PROV_DH_SCHANNEL = 18, + PROV_SPYRUS_LYNKS = 20, + PROV_RNG = 21, + PROV_INTEL_SEC = 22, + PROV_RSA_AES = 24, + MAXUIDLEN = 64, +} + +enum { + CUR_BLOB_VERSION = 2, +} + +enum { + X509_ASN_ENCODING = 1, + PKCS_7_ASN_ENCODING = 65536, +} + +enum { + CERT_V1 = 0, + CERT_V2 = 1, + CERT_V3 = 2, +} + +enum { + CERT_E_CHAINING = (-2146762486), + CERT_E_CN_NO_MATCH = (-2146762481), + CERT_E_EXPIRED = (-2146762495), + CERT_E_PURPOSE = (-2146762490), + CERT_E_REVOCATION_FAILURE = (-2146762482), + CERT_E_REVOKED = (-2146762484), + CERT_E_ROLE = (-2146762493), + CERT_E_UNTRUSTEDROOT = (-2146762487), + CERT_E_UNTRUSTEDTESTROOT = (-2146762483), + CERT_E_VALIDITYPERIODNESTING = (-2146762494), + CERT_E_WRONG_USAGE = (-2146762480), + CERT_E_PATHLENCONST = (-2146762492), + CERT_E_CRITICAL = (-2146762491), + CERT_E_ISSUERCHAINING = (-2146762489), + CERT_E_MALFORMED = (-2146762488), + CRYPT_E_REVOCATION_OFFLINE = (-2146885613), + CRYPT_E_REVOKED = (-2146885616), + TRUST_E_BASIC_CONSTRAINTS = (-2146869223), + TRUST_E_CERT_SIGNATURE = (-2146869244), + TRUST_E_FAIL = (-2146762485), +} + +enum { + CERT_TRUST_NO_ERROR = 0, + CERT_TRUST_IS_NOT_TIME_VALID = 1, + CERT_TRUST_IS_NOT_TIME_NESTED = 2, + CERT_TRUST_IS_REVOKED = 4, + CERT_TRUST_IS_NOT_SIGNATURE_VALID = 8, + CERT_TRUST_IS_NOT_VALID_FOR_USAGE = 16, + CERT_TRUST_IS_UNTRUSTED_ROOT = 32, + CERT_TRUST_REVOCATION_STATUS_UNKNOWN = 64, + CERT_TRUST_IS_CYCLIC = 128, + CERT_TRUST_IS_PARTIAL_CHAIN = 65536, + CERT_TRUST_CTL_IS_NOT_TIME_VALID = 131072, + CERT_TRUST_CTL_IS_NOT_SIGNATURE_VALID = 262144, + CERT_TRUST_CTL_IS_NOT_VALID_FOR_USAGE = 524288, +} + +enum { + CERT_TRUST_HAS_EXACT_MATCH_ISSUER = 1, + CERT_TRUST_HAS_KEY_MATCH_ISSUER = 2, + CERT_TRUST_HAS_NAME_MATCH_ISSUER = 4, + CERT_TRUST_IS_SELF_SIGNED = 8, + CERT_TRUST_IS_COMPLEX_CHAIN = 65536, +} + +enum { + CERT_CHAIN_POLICY_BASE = cast(LPCSTR) 1, + CERT_CHAIN_POLICY_AUTHENTICODE = cast(LPCSTR) 2, + CERT_CHAIN_POLICY_AUTHENTICODE_TS = cast(LPCSTR) 3, + CERT_CHAIN_POLICY_SSL = cast(LPCSTR) 4, + CERT_CHAIN_POLICY_BASIC_CONSTRAINTS = cast(LPCSTR) 5, + CERT_CHAIN_POLICY_NT_AUTH = cast(LPCSTR) 6, +} + +enum { + USAGE_MATCH_TYPE_AND = 0, + USAGE_MATCH_TYPE_OR = 1, +} + +enum { + CERT_SIMPLE_NAME_STR = 1, + CERT_OID_NAME_STR = 2, + CERT_X500_NAME_STR = 3, +} +enum { + CERT_NAME_STR_SEMICOLON_FLAG = 1073741824, + CERT_NAME_STR_CRLF_FLAG = 134217728, + CERT_NAME_STR_NO_PLUS_FLAG = 536870912, + CERT_NAME_STR_NO_QUOTING_FLAG = 268435456, + CERT_NAME_STR_REVERSE_FLAG = 33554432, + CERT_NAME_STR_ENABLE_T61_UNICODE_FLAG = 131072, +} + +enum { + CERT_FIND_ANY = 0, + CERT_FIND_CERT_ID = 1048576, + CERT_FIND_CTL_USAGE = 655360, + CERT_FIND_ENHKEY_USAGE = 655360, + CERT_FIND_EXISTING = 851968, + CERT_FIND_HASH = 65536, + CERT_FIND_ISSUER_ATTR = 196612, + CERT_FIND_ISSUER_NAME = 131076, + CERT_FIND_ISSUER_OF = 786432, + CERT_FIND_KEY_IDENTIFIER = 983040, + CERT_FIND_KEY_SPEC = 589824, + CERT_FIND_MD5_HASH = 262144, + CERT_FIND_PROPERTY = 327680, + CERT_FIND_PUBLIC_KEY = 393216, + CERT_FIND_SHA1_HASH = 65536, + CERT_FIND_SIGNATURE_HASH = 917504, + CERT_FIND_SUBJECT_ATTR = 196615, + CERT_FIND_SUBJECT_CERT = 720896, + CERT_FIND_SUBJECT_NAME = 131079, + CERT_FIND_SUBJECT_STR_A = 458759, + CERT_FIND_SUBJECT_STR_W = 524295, + CERT_FIND_ISSUER_STR_A = 458756, + CERT_FIND_ISSUER_STR_W = 524292, +} + +enum { + CERT_FIND_OR_ENHKEY_USAGE_FLAG = 16, + CERT_FIND_OPTIONAL_ENHKEY_USAGE_FLAG = 1, + CERT_FIND_NO_ENHKEY_USAGE_FLAG = 8, + CERT_FIND_VALID_ENHKEY_USAGE_FLAG = 32, + CERT_FIND_EXT_ONLY_ENHKEY_USAGE_FLAG = 2, +} + +enum { + CERT_CASE_INSENSITIVE_IS_RDN_ATTRS_FLAG = 2, + CERT_UNICODE_IS_RDN_ATTRS_FLAG = 1, + CERT_CHAIN_FIND_BY_ISSUER = 1, +} + +enum { + CERT_CHAIN_FIND_BY_ISSUER_COMPARE_KEY_FLAG = 1, + CERT_CHAIN_FIND_BY_ISSUER_COMPLEX_CHAIN_FLAG = 2, + CERT_CHAIN_FIND_BY_ISSUER_CACHE_ONLY_URL_FLAG = 4, + CERT_CHAIN_FIND_BY_ISSUER_LOCAL_MACHINE_FLAG = 8, + CERT_CHAIN_FIND_BY_ISSUER_NO_KEY_FLAG = 16384, + CERT_CHAIN_FIND_BY_ISSUER_CACHE_ONLY_FLAG = 32768, +} + +enum { + CERT_STORE_PROV_SYSTEM = 10, + CERT_SYSTEM_STORE_LOCAL_MACHINE = 131072, +} + +enum { + szOID_PKIX_KP_SERVER_AUTH = "4235600", + szOID_SERVER_GATED_CRYPTO = "4235658", + szOID_SGC_NETSCAPE = "2.16.840.1.113730.4.1", + szOID_PKIX_KP_CLIENT_AUTH = "1.3.6.1.5.5.7.3.2", +} + +enum { + CRYPT_NOHASHOID = 0x00000001, + CRYPT_NO_SALT = 0x10, + CRYPT_PREGEN = 0x40, +} + +enum { + CRYPT_RECIPIENT = 0x10, + CRYPT_INITIATOR = 0x40, + CRYPT_ONLINE = 0x80, + CRYPT_SF = 0x100, + CRYPT_CREATE_IV = 0x200, + CRYPT_KEK = 0x400, + CRYPT_DATA_KEY = 0x800, + CRYPT_VOLATILE = 0x1000, + CRYPT_SGCKEY = 0x2000, +} + +enum { + KP_IV = 0x00000001, + KP_SALT = 0x00000002, + KP_PADDING = 0x00000003, + KP_MODE = 0x00000004, + KP_MODE_BITS = 0x00000005, + KP_PERMISSIONS = 0x00000006, + KP_ALGID = 0x00000007, + KP_BLOCKLEN = 0x00000008, + KP_KEYLEN = 0x00000009, + KP_SALT_EX = 0x0000000a, + KP_P = 0x0000000b, + KP_G = 0x0000000c, + KP_Q = 0x0000000d, + KP_X = 0x0000000e, + KP_Y = 0x0000000f, + KP_RA = 0x00000010, + KP_RB = 0x00000011, + KP_INFO = 0x00000012, + KP_EFFECTIVE_KEYLEN = 0x00000013, + KP_SCHANNEL_ALG = 0x00000014, + KP_PUB_PARAMS = 0x00000027, +} + +enum { + CRYPT_FLAG_PCT1 = 0x0001, + CRYPT_FLAG_SSL2 = 0x0002, + CRYPT_FLAG_SSL3 = 0x0004, + CRYPT_FLAG_TLS1 = 0x0008, + CRYPT_FLAG_IPSEC = 0x0010, + CRYPT_FLAG_SIGNING = 0x0020, +} + +enum { + SCHANNEL_MAC_KEY = 0x00000000, + SCHANNEL_ENC_KEY = 0x00000001, +} + +enum { + INTERNATIONAL_USAGE = 0x00000001, +} + + +alias UINT ALG_ID; +alias ULONG_PTR HCRYPTPROV, HCRYPTKEY, HCRYPTHASH; +alias PVOID HCERTSTORE, HCRYPTMSG, HCERTCHAINENGINE; + +struct VTableProvStruc { + FARPROC FuncVerifyImage; +} +alias VTableProvStruc* PVTableProvStruc; + +struct _CRYPTOAPI_BLOB { + DWORD cbData; + BYTE* pbData; +} +alias _CRYPTOAPI_BLOB CRYPT_INTEGER_BLOB, CRYPT_UINT_BLOB, + CRYPT_OBJID_BLOB, CERT_NAME_BLOB, CERT_RDN_VALUE_BLOB, CERT_BLOB, + CRL_BLOB, DATA_BLOB, CRYPT_DATA_BLOB, CRYPT_HASH_BLOB, + CRYPT_DIGEST_BLOB, CRYPT_DER_BLOB, CRYPT_ATTR_BLOB; +alias _CRYPTOAPI_BLOB* PCRYPT_INTEGER_BLOB, PCRYPT_UINT_BLOB, + PCRYPT_OBJID_BLOB, PCERT_NAME_BLOB, PCERT_RDN_VALUE_BLOB, PCERT_BLOB, + PCRL_BLOB, PDATA_BLOB, PCRYPT_DATA_BLOB, PCRYPT_HASH_BLOB, + PCRYPT_DIGEST_BLOB, PCRYPT_DER_BLOB, PCRYPT_ATTR_BLOB; + +// not described in SDK; has the same layout as HTTPSPolicyCallbackData +struct SSL_EXTRA_CERT_CHAIN_POLICY_PARA { + DWORD cbStruct; + DWORD dwAuthType; + DWORD fdwChecks; + LPWSTR pwszServerName; +} +alias SSL_EXTRA_CERT_CHAIN_POLICY_PARA HTTPSPolicyCallbackData; +alias SSL_EXTRA_CERT_CHAIN_POLICY_PARA* PSSL_EXTRA_CERT_CHAIN_POLICY_PARA, + PHTTPSPolicyCallbackData; + +/* #if (_WIN32_WINNT>=0x500) */ +struct CERT_CHAIN_POLICY_PARA { + DWORD cbSize = CERT_CHAIN_POLICY_PARA.sizeof; + DWORD dwFlags; + void* pvExtraPolicyPara; +} +alias CERT_CHAIN_POLICY_PARA* PCERT_CHAIN_POLICY_PARA; + +struct CERT_CHAIN_POLICY_STATUS { + DWORD cbSize = CERT_CHAIN_POLICY_STATUS.sizeof; + DWORD dwError; + LONG lChainIndex; + LONG lElementIndex; + void* pvExtraPolicyStatus; +} +alias CERT_CHAIN_POLICY_STATUS* PCERT_CHAIN_POLICY_STATUS; +/* #endif */ + +struct CRYPT_ALGORITHM_IDENTIFIER { + LPSTR pszObjId; + CRYPT_OBJID_BLOB Parameters; +} +alias CRYPT_ALGORITHM_IDENTIFIER* PCRYPT_ALGORITHM_IDENTIFIER; + +struct CRYPT_BIT_BLOB { + DWORD cbData; + BYTE* pbData; + DWORD cUnusedBits; +} +alias CRYPT_BIT_BLOB* PCRYPT_BIT_BLOB; + +struct CERT_PUBLIC_KEY_INFO { + CRYPT_ALGORITHM_IDENTIFIER Algorithm; + CRYPT_BIT_BLOB PublicKey; +} +alias CERT_PUBLIC_KEY_INFO* PCERT_PUBLIC_KEY_INFO; + +struct CERT_EXTENSION { + LPSTR pszObjId; + BOOL fCritical; + CRYPT_OBJID_BLOB Value; +} +alias CERT_EXTENSION* PCERT_EXTENSION; + +struct CERT_INFO { + DWORD dwVersion; + CRYPT_INTEGER_BLOB SerialNumber; + CRYPT_ALGORITHM_IDENTIFIER SignatureAlgorithm; + CERT_NAME_BLOB Issuer; + FILETIME NotBefore; + FILETIME NotAfter; + CERT_NAME_BLOB Subject; + CERT_PUBLIC_KEY_INFO SubjectPublicKeyInfo; + CRYPT_BIT_BLOB IssuerUniqueId; + CRYPT_BIT_BLOB SubjectUniqueId; + DWORD cExtension; + PCERT_EXTENSION rgExtension; +} +alias CERT_INFO* PCERT_INFO; + +struct CERT_CONTEXT { + DWORD dwCertEncodingType; + BYTE* pbCertEncoded; + DWORD cbCertEncoded; + PCERT_INFO pCertInfo; + HCERTSTORE hCertStore; +} +alias CERT_CONTEXT* PCERT_CONTEXT; +alias const(CERT_CONTEXT)* PCCERT_CONTEXT; + +struct CTL_USAGE { + DWORD cUsageIdentifier; + LPSTR* rgpszUsageIdentifier; +} +alias CTL_USAGE CERT_ENHKEY_USAGE; +alias CTL_USAGE* PCTRL_USAGE, PCERT_ENHKEY_USAGE; + +struct CERT_USAGE_MATCH { + DWORD dwType; + CERT_ENHKEY_USAGE Usage; +} +alias CERT_USAGE_MATCH* PCERT_USAGE_MATCH; +/* #if (_WIN32_WINNT>=0x500) */ + +struct CERT_CHAIN_PARA { + DWORD cbSize = CERT_CHAIN_PARA.sizeof; + CERT_USAGE_MATCH RequestedUsage; +//#if CERT_CHAIN_PARA_HAS_EXTRA_FIELDS + CERT_USAGE_MATCH RequestedIssuancePolicy; + DWORD dwUrlRetrievalTimeout; + BOOL fCheckRevocationFreshnessTime; + DWORD dwRevocationFreshnessTime; +//#endif +} +alias CERT_CHAIN_PARA* PCERT_CHAIN_PARA; + +extern (Windows) alias BOOL function(PCCERT_CONTEXT, void*) + PFN_CERT_CHAIN_FIND_BY_ISSUER_CALLBACK; + +struct CERT_CHAIN_FIND_BY_ISSUER_PARA { + DWORD cbSize = CERT_CHAIN_FIND_BY_ISSUER_PARA.sizeof; + LPCSTR pszUsageIdentifier; + DWORD dwKeySpec; + DWORD dwAcquirePrivateKeyFlags; + DWORD cIssuer; + CERT_NAME_BLOB* rgIssuer; + PFN_CERT_CHAIN_FIND_BY_ISSUER_CALLBACK pfnFIndCallback; + void* pvFindArg; + DWORD* pdwIssuerChainIndex; + DWORD* pdwIssuerElementIndex; +} +alias CERT_CHAIN_FIND_BY_ISSUER_PARA* PCERT_CHAIN_FIND_BY_ISSUER_PARA; +/* #endif */ + +struct CERT_TRUST_STATUS { + DWORD dwErrorStatus; + DWORD dwInfoStatus; +} +alias CERT_TRUST_STATUS* PCERT_TRUST_STATUS; + +struct CRL_ENTRY { + CRYPT_INTEGER_BLOB SerialNumber; + FILETIME RevocationDate; + DWORD cExtension; + PCERT_EXTENSION rgExtension; +} +alias CRL_ENTRY* PCRL_ENTRY; + +struct CRL_INFO { + DWORD dwVersion; + CRYPT_ALGORITHM_IDENTIFIER SignatureAlgorithm; + CERT_NAME_BLOB Issuer; + FILETIME ThisUpdate; + FILETIME NextUpdate; + DWORD cCRLEntry; + PCRL_ENTRY rgCRLEntry; + DWORD cExtension; + PCERT_EXTENSION rgExtension; +} +alias CRL_INFO* PCRL_INFO; + +struct CRL_CONTEXT { + DWORD dwCertEncodingType; + BYTE* pbCrlEncoded; + DWORD cbCrlEncoded; + PCRL_INFO pCrlInfo; + HCERTSTORE hCertStore; +} +alias CRL_CONTEXT* PCRL_CONTEXT; +alias const(CRL_CONTEXT)* PCCRL_CONTEXT; + +struct CERT_REVOCATION_CRL_INFO { + DWORD cbSize = CERT_REVOCATION_CRL_INFO.sizeof; + PCCRL_CONTEXT pBaseCRLContext; + PCCRL_CONTEXT pDeltaCRLContext; + PCRL_ENTRY pCrlEntry; + BOOL fDeltaCrlEntry; +} +alias CERT_REVOCATION_CRL_INFO* PCERT_REVOCATION_CRL_INFO; + +struct CERT_REVOCATION_INFO { + DWORD cbSize = CERT_REVOCATION_INFO.sizeof; + DWORD dwRevocationResult; + LPCSTR pszRevocationOid; + LPVOID pvOidSpecificInfo; + BOOL fHasFreshnessTime; + DWORD dwFreshnessTime; + PCERT_REVOCATION_CRL_INFO pCrlInfo; +} +alias CERT_REVOCATION_INFO* PCERT_REVOCATION_INFO; + +/* #if (_WIN32_WINNT>=0x500) */ +struct CERT_CHAIN_ELEMENT { + DWORD cbSize = CERT_CHAIN_ELEMENT.sizeof; + PCCERT_CONTEXT pCertContext; + CERT_TRUST_STATUS TrustStatus; + PCERT_REVOCATION_INFO pRevocationInfo; + PCERT_ENHKEY_USAGE pIssuanceUsage; + PCERT_ENHKEY_USAGE pApplicationUsage; +} +alias CERT_CHAIN_ELEMENT* PCERT_CHAIN_ELEMENT; +/* #endif */ + +struct CRYPT_ATTRIBUTE { + LPSTR pszObjId; + DWORD cValue; + PCRYPT_ATTR_BLOB rgValue; +} +alias CRYPT_ATTRIBUTE* PCRYPT_ATTRIBUTE; + +struct CTL_ENTRY { + CRYPT_DATA_BLOB SubjectIdentifier; + DWORD cAttribute; + PCRYPT_ATTRIBUTE rgAttribute; +} +alias CTL_ENTRY* PCTL_ENTRY; + +struct CTL_INFO { + DWORD dwVersion; + CTL_USAGE SubjectUsage; + CRYPT_DATA_BLOB ListIdentifier; + CRYPT_INTEGER_BLOB SequenceNumber; + FILETIME ThisUpdate; + FILETIME NextUpdate; + CRYPT_ALGORITHM_IDENTIFIER SubjectAlgorithm; + DWORD cCTLEntry; + PCTL_ENTRY rgCTLEntry; + DWORD cExtension; + PCERT_EXTENSION rgExtension; +} +alias CTL_INFO* PCTL_INFO; + +struct CTL_CONTEXT { + DWORD dwMsgAndCertEncodingType; + BYTE* pbCtlEncoded; + DWORD cbCtlEncoded; + PCTL_INFO pCtlInfo; + HCERTSTORE hCertStore; + HCRYPTMSG hCryptMsg; + BYTE* pbCtlContent; + DWORD cbCtlContent; +} +alias CTL_CONTEXT* PCTL_CONTEXT; +alias const(CTL_CONTEXT)* PCCTL_CONTEXT; + +struct CERT_TRUST_LIST_INFO { + DWORD cbSize = CERT_TRUST_LIST_INFO.sizeof; + PCTL_ENTRY pCtlEntry; + PCCTL_CONTEXT pCtlContext; +} +alias CERT_TRUST_LIST_INFO* PCERT_TRUST_LIST_INFO; + +struct CERT_SIMPLE_CHAIN { + DWORD cbSize = CERT_SIMPLE_CHAIN.sizeof; + CERT_TRUST_STATUS TrustStatus; + DWORD cElement; + PCERT_CHAIN_ELEMENT* rgpElement; + PCERT_TRUST_LIST_INFO pTrustListInfo; + BOOL fHasRevocationFreshnessTime; + DWORD dwRevocationFreshnessTime; +} +alias CERT_SIMPLE_CHAIN* PCERT_SIMPLE_CHAIN; + +/* #if (_WIN32_WINNT>=0x500) */ +alias const(CERT_CHAIN_CONTEXT)* PCCERT_CHAIN_CONTEXT; +struct CERT_CHAIN_CONTEXT { + DWORD cbSize = CERT_CHAIN_CONTEXT.sizeof; + CERT_TRUST_STATUS TrustStatus; + DWORD cChain; + PCERT_SIMPLE_CHAIN* rgpChain; + DWORD cLowerQualityChainContext; + PCCERT_CHAIN_CONTEXT* rgpLowerQualityChainContext; + BOOL fHasRevocationFreshnessTime; + DWORD dwRevocationFreshnessTime; +} +alias CERT_CHAIN_CONTEXT* PCERT_CHAIN_CONTEXT; +/* #endif */ + +struct PROV_ENUMALGS { + ALG_ID aiAlgid; + DWORD dwBitLen; + DWORD dwNameLen; + CHAR[20] szName = 0; +} + +struct PUBLICKEYSTRUC { + BYTE bType; + BYTE bVersion; + WORD reserved; + ALG_ID aiKeyAlg; +} +alias PUBLICKEYSTRUC BLOBHEADER; + +struct RSAPUBKEY { + DWORD magic; + DWORD bitlen; + DWORD pubexp; +} + +struct HMAC_INFO { + ALG_ID HashAlgid; + BYTE* pbInnerString; + DWORD cbInnerString; + BYTE* pbOuterString; + DWORD cbOuterString; +} +alias HMAC_INFO* PHMAC_INFO; + +extern (Windows) nothrow @nogc { + BOOL CertCloseStore(HCERTSTORE, DWORD); + BOOL CertGetCertificateChain(HCERTCHAINENGINE, PCCERT_CONTEXT, LPFILETIME, + HCERTSTORE, PCERT_CHAIN_PARA, DWORD, LPVOID, PCCERT_CHAIN_CONTEXT*); + BOOL CertVerifyCertificateChainPolicy(LPCSTR, PCCERT_CHAIN_CONTEXT, + PCERT_CHAIN_POLICY_PARA, PCERT_CHAIN_POLICY_STATUS); + void CertFreeCertificateChain(PCCERT_CHAIN_CONTEXT); + DWORD CertNameToStrA(DWORD, PCERT_NAME_BLOB, DWORD, LPSTR, DWORD); + DWORD CertNameToStrW(DWORD, PCERT_NAME_BLOB, DWORD, LPWSTR, DWORD); + HCERTSTORE CertOpenSystemStoreA(HCRYPTPROV, LPCSTR); + HCERTSTORE CertOpenSystemStoreW(HCRYPTPROV, LPCWSTR); + HCERTSTORE CertOpenStore(LPCSTR, DWORD, HCRYPTPROV, DWORD, const(void)*); + PCCERT_CONTEXT CertFindCertificateInStore(HCERTSTORE, DWORD, DWORD, DWORD, +const(void)*, PCCERT_CONTEXT); + BOOL CertFreeCertificateContext(PCCERT_CONTEXT); + PCCERT_CONTEXT CertGetIssuerCertificateFromStore(HCERTSTORE, + PCCERT_CONTEXT, PCCERT_CONTEXT, DWORD*); + PCCERT_CHAIN_CONTEXT CertFindChainInStore(HCERTSTORE, DWORD, DWORD, DWORD, +const(void)*, PCCERT_CHAIN_CONTEXT); + + BOOL CryptAcquireContextA(HCRYPTPROV*, LPCSTR, LPCSTR, DWORD, DWORD); + BOOL CryptAcquireContextW(HCRYPTPROV*, LPCWSTR, LPCWSTR, DWORD, DWORD); + BOOL CryptContextAddRef(HCRYPTPROV, DWORD*, DWORD); + BOOL CryptReleaseContext(HCRYPTPROV, ULONG_PTR); + BOOL CryptGenKey(HCRYPTPROV, ALG_ID, DWORD, HCRYPTKEY*); + BOOL CryptDeriveKey(HCRYPTPROV, ALG_ID, HCRYPTHASH, DWORD, HCRYPTKEY*); + BOOL CryptDestroyKey(HCRYPTKEY); + static if (_WIN32_WINNT >= 0x500) { + BOOL CryptDuplicateHash(HCRYPTHASH, DWORD*, DWORD, HCRYPTHASH*); + BOOL CryptDuplicateKey(HCRYPTKEY, DWORD*, DWORD, HCRYPTKEY*); + } + BOOL CryptSetKeyParam(HCRYPTKEY, DWORD, PBYTE, DWORD); + BOOL CryptGetKeyParam(HCRYPTKEY, DWORD, PBYTE, PDWORD, DWORD); + BOOL CryptSetHashParam(HCRYPTHASH, DWORD, PBYTE, DWORD); + BOOL CryptGetHashParam(HCRYPTHASH, DWORD, PBYTE, PDWORD, DWORD); + BOOL CryptSetProvParam(HCRYPTPROV, DWORD, PBYTE, DWORD); + BOOL CryptGetProvParam(HCRYPTPROV, DWORD, PBYTE, PDWORD, DWORD); + BOOL CryptGenRandom(HCRYPTPROV, DWORD, PBYTE); + BOOL CryptGetUserKey(HCRYPTPROV, DWORD, HCRYPTKEY*); + BOOL CryptExportKey(HCRYPTKEY, HCRYPTKEY, DWORD, DWORD, PBYTE, PDWORD); + BOOL CryptImportKey(HCRYPTPROV, PBYTE, DWORD, HCRYPTKEY, DWORD, + HCRYPTKEY*); + BOOL CryptEncrypt(HCRYPTKEY, HCRYPTHASH, BOOL, DWORD, PBYTE, PDWORD, + DWORD); + BOOL CryptDecrypt(HCRYPTKEY, HCRYPTHASH, BOOL, DWORD, PBYTE, PDWORD); + BOOL CryptCreateHash(HCRYPTPROV, ALG_ID, HCRYPTKEY, DWORD, HCRYPTHASH*); + BOOL CryptHashData(HCRYPTHASH, PBYTE, DWORD, DWORD); + BOOL CryptHashSessionKey(HCRYPTHASH, HCRYPTKEY, DWORD); + BOOL CryptGetHashValue(HCRYPTHASH, DWORD, PBYTE, PDWORD); + BOOL CryptDestroyHash(HCRYPTHASH); + BOOL CryptSignHashA(HCRYPTHASH, DWORD, LPCSTR, DWORD, PBYTE, PDWORD); + BOOL CryptSignHashW(HCRYPTHASH, DWORD, LPCWSTR, DWORD, PBYTE, PDWORD); + BOOL CryptVerifySignatureA(HCRYPTHASH, PBYTE, DWORD, HCRYPTKEY, LPCSTR, + DWORD); + BOOL CryptVerifySignatureW(HCRYPTHASH, PBYTE, DWORD, HCRYPTKEY, LPCWSTR, + DWORD); + BOOL CryptSetProviderA(LPCSTR, DWORD); + BOOL CryptSetProviderW(LPCWSTR, DWORD); +} + +version (Unicode) { + alias CertNameToStrW CertNameToStr; + alias CryptAcquireContextW CryptAcquireContext; + alias CryptSignHashW CryptSignHash; + alias CryptVerifySignatureW CryptVerifySignature; + alias CryptSetProviderW CryptSetProvider; + alias CertOpenSystemStoreW CertOpenSystemStore; + /+alias CERT_FIND_SUBJECT_STR_W CERT_FIND_SUBJECT_STR; + alias CERT_FIND_ISSUER_STR_W CERT_FIND_ISSUER_STR;+/ +} else { + alias CertNameToStrA CertNameToStr; + alias CryptAcquireContextA CryptAcquireContext; + alias CryptSignHashA CryptSignHash; + alias CryptVerifySignatureA CryptVerifySignature; + alias CryptSetProviderA CryptSetProvider; + alias CertOpenSystemStoreA CertOpenSystemStore; + /+alias CERT_FIND_SUBJECT_STR_A CERT_FIND_SUBJECT_STR; + alias CERT_FIND_ISSUER_STR_A CERT_FIND_ISSUER_STR;+/ +} diff --git a/src/urt/internal/sys/windows/windef.d b/src/urt/internal/sys/windows/windef.d new file mode 100644 index 0000000..20767e1 --- /dev/null +++ b/src/urt/internal/sys/windows/windef.d @@ -0,0 +1,151 @@ +/** + * Windows API header module + * + * Translated from MinGW Windows headers + * + * Authors: Stewart Gordon + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_windef.d) + */ +module urt.internal.sys.windows.windef; +version (Windows): + +public import urt.internal.sys.windows.winnt; +import urt.internal.sys.windows.w32api; + +enum size_t MAX_PATH = 260; + +pure nothrow @nogc { + ushort MAKEWORD(ubyte a, ubyte b) { + return cast(ushort) ((b << 8) | a); + } + + ushort MAKEWORD(ushort a, ushort b) { + assert((a & 0xFF00) == 0); + assert((b & 0xFF00) == 0); + return MAKEWORD(cast(ubyte)a, cast(ubyte)b); + } + + uint MAKELONG(ushort a, ushort b) { + return cast(uint) ((b << 16) | a); + } + + uint MAKELONG(uint a, uint b) { + assert((a & 0xFFFF0000) == 0); + assert((b & 0xFFFF0000) == 0); + return MAKELONG(cast(ushort)a, cast(ushort)b); + } + + ushort LOWORD(ulong l) { + return cast(ushort) l; + } + + ushort HIWORD(ulong l) { + return cast(ushort) (l >>> 16); + } + + ubyte LOBYTE(ushort w) { + return cast(ubyte) w; + } + + ubyte HIBYTE(ushort w) { + return cast(ubyte) (w >>> 8); + } +} + +enum NULL = null; +static assert (is(typeof({ + void test(int* p) {} + test(NULL); +}))); + +alias ubyte BYTE; +alias ubyte* PBYTE, LPBYTE; +alias ushort USHORT, WORD, ATOM; +alias ushort* PUSHORT, PWORD, LPWORD; +alias uint ULONG, DWORD, UINT, COLORREF; +alias uint* PULONG, PDWORD, LPDWORD, PUINT, LPUINT, LPCOLORREF; +alias int WINBOOL, BOOL, INT, LONG, HFILE, HRESULT; +alias int* PWINBOOL, LPWINBOOL, PBOOL, LPBOOL, PINT, LPINT, LPLONG; +alias float FLOAT; +alias float* PFLOAT; +alias const(void)* PCVOID, LPCVOID; + +alias UINT_PTR WPARAM; +alias LONG_PTR LPARAM, LRESULT; + +alias HHOOK = HANDLE; +alias HGLOBAL = HANDLE; +alias HLOCAL = HANDLE; +alias GLOBALHANDLE = HANDLE; +alias LOCALHANDLE = HANDLE; +alias HGDIOBJ = HANDLE; +alias HACCEL = HANDLE; +alias HBITMAP = HGDIOBJ; +alias HBRUSH = HGDIOBJ; +alias HCOLORSPACE = HANDLE; +alias HDC = HANDLE; +alias HGLRC = HANDLE; +alias HDESK = HANDLE; +alias HENHMETAFILE = HANDLE; +alias HFONT = HGDIOBJ; +alias HICON = HANDLE; +alias HINSTANCE = HANDLE; +alias HKEY = HANDLE; +alias HMENU = HANDLE; +alias HMETAFILE = HANDLE; +alias HMODULE = HANDLE; +alias HMONITOR = HANDLE; +alias HPALETTE = HANDLE; +alias HPEN = HGDIOBJ; +alias HRGN = HGDIOBJ; +alias HRSRC = HANDLE; +alias HSTR = HANDLE; +alias HTASK = HANDLE; +alias HWND = HANDLE; +alias HWINSTA = HANDLE; +alias HKL = HANDLE; +alias HCURSOR = HANDLE; +alias HKEY* PHKEY; + +static if (_WIN32_WINNT >= 0x500) { + alias HTERMINAL = HANDLE; + alias HWINEVENTHOOK = HANDLE; +} + +alias extern (Windows) INT_PTR function() nothrow FARPROC, NEARPROC, PROC; + +struct RECT { + LONG left; + LONG top; + LONG right; + LONG bottom; +} +alias RECT RECTL; +alias RECT* PRECT, NPRECT, LPRECT, PRECTL, LPRECTL; +alias const(RECT)* LPCRECT, LPCRECTL; + +struct POINT { + LONG x; + LONG y; +} +alias POINT POINTL; +alias POINT* PPOINT, NPPOINT, LPPOINT, PPOINTL, LPPOINTL; + +struct SIZE { + LONG cx; + LONG cy; +} +alias SIZE SIZEL; +alias SIZE* PSIZE, LPSIZE, PSIZEL, LPSIZEL; + +struct POINTS { + SHORT x; + SHORT y; +} +alias POINTS* PPOINTS, LPPOINTS; + +enum : BOOL { + FALSE = 0, + TRUE = 1 +} diff --git a/src/urt/internal/sys/windows/winerror.d b/src/urt/internal/sys/windows/winerror.d new file mode 100644 index 0000000..6178028 --- /dev/null +++ b/src/urt/internal/sys/windows/winerror.d @@ -0,0 +1,2312 @@ +/** + * Windows API header module + * + * Translated from MinGW Windows headers + * + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_winerror.d) + */ +module urt.internal.sys.windows.winerror; +version (Windows): + +/* Comments from the Mingw header: + * WAIT_TIMEOUT is also defined in winbase.h + */ + +import urt.internal.sys.windows.windef; + +alias int SCODE; // was in urt.internal.sys.windows.wtypes. + +enum : uint { + ERROR_SUCCESS = 0, + NO_ERROR = 0, + ERROR_INVALID_FUNCTION, + ERROR_FILE_NOT_FOUND, + ERROR_PATH_NOT_FOUND, + ERROR_TOO_MANY_OPEN_FILES, + ERROR_ACCESS_DENIED, + ERROR_INVALID_HANDLE, + ERROR_ARENA_TRASHED, + ERROR_NOT_ENOUGH_MEMORY, + ERROR_INVALID_BLOCK, + ERROR_BAD_ENVIRONMENT, + ERROR_BAD_FORMAT, + ERROR_INVALID_ACCESS, + ERROR_INVALID_DATA, + ERROR_OUTOFMEMORY, + ERROR_INVALID_DRIVE, + ERROR_CURRENT_DIRECTORY, + ERROR_NOT_SAME_DEVICE, + ERROR_NO_MORE_FILES, + ERROR_WRITE_PROTECT, + ERROR_BAD_UNIT, + ERROR_NOT_READY, + ERROR_BAD_COMMAND, + ERROR_CRC, + ERROR_BAD_LENGTH, + ERROR_SEEK, + ERROR_NOT_DOS_DISK, + ERROR_SECTOR_NOT_FOUND, + ERROR_OUT_OF_PAPER, + ERROR_WRITE_FAULT, + ERROR_READ_FAULT, + ERROR_GEN_FAILURE, + ERROR_SHARING_VIOLATION, + ERROR_LOCK_VIOLATION, + ERROR_WRONG_DISK, // = 34 + ERROR_SHARING_BUFFER_EXCEEDED = 36, + ERROR_HANDLE_EOF = 38, + ERROR_HANDLE_DISK_FULL, // = 39 + ERROR_NOT_SUPPORTED = 50, + ERROR_REM_NOT_LIST, + ERROR_DUP_NAME, + ERROR_BAD_NETPATH, + ERROR_NETWORK_BUSY, + ERROR_DEV_NOT_EXIST, + ERROR_TOO_MANY_CMDS, + ERROR_ADAP_HDW_ERR, + ERROR_BAD_NET_RESP, + ERROR_UNEXP_NET_ERR, + ERROR_BAD_REM_ADAP, + ERROR_PRINTQ_FULL, + ERROR_NO_SPOOL_SPACE, + ERROR_PRINT_CANCELLED, + ERROR_NETNAME_DELETED, + ERROR_NETWORK_ACCESS_DENIED, + ERROR_BAD_DEV_TYPE, + ERROR_BAD_NET_NAME, + ERROR_TOO_MANY_NAMES, + ERROR_TOO_MANY_SESS, + ERROR_SHARING_PAUSED, + ERROR_REQ_NOT_ACCEP, + ERROR_REDIR_PAUSED, // = 72 + ERROR_FILE_EXISTS = 80, + ERROR_CANNOT_MAKE = 82, + ERROR_FAIL_I24, + ERROR_OUT_OF_STRUCTURES, + ERROR_ALREADY_ASSIGNED, + ERROR_INVALID_PASSWORD, + ERROR_INVALID_PARAMETER, + ERROR_NET_WRITE_FAULT, + ERROR_NO_PROC_SLOTS, // = 89 + ERROR_TOO_MANY_SEMAPHORES = 100, + ERROR_EXCL_SEM_ALREADY_OWNED, + ERROR_SEM_IS_SET, + ERROR_TOO_MANY_SEM_REQUESTS, + ERROR_INVALID_AT_INTERRUPT_TIME, + ERROR_SEM_OWNER_DIED, + ERROR_SEM_USER_LIMIT, + ERROR_DISK_CHANGE, + ERROR_DRIVE_LOCKED, + ERROR_BROKEN_PIPE, + ERROR_OPEN_FAILED, + ERROR_BUFFER_OVERFLOW, + ERROR_DISK_FULL, + ERROR_NO_MORE_SEARCH_HANDLES, + ERROR_INVALID_TARGET_HANDLE, // = 114 + ERROR_INVALID_CATEGORY = 117, + ERROR_INVALID_VERIFY_SWITCH, + ERROR_BAD_DRIVER_LEVEL, + ERROR_CALL_NOT_IMPLEMENTED, + ERROR_SEM_TIMEOUT, + ERROR_INSUFFICIENT_BUFFER, + ERROR_INVALID_NAME, + ERROR_INVALID_LEVEL, + ERROR_NO_VOLUME_LABEL, + ERROR_MOD_NOT_FOUND, + ERROR_PROC_NOT_FOUND, + ERROR_WAIT_NO_CHILDREN, + ERROR_CHILD_NOT_COMPLETE, + ERROR_DIRECT_ACCESS_HANDLE, + ERROR_NEGATIVE_SEEK, + ERROR_SEEK_ON_DEVICE, + ERROR_IS_JOIN_TARGET, + ERROR_IS_JOINED, + ERROR_IS_SUBSTED, + ERROR_NOT_JOINED, + ERROR_NOT_SUBSTED, + ERROR_JOIN_TO_JOIN, + ERROR_SUBST_TO_SUBST, + ERROR_JOIN_TO_SUBST, + ERROR_SUBST_TO_JOIN, + ERROR_BUSY_DRIVE, + ERROR_SAME_DRIVE, + ERROR_DIR_NOT_ROOT, + ERROR_DIR_NOT_EMPTY, + ERROR_IS_SUBST_PATH, + ERROR_IS_JOIN_PATH, + ERROR_PATH_BUSY, + ERROR_IS_SUBST_TARGET, + ERROR_SYSTEM_TRACE, + ERROR_INVALID_EVENT_COUNT, + ERROR_TOO_MANY_MUXWAITERS, + ERROR_INVALID_LIST_FORMAT, + ERROR_LABEL_TOO_LONG, + ERROR_TOO_MANY_TCBS, + ERROR_SIGNAL_REFUSED, + ERROR_DISCARDED, + ERROR_NOT_LOCKED, + ERROR_BAD_THREADID_ADDR, + ERROR_BAD_ARGUMENTS, + ERROR_BAD_PATHNAME, + ERROR_SIGNAL_PENDING, // = 162 + ERROR_MAX_THRDS_REACHED = 164, + ERROR_LOCK_FAILED = 167, + ERROR_BUSY = 170, + ERROR_CANCEL_VIOLATION = 173, + ERROR_ATOMIC_LOCKS_NOT_SUPPORTED, // = 174 + ERROR_INVALID_SEGMENT_NUMBER = 180, + ERROR_INVALID_ORDINAL = 182, + ERROR_ALREADY_EXISTS, // = 183 + ERROR_INVALID_FLAG_NUMBER = 186, + ERROR_SEM_NOT_FOUND, + ERROR_INVALID_STARTING_CODESEG, + ERROR_INVALID_STACKSEG, + ERROR_INVALID_MODULETYPE, + ERROR_INVALID_EXE_SIGNATURE, + ERROR_EXE_MARKED_INVALID, + ERROR_BAD_EXE_FORMAT, + ERROR_ITERATED_DATA_EXCEEDS_64k, + ERROR_INVALID_MINALLOCSIZE, + ERROR_DYNLINK_FROM_INVALID_RING, + ERROR_IOPL_NOT_ENABLED, + ERROR_INVALID_SEGDPL, + ERROR_AUTODATASEG_EXCEEDS_64k, + ERROR_RING2SEG_MUST_BE_MOVABLE, + ERROR_RELOC_CHAIN_XEEDS_SEGLIM, + ERROR_INFLOOP_IN_RELOC_CHAIN, + ERROR_ENVVAR_NOT_FOUND, // = 203 + ERROR_NO_SIGNAL_SENT = 205, + ERROR_FILENAME_EXCED_RANGE, + ERROR_RING2_STACK_IN_USE, + ERROR_META_EXPANSION_TOO_LONG, + ERROR_INVALID_SIGNAL_NUMBER, + ERROR_THREAD_1_INACTIVE, // = 210 + ERROR_LOCKED = 212, + ERROR_TOO_MANY_MODULES = 214, + ERROR_NESTING_NOT_ALLOWED, + ERROR_EXE_MACHINE_TYPE_MISMATCH, + ERROR_EXE_CANNOT_MODIFY_SIGNED_BINARY, + ERROR_EXE_CANNOT_MODIFY_STRONG_SIGNED_BINARY, // = 218 + ERROR_BAD_PIPE = 230, + ERROR_PIPE_BUSY, + ERROR_NO_DATA, + ERROR_PIPE_NOT_CONNECTED, + ERROR_MORE_DATA, // = 234 + ERROR_VC_DISCONNECTED = 240, + ERROR_INVALID_EA_NAME = 254, + ERROR_EA_LIST_INCONSISTENT, // = 255 + WAIT_TIMEOUT = 258, + ERROR_NO_MORE_ITEMS, // = 259 + ERROR_CANNOT_COPY = 266, + ERROR_DIRECTORY, // = 267 + ERROR_EAS_DIDNT_FIT = 275, + ERROR_EA_FILE_CORRUPT, + ERROR_EA_TABLE_FULL, + ERROR_INVALID_EA_HANDLE, // = 278 + ERROR_EAS_NOT_SUPPORTED = 282, + ERROR_NOT_OWNER = 288, + ERROR_TOO_MANY_POSTS = 298, + ERROR_PARTIAL_COPY, + ERROR_OPLOCK_NOT_GRANTED, + ERROR_INVALID_OPLOCK_PROTOCOL, + ERROR_DISK_TOO_FRAGMENTED, + ERROR_DELETE_PENDING, // = 303 + ERROR_MR_MID_NOT_FOUND = 317, + ERROR_SCOPE_NOT_FOUND, // = 318 + ERROR_INVALID_ADDRESS = 487, + ERROR_ARITHMETIC_OVERFLOW = 534, + ERROR_PIPE_CONNECTED, + ERROR_PIPE_LISTENING, // = 536 + ERROR_EA_ACCESS_DENIED = 994, + ERROR_OPERATION_ABORTED, + ERROR_IO_INCOMPLETE, + ERROR_IO_PENDING, + ERROR_NOACCESS, + ERROR_SWAPERROR, // = 999 + ERROR_STACK_OVERFLOW = 1001, + ERROR_INVALID_MESSAGE, + ERROR_CAN_NOT_COMPLETE, + ERROR_INVALID_FLAGS, + ERROR_UNRECOGNIZED_VOLUME, + ERROR_FILE_INVALID, + ERROR_FULLSCREEN_MODE, + ERROR_NO_TOKEN, + ERROR_BADDB, + ERROR_BADKEY, + ERROR_CANTOPEN, + ERROR_CANTREAD, + ERROR_CANTWRITE, + ERROR_REGISTRY_RECOVERED, + ERROR_REGISTRY_CORRUPT, + ERROR_REGISTRY_IO_FAILED, + ERROR_NOT_REGISTRY_FILE, + ERROR_KEY_DELETED, + ERROR_NO_LOG_SPACE, + ERROR_KEY_HAS_CHILDREN, + ERROR_CHILD_MUST_BE_VOLATILE, + ERROR_NOTIFY_ENUM_DIR, // = 1022 + ERROR_DEPENDENT_SERVICES_RUNNING = 1051, + ERROR_INVALID_SERVICE_CONTROL, + ERROR_SERVICE_REQUEST_TIMEOUT, + ERROR_SERVICE_NO_THREAD, + ERROR_SERVICE_DATABASE_LOCKED, + ERROR_SERVICE_ALREADY_RUNNING, + ERROR_INVALID_SERVICE_ACCOUNT, + ERROR_SERVICE_DISABLED, + ERROR_CIRCULAR_DEPENDENCY, + ERROR_SERVICE_DOES_NOT_EXIST, + ERROR_SERVICE_CANNOT_ACCEPT_CTRL, + ERROR_SERVICE_NOT_ACTIVE, + ERROR_FAILED_SERVICE_CONTROLLER_CONNECT, + ERROR_EXCEPTION_IN_SERVICE, + ERROR_DATABASE_DOES_NOT_EXIST, + ERROR_SERVICE_SPECIFIC_ERROR, + ERROR_PROCESS_ABORTED, + ERROR_SERVICE_DEPENDENCY_FAIL, + ERROR_SERVICE_LOGON_FAILED, + ERROR_SERVICE_START_HANG, + ERROR_INVALID_SERVICE_LOCK, + ERROR_SERVICE_MARKED_FOR_DELETE, + ERROR_SERVICE_EXISTS, + ERROR_ALREADY_RUNNING_LKG, + ERROR_SERVICE_DEPENDENCY_DELETED, + ERROR_BOOT_ALREADY_ACCEPTED, + ERROR_SERVICE_NEVER_STARTED, + ERROR_DUPLICATE_SERVICE_NAME, + ERROR_DIFFERENT_SERVICE_ACCOUNT, + ERROR_CANNOT_DETECT_DRIVER_FAILURE, + ERROR_CANNOT_DETECT_PROCESS_ABORT, + ERROR_NO_RECOVERY_PROGRAM, + ERROR_SERVICE_NOT_IN_EXE, + ERROR_NOT_SAFEBOOT_SERVICE, // = 1084 + ERROR_END_OF_MEDIA = 1100, + ERROR_FILEMARK_DETECTED, + ERROR_BEGINNING_OF_MEDIA, + ERROR_SETMARK_DETECTED, + ERROR_NO_DATA_DETECTED, + ERROR_PARTITION_FAILURE, + ERROR_INVALID_BLOCK_LENGTH, + ERROR_DEVICE_NOT_PARTITIONED, + ERROR_UNABLE_TO_LOCK_MEDIA, + ERROR_UNABLE_TO_UNLOAD_MEDIA, + ERROR_MEDIA_CHANGED, + ERROR_BUS_RESET, + ERROR_NO_MEDIA_IN_DRIVE, + ERROR_NO_UNICODE_TRANSLATION, + ERROR_DLL_INIT_FAILED, + ERROR_SHUTDOWN_IN_PROGRESS, + ERROR_NO_SHUTDOWN_IN_PROGRESS, + ERROR_IO_DEVICE, + ERROR_SERIAL_NO_DEVICE, + ERROR_IRQ_BUSY, + ERROR_MORE_WRITES, + ERROR_COUNTER_TIMEOUT, + ERROR_FLOPPY_ID_MARK_NOT_FOUND, + ERROR_FLOPPY_WRONG_CYLINDER, + ERROR_FLOPPY_UNKNOWN_ERROR, + ERROR_FLOPPY_BAD_REGISTERS, + ERROR_DISK_RECALIBRATE_FAILED, + ERROR_DISK_OPERATION_FAILED, + ERROR_DISK_RESET_FAILED, + ERROR_EOM_OVERFLOW, + ERROR_NOT_ENOUGH_SERVER_MEMORY, + ERROR_POSSIBLE_DEADLOCK, + ERROR_MAPPED_ALIGNMENT, // = 1132 + ERROR_SET_POWER_STATE_VETOED = 1140, + ERROR_SET_POWER_STATE_FAILED, + ERROR_TOO_MANY_LINKS, // = 1142 + ERROR_OLD_WIN_VERSION = 1150, + ERROR_APP_WRONG_OS, + ERROR_SINGLE_INSTANCE_APP, + ERROR_RMODE_APP, + ERROR_INVALID_DLL, + ERROR_NO_ASSOCIATION, + ERROR_DDE_FAIL, + ERROR_DLL_NOT_FOUND, + ERROR_NO_MORE_USER_HANDLES, + ERROR_MESSAGE_SYNC_ONLY, + ERROR_SOURCE_ELEMENT_EMPTY, + ERROR_DESTINATION_ELEMENT_FULL, + ERROR_ILLEGAL_ELEMENT_ADDRESS, + ERROR_MAGAZINE_NOT_PRESENT, + ERROR_DEVICE_REINITIALIZATION_NEEDED, + ERROR_DEVICE_REQUIRES_CLEANING, + ERROR_DEVICE_DOOR_OPEN, + ERROR_DEVICE_NOT_CONNECTED, + ERROR_NOT_FOUND, + ERROR_NO_MATCH, + ERROR_SET_NOT_FOUND, + ERROR_POINT_NOT_FOUND, + ERROR_NO_TRACKING_SERVICE, + ERROR_NO_VOLUME_ID, // = 1173 + ERROR_UNABLE_TO_REMOVE_REPLACED = 1175, + ERROR_UNABLE_TO_MOVE_REPLACEMENT, + ERROR_UNABLE_TO_MOVE_REPLACEMENT_2, + ERROR_JOURNAL_DELETE_IN_PROGRESS, + ERROR_JOURNAL_NOT_ACTIVE, + ERROR_POTENTIAL_FILE_FOUND, + ERROR_JOURNAL_ENTRY_DELETED, // = 1181 + ERROR_BAD_DEVICE = 1200, + ERROR_CONNECTION_UNAVAIL, + ERROR_DEVICE_ALREADY_REMEMBERED, + ERROR_NO_NET_OR_BAD_PATH, + ERROR_BAD_PROVIDER, + ERROR_CANNOT_OPEN_PROFILE, + ERROR_BAD_PROFILE, + ERROR_NOT_CONTAINER, + ERROR_EXTENDED_ERROR, + ERROR_INVALID_GROUPNAME, + ERROR_INVALID_COMPUTERNAME, + ERROR_INVALID_EVENTNAME, + ERROR_INVALID_DOMAINNAME, + ERROR_INVALID_SERVICENAME, + ERROR_INVALID_NETNAME, + ERROR_INVALID_SHARENAME, + ERROR_INVALID_PASSWORDNAME, + ERROR_INVALID_MESSAGENAME, + ERROR_INVALID_MESSAGEDEST, + ERROR_SESSION_CREDENTIAL_CONFLICT, + ERROR_REMOTE_SESSION_LIMIT_EXCEEDED, + ERROR_DUP_DOMAINNAME, + ERROR_NO_NETWORK, + ERROR_CANCELLED, + ERROR_USER_MAPPED_FILE, + ERROR_CONNECTION_REFUSED, + ERROR_GRACEFUL_DISCONNECT, + ERROR_ADDRESS_ALREADY_ASSOCIATED, + ERROR_ADDRESS_NOT_ASSOCIATED, + ERROR_CONNECTION_INVALID, + ERROR_CONNECTION_ACTIVE, + ERROR_NETWORK_UNREACHABLE, + ERROR_HOST_UNREACHABLE, + ERROR_PROTOCOL_UNREACHABLE, + ERROR_PORT_UNREACHABLE, + ERROR_REQUEST_ABORTED, + ERROR_CONNECTION_ABORTED, + ERROR_RETRY, + ERROR_CONNECTION_COUNT_LIMIT, + ERROR_LOGIN_TIME_RESTRICTION, + ERROR_LOGIN_WKSTA_RESTRICTION, + ERROR_INCORRECT_ADDRESS, + ERROR_ALREADY_REGISTERED, + ERROR_SERVICE_NOT_FOUND, + ERROR_NOT_AUTHENTICATED, + ERROR_NOT_LOGGED_ON, + ERROR_CONTINUE, + ERROR_ALREADY_INITIALIZED, + ERROR_NO_MORE_DEVICES, + ERROR_NO_SUCH_SITE, + ERROR_DOMAIN_CONTROLLER_EXISTS, + ERROR_ONLY_IF_CONNECTED, + ERROR_OVERRIDE_NOCHANGES, + ERROR_BAD_USER_PROFILE, + ERROR_NOT_SUPPORTED_ON_SBS, + ERROR_SERVER_SHUTDOWN_IN_PROGRESS, + ERROR_HOST_DOWN, + ERROR_NON_ACCOUNT_SID, + ERROR_NON_DOMAIN_SID, + ERROR_APPHELP_BLOCK, + ERROR_ACCESS_DISABLED_BY_POLICY, + ERROR_REG_NAT_CONSUMPTION, + ERROR_CSCSHARE_OFFLINE, + ERROR_PKINIT_FAILURE, + ERROR_SMARTCARD_SUBSYSTEM_FAILURE, + ERROR_DOWNGRADE_DETECTED, + SEC_E_SMARTCARD_CERT_REVOKED, + SEC_E_ISSUING_CA_UNTRUSTED, + SEC_E_REVOCATION_OFFLINE_C, + SEC_E_PKINIT_CLIENT_FAILUR, + SEC_E_SMARTCARD_CERT_EXPIRED, + ERROR_MACHINE_LOCKED, // = 1271 + ERROR_CALLBACK_SUPPLIED_INVALID_DATA = 1273, + ERROR_SYNC_FOREGROUND_REFRESH_REQUIRED, + ERROR_DRIVER_BLOCKED, + ERROR_INVALID_IMPORT_OF_NON_DLL, + ERROR_ACCESS_DISABLED_WEBBLADE, + ERROR_ACCESS_DISABLED_WEBBLADE_TAMPER, + ERROR_RECOVERY_FAILURE, + ERROR_ALREADY_FIBER, + ERROR_ALREADY_THREAD, + ERROR_STACK_BUFFER_OVERRUN, + ERROR_PARAMETER_QUOTA_EXCEEDED, + ERROR_DEBUGGER_INACTIVE, // = 1284 + ERROR_NOT_ALL_ASSIGNED = 1300, + ERROR_SOME_NOT_MAPPED, + ERROR_NO_QUOTAS_FOR_ACCOUNT, + ERROR_LOCAL_USER_SESSION_KEY, + ERROR_NULL_LM_PASSWORD, + ERROR_UNKNOWN_REVISION, + ERROR_REVISION_MISMATCH, + ERROR_INVALID_OWNER, + ERROR_INVALID_PRIMARY_GROUP, + ERROR_NO_IMPERSONATION_TOKEN, + ERROR_CANT_DISABLE_MANDATORY, + ERROR_NO_LOGON_SERVERS, + ERROR_NO_SUCH_LOGON_SESSION, + ERROR_NO_SUCH_PRIVILEGE, + ERROR_PRIVILEGE_NOT_HELD, + ERROR_INVALID_ACCOUNT_NAME, + ERROR_USER_EXISTS, + ERROR_NO_SUCH_USER, + ERROR_GROUP_EXISTS, + ERROR_NO_SUCH_GROUP, + ERROR_MEMBER_IN_GROUP, + ERROR_MEMBER_NOT_IN_GROUP, + ERROR_LAST_ADMIN, + ERROR_WRONG_PASSWORD, + ERROR_ILL_FORMED_PASSWORD, + ERROR_PASSWORD_RESTRICTION, + ERROR_LOGON_FAILURE, + ERROR_ACCOUNT_RESTRICTION, + ERROR_INVALID_LOGON_HOURS, + ERROR_INVALID_WORKSTATION, + ERROR_PASSWORD_EXPIRED, + ERROR_ACCOUNT_DISABLED, + ERROR_NONE_MAPPED, + ERROR_TOO_MANY_LUIDS_REQUESTED, + ERROR_LUIDS_EXHAUSTED, + ERROR_INVALID_SUB_AUTHORITY, + ERROR_INVALID_ACL, + ERROR_INVALID_SID, + ERROR_INVALID_SECURITY_DESCR, // = 1338 + ERROR_BAD_INHERITANCE_ACL = 1340, + ERROR_SERVER_DISABLED, + ERROR_SERVER_NOT_DISABLED, + ERROR_INVALID_ID_AUTHORITY, + ERROR_ALLOTTED_SPACE_EXCEEDED, + ERROR_INVALID_GROUP_ATTRIBUTES, + ERROR_BAD_IMPERSONATION_LEVEL, + ERROR_CANT_OPEN_ANONYMOUS, + ERROR_BAD_VALIDATION_CLASS, + ERROR_BAD_TOKEN_TYPE, + ERROR_NO_SECURITY_ON_OBJECT, + ERROR_CANT_ACCESS_DOMAIN_INFO, + ERROR_INVALID_SERVER_STATE, + ERROR_INVALID_DOMAIN_STATE, + ERROR_INVALID_DOMAIN_ROLE, + ERROR_NO_SUCH_DOMAIN, + ERROR_DOMAIN_EXISTS, + ERROR_DOMAIN_LIMIT_EXCEEDED, + ERROR_INTERNAL_DB_CORRUPTION, + ERROR_INTERNAL_ERROR, + ERROR_GENERIC_NOT_MAPPED, + ERROR_BAD_DESCRIPTOR_FORMAT, + ERROR_NOT_LOGON_PROCESS, + ERROR_LOGON_SESSION_EXISTS, + ERROR_NO_SUCH_PACKAGE, + ERROR_BAD_LOGON_SESSION_STATE, + ERROR_LOGON_SESSION_COLLISION, + ERROR_INVALID_LOGON_TYPE, + ERROR_CANNOT_IMPERSONATE, + ERROR_RXACT_INVALID_STATE, + ERROR_RXACT_COMMIT_FAILURE, + ERROR_SPECIAL_ACCOUNT, + ERROR_SPECIAL_GROUP, + ERROR_SPECIAL_USER, + ERROR_MEMBERS_PRIMARY_GROUP, + ERROR_TOKEN_ALREADY_IN_USE, + ERROR_NO_SUCH_ALIAS, + ERROR_MEMBER_NOT_IN_ALIAS, + ERROR_MEMBER_IN_ALIAS, + ERROR_ALIAS_EXISTS, + ERROR_LOGON_NOT_GRANTED, + ERROR_TOO_MANY_SECRETS, + ERROR_SECRET_TOO_LONG, + ERROR_INTERNAL_DB_ERROR, + ERROR_TOO_MANY_CONTEXT_IDS, + ERROR_LOGON_TYPE_NOT_GRANTED, + ERROR_NT_CROSS_ENCRYPTION_REQUIRED, + ERROR_NO_SUCH_MEMBER, + ERROR_INVALID_MEMBER, + ERROR_TOO_MANY_SIDS, + ERROR_LM_CROSS_ENCRYPTION_REQUIRED, + ERROR_NO_INHERITANCE, + ERROR_FILE_CORRUPT, + ERROR_DISK_CORRUPT, + ERROR_NO_USER_SESSION_KEY, + ERROR_LICENSE_QUOTA_EXCEEDED, + ERROR_WRONG_TARGET_NAME, + ERROR_MUTUAL_AUTH_FAILED, + ERROR_TIME_SKEW, + ERROR_CURRENT_DOMAIN_NOT_ALLOWED, + ERROR_INVALID_WINDOW_HANDLE, + ERROR_INVALID_MENU_HANDLE, + ERROR_INVALID_CURSOR_HANDLE, + ERROR_INVALID_ACCEL_HANDLE, + ERROR_INVALID_HOOK_HANDLE, + ERROR_INVALID_DWP_HANDLE, + ERROR_TLW_WITH_WSCHILD, + ERROR_CANNOT_FIND_WND_CLASS, + ERROR_WINDOW_OF_OTHER_THREAD, + ERROR_HOTKEY_ALREADY_REGISTERED, + ERROR_CLASS_ALREADY_EXISTS, + ERROR_CLASS_DOES_NOT_EXIST, + ERROR_CLASS_HAS_WINDOWS, + ERROR_INVALID_INDEX, + ERROR_INVALID_ICON_HANDLE, + ERROR_PRIVATE_DIALOG_INDEX, + ERROR_LISTBOX_ID_NOT_FOUND, + ERROR_NO_WILDCARD_CHARACTERS, + ERROR_CLIPBOARD_NOT_OPEN, + ERROR_HOTKEY_NOT_REGISTERED, + ERROR_WINDOW_NOT_DIALOG, + ERROR_CONTROL_ID_NOT_FOUND, + ERROR_INVALID_COMBOBOX_MESSAGE, + ERROR_WINDOW_NOT_COMBOBOX, + ERROR_INVALID_EDIT_HEIGHT, + ERROR_DC_NOT_FOUND, + ERROR_INVALID_HOOK_FILTER, + ERROR_INVALID_FILTER_PROC, + ERROR_HOOK_NEEDS_HMOD, + ERROR_GLOBAL_ONLY_HOOK, + ERROR_JOURNAL_HOOK_SET, + ERROR_HOOK_NOT_INSTALLED, + ERROR_INVALID_LB_MESSAGE, + ERROR_SETCOUNT_ON_BAD_LB, + ERROR_LB_WITHOUT_TABSTOPS, + ERROR_DESTROY_OBJECT_OF_OTHER_THREAD, + ERROR_CHILD_WINDOW_MENU, + ERROR_NO_SYSTEM_MENU, + ERROR_INVALID_MSGBOX_STYLE, + ERROR_INVALID_SPI_VALUE, + ERROR_SCREEN_ALREADY_LOCKED, + ERROR_HWNDS_HAVE_DIFF_PARENT, + ERROR_NOT_CHILD_WINDOW, + ERROR_INVALID_GW_COMMAND, + ERROR_INVALID_THREAD_ID, + ERROR_NON_MDICHILD_WINDOW, + ERROR_POPUP_ALREADY_ACTIVE, + ERROR_NO_SCROLLBARS, + ERROR_INVALID_SCROLLBAR_RANGE, + ERROR_INVALID_SHOWWIN_COMMAND, + ERROR_NO_SYSTEM_RESOURCES, + ERROR_NONPAGED_SYSTEM_RESOURCES, + ERROR_PAGED_SYSTEM_RESOURCES, + ERROR_WORKING_SET_QUOTA, + ERROR_PAGEFILE_QUOTA, + ERROR_COMMITMENT_LIMIT, + ERROR_MENU_ITEM_NOT_FOUND, + ERROR_INVALID_KEYBOARD_HANDLE, + ERROR_HOOK_TYPE_NOT_ALLOWED, + ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION, + ERROR_TIMEOUT, + ERROR_INVALID_MONITOR_HANDLE, // = 1461 + ERROR_EVENTLOG_FILE_CORRUPT = 1500, + ERROR_EVENTLOG_CANT_START, + ERROR_LOG_FILE_FULL, + ERROR_EVENTLOG_FILE_CHANGED, // = 1503 + ERROR_INSTALL_SERVICE_FAILURE = 1601, + ERROR_INSTALL_USEREXIT, + ERROR_INSTALL_FAILURE, + ERROR_INSTALL_SUSPEND, + ERROR_UNKNOWN_PRODUCT, + ERROR_UNKNOWN_FEATURE, + ERROR_UNKNOWN_COMPONENT, + ERROR_UNKNOWN_PROPERTY, + ERROR_INVALID_HANDLE_STATE, + ERROR_BAD_CONFIGURATION, + ERROR_INDEX_ABSENT, + ERROR_INSTALL_SOURCE_ABSENT, + ERROR_INSTALL_PACKAGE_VERSION, + ERROR_PRODUCT_UNINSTALLED, + ERROR_BAD_QUERY_SYNTAX, + ERROR_INVALID_FIELD, + ERROR_DEVICE_REMOVED, + ERROR_INSTALL_ALREADY_RUNNING, + ERROR_INSTALL_PACKAGE_OPEN_FAILED, + ERROR_INSTALL_PACKAGE_INVALID, + ERROR_INSTALL_UI_FAILURE, + ERROR_INSTALL_LOG_FAILURE, + ERROR_INSTALL_LANGUAGE_UNSUPPORTED, + ERROR_INSTALL_TRANSFORM_FAILURE, + ERROR_INSTALL_PACKAGE_REJECTED, + ERROR_FUNCTION_NOT_CALLED, + ERROR_FUNCTION_FAILED, + ERROR_INVALID_TABLE, + ERROR_DATATYPE_MISMATCH, + ERROR_UNSUPPORTED_TYPE, + ERROR_CREATE_FAILED, + ERROR_INSTALL_TEMP_UNWRITABLE, + ERROR_INSTALL_PLATFORM_UNSUPPORTED, + ERROR_INSTALL_NOTUSED, + ERROR_PATCH_PACKAGE_OPEN_FAILED, + ERROR_PATCH_PACKAGE_INVALID, + ERROR_PATCH_PACKAGE_UNSUPPORTED, + ERROR_PRODUCT_VERSION, + ERROR_INVALID_COMMAND_LINE, + ERROR_INSTALL_REMOTE_DISALLOWED, + ERROR_SUCCESS_REBOOT_INITIATED, + ERROR_PATCH_TARGET_NOT_FOUND, + ERROR_PATCH_PACKAGE_REJECTED, + ERROR_INSTALL_TRANSFORM_REJECTED, + ERROR_INSTALL_REMOTE_PROHIBITED, // = 1645 + RPC_S_INVALID_STRING_BINDING = 1700, + RPC_S_WRONG_KIND_OF_BINDING, + RPC_S_INVALID_BINDING, + RPC_S_PROTSEQ_NOT_SUPPORTED, + RPC_S_INVALID_RPC_PROTSEQ, + RPC_S_INVALID_STRING_UUID, + RPC_S_INVALID_ENDPOINT_FORMAT, + RPC_S_INVALID_NET_ADDR, + RPC_S_NO_ENDPOINT_FOUND, + RPC_S_INVALID_TIMEOUT, + RPC_S_OBJECT_NOT_FOUND, + RPC_S_ALREADY_REGISTERED, + RPC_S_TYPE_ALREADY_REGISTERED, + RPC_S_ALREADY_LISTENING, + RPC_S_NO_PROTSEQS_REGISTERED, + RPC_S_NOT_LISTENING, + RPC_S_UNKNOWN_MGR_TYPE, + RPC_S_UNKNOWN_IF, + RPC_S_NO_BINDINGS, + RPC_S_NO_PROTSEQS, + RPC_S_CANT_CREATE_ENDPOINT, + RPC_S_OUT_OF_RESOURCES, + RPC_S_SERVER_UNAVAILABLE, + RPC_S_SERVER_TOO_BUSY, + RPC_S_INVALID_NETWORK_OPTIONS, + RPC_S_NO_CALL_ACTIVE, + RPC_S_CALL_FAILED, + RPC_S_CALL_FAILED_DNE, + RPC_S_PROTOCOL_ERROR, // = 1728 + RPC_S_UNSUPPORTED_TRANS_SYN = 1730, + RPC_S_UNSUPPORTED_TYPE = 1732, + RPC_S_INVALID_TAG, + RPC_S_INVALID_BOUND, + RPC_S_NO_ENTRY_NAME, + RPC_S_INVALID_NAME_SYNTAX, + RPC_S_UNSUPPORTED_NAME_SYNTAX, // = 1737 + RPC_S_UUID_NO_ADDRESS = 1739, + RPC_S_DUPLICATE_ENDPOINT, + RPC_S_UNKNOWN_AUTHN_TYPE, + RPC_S_MAX_CALLS_TOO_SMALL, + RPC_S_STRING_TOO_LONG, + RPC_S_PROTSEQ_NOT_FOUND, + RPC_S_PROCNUM_OUT_OF_RANGE, + RPC_S_BINDING_HAS_NO_AUTH, + RPC_S_UNKNOWN_AUTHN_SERVICE, + RPC_S_UNKNOWN_AUTHN_LEVEL, + RPC_S_INVALID_AUTH_IDENTITY, + RPC_S_UNKNOWN_AUTHZ_SERVICE, + EPT_S_INVALID_ENTRY, + EPT_S_CANT_PERFORM_OP, + EPT_S_NOT_REGISTERED, + RPC_S_NOTHING_TO_EXPORT, + RPC_S_INCOMPLETE_NAME, + RPC_S_INVALID_VERS_OPTION, + RPC_S_NO_MORE_MEMBERS, + RPC_S_NOT_ALL_OBJS_UNEXPORTED, + RPC_S_INTERFACE_NOT_FOUND, + RPC_S_ENTRY_ALREADY_EXISTS, + RPC_S_ENTRY_NOT_FOUND, + RPC_S_NAME_SERVICE_UNAVAILABLE, + RPC_S_INVALID_NAF_ID, + RPC_S_CANNOT_SUPPORT, + RPC_S_NO_CONTEXT_AVAILABLE, + RPC_S_INTERNAL_ERROR, + RPC_S_ZERO_DIVIDE, + RPC_S_ADDRESS_ERROR, + RPC_S_FP_DIV_ZERO, + RPC_S_FP_UNDERFLOW, + RPC_S_FP_OVERFLOW, + RPC_X_NO_MORE_ENTRIES, + RPC_X_SS_CHAR_TRANS_OPEN_FAIL, + RPC_X_SS_CHAR_TRANS_SHORT_FILE, + RPC_X_SS_IN_NULL_CONTEXT, // = 1775 + RPC_X_SS_CONTEXT_DAMAGED = 1777, + RPC_X_SS_HANDLES_MISMATCH, + RPC_X_SS_CANNOT_GET_CALL_HANDLE, + RPC_X_NULL_REF_POINTER, + RPC_X_ENUM_VALUE_OUT_OF_RANGE, + RPC_X_BYTE_COUNT_TOO_SMALL, + RPC_X_BAD_STUB_DATA, + ERROR_INVALID_USER_BUFFER, + ERROR_UNRECOGNIZED_MEDIA, + ERROR_NO_TRUST_LSA_SECRET, + ERROR_NO_TRUST_SAM_ACCOUNT, + ERROR_TRUSTED_DOMAIN_FAILURE, + ERROR_TRUSTED_RELATIONSHIP_FAILURE, + ERROR_TRUST_FAILURE, + RPC_S_CALL_IN_PROGRESS, + ERROR_NETLOGON_NOT_STARTED, + ERROR_ACCOUNT_EXPIRED, + ERROR_REDIRECTOR_HAS_OPEN_HANDLES, + ERROR_PRINTER_DRIVER_ALREADY_INSTALLED, + ERROR_UNKNOWN_PORT, + ERROR_UNKNOWN_PRINTER_DRIVER, + ERROR_UNKNOWN_PRINTPROCESSOR, + ERROR_INVALID_SEPARATOR_FILE, + ERROR_INVALID_PRIORITY, + ERROR_INVALID_PRINTER_NAME, + ERROR_PRINTER_ALREADY_EXISTS, + ERROR_INVALID_PRINTER_COMMAND, + ERROR_INVALID_DATATYPE, + ERROR_INVALID_ENVIRONMENT, + RPC_S_NO_MORE_BINDINGS, + ERROR_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT, + ERROR_NOLOGON_WORKSTATION_TRUST_ACCOUNT, + ERROR_NOLOGON_SERVER_TRUST_ACCOUNT, + ERROR_DOMAIN_TRUST_INCONSISTENT, + ERROR_SERVER_HAS_OPEN_HANDLES, + ERROR_RESOURCE_DATA_NOT_FOUND, + ERROR_RESOURCE_TYPE_NOT_FOUND, + ERROR_RESOURCE_NAME_NOT_FOUND, + ERROR_RESOURCE_LANG_NOT_FOUND, + ERROR_NOT_ENOUGH_QUOTA, + RPC_S_NO_INTERFACES, + RPC_S_CALL_CANCELLED, + RPC_S_BINDING_INCOMPLETE, + RPC_S_COMM_FAILURE, + RPC_S_UNSUPPORTED_AUTHN_LEVEL, + RPC_S_NO_PRINC_NAME, + RPC_S_NOT_RPC_ERROR, + RPC_S_UUID_LOCAL_ONLY, + RPC_S_SEC_PKG_ERROR, + RPC_S_NOT_CANCELLED, + RPC_X_INVALID_ES_ACTION, + RPC_X_WRONG_ES_VERSION, + RPC_X_WRONG_STUB_VERSION, + RPC_X_INVALID_PIPE_OBJECT, + RPC_X_WRONG_PIPE_ORDER, + RPC_X_WRONG_PIPE_VERSION, // = 1832 + RPC_S_GROUP_MEMBER_NOT_FOUND = 1898, + EPT_S_CANT_CREATE, + RPC_S_INVALID_OBJECT, + ERROR_INVALID_TIME, + ERROR_INVALID_FORM_NAME, + ERROR_INVALID_FORM_SIZE, + ERROR_ALREADY_WAITING, + ERROR_PRINTER_DELETED, + ERROR_INVALID_PRINTER_STATE, + ERROR_PASSWORD_MUST_CHANGE, + ERROR_DOMAIN_CONTROLLER_NOT_FOUND, + ERROR_ACCOUNT_LOCKED_OUT, + OR_INVALID_OXID, + OR_INVALID_OID, + OR_INVALID_SET, + RPC_S_SEND_INCOMPLETE, + RPC_S_INVALID_ASYNC_HANDLE, + RPC_S_INVALID_ASYNC_CALL, + RPC_X_PIPE_CLOSED, + RPC_X_PIPE_DISCIPLINE_ERROR, + RPC_X_PIPE_EMPTY, + ERROR_NO_SITENAME, + ERROR_CANT_ACCESS_FILE, + ERROR_CANT_RESOLVE_FILENAME, + RPC_S_ENTRY_TYPE_MISMATCH, + RPC_S_NOT_ALL_OBJS_EXPORTED, + RPC_S_INTERFACE_NOT_EXPORTED, + RPC_S_PROFILE_NOT_ADDED, + RPC_S_PRF_ELT_NOT_ADDED, + RPC_S_PRF_ELT_NOT_REMOVED, + RPC_S_GRP_ELT_NOT_ADDED, + RPC_S_GRP_ELT_NOT_REMOVED, + ERROR_KM_DRIVER_BLOCKED, + ERROR_CONTEXT_EXPIRED, + ERROR_PER_USER_TRUST_QUOTA_EXCEEDED, + ERROR_ALL_USER_TRUST_QUOTA_EXCEEDED, + ERROR_USER_DELETE_TRUST_QUOTA_EXCEEDED, // = 1934 + ERROR_INVALID_PIXEL_FORMAT = 2000, + ERROR_BAD_DRIVER, + ERROR_INVALID_WINDOW_STYLE, + ERROR_METAFILE_NOT_SUPPORTED, + ERROR_TRANSFORM_NOT_SUPPORTED, + ERROR_CLIPPING_NOT_SUPPORTED, // = 2005 + ERROR_INVALID_CMM = 2010, + ERROR_INVALID_PROFILE, + ERROR_TAG_NOT_FOUND, + ERROR_TAG_NOT_PRESENT, + ERROR_DUPLICATE_TAG, + ERROR_PROFILE_NOT_ASSOCIATED_WITH_DEVICE, + ERROR_PROFILE_NOT_FOUND, + ERROR_INVALID_COLORSPACE, + ERROR_ICM_NOT_ENABLED, + ERROR_DELETING_ICM_XFORM, + ERROR_INVALID_TRANSFORM, + ERROR_COLORSPACE_MISMATCH, + ERROR_INVALID_COLORINDEX, // = 2022 + ERROR_CONNECTED_OTHER_PASSWORD = 2108, + ERROR_CONNECTED_OTHER_PASSWORD_DEFAULT, // = 2109 + ERROR_BAD_USERNAME = 2202, + ERROR_NOT_CONNECTED = 2250, + ERROR_OPEN_FILES = 2401, + ERROR_ACTIVE_CONNECTIONS, // = 2402 + ERROR_DEVICE_IN_USE = 2404, + ERROR_UNKNOWN_PRINT_MONITOR = 3000, + ERROR_PRINTER_DRIVER_IN_USE, + ERROR_SPOOL_FILE_NOT_FOUND, + ERROR_SPL_NO_STARTDOC, + ERROR_SPL_NO_ADDJOB, + ERROR_PRINT_PROCESSOR_ALREADY_INSTALLED, + ERROR_PRINT_MONITOR_ALREADY_INSTALLED, + ERROR_INVALID_PRINT_MONITOR, + ERROR_PRINT_MONITOR_IN_USE, + ERROR_PRINTER_HAS_JOBS_QUEUED, + ERROR_SUCCESS_REBOOT_REQUIRED, + ERROR_SUCCESS_RESTART_REQUIRED, + ERROR_PRINTER_NOT_FOUND, + ERROR_PRINTER_DRIVER_WARNED, + ERROR_PRINTER_DRIVER_BLOCKED, // = 3014 + ERROR_WINS_INTERNAL = 4000, + ERROR_CAN_NOT_DEL_LOCAL_WINS, + ERROR_STATIC_INIT, + ERROR_INC_BACKUP, + ERROR_FULL_BACKUP, + ERROR_REC_NON_EXISTENT, + ERROR_RPL_NOT_ALLOWED, // = 4006 + ERROR_DHCP_ADDRESS_CONFLICT = 4100, + ERROR_WMI_GUID_NOT_FOUND = 4200, + ERROR_WMI_INSTANCE_NOT_FOUND, + ERROR_WMI_ITEMID_NOT_FOUND, + ERROR_WMI_TRY_AGAIN, + ERROR_WMI_DP_NOT_FOUND, + ERROR_WMI_UNRESOLVED_INSTANCE_REF, + ERROR_WMI_ALREADY_ENABLED, + ERROR_WMI_GUID_DISCONNECTED, + ERROR_WMI_SERVER_UNAVAILABLE, + ERROR_WMI_DP_FAILED, + ERROR_WMI_INVALID_MOF, + ERROR_WMI_INVALID_REGINFO, + ERROR_WMI_ALREADY_DISABLED, + ERROR_WMI_READ_ONLY, + ERROR_WMI_SET_FAILURE, // = 4214 + ERROR_INVALID_MEDIA = 4300, + ERROR_INVALID_LIBRARY, + ERROR_INVALID_MEDIA_POOL, + ERROR_DRIVE_MEDIA_MISMATCH, + ERROR_MEDIA_OFFLINE, + ERROR_LIBRARY_OFFLINE, + ERROR_EMPTY, + ERROR_NOT_EMPTY, + ERROR_MEDIA_UNAVAILABLE, + ERROR_RESOURCE_DISABLED, + ERROR_INVALID_CLEANER, + ERROR_UNABLE_TO_CLEAN, + ERROR_OBJECT_NOT_FOUND, + ERROR_DATABASE_FAILURE, + ERROR_DATABASE_FULL, + ERROR_MEDIA_INCOMPATIBLE, + ERROR_RESOURCE_NOT_PRESENT, + ERROR_INVALID_OPERATION, + ERROR_MEDIA_NOT_AVAILABLE, + ERROR_DEVICE_NOT_AVAILABLE, + ERROR_REQUEST_REFUSED, + ERROR_INVALID_DRIVE_OBJECT, + ERROR_LIBRARY_FULL, + ERROR_MEDIUM_NOT_ACCESSIBLE, + ERROR_UNABLE_TO_LOAD_MEDIUM, + ERROR_UNABLE_TO_INVENTORY_DRIVE, + ERROR_UNABLE_TO_INVENTORY_SLOT, + ERROR_UNABLE_TO_INVENTORY_TRANSPORT, + ERROR_TRANSPORT_FULL, + ERROR_CONTROLLING_IEPORT, + ERROR_UNABLE_TO_EJECT_MOUNTED_MEDIA, + ERROR_CLEANER_SLOT_SET, + ERROR_CLEANER_SLOT_NOT_SET, + ERROR_CLEANER_CARTRIDGE_SPENT, + ERROR_UNEXPECTED_OMID, + ERROR_CANT_DELETE_LAST_ITEM, + ERROR_MESSAGE_EXCEEDS_MAX_SIZE, + ERROR_VOLUME_CONTAINS_SYS_FILES, + ERROR_INDIGENOUS_TYPE, + ERROR_NO_SUPPORTING_DRIVES, + ERROR_CLEANER_CARTRIDGE_INSTALLED, // = 4340 + ERROR_FILE_OFFLINE = 4350, + ERROR_REMOTE_STORAGE_NOT_ACTIVE, + ERROR_REMOTE_STORAGE_MEDIA_ERROR, // = 4352 + ERROR_NOT_A_REPARSE_POINT = 4390, + ERROR_REPARSE_ATTRIBUTE_CONFLICT, + ERROR_INVALID_REPARSE_DATA, + ERROR_REPARSE_TAG_INVALID, + ERROR_REPARSE_TAG_MISMATCH, // = 4394 + ERROR_VOLUME_NOT_SIS_ENABLED = 4500, + ERROR_DEPENDENT_RESOURCE_EXISTS = 5001, + ERROR_DEPENDENCY_NOT_FOUND, + ERROR_DEPENDENCY_ALREADY_EXISTS, + ERROR_RESOURCE_NOT_ONLINE, + ERROR_HOST_NODE_NOT_AVAILABLE, + ERROR_RESOURCE_NOT_AVAILABLE, + ERROR_RESOURCE_NOT_FOUND, + ERROR_SHUTDOWN_CLUSTER, + ERROR_CANT_EVICT_ACTIVE_NODE, + ERROR_OBJECT_ALREADY_EXISTS, + ERROR_OBJECT_IN_LIST, + ERROR_GROUP_NOT_AVAILABLE, + ERROR_GROUP_NOT_FOUND, + ERROR_GROUP_NOT_ONLINE, + ERROR_HOST_NODE_NOT_RESOURCE_OWNER, + ERROR_HOST_NODE_NOT_GROUP_OWNER, + ERROR_RESMON_CREATE_FAILED, + ERROR_RESMON_ONLINE_FAILED, + ERROR_RESOURCE_ONLINE, + ERROR_QUORUM_RESOURCE, + ERROR_NOT_QUORUM_CAPABLE, + ERROR_CLUSTER_SHUTTING_DOWN, + ERROR_INVALID_STATE, + ERROR_RESOURCE_PROPERTIES_STORED, + ERROR_NOT_QUORUM_CLASS, + ERROR_CORE_RESOURCE, + ERROR_QUORUM_RESOURCE_ONLINE_FAILED, + ERROR_QUORUMLOG_OPEN_FAILED, + ERROR_CLUSTERLOG_CORRUPT, + ERROR_CLUSTERLOG_RECORD_EXCEEDS_MAXSIZE, + ERROR_CLUSTERLOG_EXCEEDS_MAXSIZE, + ERROR_CLUSTERLOG_CHKPOINT_NOT_FOUND, + ERROR_CLUSTERLOG_NOT_ENOUGH_SPACE, + ERROR_QUORUM_OWNER_ALIVE, + ERROR_NETWORK_NOT_AVAILABLE, + ERROR_NODE_NOT_AVAILABLE, + ERROR_ALL_NODES_NOT_AVAILABLE, + ERROR_RESOURCE_FAILED, + ERROR_CLUSTER_INVALID_NODE, + ERROR_CLUSTER_NODE_EXISTS, + ERROR_CLUSTER_JOIN_IN_PROGRESS, + ERROR_CLUSTER_NODE_NOT_FOUND, + ERROR_CLUSTER_LOCAL_NODE_NOT_FOUND, + ERROR_CLUSTER_NETWORK_EXISTS, + ERROR_CLUSTER_NETWORK_NOT_FOUND, + ERROR_CLUSTER_NETINTERFACE_EXISTS, + ERROR_CLUSTER_NETINTERFACE_NOT_FOUND, + ERROR_CLUSTER_INVALID_REQUEST, + ERROR_CLUSTER_INVALID_NETWORK_PROVIDER, + ERROR_CLUSTER_NODE_DOWN, + ERROR_CLUSTER_NODE_UNREACHABLE, + ERROR_CLUSTER_NODE_NOT_MEMBER, + ERROR_CLUSTER_JOIN_NOT_IN_PROGRESS, + ERROR_CLUSTER_INVALID_NETWORK, // = 5054 + ERROR_CLUSTER_NODE_UP = 5056, + ERROR_CLUSTER_IPADDR_IN_USE, + ERROR_CLUSTER_NODE_NOT_PAUSED, + ERROR_CLUSTER_NO_SECURITY_CONTEXT, + ERROR_CLUSTER_NETWORK_NOT_INTERNAL, + ERROR_CLUSTER_NODE_ALREADY_UP, + ERROR_CLUSTER_NODE_ALREADY_DOWN, + ERROR_CLUSTER_NETWORK_ALREADY_ONLINE, + ERROR_CLUSTER_NETWORK_ALREADY_OFFLINE, + ERROR_CLUSTER_NODE_ALREADY_MEMBER, + ERROR_CLUSTER_LAST_INTERNAL_NETWORK, + ERROR_CLUSTER_NETWORK_HAS_DEPENDENTS, + ERROR_INVALID_OPERATION_ON_QUORUM, + ERROR_DEPENDENCY_NOT_ALLOWED, + ERROR_CLUSTER_NODE_PAUSED, + ERROR_NODE_CANT_HOST_RESOURCE, + ERROR_CLUSTER_NODE_NOT_READY, + ERROR_CLUSTER_NODE_SHUTTING_DOWN, + ERROR_CLUSTER_JOIN_ABORTED, + ERROR_CLUSTER_INCOMPATIBLE_VERSIONS, + ERROR_CLUSTER_MAXNUM_OF_RESOURCES_EXCEEDED, + ERROR_CLUSTER_SYSTEM_CONFIG_CHANGED, + ERROR_CLUSTER_RESOURCE_TYPE_NOT_FOUND, + ERROR_CLUSTER_RESTYPE_NOT_SUPPORTED, + ERROR_CLUSTER_RESNAME_NOT_FOUND, + ERROR_CLUSTER_NO_RPC_PACKAGES_REGISTERED, + ERROR_CLUSTER_OWNER_NOT_IN_PREFLIST, + ERROR_CLUSTER_DATABASE_SEQMISMATCH, + ERROR_RESMON_INVALID_STATE, + ERROR_CLUSTER_GUM_NOT_LOCKER, + ERROR_QUORUM_DISK_NOT_FOUND, + ERROR_DATABASE_BACKUP_CORRUPT, + ERROR_CLUSTER_NODE_ALREADY_HAS_DFS_ROOT, + ERROR_RESOURCE_PROPERTY_UNCHANGEABLE, // = 5089 + ERROR_CLUSTER_MEMBERSHIP_INVALID_STATE = 5890, + ERROR_CLUSTER_QUORUMLOG_NOT_FOUND, + ERROR_CLUSTER_MEMBERSHIP_HALT, + ERROR_CLUSTER_INSTANCE_ID_MISMATCH, + ERROR_CLUSTER_NETWORK_NOT_FOUND_FOR_IP, + ERROR_CLUSTER_PROPERTY_DATA_TYPE_MISMATCH, + ERROR_CLUSTER_EVICT_WITHOUT_CLEANUP, + ERROR_CLUSTER_PARAMETER_MISMATCH, + ERROR_NODE_CANNOT_BE_CLUSTERED, + ERROR_CLUSTER_WRONG_OS_VERSION, + ERROR_CLUSTER_CANT_CREATE_DUP_CLUSTER_NAME, + ERROR_CLUSCFG_ALREADY_COMMITTED, + ERROR_CLUSCFG_ROLLBACK_FAILED, + ERROR_CLUSCFG_SYSTEM_DISK_DRIVE_LETTER_CONFLICT, + ERROR_CLUSTER_OLD_VERSION, + ERROR_CLUSTER_MISMATCHED_COMPUTER_ACCT_NAME, // = 5905 + ERROR_ENCRYPTION_FAILED = 6000, + ERROR_DECRYPTION_FAILED, + ERROR_FILE_ENCRYPTED, + ERROR_NO_RECOVERY_POLICY, + ERROR_NO_EFS, + ERROR_WRONG_EFS, + ERROR_NO_USER_KEYS, + ERROR_FILE_NOT_ENCRYPTED, + ERROR_NOT_EXPORT_FORMAT, + ERROR_FILE_READ_ONLY, + ERROR_DIR_EFS_DISALLOWED, + ERROR_EFS_SERVER_NOT_TRUSTED, + ERROR_BAD_RECOVERY_POLICY, + ERROR_EFS_ALG_BLOB_TOO_BIG, + ERROR_VOLUME_NOT_SUPPORT_EFS, + ERROR_EFS_DISABLED, + ERROR_EFS_VERSION_NOT_SUPPORT, // = 6016 + ERROR_NO_BROWSER_SERVERS_FOUND = 6118, + SCHED_E_SERVICE_NOT_LOCALSYSTEM = 6200, + + ERROR_CTX_WINSTATION_NAME_INVALID = 7001, + ERROR_CTX_INVALID_PD, + ERROR_CTX_PD_NOT_FOUND, + ERROR_CTX_WD_NOT_FOUND, + ERROR_CTX_CANNOT_MAKE_EVENTLOG_ENTRY, + ERROR_CTX_SERVICE_NAME_COLLISION, + ERROR_CTX_CLOSE_PENDING, + ERROR_CTX_NO_OUTBUF, + ERROR_CTX_MODEM_INF_NOT_FOUND, + ERROR_CTX_INVALID_MODEMNAME, + ERROR_CTX_MODEM_RESPONSE_ERROR, + ERROR_CTX_MODEM_RESPONSE_TIMEOUT, + ERROR_CTX_MODEM_RESPONSE_NO_CARRIER, + ERROR_CTX_MODEM_RESPONSE_NO_DIALTONE, + ERROR_CTX_MODEM_RESPONSE_BUSY, + ERROR_CTX_MODEM_RESPONSE_VOICE, + ERROR_CTX_TD_ERROR, // = 7017 + ERROR_CTX_WINSTATION_NOT_FOUND = 7022, + ERROR_CTX_WINSTATION_ALREADY_EXISTS, + ERROR_CTX_WINSTATION_BUSY, + ERROR_CTX_BAD_VIDEO_MODE, // = 7025 + ERROR_CTX_GRAPHICS_INVALID = 7035, + ERROR_CTX_LOGON_DISABLED = 7037, + ERROR_CTX_NOT_CONSOLE, // = 7038 + ERROR_CTX_CLIENT_QUERY_TIMEOUT = 7040, + ERROR_CTX_CONSOLE_DISCONNECT, + ERROR_CTX_CONSOLE_CONNECT, // = 7042 + ERROR_CTX_SHADOW_DENIED = 7044, + ERROR_CTX_WINSTATION_ACCESS_DENIED, // = 7045 + ERROR_CTX_INVALID_WD = 7049, + ERROR_CTX_SHADOW_INVALID, + ERROR_CTX_SHADOW_DISABLED, + ERROR_CTX_CLIENT_LICENSE_IN_USE, + ERROR_CTX_CLIENT_LICENSE_NOT_SET, + ERROR_CTX_LICENSE_NOT_AVAILABLE, + ERROR_CTX_LICENSE_CLIENT_INVALID, + ERROR_CTX_LICENSE_EXPIRED, + ERROR_CTX_SHADOW_NOT_RUNNING, + ERROR_CTX_SHADOW_ENDED_BY_MODE_CHANGE, + ERROR_ACTIVATION_COUNT_EXCEEDED, // = 7059 + + FRS_ERR_INVALID_API_SEQUENCE = 8001, + FRS_ERR_STARTING_SERVICE, + FRS_ERR_STOPPING_SERVICE, + FRS_ERR_INTERNAL_API, + FRS_ERR_INTERNAL, + FRS_ERR_SERVICE_COMM, + FRS_ERR_INSUFFICIENT_PRIV, + FRS_ERR_AUTHENTICATION, + FRS_ERR_PARENT_INSUFFICIENT_PRIV, + FRS_ERR_PARENT_AUTHENTICATION, + FRS_ERR_CHILD_TO_PARENT_COMM, + FRS_ERR_PARENT_TO_CHILD_COMM, + FRS_ERR_SYSVOL_POPULATE, + FRS_ERR_SYSVOL_POPULATE_TIMEOUT, + FRS_ERR_SYSVOL_IS_BUSY, + FRS_ERR_SYSVOL_DEMOTE, + FRS_ERR_INVALID_SERVICE_PARAMETER, // = 8017 + ERROR_DS_NOT_INSTALLED = 8200, + ERROR_DS_MEMBERSHIP_EVALUATED_LOCALLY, + ERROR_DS_NO_ATTRIBUTE_OR_VALUE, + ERROR_DS_INVALID_ATTRIBUTE_SYNTAX, + ERROR_DS_ATTRIBUTE_TYPE_UNDEFINED, + ERROR_DS_ATTRIBUTE_OR_VALUE_EXISTS, + ERROR_DS_BUSY, + ERROR_DS_UNAVAILABLE, + ERROR_DS_NO_RIDS_ALLOCATED, + ERROR_DS_NO_MORE_RIDS, + ERROR_DS_INCORRECT_ROLE_OWNER, + ERROR_DS_RIDMGR_INIT_ERROR, + ERROR_DS_OBJ_CLASS_VIOLATION, + ERROR_DS_CANT_ON_NON_LEAF, + ERROR_DS_CANT_ON_RDN, + ERROR_DS_CANT_MOD_OBJ_CLASS, + ERROR_DS_CROSS_DOM_MOVE_ERROR, + ERROR_DS_GC_NOT_AVAILABLE, + ERROR_SHARED_POLICY, + ERROR_POLICY_OBJECT_NOT_FOUND, + ERROR_POLICY_ONLY_IN_DS, + ERROR_PROMOTION_ACTIVE, + ERROR_NO_PROMOTION_ACTIVE, // = 8222 + ERROR_DS_OPERATIONS_ERROR = 8224, + ERROR_DS_PROTOCOL_ERROR, + ERROR_DS_TIMELIMIT_EXCEEDED, + ERROR_DS_SIZELIMIT_EXCEEDED, + ERROR_DS_ADMIN_LIMIT_EXCEEDED, + ERROR_DS_COMPARE_FALSE, + ERROR_DS_COMPARE_TRUE, + ERROR_DS_AUTH_METHOD_NOT_SUPPORTED, + ERROR_DS_STRONG_AUTH_REQUIRED, + ERROR_DS_INAPPROPRIATE_AUTH, + ERROR_DS_AUTH_UNKNOWN, + ERROR_DS_REFERRAL, + ERROR_DS_UNAVAILABLE_CRIT_EXTENSION, + ERROR_DS_CONFIDENTIALITY_REQUIRED, + ERROR_DS_INAPPROPRIATE_MATCHING, + ERROR_DS_CONSTRAINT_VIOLATION, + ERROR_DS_NO_SUCH_OBJECT, + ERROR_DS_ALIAS_PROBLEM, + ERROR_DS_INVALID_DN_SYNTAX, + ERROR_DS_IS_LEAF, + ERROR_DS_ALIAS_DEREF_PROBLEM, + ERROR_DS_UNWILLING_TO_PERFORM, + ERROR_DS_LOOP_DETECT, + ERROR_DS_NAMING_VIOLATION, + ERROR_DS_OBJECT_RESULTS_TOO_LARGE, + ERROR_DS_AFFECTS_MULTIPLE_DSAS, + ERROR_DS_SERVER_DOWN, + ERROR_DS_LOCAL_ERROR, + ERROR_DS_ENCODING_ERROR, + ERROR_DS_DECODING_ERROR, + ERROR_DS_FILTER_UNKNOWN, + ERROR_DS_PARAM_ERROR, + ERROR_DS_NOT_SUPPORTED, + ERROR_DS_NO_RESULTS_RETURNED, + ERROR_DS_CONTROL_NOT_FOUND, + ERROR_DS_CLIENT_LOOP, + ERROR_DS_REFERRAL_LIMIT_EXCEEDED, + ERROR_DS_SORT_CONTROL_MISSING, + ERROR_DS_OFFSET_RANGE_ERROR, // = 8262 + ERROR_DS_ROOT_MUST_BE_NC = 8301, + ERROR_DS_ADD_REPLICA_INHIBITED, + ERROR_DS_ATT_NOT_DEF_IN_SCHEMA, + ERROR_DS_MAX_OBJ_SIZE_EXCEEDED, + ERROR_DS_OBJ_STRING_NAME_EXISTS, + ERROR_DS_NO_RDN_DEFINED_IN_SCHEMA, + ERROR_DS_RDN_DOESNT_MATCH_SCHEMA, + ERROR_DS_NO_REQUESTED_ATTS_FOUND, + ERROR_DS_USER_BUFFER_TO_SMALL, + ERROR_DS_ATT_IS_NOT_ON_OBJ, + ERROR_DS_ILLEGAL_MOD_OPERATION, + ERROR_DS_OBJ_TOO_LARGE, + ERROR_DS_BAD_INSTANCE_TYPE, + ERROR_DS_MASTERDSA_REQUIRED, + ERROR_DS_OBJECT_CLASS_REQUIRED, + ERROR_DS_MISSING_REQUIRED_ATT, + ERROR_DS_ATT_NOT_DEF_FOR_CLASS, + ERROR_DS_ATT_ALREADY_EXISTS, // = 8318 + ERROR_DS_CANT_ADD_ATT_VALUES = 8320, + ERROR_DS_SINGLE_VALUE_CONSTRAINT, + ERROR_DS_RANGE_CONSTRAINT, + ERROR_DS_ATT_VAL_ALREADY_EXISTS, + ERROR_DS_CANT_REM_MISSING_ATT, + ERROR_DS_CANT_REM_MISSING_ATT_VAL, + ERROR_DS_ROOT_CANT_BE_SUBREF, + ERROR_DS_NO_CHAINING, + ERROR_DS_NO_CHAINED_EVAL, + ERROR_DS_NO_PARENT_OBJECT, + ERROR_DS_PARENT_IS_AN_ALIAS, + ERROR_DS_CANT_MIX_MASTER_AND_REPS, + ERROR_DS_CHILDREN_EXIST, + ERROR_DS_OBJ_NOT_FOUND, + ERROR_DS_ALIASED_OBJ_MISSING, + ERROR_DS_BAD_NAME_SYNTAX, + ERROR_DS_ALIAS_POINTS_TO_ALIAS, + ERROR_DS_CANT_DEREF_ALIAS, + ERROR_DS_OUT_OF_SCOPE, + ERROR_DS_OBJECT_BEING_REMOVED, + ERROR_DS_CANT_DELETE_DSA_OBJ, + ERROR_DS_GENERIC_ERROR, + ERROR_DS_DSA_MUST_BE_INT_MASTER, + ERROR_DS_CLASS_NOT_DSA, + ERROR_DS_INSUFF_ACCESS_RIGHTS, + ERROR_DS_ILLEGAL_SUPERIOR, + ERROR_DS_ATTRIBUTE_OWNED_BY_SAM, + ERROR_DS_NAME_TOO_MANY_PARTS, + ERROR_DS_NAME_TOO_LONG, + ERROR_DS_NAME_VALUE_TOO_LONG, + ERROR_DS_NAME_UNPARSEABLE, + ERROR_DS_NAME_TYPE_UNKNOWN, + ERROR_DS_NOT_AN_OBJECT, + ERROR_DS_SEC_DESC_TOO_SHORT, + ERROR_DS_SEC_DESC_INVALID, + ERROR_DS_NO_DELETED_NAME, + ERROR_DS_SUBREF_MUST_HAVE_PARENT, + ERROR_DS_NCNAME_MUST_BE_NC, + ERROR_DS_CANT_ADD_SYSTEM_ONLY, + ERROR_DS_CLASS_MUST_BE_CONCRETE, + ERROR_DS_INVALID_DMD, + ERROR_DS_OBJ_GUID_EXISTS, + ERROR_DS_NOT_ON_BACKLINK, + ERROR_DS_NO_CROSSREF_FOR_NC, + ERROR_DS_SHUTTING_DOWN, + ERROR_DS_UNKNOWN_OPERATION, + ERROR_DS_INVALID_ROLE_OWNER, + ERROR_DS_COULDNT_CONTACT_FSMO, + ERROR_DS_CROSS_NC_DN_RENAME, + ERROR_DS_CANT_MOD_SYSTEM_ONLY, + ERROR_DS_REPLICATOR_ONLY, + ERROR_DS_OBJ_CLASS_NOT_DEFINED, + ERROR_DS_OBJ_CLASS_NOT_SUBCLASS, + ERROR_DS_NAME_REFERENCE_INVALID, + ERROR_DS_CROSS_REF_EXISTS, + ERROR_DS_CANT_DEL_MASTER_CROSSREF, + ERROR_DS_SUBTREE_NOTIFY_NOT_NC_HEAD, + ERROR_DS_NOTIFY_FILTER_TOO_COMPLEX, + ERROR_DS_DUP_RDN, + ERROR_DS_DUP_OID, + ERROR_DS_DUP_MAPI_ID, + ERROR_DS_DUP_SCHEMA_ID_GUID, + ERROR_DS_DUP_LDAP_DISPLAY_NAME, + ERROR_DS_SEMANTIC_ATT_TEST, + ERROR_DS_SYNTAX_MISMATCH, + ERROR_DS_EXISTS_IN_MUST_HAVE, + ERROR_DS_EXISTS_IN_MAY_HAVE, + ERROR_DS_NONEXISTENT_MAY_HAVE, + ERROR_DS_NONEXISTENT_MUST_HAVE, + ERROR_DS_AUX_CLS_TEST_FAIL, + ERROR_DS_NONEXISTENT_POSS_SUP, + ERROR_DS_SUB_CLS_TEST_FAIL, + ERROR_DS_BAD_RDN_ATT_ID_SYNTAX, + ERROR_DS_EXISTS_IN_AUX_CLS, + ERROR_DS_EXISTS_IN_SUB_CLS, + ERROR_DS_EXISTS_IN_POSS_SUP, + ERROR_DS_RECALCSCHEMA_FAILED, + ERROR_DS_TREE_DELETE_NOT_FINISHED, + ERROR_DS_CANT_DELETE, + ERROR_DS_ATT_SCHEMA_REQ_ID, + ERROR_DS_BAD_ATT_SCHEMA_SYNTAX, + ERROR_DS_CANT_CACHE_ATT, + ERROR_DS_CANT_CACHE_CLASS, + ERROR_DS_CANT_REMOVE_ATT_CACHE, + ERROR_DS_CANT_REMOVE_CLASS_CACHE, + ERROR_DS_CANT_RETRIEVE_DN, + ERROR_DS_MISSING_SUPREF, + ERROR_DS_CANT_RETRIEVE_INSTANCE, + ERROR_DS_CODE_INCONSISTENCY, + ERROR_DS_DATABASE_ERROR, + ERROR_DS_GOVERNSID_MISSING, + ERROR_DS_MISSING_EXPECTED_ATT, + ERROR_DS_NCNAME_MISSING_CR_REF, + ERROR_DS_SECURITY_CHECKING_ERROR, + ERROR_DS_SCHEMA_NOT_LOADED, + ERROR_DS_SCHEMA_ALLOC_FAILED, + ERROR_DS_ATT_SCHEMA_REQ_SYNTAX, + ERROR_DS_GCVERIFY_ERROR, + ERROR_DS_DRA_SCHEMA_MISMATCH, + ERROR_DS_CANT_FIND_DSA_OBJ, + ERROR_DS_CANT_FIND_EXPECTED_NC, + ERROR_DS_CANT_FIND_NC_IN_CACHE, + ERROR_DS_CANT_RETRIEVE_CHILD, + ERROR_DS_SECURITY_ILLEGAL_MODIFY, + ERROR_DS_CANT_REPLACE_HIDDEN_REC, + ERROR_DS_BAD_HIERARCHY_FILE, + ERROR_DS_BUILD_HIERARCHY_TABLE_FAILED, + ERROR_DS_CONFIG_PARAM_MISSING, + ERROR_DS_COUNTING_AB_INDICES_FAILED, + ERROR_DS_HIERARCHY_TABLE_MALLOC_FAILED, + ERROR_DS_INTERNAL_FAILURE, + ERROR_DS_UNKNOWN_ERROR, + ERROR_DS_ROOT_REQUIRES_CLASS_TOP, + ERROR_DS_REFUSING_FSMO_ROLES, + ERROR_DS_MISSING_FSMO_SETTINGS, + ERROR_DS_UNABLE_TO_SURRENDER_ROLES, + ERROR_DS_DRA_GENERIC, + ERROR_DS_DRA_INVALID_PARAMETER, + ERROR_DS_DRA_BUSY, + ERROR_DS_DRA_BAD_DN, + ERROR_DS_DRA_BAD_NC, + ERROR_DS_DRA_DN_EXISTS, + ERROR_DS_DRA_INTERNAL_ERROR, + ERROR_DS_DRA_INCONSISTENT_DIT, + ERROR_DS_DRA_CONNECTION_FAILED, + ERROR_DS_DRA_BAD_INSTANCE_TYPE, + ERROR_DS_DRA_OUT_OF_MEM, + ERROR_DS_DRA_MAIL_PROBLEM, + ERROR_DS_DRA_REF_ALREADY_EXISTS, + ERROR_DS_DRA_REF_NOT_FOUND, + ERROR_DS_DRA_OBJ_IS_REP_SOURCE, + ERROR_DS_DRA_DB_ERROR, + ERROR_DS_DRA_NO_REPLICA, + ERROR_DS_DRA_ACCESS_DENIED, + ERROR_DS_DRA_NOT_SUPPORTED, + ERROR_DS_DRA_RPC_CANCELLED, + ERROR_DS_DRA_SOURCE_DISABLED, + ERROR_DS_DRA_SINK_DISABLED, + ERROR_DS_DRA_NAME_COLLISION, + ERROR_DS_DRA_SOURCE_REINSTALLED, + ERROR_DS_DRA_MISSING_PARENT, + ERROR_DS_DRA_PREEMPTED, + ERROR_DS_DRA_ABANDON_SYNC, + ERROR_DS_DRA_SHUTDOWN, + ERROR_DS_DRA_INCOMPATIBLE_PARTIAL_SET, + ERROR_DS_DRA_SOURCE_IS_PARTIAL_REPLICA, + ERROR_DS_DRA_EXTN_CONNECTION_FAILED, + ERROR_DS_INSTALL_SCHEMA_MISMATCH, + ERROR_DS_DUP_LINK_ID, + ERROR_DS_NAME_ERROR_RESOLVING, + ERROR_DS_NAME_ERROR_NOT_FOUND, + ERROR_DS_NAME_ERROR_NOT_UNIQUE, + ERROR_DS_NAME_ERROR_NO_MAPPING, + ERROR_DS_NAME_ERROR_DOMAIN_ONLY, + ERROR_DS_NAME_ERROR_NO_SYNTACTICAL_MAPPING, + ERROR_DS_CONSTRUCTED_ATT_MOD, + ERROR_DS_WRONG_OM_OBJ_CLASS, + ERROR_DS_DRA_REPL_PENDING, + ERROR_DS_DS_REQUIRED, + ERROR_DS_INVALID_LDAP_DISPLAY_NAME, + ERROR_DS_NON_BASE_SEARCH, + ERROR_DS_CANT_RETRIEVE_ATTS, + ERROR_DS_BACKLINK_WITHOUT_LINK, + ERROR_DS_EPOCH_MISMATCH, + ERROR_DS_SRC_NAME_MISMATCH, + ERROR_DS_SRC_AND_DST_NC_IDENTICAL, + ERROR_DS_DST_NC_MISMATCH, + ERROR_DS_NOT_AUTHORITIVE_FOR_DST_NC, + ERROR_DS_SRC_GUID_MISMATCH, + ERROR_DS_CANT_MOVE_DELETED_OBJECT, + ERROR_DS_PDC_OPERATION_IN_PROGRESS, + ERROR_DS_CROSS_DOMAIN_CLEANUP_REQD, + ERROR_DS_ILLEGAL_XDOM_MOVE_OPERATION, + ERROR_DS_CANT_WITH_ACCT_GROUP_MEMBERSHPS, + ERROR_DS_NC_MUST_HAVE_NC_PARENT, + ERROR_DS_CR_IMPOSSIBLE_TO_VALIDATE, + ERROR_DS_DST_DOMAIN_NOT_NATIVE, + ERROR_DS_MISSING_INFRASTRUCTURE_CONTAINER, + ERROR_DS_CANT_MOVE_ACCOUNT_GROUP, + ERROR_DS_CANT_MOVE_RESOURCE_GROUP, + ERROR_DS_INVALID_SEARCH_FLAG, + ERROR_DS_NO_TREE_DELETE_ABOVE_NC, + ERROR_DS_COULDNT_LOCK_TREE_FOR_DELETE, + ERROR_DS_COULDNT_IDENTIFY_OBJECTS_FOR_TREE_DELETE, + ERROR_DS_SAM_INIT_FAILURE, + ERROR_DS_SENSITIVE_GROUP_VIOLATION, + ERROR_DS_CANT_MOD_PRIMARYGROUPID, + ERROR_DS_ILLEGAL_BASE_SCHEMA_MOD, + ERROR_DS_NONSAFE_SCHEMA_CHANGE, + ERROR_DS_SCHEMA_UPDATE_DISALLOWED, + ERROR_DS_CANT_CREATE_UNDER_SCHEMA, + ERROR_DS_INSTALL_NO_SRC_SCH_VERSION, + ERROR_DS_INSTALL_NO_SCH_VERSION_IN_INIFILE, + ERROR_DS_INVALID_GROUP_TYPE, + ERROR_DS_NO_NEST_GLOBALGROUP_IN_MIXEDDOMAIN, + ERROR_DS_NO_NEST_LOCALGROUP_IN_MIXEDDOMAIN, + ERROR_DS_GLOBAL_CANT_HAVE_LOCAL_MEMBER, + ERROR_DS_GLOBAL_CANT_HAVE_UNIVERSAL_MEMBER, + ERROR_DS_UNIVERSAL_CANT_HAVE_LOCAL_MEMBER, + ERROR_DS_GLOBAL_CANT_HAVE_CROSSDOMAIN_MEMBER, + ERROR_DS_LOCAL_CANT_HAVE_CROSSDOMAIN_LOCAL_MEMBER, + ERROR_DS_HAVE_PRIMARY_MEMBERS, + ERROR_DS_STRING_SD_CONVERSION_FAILED, + ERROR_DS_NAMING_MASTER_GC, + ERROR_DS_LOOKUP_FAILURE, + ERROR_DS_COULDNT_UPDATE_SPNS, + ERROR_DS_CANT_RETRIEVE_SD, + ERROR_DS_KEY_NOT_UNIQUE, + ERROR_DS_WRONG_LINKED_ATT_SYNTAX, + ERROR_DS_SAM_NEED_BOOTKEY_PASSWORD, + ERROR_DS_SAM_NEED_BOOTKEY_FLOPPY, + ERROR_DS_CANT_START, + ERROR_DS_INIT_FAILURE, + ERROR_DS_NO_PKT_PRIVACY_ON_CONNECTION, + ERROR_DS_SOURCE_DOMAIN_IN_FOREST, + ERROR_DS_DESTINATION_DOMAIN_NOT_IN_FOREST, + ERROR_DS_DESTINATION_AUDITING_NOT_ENABLED, + ERROR_DS_CANT_FIND_DC_FOR_SRC_DOMAIN, + ERROR_DS_SRC_OBJ_NOT_GROUP_OR_USER, + ERROR_DS_SRC_SID_EXISTS_IN_FOREST, + ERROR_DS_SRC_AND_DST_OBJECT_CLASS_MISMATCH, + ERROR_SAM_INIT_FAILURE, + ERROR_DS_DRA_SCHEMA_INFO_SHIP, + ERROR_DS_DRA_SCHEMA_CONFLICT, + ERROR_DS_DRA_EARLIER_SCHEMA_CONLICT, + ERROR_DS_DRA_OBJ_NC_MISMATCH, + ERROR_DS_NC_STILL_HAS_DSAS, + ERROR_DS_GC_REQUIRED, + ERROR_DS_LOCAL_MEMBER_OF_LOCAL_ONLY, + ERROR_DS_NO_FPO_IN_UNIVERSAL_GROUPS, + ERROR_DS_CANT_ADD_TO_GC, + ERROR_DS_NO_CHECKPOINT_WITH_PDC, + ERROR_DS_SOURCE_AUDITING_NOT_ENABLED, + ERROR_DS_CANT_CREATE_IN_NONDOMAIN_NC, + ERROR_DS_INVALID_NAME_FOR_SPN, + ERROR_DS_FILTER_USES_CONTRUCTED_ATTRS, + ERROR_DS_UNICODEPWD_NOT_IN_QUOTES, + ERROR_DS_MACHINE_ACCOUNT_QUOTA_EXCEEDED, + ERROR_DS_MUST_BE_RUN_ON_DST_DC, + ERROR_DS_SRC_DC_MUST_BE_SP4_OR_GREATER, + ERROR_DS_CANT_TREE_DELETE_CRITICAL_OBJ, + ERROR_DS_INIT_FAILURE_CONSOLE, + ERROR_DS_SAM_INIT_FAILURE_CONSOLE, + ERROR_DS_FOREST_VERSION_TOO_HIGH, + ERROR_DS_DOMAIN_VERSION_TOO_HIGH, + ERROR_DS_FOREST_VERSION_TOO_LOW, + ERROR_DS_DOMAIN_VERSION_TOO_LOW, + ERROR_DS_INCOMPATIBLE_VERSION, + ERROR_DS_LOW_DSA_VERSION, + ERROR_DS_NO_BEHAVIOR_VERSION_IN_MIXEDDOMAIN, + ERROR_DS_NOT_SUPPORTED_SORT_ORDER, + ERROR_DS_NAME_NOT_UNIQUE, + ERROR_DS_MACHINE_ACCOUNT_CREATED_PRENT4, + ERROR_DS_OUT_OF_VERSION_STORE, + ERROR_DS_INCOMPATIBLE_CONTROLS_USED, + ERROR_DS_NO_REF_DOMAIN, + ERROR_DS_RESERVED_LINK_ID, + ERROR_DS_LINK_ID_NOT_AVAILABLE, + ERROR_DS_AG_CANT_HAVE_UNIVERSAL_MEMBER, + ERROR_DS_MODIFYDN_DISALLOWED_BY_INSTANCE_TYPE, + ERROR_DS_NO_OBJECT_MOVE_IN_SCHEMA_NC, + ERROR_DS_MODIFYDN_DISALLOWED_BY_FLAG, + ERROR_DS_MODIFYDN_WRONG_GRANDPARENT, + ERROR_DS_NAME_ERROR_TRUST_REFERRAL, + ERROR_NOT_SUPPORTED_ON_STANDARD_SERVER, + ERROR_DS_CANT_ACCESS_REMOTE_PART_OF_AD, + ERROR_DS_CR_IMPOSSIBLE_TO_VALIDATE_V2, + ERROR_DS_THREAD_LIMIT_EXCEEDED, + ERROR_DS_NOT_CLOSEST, + ERROR_DS_CANT_DERIVE_SPN_WITHOUT_SERVER_REF, + ERROR_DS_SINGLE_USER_MODE_FAILED, + ERROR_DS_NTDSCRIPT_SYNTAX_ERROR, + ERROR_DS_NTDSCRIPT_PROCESS_ERROR, + ERROR_DS_DIFFERENT_REPL_EPOCHS, + ERROR_DS_DRS_EXTENSIONS_CHANGED, + ERROR_DS_REPLICA_SET_CHANGE_NOT_ALLOWED_ON_DISABLED_CR, + ERROR_DS_NO_MSDS_INTID, + ERROR_DS_DUP_MSDS_INTID, + ERROR_DS_EXISTS_IN_RDNATTID, + ERROR_DS_AUTHORIZATION_FAILED, + ERROR_DS_INVALID_SCRIPT, + ERROR_DS_REMOTE_CROSSREF_OP_FAILED, + ERROR_DS_CROSS_REF_BUSY, + ERROR_DS_CANT_DERIVE_SPN_FOR_DELETED_DOMAIN, + ERROR_DS_CANT_DEMOTE_WITH_WRITEABLE_NC, + ERROR_DS_DUPLICATE_ID_FOUND, + ERROR_DS_INSUFFICIENT_ATTR_TO_CREATE_OBJECT, + ERROR_DS_GROUP_CONVERSION_ERROR, + ERROR_DS_CANT_MOVE_APP_BASIC_GROUP, + ERROR_DS_CANT_MOVE_APP_QUERY_GROUP, + ERROR_DS_ROLE_NOT_VERIFIED, + ERROR_DS_WKO_CONTAINER_CANNOT_BE_SPECIAL, + ERROR_DS_DOMAIN_RENAME_IN_PROGRESS, + ERROR_DS_EXISTING_AD_CHILD_NC, // = 8613 + DNS_ERROR_RCODE_FORMAT_ERROR = 9001, + DNS_ERROR_RCODE_SERVER_FAILURE, + DNS_ERROR_RCODE_NAME_ERROR, + DNS_ERROR_RCODE_NOT_IMPLEMENTED, + DNS_ERROR_RCODE_REFUSED, + DNS_ERROR_RCODE_YXDOMAIN, + DNS_ERROR_RCODE_YXRRSET, + DNS_ERROR_RCODE_NXRRSET, + DNS_ERROR_RCODE_NOTAUTH, + DNS_ERROR_RCODE_NOTZONE, // = 9010 + DNS_ERROR_RCODE_BADSIG = 9016, + DNS_ERROR_RCODE_BADKEY, + DNS_ERROR_RCODE_BADTIME, // = 9018 + DNS_INFO_NO_RECORDS = 9501, + DNS_ERROR_BAD_PACKET, + DNS_ERROR_NO_PACKET, + DNS_ERROR_RCODE, + DNS_ERROR_UNSECURE_PACKET, // = 9505 + DNS_ERROR_INVALID_TYPE = 9551, + DNS_ERROR_INVALID_IP_ADDRESS, + DNS_ERROR_INVALID_PROPERTY, + DNS_ERROR_TRY_AGAIN_LATER, + DNS_ERROR_NOT_UNIQUE, + DNS_ERROR_NON_RFC_NAME, + DNS_STATUS_FQDN, + DNS_STATUS_DOTTED_NAME, + DNS_STATUS_SINGLE_PART_NAME, + DNS_ERROR_INVALID_NAME_CHAR, + DNS_ERROR_NUMERIC_NAME, + DNS_ERROR_NOT_ALLOWED_ON_ROOT_SERVER, + DNS_ERROR_NOT_ALLOWED_UNDER_DELEGATION, + DNS_ERROR_CANNOT_FIND_ROOT_HINTS, + DNS_ERROR_INCONSISTENT_ROOT_HINTS, // = 9565 + DNS_ERROR_ZONE_DOES_NOT_EXIST = 9601, + DNS_ERROR_NO_ZONE_INFO, + DNS_ERROR_INVALID_ZONE_OPERATION, + DNS_ERROR_ZONE_CONFIGURATION_ERROR, + DNS_ERROR_ZONE_HAS_NO_SOA_RECORD, + DNS_ERROR_ZONE_HAS_NO_NS_RECORDS, + DNS_ERROR_ZONE_LOCKED, + DNS_ERROR_ZONE_CREATION_FAILED, + DNS_ERROR_ZONE_ALREADY_EXISTS, + DNS_ERROR_AUTOZONE_ALREADY_EXISTS, + DNS_ERROR_INVALID_ZONE_TYPE, + DNS_ERROR_SECONDARY_REQUIRES_MASTER_IP, + DNS_ERROR_ZONE_NOT_SECONDARY, + DNS_ERROR_NEED_SECONDARY_ADDRESSES, + DNS_ERROR_WINS_INIT_FAILED, + DNS_ERROR_NEED_WINS_SERVERS, + DNS_ERROR_NBSTAT_INIT_FAILED, + DNS_ERROR_SOA_DELETE_INVALID, + DNS_ERROR_FORWARDER_ALREADY_EXISTS, + DNS_ERROR_ZONE_REQUIRES_MASTER_IP, + DNS_ERROR_ZONE_IS_SHUTDOWN, // = 9621 + DNS_ERROR_PRIMARY_REQUIRES_DATAFILE = 9651, + DNS_ERROR_INVALID_DATAFILE_NAME, + DNS_ERROR_DATAFILE_OPEN_FAILURE, + DNS_ERROR_FILE_WRITEBACK_FAILED, + DNS_ERROR_DATAFILE_PARSING, // = 9655 + DNS_ERROR_RECORD_DOES_NOT_EXIST = 9701, + DNS_ERROR_RECORD_FORMAT, + DNS_ERROR_NODE_CREATION_FAILED, + DNS_ERROR_UNKNOWN_RECORD_TYPE, + DNS_ERROR_RECORD_TIMED_OUT, + DNS_ERROR_NAME_NOT_IN_ZONE, + DNS_ERROR_CNAME_LOOP, + DNS_ERROR_NODE_IS_CNAME, + DNS_ERROR_CNAME_COLLISION, + DNS_ERROR_RECORD_ONLY_AT_ZONE_ROOT, + DNS_ERROR_RECORD_ALREADY_EXISTS, + DNS_ERROR_SECONDARY_DATA, + DNS_ERROR_NO_CREATE_CACHE_DATA, + DNS_ERROR_NAME_DOES_NOT_EXIST, + DNS_WARNING_PTR_CREATE_FAILED, + DNS_WARNING_DOMAIN_UNDELETED, + DNS_ERROR_DS_UNAVAILABLE, + DNS_ERROR_DS_ZONE_ALREADY_EXISTS, + DNS_ERROR_NO_BOOTFILE_IF_DS_ZONE, // = 9719 + DNS_INFO_AXFR_COMPLETE = 9751, + DNS_ERROR_AXFR, + DNS_INFO_ADDED_LOCAL_WINS, // = 9753 + DNS_STATUS_CONTINUE_NEEDED = 9801, + DNS_ERROR_NO_TCPIP = 9851, + DNS_ERROR_NO_DNS_SERVERS, // = 9852 + DNS_ERROR_DP_DOES_NOT_EXIST = 9901, + DNS_ERROR_DP_ALREADY_EXISTS, + DNS_ERROR_DP_NOT_ENLISTED, + DNS_ERROR_DP_ALREADY_ENLISTED, + DNS_ERROR_DP_NOT_AVAILABLE, // = 9905 + +/+ already in winsock2.d defined! + + WSABASEERR = 10000, + WSAEINTR = 10004, + WSAEBADF = 10009, + WSAEACCES = 10013, + WSAEFAULT, // = 10014 + WSAEINVAL = 10022, + WSAEMFILE = 10024, + WSAEWOULDBLOCK = 10035, + WSAEINPROGRESS, + WSAEALREADY, + WSAENOTSOCK, + WSAEDESTADDRREQ, + WSAEMSGSIZE, + WSAEPROTOTYPE, + WSAENOPROTOOPT, + WSAEPROTONOSUPPORT, + WSAESOCKTNOSUPPORT, + WSAEOPNOTSUPP, + WSAEPFNOSUPPORT, + WSAEAFNOSUPPORT, + WSAEADDRINUSE, + WSAEADDRNOTAVAIL, + WSAENETDOWN, + WSAENETUNREACH, + WSAENETRESET, + WSAECONNABORTED, + WSAECONNRESET, + WSAENOBUFS, + WSAEISCONN, + WSAENOTCONN, + WSAESHUTDOWN, + WSAETOOMANYREFS, + WSAETIMEDOUT, + WSAECONNREFUSED, + WSAELOOP, + WSAENAMETOOLONG, + WSAEHOSTDOWN, + WSAEHOSTUNREACH, + WSAENOTEMPTY, + WSAEPROCLIM, + WSAEUSERS, + WSAEDQUOT, + WSAESTALE, + WSAEREMOTE, // = 10071 + WSASYSNOTREADY = 10091, + WSAVERNOTSUPPORTED, + WSANOTINITIALISED, // = 10093 + WSAEDISCON = 10101, + WSAENOMORE, + WSAECANCELLED, + WSAEINVALIDPROCTABLE, + WSAEINVALIDPROVIDER, + WSAEPROVIDERFAILEDINIT, + WSASYSCALLFAILURE, + WSASERVICE_NOT_FOUND, + WSATYPE_NOT_FOUND, + WSA_E_NO_MORE, + WSA_E_CANCELLED, + WSAEREFUSED, // = 10112 + WSAHOST_NOT_FOUND = 11001, + WSATRY_AGAIN, + WSANO_RECOVERY, + WSANO_DATA, + WSA_QOS_RECEIVERS, + WSA_QOS_SENDERS, + WSA_QOS_NO_SENDERS, + WSA_QOS_NO_RECEIVERS, + WSA_QOS_REQUEST_CONFIRMED, + WSA_QOS_ADMISSION_FAILURE, + WSA_QOS_POLICY_FAILURE, + WSA_QOS_BAD_STYLE, + WSA_QOS_BAD_OBJECT, + WSA_QOS_TRAFFIC_CTRL_ERROR, + WSA_QOS_GENERIC_ERROR, + WSA_QOS_ESERVICETYPE, + WSA_QOS_EFLOWSPEC, + WSA_QOS_EPROVSPECBUF, + WSA_QOS_EFILTERSTYLE, + WSA_QOS_EFILTERTYPE, + WSA_QOS_EFILTERCOUNT, + WSA_QOS_EOBJLENGTH, + WSA_QOS_EFLOWCOUNT, + WSA_QOS_EUNKNOWNPSOBJ, + WSA_QOS_EPOLICYOBJ, + WSA_QOS_EFLOWDESC, + WSA_QOS_EPSFLOWSPEC, + WSA_QOS_EPSFILTERSPEC, + WSA_QOS_ESDMODEOBJ, + WSA_QOS_ESHAPERATEOBJ, + WSA_QOS_RESERVED_PETYPE, // = 11031 + ++/ + + ERROR_IPSEC_QM_POLICY_EXISTS = 13000, + ERROR_IPSEC_QM_POLICY_NOT_FOUND, + ERROR_IPSEC_QM_POLICY_IN_USE, + ERROR_IPSEC_MM_POLICY_EXISTS, + ERROR_IPSEC_MM_POLICY_NOT_FOUND, + ERROR_IPSEC_MM_POLICY_IN_USE, + ERROR_IPSEC_MM_FILTER_EXISTS, + ERROR_IPSEC_MM_FILTER_NOT_FOUND, + ERROR_IPSEC_TRANSPORT_FILTER_EXISTS, + ERROR_IPSEC_TRANSPORT_FILTER_NOT_FOUND, + ERROR_IPSEC_MM_AUTH_EXISTS, + ERROR_IPSEC_MM_AUTH_NOT_FOUND, + ERROR_IPSEC_MM_AUTH_IN_USE, + ERROR_IPSEC_DEFAULT_MM_POLICY_NOT_FOUND, + ERROR_IPSEC_DEFAULT_MM_AUTH_NOT_FOUND, + ERROR_IPSEC_DEFAULT_QM_POLICY_NOT_FOUND, + ERROR_IPSEC_TUNNEL_FILTER_EXISTS, + ERROR_IPSEC_TUNNEL_FILTER_NOT_FOUND, + ERROR_IPSEC_MM_FILTER_PENDING_DELETION, + ERROR_IPSEC_TRANSPORT_FILTER_PENDING_DELETION, + ERROR_IPSEC_TUNNEL_FILTER_PENDING_DELETION, + ERROR_IPSEC_MM_POLICY_PENDING_DELETION, + ERROR_IPSEC_MM_AUTH_PENDING_DELETION, + ERROR_IPSEC_QM_POLICY_PENDING_DELETION, + WARNING_IPSEC_MM_POLICY_PRUNED, + WARNING_IPSEC_QM_POLICY_PRUNED, // = 13025 + ERROR_IPSEC_IKE_AUTH_FAIL = 13801, + ERROR_IPSEC_IKE_ATTRIB_FAIL, + ERROR_IPSEC_IKE_NEGOTIATION_PENDING, + ERROR_IPSEC_IKE_GENERAL_PROCESSING_ERROR, + ERROR_IPSEC_IKE_TIMED_OUT, + ERROR_IPSEC_IKE_NO_CERT, + ERROR_IPSEC_IKE_SA_DELETED, + ERROR_IPSEC_IKE_SA_REAPED, + ERROR_IPSEC_IKE_MM_ACQUIRE_DROP, + ERROR_IPSEC_IKE_QM_ACQUIRE_DROP, + ERROR_IPSEC_IKE_QUEUE_DROP_MM, + ERROR_IPSEC_IKE_QUEUE_DROP_NO_MM, + ERROR_IPSEC_IKE_DROP_NO_RESPONSE, + ERROR_IPSEC_IKE_MM_DELAY_DROP, + ERROR_IPSEC_IKE_QM_DELAY_DROP, + ERROR_IPSEC_IKE_ERROR, + ERROR_IPSEC_IKE_CRL_FAILED, + ERROR_IPSEC_IKE_INVALID_KEY_USAGE, + ERROR_IPSEC_IKE_INVALID_CERT_TYPE, + ERROR_IPSEC_IKE_NO_PRIVATE_KEY, // = 13820 + ERROR_IPSEC_IKE_DH_FAIL = 13822, + ERROR_IPSEC_IKE_INVALID_HEADER = 13824, + ERROR_IPSEC_IKE_NO_POLICY, + ERROR_IPSEC_IKE_INVALID_SIGNATURE, + ERROR_IPSEC_IKE_KERBEROS_ERROR, + ERROR_IPSEC_IKE_NO_PUBLIC_KEY, + ERROR_IPSEC_IKE_PROCESS_ERR, + ERROR_IPSEC_IKE_PROCESS_ERR_SA, + ERROR_IPSEC_IKE_PROCESS_ERR_PROP, + ERROR_IPSEC_IKE_PROCESS_ERR_TRANS, + ERROR_IPSEC_IKE_PROCESS_ERR_KE, + ERROR_IPSEC_IKE_PROCESS_ERR_ID, + ERROR_IPSEC_IKE_PROCESS_ERR_CERT, + ERROR_IPSEC_IKE_PROCESS_ERR_CERT_REQ, + ERROR_IPSEC_IKE_PROCESS_ERR_HASH, + ERROR_IPSEC_IKE_PROCESS_ERR_SIG, + ERROR_IPSEC_IKE_PROCESS_ERR_NONCE, + ERROR_IPSEC_IKE_PROCESS_ERR_NOTIFY, + ERROR_IPSEC_IKE_PROCESS_ERR_DELETE, + ERROR_IPSEC_IKE_PROCESS_ERR_VENDOR, + ERROR_IPSEC_IKE_INVALID_PAYLOAD, + ERROR_IPSEC_IKE_LOAD_SOFT_SA, + ERROR_IPSEC_IKE_SOFT_SA_TORN_DOWN, + ERROR_IPSEC_IKE_INVALID_COOKIE, + ERROR_IPSEC_IKE_NO_PEER_CERT, + ERROR_IPSEC_IKE_PEER_CRL_FAILED, + ERROR_IPSEC_IKE_POLICY_CHANGE, + ERROR_IPSEC_IKE_NO_MM_POLICY, + ERROR_IPSEC_IKE_NOTCBPRIV, + ERROR_IPSEC_IKE_SECLOADFAIL, + ERROR_IPSEC_IKE_FAILSSPINIT, + ERROR_IPSEC_IKE_FAILQUERYSSP, + ERROR_IPSEC_IKE_SRVACQFAIL, + ERROR_IPSEC_IKE_SRVQUERYCRED, + ERROR_IPSEC_IKE_GETSPIFAIL, + ERROR_IPSEC_IKE_INVALID_FILTER, + ERROR_IPSEC_IKE_OUT_OF_MEMORY, + ERROR_IPSEC_IKE_ADD_UPDATE_KEY_FAILED, + ERROR_IPSEC_IKE_INVALID_POLICY, + ERROR_IPSEC_IKE_UNKNOWN_DOI, + ERROR_IPSEC_IKE_INVALID_SITUATION, + ERROR_IPSEC_IKE_DH_FAILURE, + ERROR_IPSEC_IKE_INVALID_GROUP, + ERROR_IPSEC_IKE_ENCRYPT, + ERROR_IPSEC_IKE_DECRYPT, + ERROR_IPSEC_IKE_POLICY_MATCH, + ERROR_IPSEC_IKE_UNSUPPORTED_ID, + ERROR_IPSEC_IKE_INVALID_HASH, + ERROR_IPSEC_IKE_INVALID_HASH_ALG, + ERROR_IPSEC_IKE_INVALID_HASH_SIZE, + ERROR_IPSEC_IKE_INVALID_ENCRYPT_ALG, + ERROR_IPSEC_IKE_INVALID_AUTH_ALG, + ERROR_IPSEC_IKE_INVALID_SIG, + ERROR_IPSEC_IKE_LOAD_FAILED, + ERROR_IPSEC_IKE_RPC_DELETE, + ERROR_IPSEC_IKE_BENIGN_REINIT, + ERROR_IPSEC_IKE_INVALID_RESPONDER_LIFETIME_NOTIFY, // = 13879 + ERROR_IPSEC_IKE_INVALID_CERT_KEYLEN = 13881, + ERROR_IPSEC_IKE_MM_LIMIT, + ERROR_IPSEC_IKE_NEGOTIATION_DISABLED, + ERROR_IPSEC_IKE_NEG_STATUS_END, + ERROR_SXS_SECTION_NOT_FOUND, + ERROR_SXS_CANT_GEN_ACTCTX, + ERROR_SXS_INVALID_ACTCTXDATA_FORMAT, + ERROR_SXS_ASSEMBLY_NOT_FOUND, + ERROR_SXS_MANIFEST_FORMAT_ERROR, + ERROR_SXS_MANIFEST_PARSE_ERROR, + ERROR_SXS_ACTIVATION_CONTEXT_DISABLED, + ERROR_SXS_KEY_NOT_FOUND, + ERROR_SXS_VERSION_CONFLICT, + ERROR_SXS_WRONG_SECTION_TYPE, + ERROR_SXS_THREAD_QUERIES_DISABLED, + ERROR_SXS_PROCESS_DEFAULT_ALREADY_SET, + ERROR_SXS_UNKNOWN_ENCODING_GROUP, + ERROR_SXS_UNKNOWN_ENCODING, + ERROR_SXS_INVALID_XML_NAMESPACE_URI, + ERROR_SXS_ROOT_MANIFEST_DEPENDENCY_NOT_INSTALLED, + ERROR_SXS_LEAF_MANIFEST_DEPENDENCY_NOT_INSTALLED, + ERROR_SXS_INVALID_ASSEMBLY_IDENTITY_ATTRIBUTE, + ERROR_SXS_MANIFEST_MISSING_REQUIRED_DEFAULT_NAMESPACE, + ERROR_SXS_MANIFEST_INVALID_REQUIRED_DEFAULT_NAMESPACE, + ERROR_SXS_PRIVATE_MANIFEST_CROSS_PATH_WITH_REPARSE_POINT, + ERROR_SXS_DUPLICATE_DLL_NAME, + ERROR_SXS_DUPLICATE_WINDOWCLASS_NAME, + ERROR_SXS_DUPLICATE_CLSID, + ERROR_SXS_DUPLICATE_IID, + ERROR_SXS_DUPLICATE_TLBID, + ERROR_SXS_DUPLICATE_PROGID, + ERROR_SXS_DUPLICATE_ASSEMBLY_NAME, + ERROR_SXS_FILE_HASH_MISMATCH, + ERROR_SXS_POLICY_PARSE_ERROR, + ERROR_SXS_XML_E_MISSINGQUOTE, + ERROR_SXS_XML_E_COMMENTSYNTAX, + ERROR_SXS_XML_E_BADSTARTNAMECHAR, + ERROR_SXS_XML_E_BADNAMECHAR, + ERROR_SXS_XML_E_BADCHARINSTRING, + ERROR_SXS_XML_E_XMLDECLSYNTAX, + ERROR_SXS_XML_E_BADCHARDATA, + ERROR_SXS_XML_E_MISSINGWHITESPACE, + ERROR_SXS_XML_E_EXPECTINGTAGEND, + ERROR_SXS_XML_E_MISSINGSEMICOLON, + ERROR_SXS_XML_E_UNBALANCEDPAREN, + ERROR_SXS_XML_E_INTERNALERROR, + ERROR_SXS_XML_E_UNEXPECTED_WHITESPACE, + ERROR_SXS_XML_E_INCOMPLETE_ENCODING, + ERROR_SXS_XML_E_MISSING_PAREN, + ERROR_SXS_XML_E_EXPECTINGCLOSEQUOTE, + ERROR_SXS_XML_E_MULTIPLE_COLONS, + ERROR_SXS_XML_E_INVALID_DECIMAL, + ERROR_SXS_XML_E_INVALID_HEXIDECIMAL, + ERROR_SXS_XML_E_INVALID_UNICODE, + ERROR_SXS_XML_E_WHITESPACEORQUESTIONMARK, + ERROR_SXS_XML_E_UNEXPECTEDENDTAG, + ERROR_SXS_XML_E_UNCLOSEDTAG, + ERROR_SXS_XML_E_DUPLICATEATTRIBUTE, + ERROR_SXS_XML_E_MULTIPLEROOTS, + ERROR_SXS_XML_E_INVALIDATROOTLEVEL, + ERROR_SXS_XML_E_BADXMLDECL, + ERROR_SXS_XML_E_MISSINGROOT, + ERROR_SXS_XML_E_UNEXPECTEDEOF, + ERROR_SXS_XML_E_BADPEREFINSUBSET, + ERROR_SXS_XML_E_UNCLOSEDSTARTTAG, + ERROR_SXS_XML_E_UNCLOSEDENDTAG, + ERROR_SXS_XML_E_UNCLOSEDSTRING, + ERROR_SXS_XML_E_UNCLOSEDCOMMENT, + ERROR_SXS_XML_E_UNCLOSEDDECL, + ERROR_SXS_XML_E_UNCLOSEDCDATA, + ERROR_SXS_XML_E_RESERVEDNAMESPACE, + ERROR_SXS_XML_E_INVALIDENCODING, + ERROR_SXS_XML_E_INVALIDSWITCH, + ERROR_SXS_XML_E_BADXMLCASE, + ERROR_SXS_XML_E_INVALID_STANDALONE, + ERROR_SXS_XML_E_UNEXPECTED_STANDALONE, + ERROR_SXS_XML_E_INVALID_VERSION, + ERROR_SXS_XML_E_MISSINGEQUALS, + ERROR_SXS_PROTECTION_RECOVERY_FAILED, + ERROR_SXS_PROTECTION_PUBLIC_KEY_TOO_SHORT, + ERROR_SXS_PROTECTION_CATALOG_NOT_VALID, + ERROR_SXS_UNTRANSLATABLE_HRESULT, + ERROR_SXS_PROTECTION_CATALOG_FILE_MISSING, + ERROR_SXS_MISSING_ASSEMBLY_IDENTITY_ATTRIBUTE, + ERROR_SXS_INVALID_ASSEMBLY_IDENTITY_ATTRIBUTE_NAME // = 14080 +} + +enum : HRESULT { + S_OK = 0x00000000, + S_FALSE = 0x00000001, + + NOERROR = 0x00000000, + + E_PENDING = 0x8000000A, + E_NOTIMPL = 0x80004001, + E_NOINTERFACE = 0x80004002, + E_POINTER = 0x80004003, + E_ABORT = 0x80004004, + E_FAIL = 0x80004005, + E_ACCESSDENIED = 0x80070005, + E_HANDLE = 0x80070006, + E_OUTOFMEMORY = 0x8007000E, + E_INVALIDARG = 0x80070057, + E_UNEXPECTED = 0x8000FFFF, + + CO_E_INIT_TLS = 0x80004006, + CO_E_INIT_SHARED_ALLOCATOR = 0x80004007, + CO_E_INIT_MEMORY_ALLOCATOR = 0x80004008, + CO_E_INIT_CLASS_CACHE = 0x80004009, + CO_E_INIT_RPC_CHANNEL = 0x8000400A, + CO_E_INIT_TLS_SET_CHANNEL_CONTROL = 0x8000400B, + CO_E_INIT_TLS_CHANNEL_CONTROL = 0x8000400C, + CO_E_INIT_UNACCEPTED_USER_ALLOCATOR = 0x8000400D, + CO_E_INIT_SCM_MUTEX_EXISTS = 0x8000400E, + CO_E_INIT_SCM_FILE_MAPPING_EXISTS = 0x8000400F, + CO_E_INIT_SCM_MAP_VIEW_OF_FILE = 0x80004010, + CO_E_INIT_SCM_EXEC_FAILURE = 0x80004011, + CO_E_INIT_ONLY_SINGLE_THREADED = 0x80004012, + + RPC_E_CALL_REJECTED = 0x80010001, + RPC_E_CALL_CANCELED = 0x80010002, + RPC_E_CANTPOST_INSENDCALL = 0x80010003, + RPC_E_CANTCALLOUT_INASYNCCALL = 0x80010004, + RPC_E_CANTCALLOUT_INEXTERNALCALL = 0x80010005, + RPC_E_CONNECTION_TERMINATED = 0x80010006, + RPC_E_SERVER_DIED = 0x80010007, + RPC_E_CLIENT_DIED = 0x80010008, + RPC_E_INVALID_DATAPACKET = 0x80010009, + RPC_E_CANTTRANSMIT_CALL = 0x8001000A, + RPC_E_CLIENT_CANTMARSHAL_DATA = 0x8001000B, + RPC_E_CLIENT_CANTUNMARSHAL_DATA = 0x8001000C, + RPC_E_SERVER_CANTMARSHAL_DATA = 0x8001000D, + RPC_E_SERVER_CANTUNMARSHAL_DATA = 0x8001000E, + RPC_E_INVALID_DATA = 0x8001000F, + RPC_E_INVALID_PARAMETER = 0x80010010, + RPC_E_CANTCALLOUT_AGAIN = 0x80010011, + RPC_E_SERVER_DIED_DNE = 0x80010012, + RPC_E_SYS_CALL_FAILED = 0x80010100, + RPC_E_OUT_OF_RESOURCES = 0x80010101, + RPC_E_ATTEMPTED_MULTITHREAD = 0x80010102, + RPC_E_NOT_REGISTERED = 0x80010103, + RPC_E_FAULT = 0x80010104, + RPC_E_SERVERFAULT = 0x80010105, + RPC_E_CHANGED_MODE = 0x80010106, + RPC_E_INVALIDMETHOD = 0x80010107, + RPC_E_DISCONNECTED = 0x80010108, + RPC_E_RETRY = 0x80010109, + RPC_E_SERVERCALL_RETRYLATER = 0x8001010A, + RPC_E_SERVERCALL_REJECTED = 0x8001010B, + RPC_E_INVALID_CALLDATA = 0x8001010C, + RPC_E_CANTCALLOUT_ININPUTSYNCCALL = 0x8001010D, + RPC_E_WRONG_THREAD = 0x8001010E, + RPC_E_THREAD_NOT_INIT = 0x8001010F, + RPC_E_UNEXPECTED = 0x8001FFFF, + + DISP_E_UNKNOWNINTERFACE = 0x80020001, + DISP_E_MEMBERNOTFOUND = 0x80020003, + DISP_E_PARAMNOTFOUND = 0x80020004, + DISP_E_TYPEMISMATCH = 0x80020005, + DISP_E_UNKNOWNNAME = 0x80020006, + DISP_E_NONAMEDARGS = 0x80020007, + DISP_E_BADVARTYPE = 0x80020008, + DISP_E_EXCEPTION = 0x80020009, + DISP_E_OVERFLOW = 0x8002000A, + DISP_E_BADINDEX = 0x8002000B, + DISP_E_UNKNOWNLCID = 0x8002000C, + DISP_E_ARRAYISLOCKED = 0x8002000D, + DISP_E_BADPARAMCOUNT = 0x8002000E, + DISP_E_PARAMNOTOPTIONAL = 0x8002000F, + DISP_E_BADCALLEE = 0x80020010, + DISP_E_NOTACOLLECTION = 0x80020011, + DISP_E_DIVBYZERO = 0x80020012, + + TYPE_E_BUFFERTOOSMALL = 0x80028016, + TYPE_E_INVDATAREAD = 0x80028018, + TYPE_E_UNSUPFORMAT = 0x80028019, + TYPE_E_REGISTRYACCESS = 0x8002801C, + TYPE_E_LIBNOTREGISTERED = 0x8002801D, + TYPE_E_UNDEFINEDTYPE = 0x80028027, + TYPE_E_QUALIFIEDNAMEDISALLOWED = 0x80028028, + TYPE_E_INVALIDSTATE = 0x80028029, + TYPE_E_WRONGTYPEKIND = 0x8002802A, + TYPE_E_ELEMENTNOTFOUND = 0x8002802B, + TYPE_E_AMBIGUOUSNAME = 0x8002802C, + TYPE_E_NAMECONFLICT = 0x8002802D, + TYPE_E_UNKNOWNLCID = 0x8002802E, + TYPE_E_DLLFUNCTIONNOTFOUND = 0x8002802F, + TYPE_E_BADMODULEKIND = 0x800288BD, + TYPE_E_SIZETOOBIG = 0x800288C5, + TYPE_E_DUPLICATEID = 0x800288C6, + TYPE_E_INVALIDID = 0x800288CF, + TYPE_E_TYPEMISMATCH = 0x80028CA0, + TYPE_E_OUTOFBOUNDS = 0x80028CA1, + TYPE_E_IOERROR = 0x80028CA2, + TYPE_E_CANTCREATETMPFILE = 0x80028CA3, + TYPE_E_CANTLOADLIBRARY = 0x80029C4A, + TYPE_E_INCONSISTENTPROPFUNCS = 0x80029C83, + TYPE_E_CIRCULARTYPE = 0x80029C84, + + STG_E_INVALIDFUNCTION = 0x80030001, + STG_E_FILENOTFOUND = 0x80030002, + STG_E_PATHNOTFOUND = 0x80030003, + STG_E_TOOMANYOPENFILES = 0x80030004, + STG_E_ACCESSDENIED = 0x80030005, + STG_E_INVALIDHANDLE = 0x80030006, + STG_E_INSUFFICIENTMEMORY = 0x80030008, + STG_E_INVALIDPOINTER = 0x80030009, + STG_E_NOMOREFILES = 0x80030012, + STG_E_DISKISWRITEPROTECTED = 0x80030013, + STG_E_SEEKERROR = 0x80030019, + STG_E_WRITEFAULT = 0x8003001D, + STG_E_READFAULT = 0x8003001E, + STG_E_SHAREVIOLATION = 0x80030020, + STG_E_LOCKVIOLATION = 0x80030021, + STG_E_FILEALREADYEXISTS = 0x80030050, + STG_E_INVALIDPARAMETER = 0x80030057, + STG_E_MEDIUMFULL = 0x80030070, + STG_E_ABNORMALAPIEXIT = 0x800300FA, + STG_E_INVALIDHEADER = 0x800300FB, + STG_E_INVALIDNAME = 0x800300FC, + STG_E_UNKNOWN = 0x800300FD, + STG_E_UNIMPLEMENTEDFUNCTION = 0x800300FE, + STG_E_INVALIDFLAG = 0x800300FF, + STG_E_INUSE = 0x80030100, + STG_E_NOTCURRENT = 0x80030101, + STG_E_REVERTED = 0x80030102, + STG_E_CANTSAVE = 0x80030103, + STG_E_OLDFORMAT = 0x80030104, + STG_E_OLDDLL = 0x80030105, + STG_E_SHAREREQUIRED = 0x80030106, + STG_E_NOTFILEBASEDSTORAGE = 0x80030107, + STG_E_EXTANTMARSHALLINGS = 0x80030108, + STG_S_CONVERTED = 0x00030200, + + OLE_E_FIRST = 0x80040000, + OLE_S_FIRST = 0x00040000, + OLE_E_OLEVERB = 0x80040000, + OLE_S_USEREG = 0x00040000, + OLE_E_ADVF = 0x80040001, + OLE_S_STATIC = 0x00040001, + OLE_E_ENUM_NOMORE = 0x80040002, + OLE_S_MAC_CLIPFORMAT = 0x00040002, + OLE_E_ADVISENOTSUPPORTED = 0x80040003, + OLE_E_NOCONNECTION = 0x80040004, + OLE_E_NOTRUNNING = 0x80040005, + OLE_E_NOCACHE = 0x80040006, + OLE_E_BLANK = 0x80040007, + OLE_E_CLASSDIFF = 0x80040008, + OLE_E_CANT_GETMONIKER = 0x80040009, + OLE_E_CANT_BINDTOSOURCE = 0x8004000A, + OLE_E_STATIC = 0x8004000B, + OLE_E_PROMPTSAVECANCELLED = 0x8004000C, + OLE_E_INVALIDRECT = 0x8004000D, + OLE_E_WRONGCOMPOBJ = 0x8004000E, + OLE_E_INVALIDHWND = 0x8004000F, + OLE_E_NOT_INPLACEACTIVE = 0x80040010, + OLE_E_CANTCONVERT = 0x80040011, + OLE_E_NOSTORAGE = 0x80040012, + + DV_E_FORMATETC = 0x80040064, + DV_E_DVTARGETDEVICE = 0x80040065, + DV_E_STGMEDIUM = 0x80040066, + DV_E_STATDATA = 0x80040067, + DV_E_LINDEX = 0x80040068, + DV_E_TYMED = 0x80040069, + DV_E_CLIPFORMAT = 0x8004006A, + DV_E_DVASPECT = 0x8004006B, + DV_E_DVTARGETDEVICE_SIZE = 0x8004006C, + DV_E_NOIVIEWOBJECT = 0x8004006D, + + OLE_E_LAST = 0x800400FF, + OLE_S_LAST = 0x000400FF, + DRAGDROP_E_FIRST = 0x80040100, + DRAGDROP_S_FIRST = 0x00040100, + DRAGDROP_E_NOTREGISTERED = 0x80040100, + DRAGDROP_S_DROP = 0x00040100, + DRAGDROP_E_ALREADYREGISTERED = 0x80040101, + DRAGDROP_S_CANCEL = 0x00040101, + DRAGDROP_E_INVALIDHWND = 0x80040102, + DRAGDROP_S_USEDEFAULTCURSORS = 0x00040102, + DRAGDROP_E_LAST = 0x8004010F, + DRAGDROP_S_LAST = 0x0004010F, + CLASSFACTORY_E_FIRST = 0x80040110, + CLASSFACTORY_S_FIRST = 0x00040110, + CLASS_E_NOAGGREGATION = 0x80040110, + CLASS_E_CLASSNOTAVAILABLE = 0x80040111, + CLASSFACTORY_E_LAST = 0x8004011F, + CLASSFACTORY_S_LAST = 0x0004011F, + MARSHAL_E_FIRST = 0x80040120, + MARSHAL_S_FIRST = 0x00040120, + MARSHAL_E_LAST = 0x8004012F, + MARSHAL_S_LAST = 0x0004012F, + DATA_E_FIRST = 0x80040130, + DATA_S_FIRST = 0x00040130, + DATA_S_SAMEFORMATETC = 0x00040130, + DATA_E_LAST = 0x8004013F, + DATA_S_LAST = 0x0004013F, + VIEW_E_FIRST = 0x80040140, + VIEW_S_FIRST = 0x00040140, + VIEW_E_DRAW = 0x80040140, + VIEW_S_ALREADY_FROZEN = 0x00040140, + VIEW_E_LAST = 0x8004014F, + VIEW_S_LAST = 0x0004014F, + REGDB_E_FIRST = 0x80040150, + REGDB_S_FIRST = 0x00040150, + REGDB_E_READREGDB = 0x80040150, + REGDB_E_WRITEREGDB = 0x80040151, + REGDB_E_KEYMISSING = 0x80040152, + REGDB_E_INVALIDVALUE = 0x80040153, + REGDB_E_CLASSNOTREG = 0x80040154, + REGDB_E_IIDNOTREG = 0x80040155, + REGDB_E_LAST = 0x8004015F, + REGDB_S_LAST = 0x0004015F, + CACHE_E_FIRST = 0x80040170, + CACHE_S_FIRST = 0x00040170, + CACHE_E_NOCACHE_UPDATED = 0x80040170, + CACHE_S_FORMATETC_NOTSUPPORTED = 0x00040170, + CACHE_S_SAMECACHE = 0x00040171, + CACHE_S_SOMECACHES_NOTUPDATED = 0x00040172, + CACHE_E_LAST = 0x8004017F, + CACHE_S_LAST = 0x0004017F, + OLEOBJ_E_FIRST = 0x80040180, + OLEOBJ_S_FIRST = 0x00040180, + OLEOBJ_E_NOVERBS = 0x80040180, + OLEOBJ_S_INVALIDVERB = 0x00040180, + OLEOBJ_E_INVALIDVERB = 0x80040181, + OLEOBJ_S_CANNOT_DOVERB_NOW = 0x00040181, + OLEOBJ_S_INVALIDHWND = 0x00040182, + OLEOBJ_E_LAST = 0x8004018F, + OLEOBJ_S_LAST = 0x0004018F, + CLIENTSITE_E_FIRST = 0x80040190, + CLIENTSITE_S_FIRST = 0x00040190, + CLIENTSITE_E_LAST = 0x8004019F, + CLIENTSITE_S_LAST = 0x0004019F, + INPLACE_E_NOTUNDOABLE = 0x800401A0, + INPLACE_E_FIRST = 0x800401A0, + INPLACE_S_FIRST = 0x000401A0, + INPLACE_S_TRUNCATED = 0x000401A0, + INPLACE_E_NOTOOLSPACE = 0x800401A1, + INPLACE_E_LAST = 0x800401AF, + INPLACE_S_LAST = 0x000401AF, + ENUM_E_FIRST = 0x800401B0, + ENUM_S_FIRST = 0x000401B0, + ENUM_E_LAST = 0x800401BF, + ENUM_S_LAST = 0x000401BF, + CONVERT10_E_FIRST = 0x800401C0, + CONVERT10_S_FIRST = 0x000401C0, + CONVERT10_E_OLESTREAM_GET = 0x800401C0, + CONVERT10_S_NO_PRESENTATION = 0x000401C0, + CONVERT10_E_OLESTREAM_PUT = 0x800401C1, + CONVERT10_E_OLESTREAM_FMT = 0x800401C2, + CONVERT10_E_OLESTREAM_BITMAP_TO_DIB = 0x800401C3, + CONVERT10_E_STG_FMT = 0x800401C4, + CONVERT10_E_STG_NO_STD_STREAM = 0x800401C5, + CONVERT10_E_STG_DIB_TO_BITMAP = 0x800401C6, + CONVERT10_E_LAST = 0x800401CF, + CONVERT10_S_LAST = 0x000401CF, + CLIPBRD_E_FIRST = 0x800401D0, + CLIPBRD_S_FIRST = 0x000401D0, + CLIPBRD_E_CANT_OPEN = 0x800401D0, + CLIPBRD_E_CANT_EMPTY = 0x800401D1, + CLIPBRD_E_CANT_SET = 0x800401D2, + CLIPBRD_E_BAD_DATA = 0x800401D3, + CLIPBRD_E_CANT_CLOSE = 0x800401D4, + CLIPBRD_E_LAST = 0x800401DF, + CLIPBRD_S_LAST = 0x000401DF, + MK_E_FIRST = 0x800401E0, + MK_S_FIRST = 0x000401E0, + MK_E_CONNECTMANUALLY = 0x800401E0, + MK_E_EXCEEDEDDEADLINE = 0x800401E1, + MK_E_NEEDGENERIC = 0x800401E2, + MK_S_REDUCED_TO_SELF = 0x000401E2, + MK_E_UNAVAILABLE = 0x800401E3, + MK_E_SYNTAX = 0x800401E4, + MK_S_ME = 0x000401E4, + MK_E_NOOBJECT = 0x800401E5, + MK_S_HIM = 0x000401E5, + MK_E_INVALIDEXTENSION = 0x800401E6, + MK_S_US = 0x000401E6, + MK_E_INTERMEDIATEINTERFACENOTSUPPORTED = 0x800401E7, + MK_S_MONIKERALREADYREGISTERED = 0x000401E7, + MK_E_NOTBINDABLE = 0x800401E8, + MK_E_NOTBOUND = 0x800401E9, + MK_E_CANTOPENFILE = 0x800401EA, + MK_E_MUSTBOTHERUSER = 0x800401EB, + MK_E_NOINVERSE = 0x800401EC, + MK_E_NOSTORAGE = 0x800401ED, + MK_E_NOPREFIX = 0x800401EE, + MK_E_LAST = 0x800401EF, + MK_S_LAST = 0x000401EF, + MK_E_ENUMERATION_FAILED = 0x800401EF, + CO_E_FIRST = 0x800401F0, + CO_S_FIRST = 0x000401F0, + CO_E_NOTINITIALIZED = 0x800401F0, + CO_E_ALREADYINITIALIZED = 0x800401F1, + CO_E_CANTDETERMINECLASS = 0x800401F2, + CO_E_CLASSSTRING = 0x800401F3, + CO_E_IIDSTRING = 0x800401F4, + CO_E_APPNOTFOUND = 0x800401F5, + CO_E_APPSINGLEUSE = 0x800401F6, + CO_E_ERRORINAPP = 0x800401F7, + CO_E_DLLNOTFOUND = 0x800401F8, + CO_E_ERRORINDLL = 0x800401F9, + CO_E_WRONGOSFORAPP = 0x800401FA, + CO_E_OBJNOTREG = 0x800401FB, + CO_E_OBJISREG = 0x800401FC, + CO_E_OBJNOTCONNECTED = 0x800401FD, + CO_E_APPDIDNTREG = 0x800401FE, + CO_E_LAST = 0x800401FF, + CO_S_LAST = 0x000401FF, + CO_E_RELEASED = 0x800401FF, + + CO_E_CLASS_CREATE_FAILED = 0x80080001, + CO_E_SCM_ERROR = 0x80080002, + CO_E_SCM_RPC_FAILURE = 0x80080003, + CO_E_BAD_PATH = 0x80080004, + CO_E_SERVER_EXEC_FAILURE = 0x80080005, + CO_E_OBJSRV_RPC_FAILURE = 0x80080006, + MK_E_NO_NORMALIZED = 0x80080007, + CO_E_SERVER_STOPPING = 0x80080008, + MEM_E_INVALID_ROOT = 0x80080009, + MEM_E_INVALID_LINK = 0x80080010, + MEM_E_INVALID_SIZE = 0x80080011, + CO_S_NOTALLINTERFACES = 0x00080012, + + NTE_BAD_UID = 0x80090001, + NTE_BAD_HASH = 0x80090002, + NTE_BAD_KEY = 0x80090003, + NTE_BAD_LEN = 0x80090004, + NTE_BAD_DATA = 0x80090005, + NTE_BAD_SIGNATURE = 0x80090006, + NTE_BAD_VER = 0x80090007, + NTE_BAD_ALGID = 0x80090008, + NTE_BAD_FLAGS = 0x80090009, + NTE_BAD_TYPE = 0x8009000A, + NTE_BAD_KEY_STATE = 0x8009000B, + NTE_BAD_HASH_STATE = 0x8009000C, + NTE_NO_KEY = 0x8009000D, + NTE_NO_MEMORY = 0x8009000E, + NTE_EXISTS = 0x8009000F, + NTE_PERM = 0x80090010, + NTE_NOT_FOUND = 0x80090011, + NTE_DOUBLE_ENCRYPT = 0x80090012, + NTE_BAD_PROVIDER = 0x80090013, + NTE_BAD_PROV_TYPE = 0x80090014, + NTE_BAD_PUBLIC_KEY = 0x80090015, + NTE_BAD_KEYSET = 0x80090016, + NTE_PROV_TYPE_NOT_DEF = 0x80090017, + NTE_PROV_TYPE_ENTRY_BAD = 0x80090018, + NTE_KEYSET_NOT_DEF = 0x80090019, + NTE_KEYSET_ENTRY_BAD = 0x8009001A, + NTE_PROV_TYPE_NO_MATCH = 0x8009001B, + NTE_SIGNATURE_FILE_BAD = 0x8009001C, + NTE_PROVIDER_DLL_FAIL = 0x8009001D, + NTE_PROV_DLL_NOT_FOUND = 0x8009001E, + NTE_BAD_KEYSET_PARAM = 0x8009001F, + NTE_FAIL = 0x80090020, + NTE_SYS_ERR = 0x80090021 +} + + +enum : uint { + SEVERITY_SUCCESS = 0, + SEVERITY_ERROR = 1 +} + +enum : uint { + FACILITY_NULL = 0, + FACILITY_RPC, + FACILITY_DISPATCH, + FACILITY_STORAGE, + FACILITY_ITF, // = 4 + FACILITY_WIN32 = 7, + FACILITY_WINDOWS = 8, + FACILITY_CONTROL = 10, + FACILITY_NT_BIT = 0x10000000 +} + +// C Macros + +pure nothrow @nogc { + bool SUCCEEDED(HRESULT Status) { + return Status >= 0; + } + + bool FAILED(HRESULT Status) { + return Status < 0; + } + + bool IS_ERROR(HRESULT Status) { + return (Status >>> 31) == SEVERITY_ERROR; + } + + ushort HRESULT_CODE(HRESULT r) { + return cast(ushort) (r & 0xFFFF); + } + + ushort SCODE_CODE(SCODE r) { + return cast(ushort) (r & 0xFFFF); + } + + ushort HRESULT_FACILITY(HRESULT r) { + return cast(ushort) ((r>>16) & 0x1fff); + } + + ushort SCODE_FACILITY(SCODE r) { + return cast(ushort) ((r>>16) & 0x1fff); + } + + ushort HRESULT_SEVERITY(HRESULT r) { + return cast(ushort) ((r>>31) & 0x1); + } + + ushort SCODE_SEVERITY(SCODE r) { + return cast(ushort) ((r>>31) & 0x1); + } + + HRESULT MAKE_HRESULT(bool s, uint f, uint c) { + return (s << 31) | (f << 16) | c; + } + + SCODE MAKE_SCODE(bool s, uint f, uint c) { + return (s << 31) | (f << 16) | c; + } + + SCODE GetScode(HRESULT hr) { + return hr; + } + + HRESULT ResultFromScode(SCODE c) { + return c; + } + + HRESULT HRESULT_FROM_NT(HRESULT x) { + return x | FACILITY_NT_BIT; + } + + HRESULT HRESULT_FROM_WIN32(HRESULT x) { + return x ? (x & 0x0000FFFF) | (FACILITY_WIN32 << 16) | 0x80000000 : 0; + } + + HRESULT PropagateResult(HRESULT hrPrevious, SCODE scBase) { + return scBase; + } +} diff --git a/src/urt/internal/sys/windows/winnt.d b/src/urt/internal/sys/windows/winnt.d new file mode 100644 index 0000000..882b21d --- /dev/null +++ b/src/urt/internal/sys/windows/winnt.d @@ -0,0 +1,4321 @@ +/** + * Windows API header module + * + * Translated from MinGW API for MS-Windows 3.12 + * + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * Source: $(DRUNTIMESRC core/sys/windows/_winnt.d) + */ +module urt.internal.sys.windows.winnt; +version (Windows): + +version (ANSI) {} else version = Unicode; + +public import urt.internal.sys.windows.basetsd, urt.internal.sys.windows.windef, urt.internal.sys.windows.winerror; +import urt.internal.sys.windows.w32api; + +/* Translation Notes: +The following macros are unneeded for D: +FIELD_OFFSET(t,f), CONTAINING_RECORD(address, type, field) +*/ + +alias void VOID; +alias char CHAR, CCHAR; +alias wchar WCHAR; +alias bool BOOLEAN; +alias byte FCHAR; +alias ubyte UCHAR; +alias short SHORT; +alias ushort LANGID, FSHORT; +alias uint LCID, FLONG, ACCESS_MASK; +alias long LONGLONG, USN; +alias ulong DWORDLONG, ULONGLONG; + +alias void* PVOID, LPVOID; +alias char* PSZ, PCHAR, PCCHAR, LPCH, PCH, LPSTR, PSTR; +alias wchar* PWCHAR, LPWCH, PWCH, LPWSTR, PWSTR; +alias bool* PBOOLEAN; +alias ubyte* PUCHAR; +alias short* PSHORT; +alias int* PLONG; +alias uint* PLCID, PACCESS_MASK; +alias long* PLONGLONG; +alias ulong* PDWORDLONG, PULONGLONG; + +// FIXME(MinGW) for __WIN64 +alias void* PVOID64; + +// const versions +alias const(char)* PCCH, LPCCH, PCSTR, LPCSTR; +alias const(wchar)* LPCWCH, PCWCH, LPCWSTR, PCWSTR; + +alias PSTR* PZPSTR; +alias PWSTR* PZPWSTR; + +version (Unicode) { + alias WCHAR TCHAR, _TCHAR; +} else { + alias CHAR TCHAR, _TCHAR; +} + +alias TCHAR TBYTE; +alias TCHAR* PTCH , PTBYTE, LPTCH , PTSTR , LPTSTR , LP, PTCHAR; +alias const(TCHAR)* PCTCH, LPCTCH, PCTSTR, LPCTSTR ; + +enum char ANSI_NULL = '\0'; +enum wchar UNICODE_NULL = '\0'; + +enum APPLICATION_ERROR_MASK = 0x20000000; +enum ERROR_SEVERITY_SUCCESS = 0x00000000; +enum ERROR_SEVERITY_INFORMATIONAL = 0x40000000; +enum ERROR_SEVERITY_WARNING = 0x80000000; +enum ERROR_SEVERITY_ERROR = 0xC0000000; + +// MinGW: also in ddk/ntifs.h +enum : USHORT { + COMPRESSION_FORMAT_NONE = 0x0000, + COMPRESSION_FORMAT_DEFAULT = 0x0001, + COMPRESSION_FORMAT_LZNT1 = 0x0002, + COMPRESSION_ENGINE_STANDARD = 0x0000, + COMPRESSION_ENGINE_MAXIMUM = 0x0100, + COMPRESSION_ENGINE_HIBER = 0x0200 +} + +// ACCESS_DENIED_OBJECT_ACE, etc +enum DWORD + ACE_OBJECT_TYPE_PRESENT = 0x00000001, + ACE_INHERITED_OBJECT_TYPE_PRESENT = 0x00000002; + +// ACE_HEADER.AceType +// also in ddk/ntifs.h +enum : BYTE { + ACCESS_ALLOWED_ACE_TYPE, + ACCESS_DENIED_ACE_TYPE, + SYSTEM_AUDIT_ACE_TYPE, + SYSTEM_ALARM_ACE_TYPE +} + +// ACE_HEADER.AceFlags +enum BYTE + OBJECT_INHERIT_ACE = 0x01, + CONTAINER_INHERIT_ACE = 0x02, + NO_PROPAGATE_INHERIT_ACE = 0x04, + INHERIT_ONLY_ACE = 0x08, + INHERITED_ACE = 0x10, + VALID_INHERIT_FLAGS = 0x1F, + SUCCESSFUL_ACCESS_ACE_FLAG = 0x40, + FAILED_ACCESS_ACE_FLAG = 0x80; + +// Access Mask Format +enum ACCESS_MASK + DELETE = 0x00010000, + READ_CONTROL = 0x00020000, + WRITE_DAC = 0x00040000, + WRITE_OWNER = 0x00080000, + SYNCHRONIZE = 0x00100000, + ACCESS_SYSTEM_SECURITY = 0x01000000, + MAXIMUM_ALLOWED = 0x02000000, + GENERIC_READ = 0x80000000, + GENERIC_WRITE = 0x40000000, + GENERIC_EXECUTE = 0x20000000, + GENERIC_ALL = 0x10000000, + STANDARD_RIGHTS_REQUIRED = 0x000F0000, + STANDARD_RIGHTS_READ = 0x00020000, + STANDARD_RIGHTS_WRITE = 0x00020000, + STANDARD_RIGHTS_EXECUTE = 0x00020000, + STANDARD_RIGHTS_ALL = 0x001F0000, + SPECIFIC_RIGHTS_ALL = 0x0000FFFF; + + +enum DWORD INVALID_FILE_ATTRIBUTES = -1; + +// MinGW: Also in ddk/winddk.h +enum DWORD + FILE_LIST_DIRECTORY = 0x00000001, + FILE_READ_DATA = 0x00000001, + FILE_ADD_FILE = 0x00000002, + FILE_WRITE_DATA = 0x00000002, + FILE_ADD_SUBDIRECTORY = 0x00000004, + FILE_APPEND_DATA = 0x00000004, + FILE_CREATE_PIPE_INSTANCE = 0x00000004, + FILE_READ_EA = 0x00000008, + FILE_READ_PROPERTIES = 0x00000008, + FILE_WRITE_EA = 0x00000010, + FILE_WRITE_PROPERTIES = 0x00000010, + FILE_EXECUTE = 0x00000020, + FILE_TRAVERSE = 0x00000020, + FILE_DELETE_CHILD = 0x00000040, + FILE_READ_ATTRIBUTES = 0x00000080, + FILE_WRITE_ATTRIBUTES = 0x00000100; + +enum DWORD + FILE_SHARE_READ = 0x00000001, + FILE_SHARE_WRITE = 0x00000002, + FILE_SHARE_DELETE = 0x00000004, + FILE_SHARE_VALID_FLAGS = 0x00000007; + +enum DWORD + FILE_ATTRIBUTE_READONLY = 0x00000001, + FILE_ATTRIBUTE_HIDDEN = 0x00000002, + FILE_ATTRIBUTE_SYSTEM = 0x00000004, + FILE_ATTRIBUTE_DIRECTORY = 0x00000010, + FILE_ATTRIBUTE_ARCHIVE = 0x00000020, + FILE_ATTRIBUTE_DEVICE = 0x00000040, + FILE_ATTRIBUTE_NORMAL = 0x00000080, + FILE_ATTRIBUTE_TEMPORARY = 0x00000100, + FILE_ATTRIBUTE_SPARSE_FILE = 0x00000200, + FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400, + FILE_ATTRIBUTE_COMPRESSED = 0x00000800, + FILE_ATTRIBUTE_OFFLINE = 0x00001000, + FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000, + FILE_ATTRIBUTE_ENCRYPTED = 0x00004000, + FILE_ATTRIBUTE_VALID_FLAGS = 0x00007fb7, + FILE_ATTRIBUTE_VALID_SET_FLAGS = 0x000031a7; + +// These are not documented on MSDN +enum FILE_COPY_STRUCTURED_STORAGE = 0x00000041; +enum FILE_STRUCTURED_STORAGE = 0x00000441; + +// Nor are these +enum FILE_VALID_OPTION_FLAGS = 0x00ffffff; +enum FILE_VALID_PIPE_OPTION_FLAGS = 0x00000032; +enum FILE_VALID_MAILSLOT_OPTION_FLAGS = 0x00000032; +enum FILE_VALID_SET_FLAGS = 0x00000036; + +enum ULONG + FILE_SUPERSEDE = 0x00000000, + FILE_OPEN = 0x00000001, + FILE_CREATE = 0x00000002, + FILE_OPEN_IF = 0x00000003, + FILE_OVERWRITE = 0x00000004, + FILE_OVERWRITE_IF = 0x00000005, + FILE_MAXIMUM_DISPOSITION = 0x00000005; + +enum ULONG + FILE_DIRECTORY_FILE = 0x00000001, + FILE_WRITE_THROUGH = 0x00000002, + FILE_SEQUENTIAL_ONLY = 0x00000004, + FILE_NO_INTERMEDIATE_BUFFERING = 0x00000008, + FILE_SYNCHRONOUS_IO_ALERT = 0x00000010, + FILE_SYNCHRONOUS_IO_NONALERT = 0x00000020, + FILE_NON_DIRECTORY_FILE = 0x00000040, + FILE_CREATE_TREE_CONNECTION = 0x00000080, + FILE_COMPLETE_IF_OPLOCKED = 0x00000100, + FILE_NO_EA_KNOWLEDGE = 0x00000200, + FILE_OPEN_FOR_RECOVERY = 0x00000400, + FILE_RANDOM_ACCESS = 0x00000800, + FILE_DELETE_ON_CLOSE = 0x00001000, + FILE_OPEN_BY_FILE_ID = 0x00002000, + FILE_OPEN_FOR_BACKUP_INTENT = 0x00004000, + FILE_NO_COMPRESSION = 0x00008000, + FILE_RESERVE_OPFILTER = 0x00100000, + FILE_OPEN_REPARSE_POINT = 0x00200000, + FILE_OPEN_NO_RECALL = 0x00400000, + FILE_OPEN_FOR_FREE_SPACE_QUERY = 0x00800000; + + +enum ACCESS_MASK + FILE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x01FF, + FILE_GENERIC_EXECUTE = STANDARD_RIGHTS_EXECUTE | FILE_READ_ATTRIBUTES + | FILE_EXECUTE | SYNCHRONIZE, + FILE_GENERIC_READ = STANDARD_RIGHTS_READ | FILE_READ_DATA + | FILE_READ_ATTRIBUTES | FILE_READ_EA | SYNCHRONIZE, + FILE_GENERIC_WRITE = STANDARD_RIGHTS_WRITE | FILE_WRITE_DATA + | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA + | SYNCHRONIZE; + +// MinGW: end winddk.h +// MinGW: also in ddk/ntifs.h +enum DWORD + FILE_NOTIFY_CHANGE_FILE_NAME = 0x00000001, + FILE_NOTIFY_CHANGE_DIR_NAME = 0x00000002, + FILE_NOTIFY_CHANGE_NAME = 0x00000003, + FILE_NOTIFY_CHANGE_ATTRIBUTES = 0x00000004, + FILE_NOTIFY_CHANGE_SIZE = 0x00000008, + FILE_NOTIFY_CHANGE_LAST_WRITE = 0x00000010, + FILE_NOTIFY_CHANGE_LAST_ACCESS = 0x00000020, + FILE_NOTIFY_CHANGE_CREATION = 0x00000040, + FILE_NOTIFY_CHANGE_EA = 0x00000080, + FILE_NOTIFY_CHANGE_SECURITY = 0x00000100, + FILE_NOTIFY_CHANGE_STREAM_NAME = 0x00000200, + FILE_NOTIFY_CHANGE_STREAM_SIZE = 0x00000400, + FILE_NOTIFY_CHANGE_STREAM_WRITE = 0x00000800, + FILE_NOTIFY_VALID_MASK = 0x00000fff; + +enum DWORD + FILE_CASE_SENSITIVE_SEARCH = 0x00000001, + FILE_CASE_PRESERVED_NAMES = 0x00000002, + FILE_UNICODE_ON_DISK = 0x00000004, + FILE_PERSISTENT_ACLS = 0x00000008, + FILE_FILE_COMPRESSION = 0x00000010, + FILE_VOLUME_QUOTAS = 0x00000020, + FILE_SUPPORTS_SPARSE_FILES = 0x00000040, + FILE_SUPPORTS_REPARSE_POINTS = 0x00000080, + FILE_SUPPORTS_REMOTE_STORAGE = 0x00000100, + FS_LFN_APIS = 0x00004000, + FILE_VOLUME_IS_COMPRESSED = 0x00008000, + FILE_SUPPORTS_OBJECT_IDS = 0x00010000, + FILE_SUPPORTS_ENCRYPTION = 0x00020000, + FILE_NAMED_STREAMS = 0x00040000, + FILE_READ_ONLY_VOLUME = 0x00080000, + FILE_SEQUENTIAL_WRITE_ONCE = 0x00100000, + FILE_SUPPORTS_TRANSACTIONS = 0x00200000; + +// These are not documented on MSDN +enum ACCESS_MASK + IO_COMPLETION_QUERY_STATE = 1, + IO_COMPLETION_MODIFY_STATE = 2, + IO_COMPLETION_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 3; +// MinGW: end ntifs.h + +// MinGW: also in ddk/winddk.h +enum DWORD + DUPLICATE_CLOSE_SOURCE = 1, + DUPLICATE_SAME_ACCESS = 2, + DUPLICATE_SAME_ATTRIBUTES = 4; +// MinGW: end winddk.k + +enum DWORD + MAILSLOT_NO_MESSAGE = -1, + MAILSLOT_WAIT_FOREVER = -1; + +enum ACCESS_MASK + PROCESS_TERMINATE = 0x0001, + PROCESS_CREATE_THREAD = 0x0002, + PROCESS_SET_SESSIONID = 0x0004, + PROCESS_VM_OPERATION = 0x0008, + PROCESS_VM_READ = 0x0010, + PROCESS_VM_WRITE = 0x0020, + PROCESS_DUP_HANDLE = 0x0040, + PROCESS_CREATE_PROCESS = 0x0080, + PROCESS_SET_QUOTA = 0x0100, + PROCESS_SET_INFORMATION = 0x0200, + PROCESS_QUERY_INFORMATION = 0x0400, + PROCESS_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x0FFF; + +enum ACCESS_MASK + THREAD_TERMINATE = 0x0001, + THREAD_SUSPEND_RESUME = 0x0002, + THREAD_GET_CONTEXT = 0x0008, + THREAD_SET_CONTEXT = 0x0010, + THREAD_SET_INFORMATION = 0x0020, + THREAD_QUERY_INFORMATION = 0x0040, + THREAD_SET_THREAD_TOKEN = 0x0080, + THREAD_IMPERSONATE = 0x0100, + THREAD_DIRECT_IMPERSONATION = 0x0200, + THREAD_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED|SYNCHRONIZE|0x3FF; + +// These are not documented on MSDN +enum THREAD_BASE_PRIORITY_LOWRT = 15; +enum THREAD_BASE_PRIORITY_MAX = 2; +enum THREAD_BASE_PRIORITY_MIN = -2; +enum THREAD_BASE_PRIORITY_IDLE = -15; + +enum DWORD EXCEPTION_NONCONTINUABLE = 1; +enum size_t EXCEPTION_MAXIMUM_PARAMETERS = 15; + +// These are not documented on MSDN +enum ACCESS_MASK + MUTANT_QUERY_STATE = 1, + MUTANT_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | MUTANT_QUERY_STATE; + +enum ACCESS_MASK + TIMER_QUERY_STATE = 1, + TIMER_MODIFY_STATE = 2, + TIMER_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | TIMER_QUERY_STATE + | TIMER_MODIFY_STATE; + +enum SID_IDENTIFIER_AUTHORITY + SECURITY_NULL_SID_AUTHORITY = {[5: 0]}, + SECURITY_WORLD_SID_AUTHORITY = {[5: 1]}, + SECURITY_LOCAL_SID_AUTHORITY = {[5: 2]}, + SECURITY_CREATOR_SID_AUTHORITY = {[5: 3]}, + SECURITY_NON_UNIQUE_AUTHORITY = {[5: 4]}, + SECURITY_NT_AUTHORITY = {[5: 5]}, + SECURITY_MANDATORY_LABEL_AUTHORITY = {[5: 6]}; + +enum DWORD + SECURITY_NULL_RID = 0, + SECURITY_WORLD_RID = 0, + SECURITY_LOCAL_RID = 0, + SECURITY_CREATOR_OWNER_RID = 0, + SECURITY_CREATOR_GROUP_RID = 1, + SECURITY_DIALUP_RID = 1, + SECURITY_NETWORK_RID = 2, + SECURITY_BATCH_RID = 3, + SECURITY_INTERACTIVE_RID = 4, + SECURITY_LOGON_IDS_RID = 5, + SECURITY_SERVICE_RID = 6, + SECURITY_LOCAL_SYSTEM_RID = 18, + SECURITY_BUILTIN_DOMAIN_RID = 32, + SECURITY_PRINCIPAL_SELF_RID = 10, + SECURITY_CREATOR_OWNER_SERVER_RID = 2, + SECURITY_CREATOR_GROUP_SERVER_RID = 3, + SECURITY_LOGON_IDS_RID_COUNT = 3, + SECURITY_ANONYMOUS_LOGON_RID = 7, + SECURITY_PROXY_RID = 8, + SECURITY_ENTERPRISE_CONTROLLERS_RID = 9, + SECURITY_SERVER_LOGON_RID = SECURITY_ENTERPRISE_CONTROLLERS_RID, + SECURITY_AUTHENTICATED_USER_RID = 11, + SECURITY_RESTRICTED_CODE_RID = 12, + SECURITY_NT_NON_UNIQUE_RID = 21, + SID_REVISION = 1; + +enum : DWORD { + DOMAIN_USER_RID_ADMIN = 0x01F4, + DOMAIN_USER_RID_GUEST = 0x01F5, + DOMAIN_GROUP_RID_ADMINS = 0x0200, + DOMAIN_GROUP_RID_USERS = 0x0201, + DOMAIN_ALIAS_RID_ADMINS = 0x0220, + DOMAIN_ALIAS_RID_USERS = 0x0221, + DOMAIN_ALIAS_RID_GUESTS = 0x0222, + DOMAIN_ALIAS_RID_POWER_USERS = 0x0223, + DOMAIN_ALIAS_RID_ACCOUNT_OPS = 0x0224, + DOMAIN_ALIAS_RID_SYSTEM_OPS = 0x0225, + DOMAIN_ALIAS_RID_PRINT_OPS = 0x0226, + DOMAIN_ALIAS_RID_BACKUP_OPS = 0x0227, + DOMAIN_ALIAS_RID_REPLICATOR = 0x0228 +} + +enum : WORD { + SECURITY_MANDATORY_UNTRUSTED_RID = 0, + SECURITY_MANDATORY_LOW_RID = 0x1000, + SECURITY_MANDATORY_MEDIUM_RID = 0x2000, + SECURITY_MANDATORY_HIGH_RID = 0x3000, + SECURITY_MANDATORY_SYSTEM_RID = 0x4000, + SECURITY_MANDATORY_PROTECTED_PROCESS_RID = 0x5000, + SECURITY_MANDATORY_MAXIMUM_USER_RID = SECURITY_MANDATORY_SYSTEM_RID +} + +const TCHAR[] + SE_CREATE_TOKEN_NAME = "SeCreateTokenPrivilege", + SE_ASSIGNPRIMARYTOKEN_NAME = "SeAssignPrimaryTokenPrivilege", + SE_LOCK_MEMORY_NAME = "SeLockMemoryPrivilege", + SE_INCREASE_QUOTA_NAME = "SeIncreaseQuotaPrivilege", + SE_UNSOLICITED_INPUT_NAME = "SeUnsolicitedInputPrivilege", + SE_MACHINE_ACCOUNT_NAME = "SeMachineAccountPrivilege", + SE_TCB_NAME = "SeTcbPrivilege", + SE_SECURITY_NAME = "SeSecurityPrivilege", + SE_TAKE_OWNERSHIP_NAME = "SeTakeOwnershipPrivilege", + SE_LOAD_DRIVER_NAME = "SeLoadDriverPrivilege", + SE_SYSTEM_PROFILE_NAME = "SeSystemProfilePrivilege", + SE_SYSTEMTIME_NAME = "SeSystemtimePrivilege", + SE_PROF_SINGLE_PROCESS_NAME = "SeProfileSingleProcessPrivilege", + SE_INC_BASE_PRIORITY_NAME = "SeIncreaseBasePriorityPrivilege", + SE_CREATE_PAGEFILE_NAME = "SeCreatePagefilePrivilege", + SE_CREATE_PERMANENT_NAME = "SeCreatePermanentPrivilege", + SE_BACKUP_NAME = "SeBackupPrivilege", + SE_RESTORE_NAME = "SeRestorePrivilege", + SE_SHUTDOWN_NAME = "SeShutdownPrivilege", + SE_DEBUG_NAME = "SeDebugPrivilege", + SE_AUDIT_NAME = "SeAuditPrivilege", + SE_SYSTEM_ENVIRONMENT_NAME = "SeSystemEnvironmentPrivilege", + SE_CHANGE_NOTIFY_NAME = "SeChangeNotifyPrivilege", + SE_REMOTE_SHUTDOWN_NAME = "SeRemoteShutdownPrivilege", + SE_CREATE_GLOBAL_NAME = "SeCreateGlobalPrivilege", + SE_UNDOCK_NAME = "SeUndockPrivilege", + SE_MANAGE_VOLUME_NAME = "SeManageVolumePrivilege", + SE_IMPERSONATE_NAME = "SeImpersonatePrivilege", + SE_ENABLE_DELEGATION_NAME = "SeEnableDelegationPrivilege", + SE_SYNC_AGENT_NAME = "SeSyncAgentPrivilege", + SE_TRUSTED_CREDMAN_ACCESS_NAME = "SeTrustedCredManAccessPrivilege", + SE_RELABEL_NAME = "SeRelabelPrivilege", + SE_INCREASE_WORKING_SET_NAME = "SeIncreaseWorkingSetPrivilege", + SE_TIME_ZONE_NAME = "SeTimeZonePrivilege", + SE_CREATE_SYMBOLIC_LINK_NAME = "SeCreateSymbolicLinkPrivilege"; + +enum DWORD + SE_GROUP_MANDATORY = 0x00000001, + SE_GROUP_ENABLED_BY_DEFAULT = 0x00000002, + SE_GROUP_ENABLED = 0x00000004, + SE_GROUP_OWNER = 0x00000008, + SE_GROUP_USE_FOR_DENY_ONLY = 0x00000010, + SE_GROUP_INTEGRITY = 0x00000020, + SE_GROUP_INTEGRITY_ENABLED = 0x00000040, + SE_GROUP_RESOURCE = 0x20000000, + SE_GROUP_LOGON_ID = 0xC0000000; + +// Primary language identifiers +enum : USHORT { + LANG_NEUTRAL, + LANG_ARABIC, + LANG_BULGARIAN, + LANG_CATALAN, + LANG_CHINESE, + LANG_CZECH, + LANG_DANISH, + LANG_GERMAN, + LANG_GREEK, + LANG_ENGLISH, + LANG_SPANISH, + LANG_FINNISH, + LANG_FRENCH, + LANG_HEBREW, + LANG_HUNGARIAN, + LANG_ICELANDIC, + LANG_ITALIAN, + LANG_JAPANESE, + LANG_KOREAN, + LANG_DUTCH, + LANG_NORWEGIAN, + LANG_POLISH, + LANG_PORTUGUESE, // = 0x16 + LANG_ROMANIAN = 0x18, + LANG_RUSSIAN, + LANG_CROATIAN, // = 0x1A + LANG_SERBIAN = 0x1A, + LANG_BOSNIAN = 0x1A, + LANG_SLOVAK, + LANG_ALBANIAN, + LANG_SWEDISH, + LANG_THAI, + LANG_TURKISH, + LANG_URDU, + LANG_INDONESIAN, + LANG_UKRAINIAN, + LANG_BELARUSIAN, + LANG_SLOVENIAN, + LANG_ESTONIAN, + LANG_LATVIAN, + LANG_LITHUANIAN, // = 0x27 + LANG_FARSI = 0x29, + LANG_PERSIAN = 0x29, + LANG_VIETNAMESE, + LANG_ARMENIAN, + LANG_AZERI, + LANG_BASQUE, + LANG_LOWER_SORBIAN, // = 0x2E + LANG_UPPER_SORBIAN = 0x2E, + LANG_MACEDONIAN, // = 0x2F + LANG_TSWANA = 0x32, + LANG_XHOSA = 0x34, + LANG_ZULU, + LANG_AFRIKAANS, + LANG_GEORGIAN, + LANG_FAEROESE, + LANG_HINDI, + LANG_MALTESE, + LANG_SAMI, + LANG_IRISH, // = 0x3C + LANG_MALAY = 0x3E, + LANG_KAZAK, + LANG_KYRGYZ, + LANG_SWAHILI, // = 0x41 + LANG_UZBEK = 0x43, + LANG_TATAR, + LANG_BENGALI, + LANG_PUNJABI, + LANG_GUJARATI, + LANG_ORIYA, + LANG_TAMIL, + LANG_TELUGU, + LANG_KANNADA, + LANG_MALAYALAM, + LANG_ASSAMESE, + LANG_MARATHI, + LANG_SANSKRIT, + LANG_MONGOLIAN, + LANG_TIBETAN, + LANG_WELSH, + LANG_KHMER, + LANG_LAO, // = 0x54 + LANG_GALICIAN = 0x56, + LANG_KONKANI, + LANG_MANIPURI, + LANG_SINDHI, + LANG_SYRIAC, + LANG_SINHALESE, // = 0x5B + LANG_INUKTITUT = 0x5D, + LANG_AMHARIC, + LANG_TAMAZIGHT, + LANG_KASHMIRI, + LANG_NEPALI, + LANG_FRISIAN, + LANG_PASHTO, + LANG_FILIPINO, + LANG_DIVEHI, // = 0x65 + LANG_HAUSA = 0x68, + LANG_YORUBA = 0x6A, + LANG_QUECHUA, + LANG_SOTHO, + LANG_BASHKIR, + LANG_LUXEMBOURGISH, + LANG_GREENLANDIC, + LANG_IGBO, // = 0x70 + LANG_TIGRIGNA = 0x73, + LANG_YI = 0x78, + LANG_MAPUDUNGUN = 0x7A, + LANG_MOHAWK = 0x7C, + LANG_BRETON = 0x7E, + LANG_UIGHUR = 0x80, + LANG_MAORI, + LANG_OCCITAN, + LANG_CORSICAN, + LANG_ALSATIAN, + LANG_YAKUT, + LANG_KICHE, + LANG_KINYARWANDA, + LANG_WOLOF, // = 0x88 + LANG_DARI = 0x8C, + LANG_MALAGASY, // = 0x8D + + LANG_SERBIAN_NEUTRAL = 0x7C1A, + LANG_BOSNIAN_NEUTRAL = 0x781A, + + LANG_INVARIANT = 0x7F +} + + +// Sublanguage identifiers +enum : USHORT { + SUBLANG_NEUTRAL, + SUBLANG_DEFAULT, + SUBLANG_SYS_DEFAULT, + SUBLANG_CUSTOM_DEFAULT, // = 3 + SUBLANG_UI_CUSTOM_DEFAULT = 3, + SUBLANG_CUSTOM_UNSPECIFIED, // = 4 + + SUBLANG_AFRIKAANS_SOUTH_AFRICA = 1, + SUBLANG_ALBANIAN_ALBANIA = 1, + SUBLANG_ALSATIAN_FRANCE = 1, + SUBLANG_AMHARIC_ETHIOPIA = 1, + + SUBLANG_ARABIC_SAUDI_ARABIA = 1, + SUBLANG_ARABIC_IRAQ, + SUBLANG_ARABIC_EGYPT, + SUBLANG_ARABIC_LIBYA, + SUBLANG_ARABIC_ALGERIA, + SUBLANG_ARABIC_MOROCCO, + SUBLANG_ARABIC_TUNISIA, + SUBLANG_ARABIC_OMAN, + SUBLANG_ARABIC_YEMEN, + SUBLANG_ARABIC_SYRIA, + SUBLANG_ARABIC_JORDAN, + SUBLANG_ARABIC_LEBANON, + SUBLANG_ARABIC_KUWAIT, + SUBLANG_ARABIC_UAE, + SUBLANG_ARABIC_BAHRAIN, + SUBLANG_ARABIC_QATAR, // = 16 + + SUBLANG_ARMENIAN_ARMENIA = 1, + SUBLANG_ASSAMESE_INDIA = 1, + + SUBLANG_AZERI_LATIN = 1, + SUBLANG_AZERI_CYRILLIC, // = 2 + + SUBLANG_BASHKIR_RUSSIA = 1, + SUBLANG_BASQUE_BASQUE = 1, + SUBLANG_BELARUSIAN_BELARUS = 1, + SUBLANG_BENGALI_INDIA = 1, + + SUBLANG_BOSNIAN_BOSNIA_HERZEGOVINA_LATIN = 5, + SUBLANG_BOSNIAN_BOSNIA_HERZEGOVINA_CYRILLIC = 8, + + SUBLANG_BRETON_FRANCE = 1, + SUBLANG_BULGARIAN_BULGARIA = 1, + SUBLANG_CATALAN_CATALAN = 1, + + SUBLANG_CHINESE_TRADITIONAL = 1, + SUBLANG_CHINESE_SIMPLIFIED, + SUBLANG_CHINESE_HONGKONG, + SUBLANG_CHINESE_SINGAPORE, + SUBLANG_CHINESE_MACAU, // = 5 + + SUBLANG_CORSICAN_FRANCE = 1, + + SUBLANG_CROATIAN_CROATIA = 1, + SUBLANG_CROATIAN_BOSNIA_HERZEGOVINA_LATIN = 4, + + SUBLANG_CZECH_CZECH_REPUBLIC = 1, + SUBLANG_DANISH_DENMARK = 1, + SUBLANG_DIVEHI_MALDIVES = 1, + + SUBLANG_DUTCH = 1, + SUBLANG_DUTCH_BELGIAN, // = 2 + + SUBLANG_ENGLISH_US = 1, + SUBLANG_ENGLISH_UK, + SUBLANG_ENGLISH_AUS, + SUBLANG_ENGLISH_CAN, + SUBLANG_ENGLISH_NZ, + SUBLANG_ENGLISH_EIRE, // = 6 + SUBLANG_ENGLISH_IRELAND = 6, + SUBLANG_ENGLISH_SOUTH_AFRICA, + SUBLANG_ENGLISH_JAMAICA, + SUBLANG_ENGLISH_CARIBBEAN, + SUBLANG_ENGLISH_BELIZE, + SUBLANG_ENGLISH_TRINIDAD, + SUBLANG_ENGLISH_ZIMBABWE, + SUBLANG_ENGLISH_PHILIPPINES, // = 13 + SUBLANG_ENGLISH_INDIA = 16, + SUBLANG_ENGLISH_MALAYSIA, + SUBLANG_ENGLISH_SINGAPORE, // = 18 + + SUBLANG_ESTONIAN_ESTONIA = 1, + SUBLANG_FAEROESE_FAROE_ISLANDS = 1, + SUBLANG_FILIPINO_PHILIPPINES = 1, + SUBLANG_FINNISH_FINLAND = 1, + + SUBLANG_FRENCH = 1, + SUBLANG_FRENCH_BELGIAN, + SUBLANG_FRENCH_CANADIAN, + SUBLANG_FRENCH_SWISS, + SUBLANG_FRENCH_LUXEMBOURG, + SUBLANG_FRENCH_MONACO, // = 6 + + SUBLANG_FRISIAN_NETHERLANDS = 1, + SUBLANG_GALICIAN_GALICIAN = 1, + SUBLANG_GEORGIAN_GEORGIA = 1, + + SUBLANG_GERMAN = 1, + SUBLANG_GERMAN_SWISS, + SUBLANG_GERMAN_AUSTRIAN, + SUBLANG_GERMAN_LUXEMBOURG, + SUBLANG_GERMAN_LIECHTENSTEIN, // = 5 + + SUBLANG_GREEK_GREECE = 1, + SUBLANG_GREENLANDIC_GREENLAND = 1, + SUBLANG_GUJARATI_INDIA = 1, + SUBLANG_HAUSA_NIGERIA = 1, + SUBLANG_HEBREW_ISRAEL = 1, + SUBLANG_HINDI_INDIA = 1, + SUBLANG_HUNGARIAN_HUNGARY = 1, + SUBLANG_ICELANDIC_ICELAND = 1, + SUBLANG_IGBO_NIGERIA = 1, + SUBLANG_INDONESIAN_INDONESIA = 1, + + SUBLANG_INUKTITUT_CANADA = 1, + SUBLANG_INUKTITUT_CANADA_LATIN = 1, + + SUBLANG_IRISH_IRELAND = 1, + + SUBLANG_ITALIAN = 1, + SUBLANG_ITALIAN_SWISS, // = 2 + + SUBLANG_JAPANESE_JAPAN = 1, + + SUBLANG_KASHMIRI_INDIA = 2, + SUBLANG_KASHMIRI_SASIA = 2, + + SUBLANG_KAZAK_KAZAKHSTAN = 1, + SUBLANG_KHMER_CAMBODIA = 1, + SUBLANG_KICHE_GUATEMALA = 1, + SUBLANG_KINYARWANDA_RWANDA = 1, + SUBLANG_KONKANI_INDIA = 1, + SUBLANG_KOREAN = 1, + SUBLANG_KOREAN_JOHAB = 2, + SUBLANG_KYRGYZ_KYRGYZSTAN = 1, + SUBLANG_LAO_LAO_PDR = 1, + SUBLANG_LATVIAN_LATVIA = 1, + + SUBLANG_LITHUANIAN = 1, + SUBLANG_LITHUANIAN_LITHUANIA = 1, + + SUBLANG_LOWER_SORBIAN_GERMANY = 1, + SUBLANG_LUXEMBOURGISH_LUXEMBOURG = 1, + SUBLANG_MACEDONIAN_MACEDONIA = 1, + SUBLANG_MALAYALAM_INDIA = 1, + SUBLANG_MALTESE_MALTA = 1, + SUBLANG_MAORI_NEW_ZEALAND = 1, + SUBLANG_MAPUDUNGUN_CHILE = 1, + SUBLANG_MARATHI_INDIA = 1, + SUBLANG_MOHAWK_MOHAWK = 1, + + SUBLANG_MONGOLIAN_CYRILLIC_MONGOLIA = 1, + SUBLANG_MONGOLIAN_PRC, // = 2 + + SUBLANG_MALAY_MALAYSIA = 1, + SUBLANG_MALAY_BRUNEI_DARUSSALAM, // = 2 + + SUBLANG_NEPALI_NEPAL = 1, + SUBLANG_NEPALI_INDIA, // = 2 + + SUBLANG_NORWEGIAN_BOKMAL = 1, + SUBLANG_NORWEGIAN_NYNORSK, // = 2 + + SUBLANG_OCCITAN_FRANCE = 1, + SUBLANG_ORIYA_INDIA = 1, + SUBLANG_PASHTO_AFGHANISTAN = 1, + SUBLANG_PERSIAN_IRAN = 1, + SUBLANG_POLISH_POLAND = 1, + + SUBLANG_PORTUGUESE_BRAZILIAN = 1, + SUBLANG_PORTUGUESE = 2, + SUBLANG_PORTUGUESE_PORTUGAL, // = 2 + + SUBLANG_PUNJABI_INDIA = 1, + + SUBLANG_QUECHUA_BOLIVIA = 1, + SUBLANG_QUECHUA_ECUADOR, + SUBLANG_QUECHUA_PERU, // = 3 + + SUBLANG_ROMANIAN_ROMANIA = 1, + SUBLANG_ROMANSH_SWITZERLAND = 1, + SUBLANG_RUSSIAN_RUSSIA = 1, + + SUBLANG_SAMI_NORTHERN_NORWAY = 1, + SUBLANG_SAMI_NORTHERN_SWEDEN, + SUBLANG_SAMI_NORTHERN_FINLAND, // = 3 + SUBLANG_SAMI_SKOLT_FINLAND = 3, + SUBLANG_SAMI_INARI_FINLAND = 3, + SUBLANG_SAMI_LULE_NORWAY, + SUBLANG_SAMI_LULE_SWEDEN, + SUBLANG_SAMI_SOUTHERN_NORWAY, + SUBLANG_SAMI_SOUTHERN_SWEDEN, // = 7 + + SUBLANG_SANSKRIT_INDIA = 1, + + SUBLANG_SERBIAN_LATIN = 2, + SUBLANG_SERBIAN_CYRILLIC, // = 3 + SUBLANG_SERBIAN_BOSNIA_HERZEGOVINA_LATIN = 6, + SUBLANG_SERBIAN_BOSNIA_HERZEGOVINA_CYRILLIC = 7, + + SUBLANG_SINDHI_AFGHANISTAN = 2, + SUBLANG_SINHALESE_SRI_LANKA = 1, + SUBLANG_SOTHO_NORTHERN_SOUTH_AFRICA = 1, + SUBLANG_SLOVAK_SLOVAKIA = 1, + SUBLANG_SLOVENIAN_SLOVENIA = 1, + + SUBLANG_SPANISH = 1, + SUBLANG_SPANISH_MEXICAN, + SUBLANG_SPANISH_MODERN, + SUBLANG_SPANISH_GUATEMALA, + SUBLANG_SPANISH_COSTA_RICA, + SUBLANG_SPANISH_PANAMA, + SUBLANG_SPANISH_DOMINICAN_REPUBLIC, + SUBLANG_SPANISH_VENEZUELA, + SUBLANG_SPANISH_COLOMBIA, + SUBLANG_SPANISH_PERU, + SUBLANG_SPANISH_ARGENTINA, + SUBLANG_SPANISH_ECUADOR, + SUBLANG_SPANISH_CHILE, + SUBLANG_SPANISH_URUGUAY, + SUBLANG_SPANISH_PARAGUAY, + SUBLANG_SPANISH_BOLIVIA, + SUBLANG_SPANISH_EL_SALVADOR, + SUBLANG_SPANISH_HONDURAS, + SUBLANG_SPANISH_NICARAGUA, + SUBLANG_SPANISH_PUERTO_RICO, + SUBLANG_SPANISH_US, // = 21 + + SUBLANG_SWEDISH = 1, + SUBLANG_SWEDISH_SWEDEN = 1, + SUBLANG_SWEDISH_FINLAND, // = 2 + + SUBLANG_SYRIAC = 1, + SUBLANG_TAJIK_TAJIKISTAN = 1, + SUBLANG_TAMAZIGHT_ALGERIA_LATIN = 2, + SUBLANG_TAMIL_INDIA = 1, + SUBLANG_TATAR_RUSSIA = 1, + SUBLANG_TELUGU_INDIA = 1, + SUBLANG_THAI_THAILAND = 1, + SUBLANG_TIBETAN_PRC = 1, + SUBLANG_TIBETAN_BHUTAN = 2, + SUBLANG_TIGRIGNA_ERITREA = 1, + SUBLANG_TSWANA_SOUTH_AFRICA = 1, + SUBLANG_TURKISH_TURKEY = 1, + SUBLANG_TURKMEN_TURKMENISTAN = 1, + SUBLANG_UIGHUR_PRC = 1, + SUBLANG_UKRAINIAN_UKRAINE = 1, + SUBLANG_UPPER_SORBIAN_GERMANY = 1, + + SUBLANG_URDU_PAKISTAN = 1, + SUBLANG_URDU_INDIA, // = 2 + + SUBLANG_UZBEK_LATIN = 1, + SUBLANG_UZBEK_CYRILLIC, // = 2 + + SUBLANG_VIETNAMESE_VIETNAM = 1, + SUBLANG_WELSH_UNITED_KINGDOM = 1, + SUBLANG_WOLOF_SENEGAL = 1, + SUBLANG_YORUBA_NIGERIA = 1, + SUBLANG_XHOSA_SOUTH_AFRICA = 1, + SUBLANG_YAKUT_RUSSIA = 1, + SUBLANG_YI_PRC = 1, + SUBLANG_ZULU_SOUTH_AFRICA = 1 +} + +// This is not documented on MSDN +enum NLS_VALID_LOCALE_MASK = 1048575; + +// Sorting identifiers +enum : WORD { + SORT_DEFAULT = 0, + SORT_JAPANESE_XJIS = 0, + SORT_JAPANESE_UNICODE = 1, + SORT_CHINESE_BIG5 = 0, + SORT_CHINESE_PRCP = 0, + SORT_CHINESE_UNICODE = 1, + SORT_CHINESE_PRC = 2, + SORT_CHINESE_BOPOMOFO = 3, + SORT_KOREAN_KSC = 0, + SORT_KOREAN_UNICODE = 1, + SORT_GERMAN_PHONE_BOOK = 1, + SORT_HUNGARIAN_DEFAULT = 0, + SORT_HUNGARIAN_TECHNICAL = 1, + SORT_GEORGIAN_TRADITIONAL = 0, + SORT_GEORGIAN_MODERN = 1 +} + +pure nothrow @nogc { + WORD MAKELANGID()(/*USHORT*/uint p, /*USHORT*/ uint s) { return cast(WORD)((s << 10) | p); } + WORD PRIMARYLANGID()(/*WORD*/uint lgid) { return cast(WORD)(lgid & 0x3FF); } + WORD SUBLANGID()(/*WORD*/uint lgid) { return cast(WORD)(lgid >>> 10); } + + DWORD MAKELCID()(/*WORD*/uint lgid, /*WORD*/uint srtid) { return (cast(DWORD) srtid << 16) | cast(DWORD) lgid; } + // ??? + //DWORD MAKESORTLCID()(WORD lgid, WORD srtid, WORD ver) { return (MAKELCID(lgid, srtid)) | ((cast(DWORD)ver) << 20); } + WORD LANGIDFROMLCID()(LCID lcid) { return cast(WORD) lcid; } + WORD SORTIDFROMLCID()(LCID lcid) { return cast(WORD) ((lcid >>> 16) & 0x0F); } + WORD SORTVERSIONFROMLCID()(LCID lcid) { return cast(WORD) ((lcid >>> 20) & 0x0F); } +} + +enum WORD LANG_SYSTEM_DEFAULT = (SUBLANG_SYS_DEFAULT << 10) | LANG_NEUTRAL; +enum WORD LANG_USER_DEFAULT = (SUBLANG_DEFAULT << 10) | LANG_NEUTRAL; +enum DWORD LOCALE_NEUTRAL = (SORT_DEFAULT << 16) + | (SUBLANG_NEUTRAL << 10) | LANG_NEUTRAL; + +// --- +enum : BYTE { + ACL_REVISION = 2, + ACL_REVISION_DS = 4 +} + +// These are not documented on MSDN +enum : BYTE { + ACL_REVISION1 = 1, + ACL_REVISION2, + ACL_REVISION3, + ACL_REVISION4 // = 4 +} + +enum BYTE + MIN_ACL_REVISION = 2, + MAX_ACL_REVISION = 4; + +/+ +// These aren't necessary for D. +enum MINCHAR=0x80; +enum MAXCHAR=0x7f; +enum MINSHORT=0x8000; +enum MAXSHORT=0x7fff; +enum MINLONG=0x80000000; +enum MAXLONG=0x7fffffff; +enum MAXBYTE=0xff; +enum MAXWORD=0xffff; +enum MAXDWORD=0xffffffff; ++/ + +// SYSTEM_INFO.dwProcessorType +enum : DWORD { + PROCESSOR_INTEL_386 = 386, + PROCESSOR_INTEL_486 = 486, + PROCESSOR_INTEL_PENTIUM = 586, + PROCESSOR_MIPS_R4000 = 4000, + PROCESSOR_ALPHA_21064 = 21064, + PROCESSOR_INTEL_IA64 = 2200 +} + +// SYSTEM_INFO.wProcessorArchitecture +enum : WORD { + PROCESSOR_ARCHITECTURE_INTEL, + PROCESSOR_ARCHITECTURE_MIPS, + PROCESSOR_ARCHITECTURE_ALPHA, + PROCESSOR_ARCHITECTURE_PPC, + PROCESSOR_ARCHITECTURE_SHX, + PROCESSOR_ARCHITECTURE_ARM, + PROCESSOR_ARCHITECTURE_IA64, + PROCESSOR_ARCHITECTURE_ALPHA64, + PROCESSOR_ARCHITECTURE_MSIL, + PROCESSOR_ARCHITECTURE_AMD64, + PROCESSOR_ARCHITECTURE_IA32_ON_WIN64, // = 10 + PROCESSOR_ARCHITECTURE_UNKNOWN = 0xFFFF +} + +// IsProcessorFeaturePresent() +enum : DWORD { + PF_FLOATING_POINT_PRECISION_ERRATA, + PF_FLOATING_POINT_EMULATED, + PF_COMPARE_EXCHANGE_DOUBLE, + PF_MMX_INSTRUCTIONS_AVAILABLE, + PF_PPC_MOVEMEM_64BIT_OK, + PF_ALPHA_BYTE_INSTRUCTIONS, + PF_XMMI_INSTRUCTIONS_AVAILABLE, + PF_3DNOW_INSTRUCTIONS_AVAILABLE, + PF_RDTSC_INSTRUCTION_AVAILABLE, + PF_PAE_ENABLED, + PF_XMMI64_INSTRUCTIONS_AVAILABLE +} + +// MinGW: also in ddk/ntifs.h +enum : DWORD { + FILE_ACTION_ADDED = 1, + FILE_ACTION_REMOVED, + FILE_ACTION_MODIFIED, + FILE_ACTION_RENAMED_OLD_NAME, + FILE_ACTION_RENAMED_NEW_NAME, + FILE_ACTION_ADDED_STREAM, + FILE_ACTION_REMOVED_STREAM, + FILE_ACTION_MODIFIED_STREAM, + FILE_ACTION_REMOVED_BY_DELETE, + FILE_ACTION_ID_NOT_TUNNELLED, + FILE_ACTION_TUNNELLED_ID_COLLISION // = 11 +} +// MinGW: end ntifs.h + +enum DWORD + HEAP_NO_SERIALIZE = 0x01, + HEAP_GROWABLE = 0x02, + HEAP_GENERATE_EXCEPTIONS = 0x04, + HEAP_ZERO_MEMORY = 0x08, + HEAP_REALLOC_IN_PLACE_ONLY = 0x10, + HEAP_TAIL_CHECKING_ENABLED = 0x20, + HEAP_FREE_CHECKING_ENABLED = 0x40, + HEAP_DISABLE_COALESCE_ON_FREE = 0x80; + +// These are not documented on MSDN +enum HEAP_CREATE_ALIGN_16 = 0; +enum HEAP_CREATE_ENABLE_TRACING = 0x020000; +enum HEAP_MAXIMUM_TAG = 0x000FFF; +enum HEAP_PSEUDO_TAG_FLAG = 0x008000; +enum HEAP_TAG_SHIFT = 16; +// ??? +//MACRO #define HEAP_MAKE_TAG_FLAGS(b,o) ((DWORD)((b)+(o)<<16))) + +enum ACCESS_MASK + KEY_QUERY_VALUE = 0x000001, + KEY_SET_VALUE = 0x000002, + KEY_CREATE_SUB_KEY = 0x000004, + KEY_ENUMERATE_SUB_KEYS = 0x000008, + KEY_NOTIFY = 0x000010, + KEY_CREATE_LINK = 0x000020, + KEY_WRITE = 0x020006, + KEY_EXECUTE = 0x020019, + KEY_READ = 0x020019, + KEY_ALL_ACCESS = 0x0F003F; + +static if (_WIN32_WINNT >= 0x502) { +enum ACCESS_MASK + KEY_WOW64_64KEY = 0x000100, + KEY_WOW64_32KEY = 0x000200; +} + +enum DWORD + REG_WHOLE_HIVE_VOLATILE = 1, + REG_REFRESH_HIVE = 2, + REG_NO_LAZY_FLUSH = 4; + +enum DWORD + REG_OPTION_RESERVED = 0, + REG_OPTION_NON_VOLATILE = 0, + REG_OPTION_VOLATILE = 1, + REG_OPTION_CREATE_LINK = 2, + REG_OPTION_BACKUP_RESTORE = 4, + REG_OPTION_OPEN_LINK = 8, + REG_LEGAL_OPTION = 15; + +enum SECURITY_INFORMATION + OWNER_SECURITY_INFORMATION = 0x00000001, + GROUP_SECURITY_INFORMATION = 0x00000002, + DACL_SECURITY_INFORMATION = 0x00000004, + SACL_SECURITY_INFORMATION = 0x00000008, + LABEL_SECURITY_INFORMATION = 0x00000010, + UNPROTECTED_SACL_SECURITY_INFORMATION = 0x10000000, + UNPROTECTED_DACL_SECURITY_INFORMATION = 0x20000000, + PROTECTED_SACL_SECURITY_INFORMATION = 0x40000000, + PROTECTED_DACL_SECURITY_INFORMATION = 0x80000000; + +enum DWORD MAXIMUM_PROCESSORS = 32; + +// VirtualAlloc(), etc +// ------------------- + +enum : DWORD { + PAGE_NOACCESS = 0x0001, + PAGE_READONLY = 0x0002, + PAGE_READWRITE = 0x0004, + PAGE_WRITECOPY = 0x0008, + PAGE_EXECUTE = 0x0010, + PAGE_EXECUTE_READ = 0x0020, + PAGE_EXECUTE_READWRITE = 0x0040, + PAGE_EXECUTE_WRITECOPY = 0x0080, + PAGE_GUARD = 0x0100, + PAGE_NOCACHE = 0x0200 +} + +enum : DWORD { + MEM_COMMIT = 0x00001000, + MEM_RESERVE = 0x00002000, + MEM_DECOMMIT = 0x00004000, + MEM_RELEASE = 0x00008000, + MEM_FREE = 0x00010000, + MEM_PRIVATE = 0x00020000, + MEM_MAPPED = 0x00040000, + MEM_RESET = 0x00080000, + MEM_TOP_DOWN = 0x00100000, + MEM_WRITE_WATCH = 0x00200000, // MinGW (???): 98/Me + MEM_PHYSICAL = 0x00400000, + MEM_4MB_PAGES = 0x80000000 +} + +// MinGW: also in ddk/ntifs.h +// CreateFileMapping() +enum DWORD + SEC_BASED = 0x00200000, + SEC_NO_CHANGE = 0x00400000, + SEC_FILE = 0x00800000, + SEC_IMAGE = 0x01000000, + SEC_VLM = 0x02000000, + SEC_RESERVE = 0x04000000, + SEC_COMMIT = 0x08000000, + SEC_NOCACHE = 0x10000000, + MEM_IMAGE = SEC_IMAGE; +// MinGW: end ntifs.h + +// ??? +enum ACCESS_MASK + SECTION_QUERY = 0x000001, + SECTION_MAP_WRITE = 0x000002, + SECTION_MAP_READ = 0x000004, + SECTION_MAP_EXECUTE = 0x000008, + SECTION_EXTEND_SIZE = 0x000010, + SECTION_ALL_ACCESS = 0x0F001F; + +// These are not documented on MSDN +enum MESSAGE_RESOURCE_UNICODE = 1; +enum RTL_CRITSECT_TYPE = 0; +enum RTL_RESOURCE_TYPE = 1; + +// COFF file format +// ---------------- + +// IMAGE_FILE_HEADER.Characteristics +enum WORD + IMAGE_FILE_RELOCS_STRIPPED = 0x0001, + IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002, + IMAGE_FILE_LINE_NUMS_STRIPPED = 0x0004, + IMAGE_FILE_LOCAL_SYMS_STRIPPED = 0x0008, + IMAGE_FILE_AGGRESIVE_WS_TRIM = 0x0010, + IMAGE_FILE_LARGE_ADDRESS_AWARE = 0x0020, + IMAGE_FILE_BYTES_REVERSED_LO = 0x0080, + IMAGE_FILE_32BIT_MACHINE = 0x0100, + IMAGE_FILE_DEBUG_STRIPPED = 0x0200, + IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP = 0x0400, + IMAGE_FILE_NET_RUN_FROM_SWAP = 0x0800, + IMAGE_FILE_SYSTEM = 0x1000, + IMAGE_FILE_DLL = 0x2000, + IMAGE_FILE_UP_SYSTEM_ONLY = 0x4000, + IMAGE_FILE_BYTES_REVERSED_HI = 0x8000; + +// IMAGE_FILE_HEADER.Machine +enum : WORD { + IMAGE_FILE_MACHINE_UNKNOWN = 0x0000, + IMAGE_FILE_MACHINE_I386 = 0x014C, + IMAGE_FILE_MACHINE_R3000 = 0x0162, + IMAGE_FILE_MACHINE_R4000 = 0x0166, + IMAGE_FILE_MACHINE_R10000 = 0x0168, + IMAGE_FILE_MACHINE_WCEMIPSV2 = 0x0169, + IMAGE_FILE_MACHINE_ALPHA = 0x0184, + IMAGE_FILE_MACHINE_SH3 = 0x01A2, + IMAGE_FILE_MACHINE_SH3DSP = 0x01A3, + IMAGE_FILE_MACHINE_SH4 = 0x01A6, + IMAGE_FILE_MACHINE_SH5 = 0x01A8, + IMAGE_FILE_MACHINE_ARM = 0x01C0, + IMAGE_FILE_MACHINE_THUMB = 0x01C2, + IMAGE_FILE_MACHINE_AM33 = 0x01D3, + IMAGE_FILE_MACHINE_POWERPC = 0x01F0, + IMAGE_FILE_MACHINE_POWERPCFP = 0x01F1, + IMAGE_FILE_MACHINE_IA64 = 0x0200, + IMAGE_FILE_MACHINE_MIPS16 = 0x0266, + IMAGE_FILE_MACHINE_MIPSFPU = 0x0366, + IMAGE_FILE_MACHINE_MIPSFPU16 = 0x0466, + IMAGE_FILE_MACHINE_EBC = 0x0EBC, + IMAGE_FILE_MACHINE_AMD64 = 0x8664, + IMAGE_FILE_MACHINE_M32R = 0x9041, + IMAGE_FILE_MACHINE_ARM64 = 0xAA64, +} + +// ??? +enum { + IMAGE_DOS_SIGNATURE = 0x5A4D, + IMAGE_OS2_SIGNATURE = 0x454E, + IMAGE_OS2_SIGNATURE_LE = 0x454C, + IMAGE_VXD_SIGNATURE = 0x454C, + IMAGE_NT_SIGNATURE = 0x4550 +} + +// IMAGE_OPTIONAL_HEADER.Magic +enum : WORD { + IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x010B, + IMAGE_ROM_OPTIONAL_HDR_MAGIC = 0x0107, + IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x020B +} + +// IMAGE_OPTIONAL_HEADER.Subsystem +enum : WORD { + IMAGE_SUBSYSTEM_UNKNOWN = 0, + IMAGE_SUBSYSTEM_NATIVE, + IMAGE_SUBSYSTEM_WINDOWS_GUI, + IMAGE_SUBSYSTEM_WINDOWS_CUI, // = 3 + IMAGE_SUBSYSTEM_OS2_CUI = 5, + IMAGE_SUBSYSTEM_POSIX_CUI = 7, + IMAGE_SUBSYSTEM_NATIVE_WINDOWS, + IMAGE_SUBSYSTEM_WINDOWS_CE_GUI, + IMAGE_SUBSYSTEM_EFI_APPLICATION, + IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER, + IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER, + IMAGE_SUBSYSTEM_EFI_ROM, + IMAGE_SUBSYSTEM_XBOX, // = 14 + IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION = 16 +} + +// IMAGE_OPTIONAL_HEADER.DllCharacteristics +enum WORD + IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE = 0x0040, + IMAGE_DLL_CHARACTERISTICS_FORCE_INTEGRITY = 0x0080, + IMAGE_DLL_CHARACTERISTICS_NX_COMPAT = 0x0100, + IMAGE_DLLCHARACTERISTICS_NO_ISOLATION = 0x0200, + IMAGE_DLLCHARACTERISTICS_NO_SEH = 0x0400, + IMAGE_DLLCHARACTERISTICS_NO_BIND = 0x0800, + IMAGE_DLLCHARACTERISTICS_WDM_DRIVER = 0x2000, + IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE = 0x8000; + +// ??? +enum IMAGE_SEPARATE_DEBUG_SIGNATURE = 0x4944; + +enum size_t + IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16, + IMAGE_SIZEOF_ROM_OPTIONAL_HEADER = 56, + IMAGE_SIZEOF_STD_OPTIONAL_HEADER = 28, + IMAGE_SIZEOF_NT_OPTIONAL_HEADER = 224, + IMAGE_SIZEOF_SHORT_NAME = 8, + IMAGE_SIZEOF_SECTION_HEADER = 40, + IMAGE_SIZEOF_SYMBOL = 18, + IMAGE_SIZEOF_AUX_SYMBOL = 18, + IMAGE_SIZEOF_RELOCATION = 10, + IMAGE_SIZEOF_BASE_RELOCATION = 8, + IMAGE_SIZEOF_LINENUMBER = 6, + IMAGE_SIZEOF_ARCHIVE_MEMBER_HDR = 60, + SIZEOF_RFPO_DATA = 16; + +PIMAGE_SECTION_HEADER IMAGE_FIRST_SECTION(PIMAGE_NT_HEADERS h) { + return cast(PIMAGE_SECTION_HEADER) + (cast(ubyte*) &h.OptionalHeader + h.FileHeader.SizeOfOptionalHeader); +} + +// ImageDirectoryEntryToDataEx() +enum : USHORT { + IMAGE_DIRECTORY_ENTRY_EXPORT = 0, + IMAGE_DIRECTORY_ENTRY_IMPORT, + IMAGE_DIRECTORY_ENTRY_RESOURCE, + IMAGE_DIRECTORY_ENTRY_EXCEPTION, + IMAGE_DIRECTORY_ENTRY_SECURITY, + IMAGE_DIRECTORY_ENTRY_BASERELOC, + IMAGE_DIRECTORY_ENTRY_DEBUG, + IMAGE_DIRECTORY_ENTRY_COPYRIGHT, // = 7 + IMAGE_DIRECTORY_ENTRY_ARCHITECTURE = 7, + IMAGE_DIRECTORY_ENTRY_GLOBALPTR, + IMAGE_DIRECTORY_ENTRY_TLS, + IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG, + IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT, + IMAGE_DIRECTORY_ENTRY_IAT, + IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT, + IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR, // = 14 +} + +// IMAGE_SECTION_HEADER.Characteristics +enum DWORD + IMAGE_SCN_TYPE_REG = 0x00000000, + IMAGE_SCN_TYPE_DSECT = 0x00000001, + IMAGE_SCN_TYPE_NOLOAD = 0x00000002, + IMAGE_SCN_TYPE_GROUP = 0x00000004, + IMAGE_SCN_TYPE_NO_PAD = 0x00000008, + IMAGE_SCN_TYPE_COPY = 0x00000010, + IMAGE_SCN_CNT_CODE = 0x00000020, + IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040, + IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080, + IMAGE_SCN_LNK_OTHER = 0x00000100, + IMAGE_SCN_LNK_INFO = 0x00000200, + IMAGE_SCN_TYPE_OVER = 0x00000400, + IMAGE_SCN_LNK_REMOVE = 0x00000800, + IMAGE_SCN_LNK_COMDAT = 0x00001000, + IMAGE_SCN_MEM_FARDATA = 0x00008000, + IMAGE_SCN_GPREL = 0x00008000, + IMAGE_SCN_MEM_PURGEABLE = 0x00020000, + IMAGE_SCN_MEM_16BIT = 0x00020000, + IMAGE_SCN_MEM_LOCKED = 0x00040000, + IMAGE_SCN_MEM_PRELOAD = 0x00080000, + IMAGE_SCN_ALIGN_1BYTES = 0x00100000, + IMAGE_SCN_ALIGN_2BYTES = 0x00200000, + IMAGE_SCN_ALIGN_4BYTES = 0x00300000, + IMAGE_SCN_ALIGN_8BYTES = 0x00400000, + IMAGE_SCN_ALIGN_16BYTES = 0x00500000, + IMAGE_SCN_ALIGN_32BYTES = 0x00600000, + IMAGE_SCN_ALIGN_64BYTES = 0x00700000, + IMAGE_SCN_ALIGN_128BYTES = 0x00800000, + IMAGE_SCN_ALIGN_256BYTES = 0x00900000, + IMAGE_SCN_ALIGN_512BYTES = 0x00A00000, + IMAGE_SCN_ALIGN_1024BYTES = 0x00B00000, + IMAGE_SCN_ALIGN_2048BYTES = 0x00C00000, + IMAGE_SCN_ALIGN_4096BYTES = 0x00D00000, + IMAGE_SCN_ALIGN_8192BYTES = 0x00E00000, + IMAGE_SCN_LNK_NRELOC_OVFL = 0x01000000, + IMAGE_SCN_MEM_DISCARDABLE = 0x02000000, + IMAGE_SCN_MEM_NOT_CACHED = 0x04000000, + IMAGE_SCN_MEM_NOT_PAGED = 0x08000000, + IMAGE_SCN_MEM_SHARED = 0x10000000, + IMAGE_SCN_MEM_EXECUTE = 0x20000000, + IMAGE_SCN_MEM_READ = 0x40000000, + IMAGE_SCN_MEM_WRITE = 0x80000000; + +/* The following constants are mostlydocumented at + * http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/pecoff.doc + * but don't seem to be defined in the HTML docs. + */ +enum : SHORT { + IMAGE_SYM_UNDEFINED = 0, + IMAGE_SYM_ABSOLUTE = -1, + IMAGE_SYM_DEBUG = -2 +} + +enum : ubyte { + IMAGE_SYM_TYPE_NULL, + IMAGE_SYM_TYPE_VOID, + IMAGE_SYM_TYPE_CHAR, + IMAGE_SYM_TYPE_SHORT, + IMAGE_SYM_TYPE_INT, + IMAGE_SYM_TYPE_LONG, + IMAGE_SYM_TYPE_FLOAT, + IMAGE_SYM_TYPE_DOUBLE, + IMAGE_SYM_TYPE_STRUCT, + IMAGE_SYM_TYPE_UNION, + IMAGE_SYM_TYPE_ENUM, + IMAGE_SYM_TYPE_MOE, + IMAGE_SYM_TYPE_BYTE, + IMAGE_SYM_TYPE_WORD, + IMAGE_SYM_TYPE_UINT, + IMAGE_SYM_TYPE_DWORD // = 15 +} +enum IMAGE_SYM_TYPE_PCODE = 32768; // ??? + +enum : ubyte { + IMAGE_SYM_DTYPE_NULL, + IMAGE_SYM_DTYPE_POINTER, + IMAGE_SYM_DTYPE_FUNCTION, + IMAGE_SYM_DTYPE_ARRAY +} + +enum : BYTE { + IMAGE_SYM_CLASS_END_OF_FUNCTION = 0xFF, + IMAGE_SYM_CLASS_NULL = 0, + IMAGE_SYM_CLASS_AUTOMATIC, + IMAGE_SYM_CLASS_EXTERNAL, + IMAGE_SYM_CLASS_STATIC, + IMAGE_SYM_CLASS_REGISTER, + IMAGE_SYM_CLASS_EXTERNAL_DEF, + IMAGE_SYM_CLASS_LABEL, + IMAGE_SYM_CLASS_UNDEFINED_LABEL, + IMAGE_SYM_CLASS_MEMBER_OF_STRUCT, + IMAGE_SYM_CLASS_ARGUMENT, + IMAGE_SYM_CLASS_STRUCT_TAG, + IMAGE_SYM_CLASS_MEMBER_OF_UNION, + IMAGE_SYM_CLASS_UNION_TAG, + IMAGE_SYM_CLASS_TYPE_DEFINITION, + IMAGE_SYM_CLASS_UNDEFINED_STATIC, + IMAGE_SYM_CLASS_ENUM_TAG, + IMAGE_SYM_CLASS_MEMBER_OF_ENUM, + IMAGE_SYM_CLASS_REGISTER_PARAM, + IMAGE_SYM_CLASS_BIT_FIELD, // = 18 + IMAGE_SYM_CLASS_FAR_EXTERNAL = 68, + IMAGE_SYM_CLASS_BLOCK = 100, + IMAGE_SYM_CLASS_FUNCTION, + IMAGE_SYM_CLASS_END_OF_STRUCT, + IMAGE_SYM_CLASS_FILE, + IMAGE_SYM_CLASS_SECTION, + IMAGE_SYM_CLASS_WEAK_EXTERNAL,// = 105 + IMAGE_SYM_CLASS_CLR_TOKEN = 107 +} + +enum : BYTE { + IMAGE_COMDAT_SELECT_NODUPLICATES = 1, + IMAGE_COMDAT_SELECT_ANY, + IMAGE_COMDAT_SELECT_SAME_SIZE, + IMAGE_COMDAT_SELECT_EXACT_MATCH, + IMAGE_COMDAT_SELECT_ASSOCIATIVE, + IMAGE_COMDAT_SELECT_LARGEST, + IMAGE_COMDAT_SELECT_NEWEST // = 7 +} + +enum : DWORD { + IMAGE_WEAK_EXTERN_SEARCH_NOLIBRARY = 1, + IMAGE_WEAK_EXTERN_SEARCH_LIBRARY, + IMAGE_WEAK_EXTERN_SEARCH_ALIAS +} + +enum : WORD { + IMAGE_REL_I386_ABSOLUTE = 0x0000, + IMAGE_REL_I386_DIR16 = 0x0001, + IMAGE_REL_I386_REL16 = 0x0002, + IMAGE_REL_I386_DIR32 = 0x0006, + IMAGE_REL_I386_DIR32NB = 0x0007, + IMAGE_REL_I386_SEG12 = 0x0009, + IMAGE_REL_I386_SECTION = 0x000A, + IMAGE_REL_I386_SECREL = 0x000B, + IMAGE_REL_I386_TOKEN = 0x000C, + IMAGE_REL_I386_SECREL7 = 0x000D, + IMAGE_REL_I386_REL32 = 0x0014 +} + +enum : WORD { + IMAGE_REL_AMD64_ABSOLUTE = 0x0000, + IMAGE_REL_AMD64_ADDR64 = 0x0001, + IMAGE_REL_AMD64_ADDR32 = 0x0002, + IMAGE_REL_AMD64_ADDR32NB = 0x0003, + IMAGE_REL_AMD64_REL32 = 0x0004, + IMAGE_REL_AMD64_REL32_1 = 0x0005, + IMAGE_REL_AMD64_REL32_2 = 0x0006, + IMAGE_REL_AMD64_REL32_3 = 0x0007, + IMAGE_REL_AMD64_REL32_4 = 0x0008, + IMAGE_REL_AMD64_REL32_5 = 0x0009, + IMAGE_REL_AMD64_SECTION = 0x000A, + IMAGE_REL_AMD64_SECREL = 0x000B, + IMAGE_REL_AMD64_SECREL7 = 0x000C, + IMAGE_REL_AMD64_TOKEN = 0x000D, + IMAGE_REL_AMD64_SREL32 = 0x000E, + IMAGE_REL_AMD64_PAIR = 0x000F, + IMAGE_REL_AMD64_SSPAN32 = 0x0010 +} + +enum : WORD { + IMAGE_REL_IA64_ABSOLUTE = 0x0000, + IMAGE_REL_IA64_IMM14 = 0x0001, + IMAGE_REL_IA64_IMM22 = 0x0002, + IMAGE_REL_IA64_IMM64 = 0x0003, + IMAGE_REL_IA64_DIR32 = 0x0004, + IMAGE_REL_IA64_DIR64 = 0x0005, + IMAGE_REL_IA64_PCREL21B = 0x0006, + IMAGE_REL_IA64_PCREL21M = 0x0007, + IMAGE_REL_IA64_PCREL21F = 0x0008, + IMAGE_REL_IA64_GPREL22 = 0x0009, + IMAGE_REL_IA64_LTOFF22 = 0x000A, + IMAGE_REL_IA64_SECTION = 0x000B, + IMAGE_REL_IA64_SECREL22 = 0x000C, + IMAGE_REL_IA64_SECREL64I = 0x000D, + IMAGE_REL_IA64_SECREL32 = 0x000E, + IMAGE_REL_IA64_DIR32NB = 0x0010, + IMAGE_REL_IA64_SREL14 = 0x0011, + IMAGE_REL_IA64_SREL22 = 0x0012, + IMAGE_REL_IA64_SREL32 = 0x0013, + IMAGE_REL_IA64_UREL32 = 0x0014, + IMAGE_REL_IA64_PCREL60X = 0x0015, + IMAGE_REL_IA64_PCREL60B = 0x0016, + IMAGE_REL_IA64_PCREL60F = 0x0017, + IMAGE_REL_IA64_PCREL60I = 0x0018, + IMAGE_REL_IA64_PCREL60M = 0x0019, + IMAGE_REL_IA64_IMMGPREL64 = 0x001A, + IMAGE_REL_IA64_TOKEN = 0x001B, + IMAGE_REL_IA64_GPREL32 = 0x001C, + IMAGE_REL_IA64_ADDEND = 0x001F +} + +enum : WORD { + IMAGE_REL_SH3_ABSOLUTE = 0x0000, + IMAGE_REL_SH3_DIRECT16 = 0x0001, + IMAGE_REL_SH3_DIRECT32 = 0x0002, + IMAGE_REL_SH3_DIRECT8 = 0x0003, + IMAGE_REL_SH3_DIRECT8_WORD = 0x0004, + IMAGE_REL_SH3_DIRECT8_LONG = 0x0005, + IMAGE_REL_SH3_DIRECT4 = 0x0006, + IMAGE_REL_SH3_DIRECT4_WORD = 0x0007, + IMAGE_REL_SH3_DIRECT4_LONG = 0x0008, + IMAGE_REL_SH3_PCREL8_WORD = 0x0009, + IMAGE_REL_SH3_PCREL8_LONG = 0x000A, + IMAGE_REL_SH3_PCREL12_WORD = 0x000B, + IMAGE_REL_SH3_STARTOF_SECTION = 0x000C, + IMAGE_REL_SH3_SIZEOF_SECTION = 0x000D, + IMAGE_REL_SH3_SECTION = 0x000E, + IMAGE_REL_SH3_SECREL = 0x000F, + IMAGE_REL_SH3_DIRECT32_NB = 0x0010, + IMAGE_REL_SH3_GPREL4_LONG = 0x0011, + IMAGE_REL_SH3_TOKEN = 0x0012, + IMAGE_REL_SHM_PCRELPT = 0x0013, + IMAGE_REL_SHM_REFLO = 0x0014, + IMAGE_REL_SHM_REFHALF = 0x0015, + IMAGE_REL_SHM_RELLO = 0x0016, + IMAGE_REL_SHM_RELHALF = 0x0017, + IMAGE_REL_SHM_PAIR = 0x0018, + IMAGE_REL_SHM_NOMODE = 0x8000 +} + +enum : WORD { + IMAGE_REL_M32R_ABSOLUTE = 0x0000, + IMAGE_REL_M32R_ADDR32 = 0x0001, + IMAGE_REL_M32R_ADDR32NB = 0x0002, + IMAGE_REL_M32R_ADDR24 = 0x0003, + IMAGE_REL_M32R_GPREL16 = 0x0004, + IMAGE_REL_M32R_PCREL24 = 0x0005, + IMAGE_REL_M32R_PCREL16 = 0x0006, + IMAGE_REL_M32R_PCREL8 = 0x0007, + IMAGE_REL_M32R_REFHALF = 0x0008, + IMAGE_REL_M32R_REFHI = 0x0009, + IMAGE_REL_M32R_REFLO = 0x000A, + IMAGE_REL_M32R_PAIR = 0x000B, + IMAGE_REL_M32R_SECTION = 0x000C, + IMAGE_REL_M32R_SECREL = 0x000D, + IMAGE_REL_M32R_TOKEN = 0x000E +} + +enum : WORD { + IMAGE_REL_MIPS_ABSOLUTE = 0x0000, + IMAGE_REL_MIPS_REFHALF = 0x0001, + IMAGE_REL_MIPS_REFWORD = 0x0002, + IMAGE_REL_MIPS_JMPADDR = 0x0003, + IMAGE_REL_MIPS_REFHI = 0x0004, + IMAGE_REL_MIPS_REFLO = 0x0005, + IMAGE_REL_MIPS_GPREL = 0x0006, + IMAGE_REL_MIPS_LITERAL = 0x0007, + IMAGE_REL_MIPS_SECTION = 0x000A, + IMAGE_REL_MIPS_SECREL = 0x000B, + IMAGE_REL_MIPS_SECRELLO = 0x000C, + IMAGE_REL_MIPS_SECRELHI = 0x000D, + IMAGE_REL_MIPS_JMPADDR16 = 0x0010, + IMAGE_REL_MIPS_REFWORDNB = 0x0022, + IMAGE_REL_MIPS_PAIR = 0x0025 +} + + +enum : WORD { + IMAGE_REL_ALPHA_ABSOLUTE, + IMAGE_REL_ALPHA_REFLONG, + IMAGE_REL_ALPHA_REFQUAD, + IMAGE_REL_ALPHA_GPREL32, + IMAGE_REL_ALPHA_LITERAL, + IMAGE_REL_ALPHA_LITUSE, + IMAGE_REL_ALPHA_GPDISP, + IMAGE_REL_ALPHA_BRADDR, + IMAGE_REL_ALPHA_HINT, + IMAGE_REL_ALPHA_INLINE_REFLONG, + IMAGE_REL_ALPHA_REFHI, + IMAGE_REL_ALPHA_REFLO, + IMAGE_REL_ALPHA_PAIR, + IMAGE_REL_ALPHA_MATCH, + IMAGE_REL_ALPHA_SECTION, + IMAGE_REL_ALPHA_SECREL, + IMAGE_REL_ALPHA_REFLONGNB, + IMAGE_REL_ALPHA_SECRELLO, + IMAGE_REL_ALPHA_SECRELHI // = 18 +} + +enum : WORD { + IMAGE_REL_PPC_ABSOLUTE, + IMAGE_REL_PPC_ADDR64, + IMAGE_REL_PPC_ADDR32, + IMAGE_REL_PPC_ADDR24, + IMAGE_REL_PPC_ADDR16, + IMAGE_REL_PPC_ADDR14, + IMAGE_REL_PPC_REL24, + IMAGE_REL_PPC_REL14, + IMAGE_REL_PPC_TOCREL16, + IMAGE_REL_PPC_TOCREL14, + IMAGE_REL_PPC_ADDR32NB, + IMAGE_REL_PPC_SECREL, + IMAGE_REL_PPC_SECTION, + IMAGE_REL_PPC_IFGLUE, + IMAGE_REL_PPC_IMGLUE, + IMAGE_REL_PPC_SECREL16, + IMAGE_REL_PPC_REFHI, + IMAGE_REL_PPC_REFLO, + IMAGE_REL_PPC_PAIR // = 18 +} + +// ??? +enum IMAGE_REL_PPC_TYPEMASK = 0x00FF; +enum IMAGE_REL_PPC_NEG = 0x0100; +enum IMAGE_REL_PPC_BRTAKEN = 0x0200; +enum IMAGE_REL_PPC_BRNTAKEN = 0x0400; +enum IMAGE_REL_PPC_TOCDEFN = 0x0800; + +enum { + IMAGE_REL_BASED_ABSOLUTE, + IMAGE_REL_BASED_HIGH, + IMAGE_REL_BASED_LOW, + IMAGE_REL_BASED_HIGHLOW, + IMAGE_REL_BASED_HIGHADJ, + IMAGE_REL_BASED_MIPS_JMPADDR +} +// End of constants documented in pecoff.doc + +enum size_t IMAGE_ARCHIVE_START_SIZE = 8; + +const TCHAR[] + IMAGE_ARCHIVE_START = "!\n", + IMAGE_ARCHIVE_END = "`\n", + IMAGE_ARCHIVE_PAD = "\n", + IMAGE_ARCHIVE_LINKER_MEMBER = "/ ", + IMAGE_ARCHIVE_LONGNAMES_MEMBER = "// "; + +enum IMAGE_ORDINAL_FLAG32 = 0x80000000; + +ulong IMAGE_ORDINAL64()(ulong Ordinal) { return Ordinal & 0xFFFF; } +uint IMAGE_ORDINAL32()(uint Ordinal) { return Ordinal & 0xFFFF; } + +bool IMAGE_SNAP_BY_ORDINAL32(uint Ordinal) { + return (Ordinal & IMAGE_ORDINAL_FLAG32) != 0; +} + +enum ulong IMAGE_ORDINAL_FLAG64 = 0x8000000000000000; + +bool IMAGE_SNAP_BY_ORDINAL64(ulong Ordinal) { + return (Ordinal & IMAGE_ORDINAL_FLAG64) != 0; +} + +// ??? +enum IMAGE_RESOURCE_NAME_IS_STRING = 0x80000000; +enum IMAGE_RESOURCE_DATA_IS_DIRECTORY = 0x80000000; + +enum : DWORD { + IMAGE_DEBUG_TYPE_UNKNOWN, + IMAGE_DEBUG_TYPE_COFF, + IMAGE_DEBUG_TYPE_CODEVIEW, + IMAGE_DEBUG_TYPE_FPO, + IMAGE_DEBUG_TYPE_MISC, + IMAGE_DEBUG_TYPE_EXCEPTION, + IMAGE_DEBUG_TYPE_FIXUP, + IMAGE_DEBUG_TYPE_OMAP_TO_SRC, + IMAGE_DEBUG_TYPE_OMAP_FROM_SRC, + IMAGE_DEBUG_TYPE_BORLAND // = 9 +} + +enum : ubyte { + FRAME_FPO, + FRAME_TRAP, + FRAME_TSS, + FRAME_NONFPO +} + +// ??? +enum IMAGE_DEBUG_MISC_EXENAME = 1; + +// ??? +enum N_BTMASK = 0x000F; +enum N_TMASK = 0x0030; +enum N_TMASK1 = 0x00C0; +enum N_TMASK2 = 0x00F0; +enum N_BTSHFT = 4; +enum N_TSHIFT = 2; + +enum int + IS_TEXT_UNICODE_ASCII16 = 0x0001, + IS_TEXT_UNICODE_STATISTICS = 0x0002, + IS_TEXT_UNICODE_CONTROLS = 0x0004, + IS_TEXT_UNICODE_SIGNATURE = 0x0008, + IS_TEXT_UNICODE_REVERSE_ASCII16 = 0x0010, + IS_TEXT_UNICODE_REVERSE_STATISTICS = 0x0020, + IS_TEXT_UNICODE_REVERSE_CONTROLS = 0x0040, + IS_TEXT_UNICODE_REVERSE_SIGNATURE = 0x0080, + IS_TEXT_UNICODE_ILLEGAL_CHARS = 0x0100, + IS_TEXT_UNICODE_ODD_LENGTH = 0x0200, + IS_TEXT_UNICODE_NULL_BYTES = 0x1000, + IS_TEXT_UNICODE_UNICODE_MASK = 0x000F, + IS_TEXT_UNICODE_REVERSE_MASK = 0x00F0, + IS_TEXT_UNICODE_NOT_UNICODE_MASK = 0x0F00, + IS_TEXT_UNICODE_NOT_ASCII_MASK = 0xF000; + +enum DWORD + SERVICE_KERNEL_DRIVER = 0x0001, + SERVICE_FILE_SYSTEM_DRIVER = 0x0002, + SERVICE_ADAPTER = 0x0004, + SERVICE_RECOGNIZER_DRIVER = 0x0008, + SERVICE_WIN32_OWN_PROCESS = 0x0010, + SERVICE_WIN32_SHARE_PROCESS = 0x0020, + SERVICE_INTERACTIVE_PROCESS = 0x0100, + SERVICE_DRIVER = 0x000B, + SERVICE_WIN32 = 0x0030, + SERVICE_TYPE_ALL = 0x013F; + +enum : DWORD { + SERVICE_BOOT_START = 0, + SERVICE_SYSTEM_START = 1, + SERVICE_AUTO_START = 2, + SERVICE_DEMAND_START = 3, + SERVICE_DISABLED = 4 +} + +enum : DWORD { + SERVICE_ERROR_IGNORE = 0, + SERVICE_ERROR_NORMAL = 1, + SERVICE_ERROR_SEVERE = 2, + SERVICE_ERROR_CRITICAL = 3 +} + + +enum uint + SE_OWNER_DEFAULTED = 0x0001, + SE_GROUP_DEFAULTED = 0x0002, + SE_DACL_PRESENT = 0x0004, + SE_DACL_DEFAULTED = 0x0008, + SE_SACL_PRESENT = 0x0010, + SE_SACL_DEFAULTED = 0x0020, + SE_DACL_AUTO_INHERIT_REQ = 0x0100, + SE_SACL_AUTO_INHERIT_REQ = 0x0200, + SE_DACL_AUTO_INHERITED = 0x0400, + SE_SACL_AUTO_INHERITED = 0x0800, + SE_DACL_PROTECTED = 0x1000, + SE_SACL_PROTECTED = 0x2000, + SE_SELF_RELATIVE = 0x8000; + +enum SECURITY_IMPERSONATION_LEVEL { + SecurityAnonymous, + SecurityIdentification, + SecurityImpersonation, + SecurityDelegation +} +alias SECURITY_IMPERSONATION_LEVEL* PSECURITY_IMPERSONATION_LEVEL; + +alias BOOLEAN SECURITY_CONTEXT_TRACKING_MODE; +alias BOOLEAN* PSECURITY_CONTEXT_TRACKING_MODE; + +enum size_t SECURITY_DESCRIPTOR_MIN_LENGTH = 20; + +enum DWORD + SECURITY_DESCRIPTOR_REVISION = 1, + SECURITY_DESCRIPTOR_REVISION1 = 1; + +enum DWORD + SE_PRIVILEGE_ENABLED_BY_DEFAULT = 0x00000001, + SE_PRIVILEGE_ENABLED = 0x00000002, + SE_PRIVILEGE_USED_FOR_ACCESS = 0x80000000; + +enum DWORD PRIVILEGE_SET_ALL_NECESSARY = 1; + +enum SECURITY_IMPERSONATION_LEVEL + SECURITY_MAX_IMPERSONATION_LEVEL = SECURITY_IMPERSONATION_LEVEL.SecurityDelegation, + DEFAULT_IMPERSONATION_LEVEL = SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation; + +enum BOOLEAN + SECURITY_DYNAMIC_TRACKING = true, + SECURITY_STATIC_TRACKING = false; + +// also in ddk/ntifs.h +enum DWORD + TOKEN_ASSIGN_PRIMARY = 0x0001, + TOKEN_DUPLICATE = 0x0002, + TOKEN_IMPERSONATE = 0x0004, + TOKEN_QUERY = 0x0008, + TOKEN_QUERY_SOURCE = 0x0010, + TOKEN_ADJUST_PRIVILEGES = 0x0020, + TOKEN_ADJUST_GROUPS = 0x0040, + TOKEN_ADJUST_DEFAULT = 0x0080, + + TOKEN_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED + | TOKEN_ASSIGN_PRIMARY + | TOKEN_DUPLICATE + | TOKEN_IMPERSONATE + | TOKEN_QUERY + | TOKEN_QUERY_SOURCE + | TOKEN_ADJUST_PRIVILEGES + | TOKEN_ADJUST_GROUPS + | TOKEN_ADJUST_DEFAULT, + TOKEN_READ = STANDARD_RIGHTS_READ | TOKEN_QUERY, + TOKEN_WRITE = STANDARD_RIGHTS_WRITE + | TOKEN_ADJUST_PRIVILEGES + | TOKEN_ADJUST_GROUPS + | TOKEN_ADJUST_DEFAULT, + TOKEN_EXECUTE = STANDARD_RIGHTS_EXECUTE; + +enum size_t TOKEN_SOURCE_LENGTH = 8; +// end ddk/ntifs.h + +enum : DWORD { + DLL_PROCESS_DETACH, + DLL_PROCESS_ATTACH, + DLL_THREAD_ATTACH, + DLL_THREAD_DETACH +} + +enum : DWORD { + DBG_CONTINUE = 0x00010002, + DBG_TERMINATE_THREAD = 0x40010003, + DBG_TERMINATE_PROCESS = 0x40010004, + DBG_CONTROL_C = 0x40010005, + DBG_CONTROL_BREAK = 0x40010008, + DBG_EXCEPTION_NOT_HANDLED = 0x80010001 +} + +enum : DWORD { + TAPE_ABSOLUTE_POSITION, + TAPE_LOGICAL_POSITION, + TAPE_PSEUDO_LOGICAL_POSITION +} + +enum : DWORD { + TAPE_REWIND, + TAPE_ABSOLUTE_BLOCK, + TAPE_LOGICAL_BLOCK, + TAPE_PSEUDO_LOGICAL_BLOCK, + TAPE_SPACE_END_OF_DATA, + TAPE_SPACE_RELATIVE_BLOCKS, + TAPE_SPACE_FILEMARKS, + TAPE_SPACE_SEQUENTIAL_FMKS, + TAPE_SPACE_SETMARKS, + TAPE_SPACE_SEQUENTIAL_SMKS +} + +enum DWORD + TAPE_DRIVE_FIXED = 0x00000001, + TAPE_DRIVE_SELECT = 0x00000002, + TAPE_DRIVE_INITIATOR = 0x00000004, + TAPE_DRIVE_ERASE_SHORT = 0x00000010, + TAPE_DRIVE_ERASE_LONG = 0x00000020, + TAPE_DRIVE_ERASE_BOP_ONLY = 0x00000040, + TAPE_DRIVE_ERASE_IMMEDIATE = 0x00000080, + TAPE_DRIVE_TAPE_CAPACITY = 0x00000100, + TAPE_DRIVE_TAPE_REMAINING = 0x00000200, + TAPE_DRIVE_FIXED_BLOCK = 0x00000400, + TAPE_DRIVE_VARIABLE_BLOCK = 0x00000800, + TAPE_DRIVE_WRITE_PROTECT = 0x00001000, + TAPE_DRIVE_EOT_WZ_SIZE = 0x00002000, + TAPE_DRIVE_ECC = 0x00010000, + TAPE_DRIVE_COMPRESSION = 0x00020000, + TAPE_DRIVE_PADDING = 0x00040000, + TAPE_DRIVE_REPORT_SMKS = 0x00080000, + TAPE_DRIVE_GET_ABSOLUTE_BLK = 0x00100000, + TAPE_DRIVE_GET_LOGICAL_BLK = 0x00200000, + TAPE_DRIVE_SET_EOT_WZ_SIZE = 0x00400000, + TAPE_DRIVE_EJECT_MEDIA = 0x01000000, + TAPE_DRIVE_CLEAN_REQUESTS = 0x02000000, + TAPE_DRIVE_SET_CMP_BOP_ONLY = 0x04000000, + TAPE_DRIVE_RESERVED_BIT = 0x80000000; + +enum DWORD + TAPE_DRIVE_LOAD_UNLOAD = 0x80000001, + TAPE_DRIVE_TENSION = 0x80000002, + TAPE_DRIVE_LOCK_UNLOCK = 0x80000004, + TAPE_DRIVE_REWIND_IMMEDIATE = 0x80000008, + TAPE_DRIVE_SET_BLOCK_SIZE = 0x80000010, + TAPE_DRIVE_LOAD_UNLD_IMMED = 0x80000020, + TAPE_DRIVE_TENSION_IMMED = 0x80000040, + TAPE_DRIVE_LOCK_UNLK_IMMED = 0x80000080, + TAPE_DRIVE_SET_ECC = 0x80000100, + TAPE_DRIVE_SET_COMPRESSION = 0x80000200, + TAPE_DRIVE_SET_PADDING = 0x80000400, + TAPE_DRIVE_SET_REPORT_SMKS = 0x80000800, + TAPE_DRIVE_ABSOLUTE_BLK = 0x80001000, + TAPE_DRIVE_ABS_BLK_IMMED = 0x80002000, + TAPE_DRIVE_LOGICAL_BLK = 0x80004000, + TAPE_DRIVE_LOG_BLK_IMMED = 0x80008000, + TAPE_DRIVE_END_OF_DATA = 0x80010000, + TAPE_DRIVE_RELATIVE_BLKS = 0x80020000, + TAPE_DRIVE_FILEMARKS = 0x80040000, + TAPE_DRIVE_SEQUENTIAL_FMKS = 0x80080000, + TAPE_DRIVE_SETMARKS = 0x80100000, + TAPE_DRIVE_SEQUENTIAL_SMKS = 0x80200000, + TAPE_DRIVE_REVERSE_POSITION = 0x80400000, + TAPE_DRIVE_SPACE_IMMEDIATE = 0x80800000, + TAPE_DRIVE_WRITE_SETMARKS = 0x81000000, + TAPE_DRIVE_WRITE_FILEMARKS = 0x82000000, + TAPE_DRIVE_WRITE_SHORT_FMKS = 0x84000000, + TAPE_DRIVE_WRITE_LONG_FMKS = 0x88000000, + TAPE_DRIVE_WRITE_MARK_IMMED = 0x90000000, + TAPE_DRIVE_FORMAT = 0xA0000000, + TAPE_DRIVE_FORMAT_IMMEDIATE = 0xC0000000, + TAPE_DRIVE_HIGH_FEATURES = 0x80000000; + +enum : DWORD { + TAPE_FIXED_PARTITIONS = 0, + TAPE_SELECT_PARTITIONS = 1, + TAPE_INITIATOR_PARTITIONS = 2 +} + +enum : DWORD { + TAPE_SETMARKS, + TAPE_FILEMARKS, + TAPE_SHORT_FILEMARKS, + TAPE_LONG_FILEMARKS +} + +enum : DWORD { + TAPE_ERASE_SHORT, + TAPE_ERASE_LONG +} + +enum : DWORD { + TAPE_LOAD, + TAPE_UNLOAD, + TAPE_TENSION, + TAPE_LOCK, + TAPE_UNLOCK, + TAPE_FORMAT +} + +enum : ULONG32 { + VER_PLATFORM_WIN32s, + VER_PLATFORM_WIN32_WINDOWS, + VER_PLATFORM_WIN32_NT +} + +enum : UCHAR { + VER_NT_WORKSTATION = 1, + VER_NT_DOMAIN_CONTROLLER, + VER_NT_SERVER +} + +enum USHORT + VER_SUITE_SMALLBUSINESS = 0x0001, + VER_SUITE_ENTERPRISE = 0x0002, + VER_SUITE_BACKOFFICE = 0x0004, + VER_SUITE_TERMINAL = 0x0010, + VER_SUITE_SMALLBUSINESS_RESTRICTED = 0x0020, + VER_SUITE_EMBEDDEDNT = 0x0040, + VER_SUITE_DATACENTER = 0x0080, + VER_SUITE_SINGLEUSERTS = 0x0100, + VER_SUITE_PERSONAL = 0x0200, + VER_SUITE_BLADE = 0x0400, + VER_SUITE_STORAGE_SERVER = 0x2000, + VER_SUITE_COMPUTE_SERVER = 0x4000; + +enum ULONG + WT_EXECUTEDEFAULT = 0x00000000, + WT_EXECUTEINIOTHREAD = 0x00000001, + WT_EXECUTEINWAITTHREAD = 0x00000004, + WT_EXECUTEONLYONCE = 0x00000008, + WT_EXECUTELONGFUNCTION = 0x00000010, + WT_EXECUTEINTIMERTHREAD = 0x00000020, + WT_EXECUTEINPERSISTENTTHREAD = 0x00000080, + WT_TRANSFER_IMPERSONATION = 0x00000100; + +static if (_WIN32_WINNT >= 0x500) { +enum DWORD + VER_MINORVERSION = 0x01, + VER_MAJORVERSION = 0x02, + VER_BUILDNUMBER = 0x04, + VER_PLATFORMID = 0x08, + VER_SERVICEPACKMINOR = 0x10, + VER_SERVICEPACKMAJOR = 0x20, + VER_SUITENAME = 0x40, + VER_PRODUCT_TYPE = 0x80; + + enum : DWORD { + VER_EQUAL = 1, + VER_GREATER, + VER_GREATER_EQUAL, + VER_LESS, + VER_LESS_EQUAL, + VER_AND, + VER_OR // = 7 + } +} + +static if (_WIN32_WINNT >= 0x501) { + enum : ULONG { + ACTIVATION_CONTEXT_SECTION_ASSEMBLY_INFORMATION = 1, + ACTIVATION_CONTEXT_SECTION_DLL_REDIRECTION, + ACTIVATION_CONTEXT_SECTION_WINDOW_CLASS_REDIRECTION, + ACTIVATION_CONTEXT_SECTION_COM_SERVER_REDIRECTION, + ACTIVATION_CONTEXT_SECTION_COM_INTERFACE_REDIRECTION, + ACTIVATION_CONTEXT_SECTION_COM_TYPE_LIBRARY_REDIRECTION, + ACTIVATION_CONTEXT_SECTION_COM_PROGID_REDIRECTION, // = 7 + ACTIVATION_CONTEXT_SECTION_CLR_SURROGATES = 9 + } +} + +// Macros +BYTE BTYPE()(BYTE x) { return cast(BYTE) (x & N_BTMASK); } +bool ISPTR()(uint x) { return (x & N_TMASK) == (IMAGE_SYM_DTYPE_POINTER << N_BTSHFT); } +bool ISFCN()(uint x) { return (x & N_TMASK) == (IMAGE_SYM_DTYPE_FUNCTION << N_BTSHFT); } +bool ISARY()(uint x) { return (x & N_TMASK) == (IMAGE_SYM_DTYPE_ARRAY << N_BTSHFT); } +bool ISTAG(uint x) { + return x == IMAGE_SYM_CLASS_STRUCT_TAG + || x == IMAGE_SYM_CLASS_UNION_TAG + || x == IMAGE_SYM_CLASS_ENUM_TAG; +} +uint INCREF(uint x) { + return ((x & ~N_BTMASK) << N_TSHIFT) | (IMAGE_SYM_DTYPE_POINTER << N_BTSHFT) + | (x & N_BTMASK); +} +uint DECREF()(uint x) { return ((x >>> N_TSHIFT) & ~N_BTMASK) | (x & N_BTMASK); } + +enum DWORD TLS_MINIMUM_AVAILABLE = 64; + +enum ULONG + IO_REPARSE_TAG_RESERVED_ZERO = 0, + IO_REPARSE_TAG_RESERVED_ONE = 1, + IO_REPARSE_TAG_RESERVED_RANGE = IO_REPARSE_TAG_RESERVED_ONE, + IO_REPARSE_TAG_SYMBOLIC_LINK = IO_REPARSE_TAG_RESERVED_ZERO, + IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003, + IO_REPARSE_TAG_SYMLINK = 0xA000000C, + IO_REPARSE_TAG_VALID_VALUES = 0xE000FFFF; + +/* Although these are semantically boolean, they are documented and + * implemented to return ULONG; this behaviour is preserved for compatibility + */ +ULONG IsReparseTagMicrosoft()(ULONG x) { return x & 0x80000000; } +ULONG IsReparseTagHighLatency()(ULONG x) { return x & 0x40000000; } +ULONG IsReparseTagNameSurrogate()(ULONG x) { return x & 0x20000000; } + +bool IsReparseTagValid(ULONG x) { + return !(x & ~IO_REPARSE_TAG_VALID_VALUES) && (x > IO_REPARSE_TAG_RESERVED_RANGE); +} + +// Doesn't seem to make sense, but anyway.... +ULONG WT_SET_MAX_THREADPOOL_THREADS(ref ULONG Flags, ushort Limit) { + return Flags |= Limit << 16; +} + +import urt.internal.sys.windows.basetyps; +/* also in urt.internal.sys.windows.basetyps +struct GUID { + uint Data1; + ushort Data2; + ushort Data3; + ubyte Data4[8]; +} +alias GUID* REFGUID, LPGUID; +*/ + +struct GENERIC_MAPPING { + ACCESS_MASK GenericRead; + ACCESS_MASK GenericWrite; + ACCESS_MASK GenericExecute; + ACCESS_MASK GenericAll; +} +alias GENERIC_MAPPING* PGENERIC_MAPPING; + +struct ACE_HEADER { + BYTE AceType; + BYTE AceFlags; + WORD AceSize; +} +alias ACE_HEADER* PACE_HEADER; + +struct ACCESS_ALLOWED_ACE { + ACE_HEADER Header; + ACCESS_MASK Mask; + DWORD SidStart; +} +alias ACCESS_ALLOWED_ACE* PACCESS_ALLOWED_ACE; + +struct ACCESS_DENIED_ACE { + ACE_HEADER Header; + ACCESS_MASK Mask; + DWORD SidStart; +} +alias ACCESS_DENIED_ACE* PACCESS_DENIED_ACE; + +struct SYSTEM_AUDIT_ACE { + ACE_HEADER Header; + ACCESS_MASK Mask; + DWORD SidStart; +} +alias SYSTEM_AUDIT_ACE *PSYSTEM_AUDIT_ACE; + +struct SYSTEM_ALARM_ACE { + ACE_HEADER Header; + ACCESS_MASK Mask; + DWORD SidStart; +} +alias SYSTEM_ALARM_ACE* PSYSTEM_ALARM_ACE; + +struct ACCESS_ALLOWED_OBJECT_ACE { + ACE_HEADER Header; + ACCESS_MASK Mask; + DWORD Flags; + GUID ObjectType; + GUID InheritedObjectType; + DWORD SidStart; +} +alias ACCESS_ALLOWED_OBJECT_ACE* PACCESS_ALLOWED_OBJECT_ACE; + +struct ACCESS_DENIED_OBJECT_ACE { + ACE_HEADER Header; + ACCESS_MASK Mask; + DWORD Flags; + GUID ObjectType; + GUID InheritedObjectType; + DWORD SidStart; +} +alias ACCESS_DENIED_OBJECT_ACE* PACCESS_DENIED_OBJECT_ACE; + +struct SYSTEM_AUDIT_OBJECT_ACE { + ACE_HEADER Header; + ACCESS_MASK Mask; + DWORD Flags; + GUID ObjectType; + GUID InheritedObjectType; + DWORD SidStart; +} +alias SYSTEM_AUDIT_OBJECT_ACE* PSYSTEM_AUDIT_OBJECT_ACE; + +struct SYSTEM_ALARM_OBJECT_ACE { + ACE_HEADER Header; + ACCESS_MASK Mask; + DWORD Flags; + GUID ObjectType; + GUID InheritedObjectType; + DWORD SidStart; +} +alias SYSTEM_ALARM_OBJECT_ACE* PSYSTEM_ALARM_OBJECT_ACE; + +struct ACL { + BYTE AclRevision; + BYTE Sbz1; + WORD AclSize; + WORD AceCount; + WORD Sbz2; +} +alias ACL* PACL; + +struct ACL_REVISION_INFORMATION { + DWORD AclRevision; +} + +struct ACL_SIZE_INFORMATION { + DWORD AceCount; + DWORD AclBytesInUse; + DWORD AclBytesFree; +} + +version (X86) { + // ??? +enum SIZE_OF_80387_REGISTERS = 80; +enum CONTEXT_i386 = 0x010000; +enum CONTEXT_i486 = 0x010000; +enum CONTEXT_CONTROL = CONTEXT_i386 | 0x01; +enum CONTEXT_INTEGER = CONTEXT_i386 | 0x02; +enum CONTEXT_SEGMENTS = CONTEXT_i386 | 0x04; +enum CONTEXT_FLOATING_POINT = CONTEXT_i386 | 0x08; +enum CONTEXT_DEBUG_REGISTERS = CONTEXT_i386 | 0x10; +enum CONTEXT_EXTENDED_REGISTERS = CONTEXT_i386 | 0x20; +enum CONTEXT_FULL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS; +enum CONTEXT_ALL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | + CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS | + CONTEXT_EXTENDED_REGISTERS; + +enum MAXIMUM_SUPPORTED_EXTENSION = 512; + + struct FLOATING_SAVE_AREA { + DWORD ControlWord; + DWORD StatusWord; + DWORD TagWord; + DWORD ErrorOffset; + DWORD ErrorSelector; + DWORD DataOffset; + DWORD DataSelector; + BYTE[80] RegisterArea; + DWORD Cr0NpxState; + } + + struct CONTEXT { + DWORD ContextFlags; + DWORD Dr0; + DWORD Dr1; + DWORD Dr2; + DWORD Dr3; + DWORD Dr6; + DWORD Dr7; + FLOATING_SAVE_AREA FloatSave; + DWORD SegGs; + DWORD SegFs; + DWORD SegEs; + DWORD SegDs; + DWORD Edi; + DWORD Esi; + DWORD Ebx; + DWORD Edx; + DWORD Ecx; + DWORD Eax; + DWORD Ebp; + DWORD Eip; + DWORD SegCs; + DWORD EFlags; + DWORD Esp; + DWORD SegSs; + BYTE[MAXIMUM_SUPPORTED_EXTENSION] ExtendedRegisters; + } + +} else version (X86_64) +{ +enum CONTEXT_AMD64 = 0x100000; + +enum CONTEXT_CONTROL = (CONTEXT_AMD64 | 0x1L); +enum CONTEXT_INTEGER = (CONTEXT_AMD64 | 0x2L); +enum CONTEXT_SEGMENTS = (CONTEXT_AMD64 | 0x4L); +enum CONTEXT_FLOATING_POINT = (CONTEXT_AMD64 | 0x8L); +enum CONTEXT_DEBUG_REGISTERS = (CONTEXT_AMD64 | 0x10L); + +enum CONTEXT_FULL = (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT); +enum CONTEXT_ALL = (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS); + +enum CONTEXT_EXCEPTION_ACTIVE = 0x8000000; +enum CONTEXT_SERVICE_ACTIVE = 0x10000000; +enum CONTEXT_EXCEPTION_REQUEST = 0x40000000; +enum CONTEXT_EXCEPTION_REPORTING = 0x80000000; + +enum INITIAL_MXCSR = 0x1f80; +enum INITIAL_FPCSR = 0x027f; + + align(16) struct M128A + { + ULONGLONG Low; + LONGLONG High; + } + alias M128A* PM128A; + + struct XMM_SAVE_AREA32 + { + WORD ControlWord; + WORD StatusWord; + BYTE TagWord; + BYTE Reserved1; + WORD ErrorOpcode; + DWORD ErrorOffset; + WORD ErrorSelector; + WORD Reserved2; + DWORD DataOffset; + WORD DataSelector; + WORD Reserved3; + DWORD MxCsr; + DWORD MxCsr_Mask; + M128A[8] FloatRegisters; + M128A[16] XmmRegisters; + BYTE[96] Reserved4; + } + alias XMM_SAVE_AREA32 PXMM_SAVE_AREA32; +enum LEGACY_SAVE_AREA_LENGTH = XMM_SAVE_AREA32.sizeof; + + align(16) struct CONTEXT + { + DWORD64 P1Home; + DWORD64 P2Home; + DWORD64 P3Home; + DWORD64 P4Home; + DWORD64 P5Home; + DWORD64 P6Home; + DWORD ContextFlags; + DWORD MxCsr; + WORD SegCs; + WORD SegDs; + WORD SegEs; + WORD SegFs; + WORD SegGs; + WORD SegSs; + DWORD EFlags; + DWORD64 Dr0; + DWORD64 Dr1; + DWORD64 Dr2; + DWORD64 Dr3; + DWORD64 Dr6; + DWORD64 Dr7; + DWORD64 Rax; + DWORD64 Rcx; + DWORD64 Rdx; + DWORD64 Rbx; + DWORD64 Rsp; + DWORD64 Rbp; + DWORD64 Rsi; + DWORD64 Rdi; + DWORD64 R8; + DWORD64 R9; + DWORD64 R10; + DWORD64 R11; + DWORD64 R12; + DWORD64 R13; + DWORD64 R14; + DWORD64 R15; + DWORD64 Rip; + union + { + XMM_SAVE_AREA32 FltSave; + XMM_SAVE_AREA32 FloatSave; + struct + { + M128A[2] Header; + M128A[8] Legacy; + M128A Xmm0; + M128A Xmm1; + M128A Xmm2; + M128A Xmm3; + M128A Xmm4; + M128A Xmm5; + M128A Xmm6; + M128A Xmm7; + M128A Xmm8; + M128A Xmm9; + M128A Xmm10; + M128A Xmm11; + M128A Xmm12; + M128A Xmm13; + M128A Xmm14; + M128A Xmm15; + } + } + M128A[26] VectorRegister; + DWORD64 VectorControl; + DWORD64 DebugControl; + DWORD64 LastBranchToRip; + DWORD64 LastBranchFromRip; + DWORD64 LastExceptionToRip; + DWORD64 LastExceptionFromRip; + } + +} else version(AArch64) { + enum CONTEXT_ARM64 = 0x400000; + + enum CONTEXT_CONTROL = (CONTEXT_ARM64 | 0x1L); + enum CONTEXT_INTEGER = (CONTEXT_ARM64 | 0x2L); + enum CONTEXT_SEGMENTS = (CONTEXT_ARM64 | 0x4L); + enum CONTEXT_FLOATING_POINT = (CONTEXT_ARM64 | 0x8L); + enum CONTEXT_DEBUG_REGISTERS = (CONTEXT_ARM64 | 0x10L); + + enum CONTEXT_FULL = (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT); + enum CONTEXT_ALL = (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS); + + enum ARM64_MAX_BREAKPOINTS = 8; + enum ARM64_MAX_WATCHPOINTS = 2; + + union ARM64_NT_NEON128 { + struct { + ULONGLONG Low; + LONGLONG High; + }; + double[2] D; + float[4] S; + WORD[8] H; + BYTE[16] B; + } + alias PARM64_NT_NEON128 = ARM64_NT_NEON128*; + + align(16) struct CONTEXT + { + DWORD ContextFlags; + DWORD Cpsr; + DWORD64[31] X; + DWORD64 Sp; + DWORD64 Pc; + + ARM64_NT_NEON128[32] V; + DWORD Fpcr; + + DWORD Fpsr; + + DWORD[ARM64_MAX_BREAKPOINTS] Bcr; + DWORD64[ARM64_MAX_BREAKPOINTS] Bvr; + DWORD[ARM64_MAX_WATCHPOINTS] Wcr; + DWORD64[ARM64_MAX_WATCHPOINTS] Wvr; + } +} else { + static assert(false, "Unsupported CPU"); + // Versions for PowerPC, Alpha, SHX, and MIPS removed. +} + +alias CONTEXT* PCONTEXT, LPCONTEXT; + +struct EXCEPTION_RECORD { + DWORD ExceptionCode; + DWORD ExceptionFlags; + EXCEPTION_RECORD* ExceptionRecord; + PVOID ExceptionAddress; + DWORD NumberParameters; + ULONG_PTR[EXCEPTION_MAXIMUM_PARAMETERS] ExceptionInformation; +} +alias EXCEPTION_RECORD* PEXCEPTION_RECORD, LPEXCEPTION_RECORD; + +struct EXCEPTION_POINTERS { + PEXCEPTION_RECORD ExceptionRecord; + PCONTEXT ContextRecord; +} +alias EXCEPTION_POINTERS* PEXCEPTION_POINTERS, LPEXCEPTION_POINTERS; + +union LARGE_INTEGER { + struct { + uint LowPart; + int HighPart; + } + long QuadPart; +} +alias LARGE_INTEGER* PLARGE_INTEGER; + +union ULARGE_INTEGER { + struct { + uint LowPart; + uint HighPart; + } + ulong QuadPart; +} +alias ULARGE_INTEGER* PULARGE_INTEGER; + +alias LARGE_INTEGER LUID; +alias LUID* PLUID; + +enum LUID SYSTEM_LUID = { QuadPart:999 }; + +align(4) struct LUID_AND_ATTRIBUTES { + LUID Luid; + DWORD Attributes; +} +alias LUID_AND_ATTRIBUTES* PLUID_AND_ATTRIBUTES; + +align(4) struct PRIVILEGE_SET { + DWORD PrivilegeCount; + DWORD Control; + LUID_AND_ATTRIBUTES _Privilege; + + LUID_AND_ATTRIBUTES* Privilege() return { return &_Privilege; } +} +alias PRIVILEGE_SET* PPRIVILEGE_SET; + +struct SECURITY_ATTRIBUTES { + DWORD nLength; + LPVOID lpSecurityDescriptor; + BOOL bInheritHandle; +} +alias SECURITY_ATTRIBUTES* PSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES; + +struct SECURITY_QUALITY_OF_SERVICE { + DWORD Length; + SECURITY_IMPERSONATION_LEVEL ImpersonationLevel; + SECURITY_CONTEXT_TRACKING_MODE ContextTrackingMode; + BOOLEAN EffectiveOnly; +} +alias SECURITY_QUALITY_OF_SERVICE* PSECURITY_QUALITY_OF_SERVICE; + +alias PVOID PACCESS_TOKEN; + +struct SE_IMPERSONATION_STATE { + PACCESS_TOKEN Token; + BOOLEAN CopyOnOpen; + BOOLEAN EffectiveOnly; + SECURITY_IMPERSONATION_LEVEL Level; +} +alias SE_IMPERSONATION_STATE* PSE_IMPERSONATION_STATE; + +struct SID_IDENTIFIER_AUTHORITY { + BYTE[6] Value; +} +alias SID_IDENTIFIER_AUTHORITY* PSID_IDENTIFIER_AUTHORITY, LPSID_IDENTIFIER_AUTHORITY; + +alias PVOID PSID; + +struct SID { + BYTE Revision; + BYTE SubAuthorityCount; + SID_IDENTIFIER_AUTHORITY IdentifierAuthority; + DWORD _SubAuthority; + + DWORD* SubAuthority() return { return &_SubAuthority; } +} +alias SID* PISID; + +struct SID_AND_ATTRIBUTES { + PSID Sid; + DWORD Attributes; +} +alias SID_AND_ATTRIBUTES* PSID_AND_ATTRIBUTES; + +struct TOKEN_SOURCE { + CHAR[TOKEN_SOURCE_LENGTH] SourceName = 0; + LUID SourceIdentifier; +} +alias TOKEN_SOURCE* PTOKEN_SOURCE; + +struct TOKEN_CONTROL { + LUID TokenId; + LUID AuthenticationId; + LUID ModifiedId; + TOKEN_SOURCE TokenSource; +} +alias TOKEN_CONTROL* PTOKEN_CONTROL; + +struct TOKEN_DEFAULT_DACL { + PACL DefaultDacl; +} +alias TOKEN_DEFAULT_DACL* PTOKEN_DEFAULT_DACL; + +struct TOKEN_GROUPS { + DWORD GroupCount; + SID_AND_ATTRIBUTES _Groups; + + SID_AND_ATTRIBUTES* Groups() return { return &_Groups; } +} +alias TOKEN_GROUPS* PTOKEN_GROUPS, LPTOKEN_GROUPS; + +struct TOKEN_OWNER { + PSID Owner; +} +alias TOKEN_OWNER* PTOKEN_OWNER; +enum SECURITY_MAX_SID_SIZE = 68; + +struct TOKEN_PRIMARY_GROUP { + PSID PrimaryGroup; +} +alias TOKEN_PRIMARY_GROUP* PTOKEN_PRIMARY_GROUP; + +struct TOKEN_PRIVILEGES { + DWORD PrivilegeCount; + LUID_AND_ATTRIBUTES _Privileges; + + LUID_AND_ATTRIBUTES* Privileges() return { return &_Privileges; } +} +alias TOKEN_PRIVILEGES* PTOKEN_PRIVILEGES, LPTOKEN_PRIVILEGES; + +enum TOKEN_TYPE { + TokenPrimary = 1, + TokenImpersonation +} +alias TOKEN_TYPE* PTOKEN_TYPE; + +struct TOKEN_STATISTICS { + LUID TokenId; + LUID AuthenticationId; + LARGE_INTEGER ExpirationTime; + TOKEN_TYPE TokenType; + SECURITY_IMPERSONATION_LEVEL ImpersonationLevel; + DWORD DynamicCharged; + DWORD DynamicAvailable; + DWORD GroupCount; + DWORD PrivilegeCount; + LUID ModifiedId; +} +alias TOKEN_STATISTICS* PTOKEN_STATISTICS; + +struct TOKEN_USER { + SID_AND_ATTRIBUTES User; +} +alias TOKEN_USER* PTOKEN_USER; + +struct TOKEN_MANDATORY_LABEL { + SID_AND_ATTRIBUTES Label; +} +alias PTOKEN_MANDATORY_LABEL = TOKEN_MANDATORY_LABEL*; +alias DWORD SECURITY_INFORMATION; +alias SECURITY_INFORMATION* PSECURITY_INFORMATION; +alias WORD SECURITY_DESCRIPTOR_CONTROL; +alias SECURITY_DESCRIPTOR_CONTROL* PSECURITY_DESCRIPTOR_CONTROL; + +struct SECURITY_DESCRIPTOR { + BYTE Revision; + BYTE Sbz1; + SECURITY_DESCRIPTOR_CONTROL Control; + PSID Owner; + PSID Group; + PACL Sacl; + PACL Dacl; +} +alias SECURITY_DESCRIPTOR* PSECURITY_DESCRIPTOR, PISECURITY_DESCRIPTOR; +enum TOKEN_ELEVATION_TYPE { + TokenElevationTypeDefault = 1, + TokenElevationTypeFull, + TokenElevationTypeLimited +} + +alias PTOKEN_ELEVATION_TYPE = TOKEN_ELEVATION_TYPE*; + +struct TOKEN_ELEVATION { + DWORD TokenIsElevated; +} +alias PTOKEN_ELEVATION = TOKEN_ELEVATION*; + +enum TOKEN_INFORMATION_CLASS { + TokenUser = 1, + TokenGroups, + TokenPrivileges, + TokenOwner, + TokenPrimaryGroup, + TokenDefaultDacl, + TokenSource, + TokenType, + TokenImpersonationLevel, + TokenStatistics, + TokenRestrictedSids, + TokenSessionId, + TokenGroupsAndPrivileges, + TokenSessionReference, + TokenSandBoxInert, + TokenAuditPolicy, + TokenOrigin, + TokenElevationType, + TokenLinkedToken, + TokenElevation, + TokenHasRestrictions, + TokenAccessInformation, + TokenVirtualizationAllowed, + TokenVirtualizationEnabled, + TokenIntegrityLevel, + TokenUIAccess, + TokenMandatoryPolicy, + TokenLogonSid, + TokenIsAppContainer, + TokenCapabilities, + TokenAppContainerSid, + TokenAppContainerNumber, + TokenUserClaimAttributes, + TokenDeviceClaimAttributes, + TokenRestrictedUserClaimAttributes, + TokenRestrictedDeviceClaimAttributes, + TokenDeviceGroups, + TokenRestrictedDeviceGroups, + TokenSecurityAttributes, + TokenIsRestricted, + TokenProcessTrustLevel, + MaxTokenInfoClass // MaxTokenInfoClass should always be the last enum +} + +enum SID_NAME_USE { + SidTypeUser = 1, + SidTypeGroup, + SidTypeDomain, + SidTypeAlias, + SidTypeWellKnownGroup, + SidTypeDeletedAccount, + SidTypeInvalid, + SidTypeUnknown, + SidTypeComputer +} +alias SID_NAME_USE* PSID_NAME_USE; + +enum WELL_KNOWN_SID_TYPE { + WinNullSid = 0, + WinWorldSid = 1, + WinLocalSid = 2, + WinCreatorOwnerSid = 3, + WinCreatorGroupSid = 4, + WinCreatorOwnerServerSid = 5, + WinCreatorGroupServerSid = 6, + WinNtAuthoritySid = 7, + WinDialupSid = 8, + WinNetworkSid = 9, + WinBatchSid = 10, + WinInteractiveSid = 11, + WinServiceSid = 12, + WinAnonymousSid = 13, + WinProxySid = 14, + WinEnterpriseControllersSid = 15, + WinSelfSid = 16, + WinAuthenticatedUserSid = 17, + WinRestrictedCodeSid = 18, + WinTerminalServerSid = 19, + WinRemoteLogonIdSid = 20, + WinLogonIdsSid = 21, + WinLocalSystemSid = 22, + WinLocalServiceSid = 23, + WinNetworkServiceSid = 24, + WinBuiltinDomainSid = 25, + WinBuiltinAdministratorsSid = 26, + WinBuiltinUsersSid = 27, + WinBuiltinGuestsSid = 28, + WinBuiltinPowerUsersSid = 29, + WinBuiltinAccountOperatorsSid = 30, + WinBuiltinSystemOperatorsSid = 31, + WinBuiltinPrintOperatorsSid = 32, + WinBuiltinBackupOperatorsSid = 33, + WinBuiltinReplicatorSid = 34, + WinBuiltinPreWindows2000CompatibleAccessSid = 35, + WinBuiltinRemoteDesktopUsersSid = 36, + WinBuiltinNetworkConfigurationOperatorsSid = 37, + WinAccountAdministratorSid = 38, + WinAccountGuestSid = 39, + WinAccountKrbtgtSid = 40, + WinAccountDomainAdminsSid = 41, + WinAccountDomainUsersSid = 42, + WinAccountDomainGuestsSid = 43, + WinAccountComputersSid = 44, + WinAccountControllersSid = 45, + WinAccountCertAdminsSid = 46, + WinAccountSchemaAdminsSid = 47, + WinAccountEnterpriseAdminsSid = 48, + WinAccountPolicyAdminsSid = 49, + WinAccountRasAndIasServersSid = 50, + WinNTLMAuthenticationSid = 51, + WinDigestAuthenticationSid = 52, + WinSChannelAuthenticationSid = 53, + WinThisOrganizationSid = 54, + WinOtherOrganizationSid = 55, + WinBuiltinIncomingForestTrustBuildersSid = 56, + WinBuiltinPerfMonitoringUsersSid = 57, + WinBuiltinPerfLoggingUsersSid = 58, + WinBuiltinAuthorizationAccessSid = 59, + WinBuiltinTerminalServerLicenseServersSid = 60, + WinBuiltinDCOMUsersSid = 61, + WinBuiltinIUsersSid = 62, + WinIUserSid = 63, + WinBuiltinCryptoOperatorsSid = 64, + WinUntrustedLabelSid = 65, + WinLowLabelSid = 66, + WinMediumLabelSid = 67, + WinHighLabelSid = 68, + WinSystemLabelSid = 69, + WinWriteRestrictedCodeSid = 70, + WinCreatorOwnerRightsSid = 71, + WinCacheablePrincipalsGroupSid = 72, + WinNonCacheablePrincipalsGroupSid = 73, + WinEnterpriseReadonlyControllersSid = 74, + WinAccountReadonlyControllersSid = 75, + WinBuiltinEventLogReadersGroup = 76, + WinNewEnterpriseReadonlyControllersSid = 77, + WinBuiltinCertSvcDComAccessGroup = 78, + WinMediumPlusLabelSid = 79, + WinLocalLogonSid = 80, + WinConsoleLogonSid = 81, + WinThisOrganizationCertificateSid = 82, + WinApplicationPackageAuthoritySid = 83, + WinBuiltinAnyPackageSid = 84, + WinCapabilityInternetClientSid = 85, + WinCapabilityInternetClientServerSid = 86, + WinCapabilityPrivateNetworkClientServerSid = 87, + WinCapabilityPicturesLibrarySid = 88, + WinCapabilityVideosLibrarySid = 89, + WinCapabilityMusicLibrarySid = 90, + WinCapabilityDocumentsLibrarySid = 91, + WinCapabilitySharedUserCertificatesSid = 92, + WinCapabilityEnterpriseAuthenticationSid = 93, + WinCapabilityRemovableStorageSid = 94 +} +struct QUOTA_LIMITS { + SIZE_T PagedPoolLimit; + SIZE_T NonPagedPoolLimit; + SIZE_T MinimumWorkingSetSize; + SIZE_T MaximumWorkingSetSize; + SIZE_T PagefileLimit; + LARGE_INTEGER TimeLimit; +} +alias QUOTA_LIMITS* PQUOTA_LIMITS; + +struct IO_COUNTERS { + ULONGLONG ReadOperationCount; + ULONGLONG WriteOperationCount; + ULONGLONG OtherOperationCount; + ULONGLONG ReadTransferCount; + ULONGLONG WriteTransferCount; + ULONGLONG OtherTransferCount; +} +alias IO_COUNTERS* PIO_COUNTERS; + +struct FILE_NOTIFY_INFORMATION { + DWORD NextEntryOffset; + DWORD Action; + DWORD FileNameLength = 0; + WCHAR _FileName = 0; + + WCHAR* FileName() return { return &_FileName; } +} +alias FILE_NOTIFY_INFORMATION* PFILE_NOTIFY_INFORMATION; + +struct TAPE_ERASE { + DWORD Type; + BOOLEAN Immediate; +} +alias TAPE_ERASE* PTAPE_ERASE; + +struct TAPE_GET_DRIVE_PARAMETERS { + BOOLEAN ECC; + BOOLEAN Compression; + BOOLEAN DataPadding; + BOOLEAN ReportSetmarks; + DWORD DefaultBlockSize; + DWORD MaximumBlockSize; + DWORD MinimumBlockSize; + DWORD MaximumPartitionCount; + DWORD FeaturesLow; + DWORD FeaturesHigh; + DWORD EOTWarningZoneSize; +} +alias TAPE_GET_DRIVE_PARAMETERS* PTAPE_GET_DRIVE_PARAMETERS; + +struct TAPE_GET_MEDIA_PARAMETERS { + LARGE_INTEGER Capacity; + LARGE_INTEGER Remaining; + DWORD BlockSize; + DWORD PartitionCount; + BOOLEAN WriteProtected; +} +alias TAPE_GET_MEDIA_PARAMETERS* PTAPE_GET_MEDIA_PARAMETERS; + +struct TAPE_GET_POSITION { + ULONG Type; + ULONG Partition; + ULONG OffsetLow; + ULONG OffsetHigh; +} +alias TAPE_GET_POSITION* PTAPE_GET_POSITION; + +struct TAPE_PREPARE { + DWORD Operation; + BOOLEAN Immediate; +} +alias TAPE_PREPARE* PTAPE_PREPARE; + +struct TAPE_SET_DRIVE_PARAMETERS { + BOOLEAN ECC; + BOOLEAN Compression; + BOOLEAN DataPadding; + BOOLEAN ReportSetmarks; + ULONG EOTWarningZoneSize; +} +alias TAPE_SET_DRIVE_PARAMETERS* PTAPE_SET_DRIVE_PARAMETERS; + +struct TAPE_SET_MEDIA_PARAMETERS { + ULONG BlockSize; +} +alias TAPE_SET_MEDIA_PARAMETERS* PTAPE_SET_MEDIA_PARAMETERS; + +struct TAPE_SET_POSITION { + DWORD Method; + DWORD Partition; + LARGE_INTEGER Offset; + BOOLEAN Immediate; +} +alias TAPE_SET_POSITION* PTAPE_SET_POSITION; + +struct TAPE_WRITE_MARKS { + DWORD Type; + DWORD Count; + BOOLEAN Immediate; +} +alias TAPE_WRITE_MARKS* PTAPE_WRITE_MARKS; + +struct TAPE_CREATE_PARTITION { + DWORD Method; + DWORD Count; + DWORD Size; +} +alias TAPE_CREATE_PARTITION* PTAPE_CREATE_PARTITION; + +struct MEMORY_BASIC_INFORMATION { + PVOID BaseAddress; + PVOID AllocationBase; + DWORD AllocationProtect; + SIZE_T RegionSize; + DWORD State; + DWORD Protect; + DWORD Type; +} +alias MEMORY_BASIC_INFORMATION* PMEMORY_BASIC_INFORMATION; + +struct MESSAGE_RESOURCE_ENTRY { + WORD Length; + WORD Flags; + BYTE _Text; + + BYTE* Text() return { return &_Text; } +} +alias MESSAGE_RESOURCE_ENTRY* PMESSAGE_RESOURCE_ENTRY; + +struct MESSAGE_RESOURCE_BLOCK { + DWORD LowId; + DWORD HighId; + DWORD OffsetToEntries; +} +alias MESSAGE_RESOURCE_BLOCK* PMESSAGE_RESOURCE_BLOCK; + +struct MESSAGE_RESOURCE_DATA { + DWORD NumberOfBlocks; + MESSAGE_RESOURCE_BLOCK _Blocks; + + MESSAGE_RESOURCE_BLOCK* Blocks() return { return &_Blocks; } +} +alias MESSAGE_RESOURCE_DATA* PMESSAGE_RESOURCE_DATA; + +struct LIST_ENTRY { + LIST_ENTRY* Flink; + LIST_ENTRY* Blink; +} +alias LIST_ENTRY* PLIST_ENTRY; +alias LIST_ENTRY _LIST_ENTRY; + +struct SINGLE_LIST_ENTRY { + SINGLE_LIST_ENTRY* Next; +} + +version (Win64) { + align (16) + struct SLIST_ENTRY { + SLIST_ENTRY* Next; + } +} else { + alias SINGLE_LIST_ENTRY SLIST_ENTRY; +} +alias SINGLE_LIST_ENTRY* PSINGLE_LIST_ENTRY, PSLIST_ENTRY; + +union SLIST_HEADER { + ULONGLONG Alignment; + struct { + SLIST_ENTRY Next; + WORD Depth; + WORD Sequence; + } +} +alias SLIST_HEADER* PSLIST_HEADER; + +struct RTL_CRITICAL_SECTION_DEBUG { + WORD Type; + WORD CreatorBackTraceIndex; + RTL_CRITICAL_SECTION* CriticalSection; + LIST_ENTRY ProcessLocksList; + DWORD EntryCount; + DWORD ContentionCount; + DWORD[2] Spare; +} +alias RTL_CRITICAL_SECTION_DEBUG* PRTL_CRITICAL_SECTION_DEBUG; +alias RTL_CRITICAL_SECTION_DEBUG _RTL_CRITICAL_SECTION_DEBUG; + +struct RTL_CRITICAL_SECTION { + PRTL_CRITICAL_SECTION_DEBUG DebugInfo; + LONG LockCount; + LONG RecursionCount; + HANDLE OwningThread; + HANDLE LockSemaphore; + ULONG_PTR SpinCount; + alias Reserved = SpinCount; +} +alias RTL_CRITICAL_SECTION* PRTL_CRITICAL_SECTION; +alias RTL_CRITICAL_SECTION _RTL_CRITICAL_SECTION; + +struct EVENTLOGRECORD { + DWORD Length; + DWORD Reserved; + DWORD RecordNumber; + DWORD TimeGenerated; + DWORD TimeWritten; + DWORD EventID; + WORD EventType; + WORD NumStrings; + WORD EventCategory; + WORD ReservedFlags; + DWORD ClosingRecordNumber; + DWORD StringOffset; + DWORD UserSidLength; + DWORD UserSidOffset; + DWORD DataLength; + DWORD DataOffset; +} +alias EVENTLOGRECORD* PEVENTLOGRECORD; + +struct OSVERSIONINFOA { + DWORD dwOSVersionInfoSize = OSVERSIONINFOA.sizeof; + DWORD dwMajorVersion; + DWORD dwMinorVersion; + DWORD dwBuildNumber; + DWORD dwPlatformId; + CHAR[128] szCSDVersion = 0; +} +alias OSVERSIONINFOA* POSVERSIONINFOA, LPOSVERSIONINFOA; + +struct OSVERSIONINFOW { + DWORD dwOSVersionInfoSize = OSVERSIONINFOW.sizeof; + DWORD dwMajorVersion; + DWORD dwMinorVersion; + DWORD dwBuildNumber; + DWORD dwPlatformId; + WCHAR[128] szCSDVersion = 0; +} +alias OSVERSIONINFOW* POSVERSIONINFOW, LPOSVERSIONINFOW; + +struct OSVERSIONINFOEXA { + DWORD dwOSVersionInfoSize; + DWORD dwMajorVersion; + DWORD dwMinorVersion; + DWORD dwBuildNumber; + DWORD dwPlatformId; + CHAR[128] szCSDVersion = 0; + WORD wServicePackMajor; + WORD wServicePackMinor; + WORD wSuiteMask; + BYTE wProductType; + BYTE wReserved; +} +alias OSVERSIONINFOEXA* POSVERSIONINFOEXA, LPOSVERSIONINFOEXA; + +struct OSVERSIONINFOEXW { + DWORD dwOSVersionInfoSize; + DWORD dwMajorVersion; + DWORD dwMinorVersion; + DWORD dwBuildNumber; + DWORD dwPlatformId; + WCHAR[128] szCSDVersion = 0; + WORD wServicePackMajor; + WORD wServicePackMinor; + WORD wSuiteMask; + BYTE wProductType; + BYTE wReserved; +} +alias OSVERSIONINFOEXW* POSVERSIONINFOEXW, LPOSVERSIONINFOEXW; + +align(2) struct IMAGE_VXD_HEADER { + WORD e32_magic; + BYTE e32_border; + BYTE e32_worder; + DWORD e32_level; + WORD e32_cpu; + WORD e32_os; + DWORD e32_ver; + DWORD e32_mflags; + DWORD e32_mpages; + DWORD e32_startobj; + DWORD e32_eip; + DWORD e32_stackobj; + DWORD e32_esp; + DWORD e32_pagesize; + DWORD e32_lastpagesize; + DWORD e32_fixupsize; + DWORD e32_fixupsum; + DWORD e32_ldrsize; + DWORD e32_ldrsum; + DWORD e32_objtab; + DWORD e32_objcnt; + DWORD e32_objmap; + DWORD e32_itermap; + DWORD e32_rsrctab; + DWORD e32_rsrccnt; + DWORD e32_restab; + DWORD e32_enttab; + DWORD e32_dirtab; + DWORD e32_dircnt; + DWORD e32_fpagetab; + DWORD e32_frectab; + DWORD e32_impmod; + DWORD e32_impmodcnt; + DWORD e32_impproc; + DWORD e32_pagesum; + DWORD e32_datapage; + DWORD e32_preload; + DWORD e32_nrestab; + DWORD e32_cbnrestab; + DWORD e32_nressum; + DWORD e32_autodata; + DWORD e32_debuginfo; + DWORD e32_debuglen; + DWORD e32_instpreload; + DWORD e32_instdemand; + DWORD e32_heapsize; + BYTE[12] e32_res3; + DWORD e32_winresoff; + DWORD e32_winreslen; + WORD e32_devid; + WORD e32_ddkver; +} +alias IMAGE_VXD_HEADER* PIMAGE_VXD_HEADER; + +align(4): +struct IMAGE_FILE_HEADER { + WORD Machine; + WORD NumberOfSections; + DWORD TimeDateStamp; + DWORD PointerToSymbolTable; + DWORD NumberOfSymbols; + WORD SizeOfOptionalHeader; + WORD Characteristics; +} +alias IMAGE_FILE_HEADER* PIMAGE_FILE_HEADER; +// const IMAGE_SIZEOF_FILE_HEADER = IMAGE_FILE_HEADER.sizeof; + +struct IMAGE_DATA_DIRECTORY { + DWORD VirtualAddress; + DWORD Size; +} +alias IMAGE_DATA_DIRECTORY* PIMAGE_DATA_DIRECTORY; + +struct IMAGE_OPTIONAL_HEADER32 { + WORD Magic; + BYTE MajorLinkerVersion; + BYTE MinorLinkerVersion; + DWORD SizeOfCode; + DWORD SizeOfInitializedData; + DWORD SizeOfUninitializedData; + DWORD AddressOfEntryPoint; + DWORD BaseOfCode; + DWORD BaseOfData; + DWORD ImageBase; + DWORD SectionAlignment; + DWORD FileAlignment; + WORD MajorOperatingSystemVersion; + WORD MinorOperatingSystemVersion; + WORD MajorImageVersion; + WORD MinorImageVersion; + WORD MajorSubsystemVersion; + WORD MinorSubsystemVersion; + DWORD Win32VersionValue; + DWORD SizeOfImage; + DWORD SizeOfHeaders; + DWORD CheckSum; + WORD Subsystem; + WORD DllCharacteristics; + DWORD SizeOfStackReserve; + DWORD SizeOfStackCommit; + DWORD SizeOfHeapReserve; + DWORD SizeOfHeapCommit; + DWORD LoaderFlags; + DWORD NumberOfRvaAndSizes; + IMAGE_DATA_DIRECTORY[IMAGE_NUMBEROF_DIRECTORY_ENTRIES] DataDirectory; +} +alias IMAGE_OPTIONAL_HEADER32* PIMAGE_OPTIONAL_HEADER32; + +struct IMAGE_OPTIONAL_HEADER64 { + WORD Magic; + BYTE MajorLinkerVersion; + BYTE MinorLinkerVersion; + DWORD SizeOfCode; + DWORD SizeOfInitializedData; + DWORD SizeOfUninitializedData; + DWORD AddressOfEntryPoint; + DWORD BaseOfCode; + ULONGLONG ImageBase; + DWORD SectionAlignment; + DWORD FileAlignment; + WORD MajorOperatingSystemVersion; + WORD MinorOperatingSystemVersion; + WORD MajorImageVersion; + WORD MinorImageVersion; + WORD MajorSubsystemVersion; + WORD MinorSubsystemVersion; + DWORD Win32VersionValue; + DWORD SizeOfImage; + DWORD SizeOfHeaders; + DWORD CheckSum; + WORD Subsystem; + WORD DllCharacteristics; + ULONGLONG SizeOfStackReserve; + ULONGLONG SizeOfStackCommit; + ULONGLONG SizeOfHeapReserve; + ULONGLONG SizeOfHeapCommit; + DWORD LoaderFlags; + DWORD NumberOfRvaAndSizes; + IMAGE_DATA_DIRECTORY[IMAGE_NUMBEROF_DIRECTORY_ENTRIES] DataDirectory; +} +alias IMAGE_OPTIONAL_HEADER64* PIMAGE_OPTIONAL_HEADER64; + +struct IMAGE_ROM_OPTIONAL_HEADER { + WORD Magic; + BYTE MajorLinkerVersion; + BYTE MinorLinkerVersion; + DWORD SizeOfCode; + DWORD SizeOfInitializedData; + DWORD SizeOfUninitializedData; + DWORD AddressOfEntryPoint; + DWORD BaseOfCode; + DWORD BaseOfData; + DWORD BaseOfBss; + DWORD GprMask; + DWORD[4] CprMask; + DWORD GpValue; +} +alias IMAGE_ROM_OPTIONAL_HEADER* PIMAGE_ROM_OPTIONAL_HEADER; + +align(2): +struct IMAGE_DOS_HEADER { + WORD e_magic; + WORD e_cblp; + WORD e_cp; + WORD e_crlc; + WORD e_cparhdr; + WORD e_minalloc; + WORD e_maxalloc; + WORD e_ss; + WORD e_sp; + WORD e_csum; + WORD e_ip; + WORD e_cs; + WORD e_lfarlc; + WORD e_ovno; + WORD[4] e_res; + WORD e_oemid; + WORD e_oeminfo; + WORD[10] e_res2; + LONG e_lfanew; +} +alias IMAGE_DOS_HEADER* PIMAGE_DOS_HEADER; + +struct IMAGE_OS2_HEADER { + WORD ne_magic; + CHAR ne_ver = 0; + CHAR ne_rev = 0; + WORD ne_enttab; + WORD ne_cbenttab; + LONG ne_crc; + WORD ne_flags; + WORD ne_autodata; + WORD ne_heap; + WORD ne_stack; + LONG ne_csip; + LONG ne_sssp; + WORD ne_cseg; + WORD ne_cmod; + WORD ne_cbnrestab; + WORD ne_segtab; + WORD ne_rsrctab; + WORD ne_restab; + WORD ne_modtab; + WORD ne_imptab; + LONG ne_nrestab; + WORD ne_cmovent; + WORD ne_align; + WORD ne_cres; + BYTE ne_exetyp; + BYTE ne_flagsothers; + WORD ne_pretthunks; + WORD ne_psegrefbytes; + WORD ne_swaparea; + WORD ne_expver; +} +alias IMAGE_OS2_HEADER* PIMAGE_OS2_HEADER; + +align(4) struct IMAGE_NT_HEADERS32 { + DWORD Signature; + IMAGE_FILE_HEADER FileHeader; + IMAGE_OPTIONAL_HEADER32 OptionalHeader; +} +alias IMAGE_NT_HEADERS32* PIMAGE_NT_HEADERS32; + +align(4) struct IMAGE_NT_HEADERS64 { + DWORD Signature; + IMAGE_FILE_HEADER FileHeader; + IMAGE_OPTIONAL_HEADER64 OptionalHeader; +} +alias IMAGE_NT_HEADERS64* PIMAGE_NT_HEADERS64; + +struct IMAGE_ROM_HEADERS { + IMAGE_FILE_HEADER FileHeader; + IMAGE_ROM_OPTIONAL_HEADER OptionalHeader; +} +alias IMAGE_ROM_HEADERS* PIMAGE_ROM_HEADERS; + +struct IMAGE_SECTION_HEADER { + BYTE[IMAGE_SIZEOF_SHORT_NAME] Name; + union _Misc { + DWORD PhysicalAddress; + DWORD VirtualSize; + } + _Misc Misc; + DWORD VirtualAddress; + DWORD SizeOfRawData; + DWORD PointerToRawData; + DWORD PointerToRelocations; + DWORD PointerToLinenumbers; + WORD NumberOfRelocations; + WORD NumberOfLinenumbers; + DWORD Characteristics; +} +alias IMAGE_SECTION_HEADER* PIMAGE_SECTION_HEADER; + +struct IMAGE_SYMBOL { + union _N { + BYTE[8] ShortName; + struct _Name { + DWORD Short; + DWORD Long; + } + _Name Name; + DWORD[2] LongName; // PBYTE[2] + } + _N N; + DWORD Value; + SHORT SectionNumber; + WORD Type; + BYTE StorageClass; + BYTE NumberOfAuxSymbols; +} +alias IMAGE_SYMBOL* PIMAGE_SYMBOL; + +union IMAGE_AUX_SYMBOL { + struct _Sym { + DWORD TagIndex; + union _Misc { + struct _LnSz { + WORD Linenumber; + WORD Size; + } + _LnSz LnSz; + DWORD TotalSize; + } + _Misc Misc; + union _FcnAry { + struct _Function { + DWORD PointerToLinenumber; + DWORD PointerToNextFunction; + } + _Function Function; + struct _Array { + WORD[4] Dimension; + } + _Array Array; + } + _FcnAry FcnAry; + WORD TvIndex; + } + _Sym Sym; + struct _File { + BYTE[IMAGE_SIZEOF_SYMBOL] Name; + } + _File File; + struct _Section { + DWORD Length; + WORD NumberOfRelocations; + WORD NumberOfLinenumbers; + DWORD CheckSum; + SHORT Number; + BYTE Selection; + } + _Section Section; +} +alias IMAGE_AUX_SYMBOL* PIMAGE_AUX_SYMBOL; + +struct IMAGE_COFF_SYMBOLS_HEADER { + DWORD NumberOfSymbols; + DWORD LvaToFirstSymbol; + DWORD NumberOfLinenumbers; + DWORD LvaToFirstLinenumber; + DWORD RvaToFirstByteOfCode; + DWORD RvaToLastByteOfCode; + DWORD RvaToFirstByteOfData; + DWORD RvaToLastByteOfData; +} +alias IMAGE_COFF_SYMBOLS_HEADER* PIMAGE_COFF_SYMBOLS_HEADER; + +struct IMAGE_RELOCATION { + union { + DWORD VirtualAddress; + DWORD RelocCount; + } + DWORD SymbolTableIndex; + WORD Type; +} +alias IMAGE_RELOCATION* PIMAGE_RELOCATION; + +align(4) struct IMAGE_BASE_RELOCATION { + DWORD VirtualAddress; + DWORD SizeOfBlock; +} +alias IMAGE_BASE_RELOCATION* PIMAGE_BASE_RELOCATION; + +align(2) struct IMAGE_LINENUMBER { + union _Type { + DWORD SymbolTableIndex; + DWORD VirtualAddress; + } + _Type Type; + WORD Linenumber; +} +alias IMAGE_LINENUMBER* PIMAGE_LINENUMBER; + +align(4): +struct IMAGE_ARCHIVE_MEMBER_HEADER { + BYTE[16] Name; + BYTE[12] Date; + BYTE[6] UserID; + BYTE[6] GroupID; + BYTE[8] Mode; + BYTE[10] Size; + BYTE[2] EndHeader; +} +alias IMAGE_ARCHIVE_MEMBER_HEADER* PIMAGE_ARCHIVE_MEMBER_HEADER; + +struct IMAGE_EXPORT_DIRECTORY { + DWORD Characteristics; + DWORD TimeDateStamp; + WORD MajorVersion; + WORD MinorVersion; + DWORD Name; + DWORD Base; + DWORD NumberOfFunctions; + DWORD NumberOfNames; + DWORD AddressOfFunctions; + DWORD AddressOfNames; + DWORD AddressOfNameOrdinals; +} +alias IMAGE_EXPORT_DIRECTORY* PIMAGE_EXPORT_DIRECTORY; + +struct IMAGE_IMPORT_BY_NAME { + WORD Hint; + BYTE _Name; + + BYTE* Name() return { return &_Name; } +} +alias IMAGE_IMPORT_BY_NAME* PIMAGE_IMPORT_BY_NAME; + +struct IMAGE_THUNK_DATA32 { + union _u1 { + DWORD ForwarderString; + DWORD Function; + DWORD Ordinal; + DWORD AddressOfData; + } + _u1 u1; +} +alias IMAGE_THUNK_DATA32* PIMAGE_THUNK_DATA32; + +struct IMAGE_THUNK_DATA64 { + union _u1 { + ULONGLONG ForwarderString; + ULONGLONG Function; + ULONGLONG Ordinal; + ULONGLONG AddressOfData; + } + _u1 u1; +} +alias IMAGE_THUNK_DATA64* PIMAGE_THUNK_DATA64; + +struct IMAGE_IMPORT_DESCRIPTOR { + union { + DWORD Characteristics; + DWORD OriginalFirstThunk; + } + DWORD TimeDateStamp; + DWORD ForwarderChain; + DWORD Name; + DWORD FirstThunk; +} +alias IMAGE_IMPORT_DESCRIPTOR* PIMAGE_IMPORT_DESCRIPTOR; + +struct IMAGE_BOUND_IMPORT_DESCRIPTOR { + DWORD TimeDateStamp; + WORD OffsetModuleName; + WORD NumberOfModuleForwarderRefs; +} +alias IMAGE_BOUND_IMPORT_DESCRIPTOR* PIMAGE_BOUND_IMPORT_DESCRIPTOR; + +struct IMAGE_BOUND_FORWARDER_REF { + DWORD TimeDateStamp; + WORD OffsetModuleName; + WORD Reserved; +} +alias IMAGE_BOUND_FORWARDER_REF* PIMAGE_BOUND_FORWARDER_REF; + +struct IMAGE_TLS_DIRECTORY32 { + DWORD StartAddressOfRawData; + DWORD EndAddressOfRawData; + DWORD AddressOfIndex; + DWORD AddressOfCallBacks; + DWORD SizeOfZeroFill; + DWORD Characteristics; +} +alias IMAGE_TLS_DIRECTORY32* PIMAGE_TLS_DIRECTORY32; + +struct IMAGE_TLS_DIRECTORY64 { + ULONGLONG StartAddressOfRawData; + ULONGLONG EndAddressOfRawData; + ULONGLONG AddressOfIndex; + ULONGLONG AddressOfCallBacks; + DWORD SizeOfZeroFill; + DWORD Characteristics; +} +alias IMAGE_TLS_DIRECTORY64* PIMAGE_TLS_DIRECTORY64; + +struct IMAGE_RESOURCE_DIRECTORY { + DWORD Characteristics; + DWORD TimeDateStamp; + WORD MajorVersion; + WORD MinorVersion; + WORD NumberOfNamedEntries; + WORD NumberOfIdEntries; +} +alias IMAGE_RESOURCE_DIRECTORY* PIMAGE_RESOURCE_DIRECTORY; + +struct IMAGE_RESOURCE_DIRECTORY_ENTRY { + union { + /+struct { + DWORD NameOffset:31; + DWORD NameIsString:1; + }+/ + DWORD Name; + WORD Id; + } + DWORD OffsetToData; + /+struct { + DWORD OffsetToDirectory:31; + DWORD DataIsDirectory:1; + }+/ + + uint NameOffset() { return Name & 0x7FFFFFFF; } + bool NameIsString() { return cast(bool)(Name & 0x80000000); } + uint OffsetToDirectory()() { return OffsetToData & 0x7FFFFFFF; } + bool DataIsDirectory() { return cast(bool)(OffsetToData & 0x80000000); } + + uint NameOffset(uint n) { + Name = (Name & 0x80000000) | (n & 0x7FFFFFFF); + return n & 0x7FFFFFFF; + } + + bool NameIsString(bool n) { + Name = (Name & 0x7FFFFFFF) | (n << 31); return n; + } + + uint OffsetToDirectory(uint o) { + OffsetToData = (OffsetToData & 0x80000000) | (o & 0x7FFFFFFF); + return o & 0x7FFFFFFF; + } + + bool DataIsDirectory(bool d) { + OffsetToData = (OffsetToData & 0x7FFFFFFF) | (d << 31); return d; + } +} +alias IMAGE_RESOURCE_DIRECTORY_ENTRY* PIMAGE_RESOURCE_DIRECTORY_ENTRY; + +struct IMAGE_RESOURCE_DIRECTORY_STRING { + WORD Length; + CHAR _NameString = 0; + + CHAR* NameString() return { return &_NameString; } +} +alias IMAGE_RESOURCE_DIRECTORY_STRING* PIMAGE_RESOURCE_DIRECTORY_STRING; + +struct IMAGE_RESOURCE_DIR_STRING_U { + WORD Length; + WCHAR _NameString = 0; + + WCHAR* NameString() return { return &_NameString; } +} +alias IMAGE_RESOURCE_DIR_STRING_U* PIMAGE_RESOURCE_DIR_STRING_U; + +struct IMAGE_RESOURCE_DATA_ENTRY { + DWORD OffsetToData; + DWORD Size; + DWORD CodePage; + DWORD Reserved; +} +alias IMAGE_RESOURCE_DATA_ENTRY* PIMAGE_RESOURCE_DATA_ENTRY; + +struct IMAGE_LOAD_CONFIG_DIRECTORY32 { + DWORD Characteristics; + DWORD TimeDateStamp; + WORD MajorVersion; + WORD MinorVersion; + DWORD GlobalFlagsClear; + DWORD GlobalFlagsSet; + DWORD CriticalSectionDefaultTimeout; + DWORD DeCommitFreeBlockThreshold; + DWORD DeCommitTotalFreeThreshold; + PVOID LockPrefixTable; + DWORD MaximumAllocationSize; + DWORD VirtualMemoryThreshold; + DWORD ProcessHeapFlags; + DWORD[4] Reserved; +} +alias IMAGE_LOAD_CONFIG_DIRECTORY32* PIMAGE_LOAD_CONFIG_DIRECTORY32; + +struct IMAGE_LOAD_CONFIG_DIRECTORY64 { + DWORD Characteristics; + DWORD TimeDateStamp; + WORD MajorVersion; + WORD MinorVersion; + DWORD GlobalFlagsClear; + DWORD GlobalFlagsSet; + DWORD CriticalSectionDefaultTimeout; + ULONGLONG DeCommitFreeBlockThreshold; + ULONGLONG DeCommitTotalFreeThreshold; + ULONGLONG LockPrefixTable; + ULONGLONG MaximumAllocationSize; + ULONGLONG VirtualMemoryThreshold; + ULONGLONG ProcessAffinityMask; + DWORD ProcessHeapFlags; + WORD CSDFlags; + WORD Reserved1; + ULONGLONG EditList; + DWORD[2] Reserved; +} +alias IMAGE_LOAD_CONFIG_DIRECTORY64* PIMAGE_LOAD_CONFIG_DIRECTORY64; + +version (Win64) { + alias IMAGE_LOAD_CONFIG_DIRECTORY64 IMAGE_LOAD_CONFIG_DIRECTORY; +} else { + alias IMAGE_LOAD_CONFIG_DIRECTORY32 IMAGE_LOAD_CONFIG_DIRECTORY; +} +alias IMAGE_LOAD_CONFIG_DIRECTORY* PIMAGE_LOAD_CONFIG_DIRECTORY; + +// Note versions for Alpha, Alpha64, ARM removed. +struct IMAGE_RUNTIME_FUNCTION_ENTRY { + DWORD BeginAddress; + DWORD EndAddress; + union { + DWORD UnwindInfoAddress; + DWORD UnwindData; + } +} +alias IMAGE_RUNTIME_FUNCTION_ENTRY* PIMAGE_RUNTIME_FUNCTION_ENTRY; + +struct IMAGE_CE_RUNTIME_FUNCTION_ENTRY { + uint FuncStart; + union { + ubyte PrologLen; + uint _bf; + } +/+ + unsigned int FuncLen:22; + unsigned int ThirtyTwoBit:1; + unsigned int ExceptionFlag:1; ++/ + uint FuncLen() { return (_bf >> 8) & 0x3FFFFF; } + bool ThirtyTwoBit() { return cast(bool)(_bf & 0x40000000); } + bool ExceptionFlag()() { return cast(bool)(_bf & 0x80000000); } + + uint FuncLen(uint f) { + _bf = (_bf & ~0x3FFFFF00) | ((f & 0x3FFFFF) << 8); return f & 0x3FFFFF; + } + + bool ThirtyTwoBit(bool t) { + _bf = (_bf & ~0x40000000) | (t << 30); return t; + } + + bool ExceptionFlag(bool e) { + _bf = (_bf & ~0x80000000) | (e << 31); return e; + } +} +alias IMAGE_CE_RUNTIME_FUNCTION_ENTRY* PIMAGE_CE_RUNTIME_FUNCTION_ENTRY; + +struct IMAGE_DEBUG_DIRECTORY { + DWORD Characteristics; + DWORD TimeDateStamp; + WORD MajorVersion; + WORD MinorVersion; + DWORD Type; + DWORD SizeOfData; + DWORD AddressOfRawData; + DWORD PointerToRawData; +} +alias IMAGE_DEBUG_DIRECTORY* PIMAGE_DEBUG_DIRECTORY; + +struct FPO_DATA { + DWORD ulOffStart; + DWORD cbProcSize; + DWORD cdwLocals; + WORD cdwParams; + ubyte cbProlog; + ubyte _bf; +/+ + WORD cbRegs:3; + WORD fHasSEH:1; + WORD fUseBP:1; + WORD reserved:1; + WORD cbFrame:2; ++/ + ubyte cbRegs() { return cast(ubyte)(_bf & 0x07); } + bool fHasSEH() { return cast(bool)(_bf & 0x08); } + bool fUseBP() { return cast(bool)(_bf & 0x10); } + bool reserved()() { return cast(bool)(_bf & 0x20); } + ubyte cbFrame()() { return cast(ubyte)(_bf >> 6); } + + ubyte cbRegs(ubyte c) { + _bf = cast(ubyte) ((_bf & ~0x07) | (c & 0x07)); + return cast(ubyte)(c & 0x07); + } + + bool fHasSEH(bool f) { _bf = cast(ubyte)((_bf & ~0x08) | (f << 3)); return f; } + bool fUseBP(bool f) { _bf = cast(ubyte)((_bf & ~0x10) | (f << 4)); return f; } + bool reserved(bool r) { _bf = cast(ubyte)((_bf & ~0x20) | (r << 5)); return r; } + + ubyte cbFrame(ubyte c) { + _bf = cast(ubyte) ((_bf & ~0xC0) | ((c & 0x03) << 6)); + return cast(ubyte)(c & 0x03); + } +} +alias FPO_DATA* PFPO_DATA; + +struct IMAGE_DEBUG_MISC { + DWORD DataType; + DWORD Length; + BOOLEAN Unicode; + BYTE[3] Reserved; + BYTE _Data; + + BYTE* Data() return { return &_Data; } +} +alias IMAGE_DEBUG_MISC* PIMAGE_DEBUG_MISC; + +struct IMAGE_FUNCTION_ENTRY { + DWORD StartingAddress; + DWORD EndingAddress; + DWORD EndOfPrologue; +} +alias IMAGE_FUNCTION_ENTRY* PIMAGE_FUNCTION_ENTRY; + +struct IMAGE_FUNCTION_ENTRY64 { + ULONGLONG StartingAddress; + ULONGLONG EndingAddress; + union { + ULONGLONG EndOfPrologue; + ULONGLONG UnwindInfoAddress; + } +} +alias IMAGE_FUNCTION_ENTRY64* PIMAGE_FUNCTION_ENTRY64; + +struct IMAGE_SEPARATE_DEBUG_HEADER { + WORD Signature; + WORD Flags; + WORD Machine; + WORD Characteristics; + DWORD TimeDateStamp; + DWORD CheckSum; + DWORD ImageBase; + DWORD SizeOfImage; + DWORD NumberOfSections; + DWORD ExportedNamesSize; + DWORD DebugDirectorySize; + DWORD SectionAlignment; + DWORD[2] Reserved; +} +alias IMAGE_SEPARATE_DEBUG_HEADER* PIMAGE_SEPARATE_DEBUG_HEADER; + +enum SERVICE_NODE_TYPE { + DriverType = SERVICE_KERNEL_DRIVER, + FileSystemType = SERVICE_FILE_SYSTEM_DRIVER, + Win32ServiceOwnProcess = SERVICE_WIN32_OWN_PROCESS, + Win32ServiceShareProcess = SERVICE_WIN32_SHARE_PROCESS, + AdapterType = SERVICE_ADAPTER, + RecognizerType = SERVICE_RECOGNIZER_DRIVER +} + +enum SERVICE_LOAD_TYPE { + BootLoad = SERVICE_BOOT_START, + SystemLoad = SERVICE_SYSTEM_START, + AutoLoad = SERVICE_AUTO_START, + DemandLoad = SERVICE_DEMAND_START, + DisableLoad = SERVICE_DISABLED +} + +enum SERVICE_ERROR_TYPE { + IgnoreError = SERVICE_ERROR_IGNORE, + NormalError = SERVICE_ERROR_NORMAL, + SevereError = SERVICE_ERROR_SEVERE, + CriticalError = SERVICE_ERROR_CRITICAL +} +alias SERVICE_ERROR_TYPE _CM_ERROR_CONTROL_TYPE; + +//DAC: According to MSJ, 'UnderTheHood', May 1996, this +// structure is not documented in any official Microsoft header file. +alias void EXCEPTION_REGISTRATION_RECORD; + +align: +struct NT_TIB { + EXCEPTION_REGISTRATION_RECORD *ExceptionList; + PVOID StackBase; + PVOID StackLimit; + PVOID SubSystemTib; + union { + PVOID FiberData; + DWORD Version; + } + PVOID ArbitraryUserPointer; + NT_TIB *Self; +} +alias NT_TIB* PNT_TIB; + +struct REPARSE_DATA_BUFFER { + DWORD ReparseTag; + WORD ReparseDataLength; + WORD Reserved; + union { + struct _GenericReparseBuffer { + BYTE _DataBuffer; + + BYTE* DataBuffer() return { return &_DataBuffer; } + } + _GenericReparseBuffer GenericReparseBuffer; + struct _SymbolicLinkReparseBuffer { + WORD SubstituteNameOffset; + WORD SubstituteNameLength; + WORD PrintNameOffset; + WORD PrintNameLength; + // ??? This is in MinGW, but absent in MSDN docs + ULONG Flags; + WCHAR _PathBuffer = 0; + + WCHAR* PathBuffer() return { return &_PathBuffer; } + } + _SymbolicLinkReparseBuffer SymbolicLinkReparseBuffer; + struct _MountPointReparseBuffer { + WORD SubstituteNameOffset; + WORD SubstituteNameLength; + WORD PrintNameOffset; + WORD PrintNameLength; + WCHAR _PathBuffer = 0; + + WCHAR* PathBuffer() return { return &_PathBuffer; } + } + _MountPointReparseBuffer MountPointReparseBuffer; + } +} +alias REPARSE_DATA_BUFFER *PREPARSE_DATA_BUFFER; + +struct REPARSE_GUID_DATA_BUFFER { + DWORD ReparseTag; + WORD ReparseDataLength; + WORD Reserved; + GUID ReparseGuid; + struct _GenericReparseBuffer { + BYTE _DataBuffer; + + BYTE* DataBuffer() return { return &_DataBuffer; } + } + _GenericReparseBuffer GenericReparseBuffer; +} +alias REPARSE_GUID_DATA_BUFFER* PREPARSE_GUID_DATA_BUFFER; + +enum size_t + REPARSE_DATA_BUFFER_HEADER_SIZE = REPARSE_DATA_BUFFER.GenericReparseBuffer.offsetof, + REPARSE_GUID_DATA_BUFFER_HEADER_SIZE = REPARSE_GUID_DATA_BUFFER.GenericReparseBuffer.offsetof, + MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16384; + + +struct REPARSE_POINT_INFORMATION { + WORD ReparseDataLength; + WORD UnparsedNameLength; +} +alias REPARSE_POINT_INFORMATION* PREPARSE_POINT_INFORMATION; + +union FILE_SEGMENT_ELEMENT { + PVOID64 Buffer; + ULONGLONG Alignment; +} +alias FILE_SEGMENT_ELEMENT* PFILE_SEGMENT_ELEMENT; + +// JOBOBJECT_BASIC_LIMIT_INFORMATION.LimitFlags constants +enum DWORD + JOB_OBJECT_LIMIT_WORKINGSET = 0x0001, + JOB_OBJECT_LIMIT_PROCESS_TIME = 0x0002, + JOB_OBJECT_LIMIT_JOB_TIME = 0x0004, + JOB_OBJECT_LIMIT_ACTIVE_PROCESS = 0x0008, + JOB_OBJECT_LIMIT_AFFINITY = 0x0010, + JOB_OBJECT_LIMIT_PRIORITY_CLASS = 0x0020, + JOB_OBJECT_LIMIT_PRESERVE_JOB_TIME = 0x0040, + JOB_OBJECT_LIMIT_SCHEDULING_CLASS = 0x0080, + JOB_OBJECT_LIMIT_PROCESS_MEMORY = 0x0100, + JOB_OBJECT_LIMIT_JOB_MEMORY = 0x0200, + JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION = 0x0400, + JOB_OBJECT_BREAKAWAY_OK = 0x0800, + JOB_OBJECT_SILENT_BREAKAWAY = 0x1000; + +// JOBOBJECT_BASIC_UI_RESTRICTIONS.UIRestrictionsClass constants +enum DWORD + JOB_OBJECT_UILIMIT_HANDLES = 0x0001, + JOB_OBJECT_UILIMIT_READCLIPBOARD = 0x0002, + JOB_OBJECT_UILIMIT_WRITECLIPBOARD = 0x0004, + JOB_OBJECT_UILIMIT_SYSTEMPARAMETERS = 0x0008, + JOB_OBJECT_UILIMIT_DISPLAYSETTINGS = 0x0010, + JOB_OBJECT_UILIMIT_GLOBALATOMS = 0x0020, + JOB_OBJECT_UILIMIT_DESKTOP = 0x0040, + JOB_OBJECT_UILIMIT_EXITWINDOWS = 0x0080; + +// JOBOBJECT_SECURITY_LIMIT_INFORMATION.SecurityLimitFlags constants +enum DWORD + JOB_OBJECT_SECURITY_NO_ADMIN = 0x0001, + JOB_OBJECT_SECURITY_RESTRICTED_TOKEN = 0x0002, + JOB_OBJECT_SECURITY_ONLY_TOKEN = 0x0004, + JOB_OBJECT_SECURITY_FILTER_TOKENS = 0x0008; + +// JOBOBJECT_END_OF_JOB_TIME_INFORMATION.EndOfJobTimeAction constants +enum : DWORD { + JOB_OBJECT_TERMINATE_AT_END_OF_JOB, + JOB_OBJECT_POST_AT_END_OF_JOB +} + +enum : DWORD { + JOB_OBJECT_MSG_END_OF_JOB_TIME = 1, + JOB_OBJECT_MSG_END_OF_PROCESS_TIME, + JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT, + JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO, + JOB_OBJECT_MSG_NEW_PROCESS, + JOB_OBJECT_MSG_EXIT_PROCESS, + JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS, + JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT, + JOB_OBJECT_MSG_JOB_MEMORY_LIMIT +} + +enum JOBOBJECTINFOCLASS { + JobObjectBasicAccountingInformation = 1, + JobObjectBasicLimitInformation, + JobObjectBasicProcessIdList, + JobObjectBasicUIRestrictions, + JobObjectSecurityLimitInformation, + JobObjectEndOfJobTimeInformation, + JobObjectAssociateCompletionPortInformation, + JobObjectBasicAndIoAccountingInformation, + JobObjectExtendedLimitInformation, + JobObjectJobSetInformation, + MaxJobObjectInfoClass +} + +struct JOBOBJECT_BASIC_ACCOUNTING_INFORMATION { + LARGE_INTEGER TotalUserTime; + LARGE_INTEGER TotalKernelTime; + LARGE_INTEGER ThisPeriodTotalUserTime; + LARGE_INTEGER ThisPeriodTotalKernelTime; + DWORD TotalPageFaultCount; + DWORD TotalProcesses; + DWORD ActiveProcesses; + DWORD TotalTerminatedProcesses; +} +alias JOBOBJECT_BASIC_ACCOUNTING_INFORMATION* PJOBOBJECT_BASIC_ACCOUNTING_INFORMATION; + +struct JOBOBJECT_BASIC_LIMIT_INFORMATION { + LARGE_INTEGER PerProcessUserTimeLimit; + LARGE_INTEGER PerJobUserTimeLimit; + DWORD LimitFlags; + SIZE_T MinimumWorkingSetSize; + SIZE_T MaximumWorkingSetSize; + DWORD ActiveProcessLimit; + ULONG_PTR Affinity; + DWORD PriorityClass; + DWORD SchedulingClass; +} +alias JOBOBJECT_BASIC_LIMIT_INFORMATION* PJOBOBJECT_BASIC_LIMIT_INFORMATION; + +struct JOBOBJECT_BASIC_PROCESS_ID_LIST { + DWORD NumberOfAssignedProcesses; + DWORD NumberOfProcessIdsInList; + ULONG_PTR _ProcessIdList; + + ULONG_PTR* ProcessIdList() return { return &_ProcessIdList; } +} +alias JOBOBJECT_BASIC_PROCESS_ID_LIST* PJOBOBJECT_BASIC_PROCESS_ID_LIST; + +struct JOBOBJECT_BASIC_UI_RESTRICTIONS { + DWORD UIRestrictionsClass; +} +alias JOBOBJECT_BASIC_UI_RESTRICTIONS* PJOBOBJECT_BASIC_UI_RESTRICTIONS; + +struct JOBOBJECT_SECURITY_LIMIT_INFORMATION { + DWORD SecurityLimitFlags; + HANDLE JobToken; + PTOKEN_GROUPS SidsToDisable; + PTOKEN_PRIVILEGES PrivilegesToDelete; + PTOKEN_GROUPS RestrictedSids; +} +alias JOBOBJECT_SECURITY_LIMIT_INFORMATION* PJOBOBJECT_SECURITY_LIMIT_INFORMATION; + +struct JOBOBJECT_END_OF_JOB_TIME_INFORMATION { + DWORD EndOfJobTimeAction; +} +alias JOBOBJECT_END_OF_JOB_TIME_INFORMATION* PJOBOBJECT_END_OF_JOB_TIME_INFORMATION; + +struct JOBOBJECT_ASSOCIATE_COMPLETION_PORT { + PVOID CompletionKey; + HANDLE CompletionPort; +} +alias JOBOBJECT_ASSOCIATE_COMPLETION_PORT* PJOBOBJECT_ASSOCIATE_COMPLETION_PORT; + +struct JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION { + JOBOBJECT_BASIC_ACCOUNTING_INFORMATION BasicInfo; + IO_COUNTERS IoInfo; +} +alias JOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION *PJOBOBJECT_BASIC_AND_IO_ACCOUNTING_INFORMATION; + +struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION { + JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation; + IO_COUNTERS IoInfo; + SIZE_T ProcessMemoryLimit; + SIZE_T JobMemoryLimit; + SIZE_T PeakProcessMemoryUsed; + SIZE_T PeakJobMemoryUsed; +} +alias JOBOBJECT_EXTENDED_LIMIT_INFORMATION* PJOBOBJECT_EXTENDED_LIMIT_INFORMATION; + +struct JOBOBJECT_JOBSET_INFORMATION { + DWORD MemberLevel; +} +alias JOBOBJECT_JOBSET_INFORMATION* PJOBOBJECT_JOBSET_INFORMATION; + +// MinGW: Making these defines conditional on _WIN32_WINNT will break ddk includes +//static if (_WIN32_WINNT >= 0x500) { + +enum DWORD + ES_SYSTEM_REQUIRED = 0x00000001, + ES_DISPLAY_REQUIRED = 0x00000002, + ES_USER_PRESENT = 0x00000004, + ES_AWAYMODE_REQUIRED = 0x00000040, + ES_CONTINUOUS = 0x80000000; + +enum LATENCY_TIME { + LT_DONT_CARE, + LT_LOWEST_LATENCY +} +alias LATENCY_TIME* PLATENCY_TIME; + +enum SYSTEM_POWER_STATE { + PowerSystemUnspecified, + PowerSystemWorking, + PowerSystemSleeping1, + PowerSystemSleeping2, + PowerSystemSleeping3, + PowerSystemHibernate, + PowerSystemShutdown, + PowerSystemMaximum +} +alias SYSTEM_POWER_STATE* PSYSTEM_POWER_STATE; + +enum POWER_SYSTEM_MAXIMUM = SYSTEM_POWER_STATE.PowerSystemMaximum; + +enum POWER_ACTION { + PowerActionNone, + PowerActionReserved, + PowerActionSleep, + PowerActionHibernate, + PowerActionShutdown, + PowerActionShutdownReset, + PowerActionShutdownOff, + PowerActionWarmEject +} +alias POWER_ACTION* PPOWER_ACTION; + +static if (_WIN32_WINNT >= 0x600) { + enum SYSTEM_POWER_CONDITION { + PoAc, + PoDc, + PoHot, + PoConditionMaximum + } + alias SYSTEM_POWER_CONDITION* PSYSTEM_POWER_CONDITION; +} + +enum DEVICE_POWER_STATE { + PowerDeviceUnspecified, + PowerDeviceD0, + PowerDeviceD1, + PowerDeviceD2, + PowerDeviceD3, + PowerDeviceMaximum +} +alias DEVICE_POWER_STATE* PDEVICE_POWER_STATE; + +align(4): +struct BATTERY_REPORTING_SCALE { + DWORD Granularity; + DWORD Capacity; +} +alias BATTERY_REPORTING_SCALE* PBATTERY_REPORTING_SCALE; + +struct POWER_ACTION_POLICY { + POWER_ACTION Action; + ULONG Flags; + ULONG EventCode; +} +alias POWER_ACTION_POLICY* PPOWER_ACTION_POLICY; + +// POWER_ACTION_POLICY.Flags constants +enum ULONG + POWER_ACTION_QUERY_ALLOWED = 0x00000001, + POWER_ACTION_UI_ALLOWED = 0x00000002, + POWER_ACTION_OVERRIDE_APPS = 0x00000004, + POWER_ACTION_LIGHTEST_FIRST = 0x10000000, + POWER_ACTION_LOCK_CONSOLE = 0x20000000, + POWER_ACTION_DISABLE_WAKES = 0x40000000, + POWER_ACTION_CRITICAL = 0x80000000; + +// POWER_ACTION_POLICY.EventCode constants +enum ULONG + POWER_LEVEL_USER_NOTIFY_TEXT = 0x00000001, + POWER_LEVEL_USER_NOTIFY_SOUND = 0x00000002, + POWER_LEVEL_USER_NOTIFY_EXEC = 0x00000004, + POWER_USER_NOTIFY_BUTTON = 0x00000008, + POWER_USER_NOTIFY_SHUTDOWN = 0x00000010, + POWER_FORCE_TRIGGER_RESET = 0x80000000; + +enum size_t + DISCHARGE_POLICY_CRITICAL = 0, + DISCHARGE_POLICY_LOW = 1, + NUM_DISCHARGE_POLICIES = 4; + +enum : BYTE { + PO_THROTTLE_NONE, + PO_THROTTLE_CONSTANT, + PO_THROTTLE_DEGRADE, + PO_THROTTLE_ADAPTIVE, + PO_THROTTLE_MAXIMUM +} + +struct SYSTEM_POWER_LEVEL { + BOOLEAN Enable; + UCHAR[3] Spare; + ULONG BatteryLevel; + POWER_ACTION_POLICY PowerPolicy; + SYSTEM_POWER_STATE MinSystemState; +} +alias SYSTEM_POWER_LEVEL* PSYSTEM_POWER_LEVEL; + +struct SYSTEM_POWER_POLICY { + ULONG Revision; + POWER_ACTION_POLICY PowerButton; + POWER_ACTION_POLICY SleepButton; + POWER_ACTION_POLICY LidClose; + SYSTEM_POWER_STATE LidOpenWake; + ULONG Reserved; + POWER_ACTION_POLICY Idle; + ULONG IdleTimeout; + UCHAR IdleSensitivity; + UCHAR DynamicThrottle; + UCHAR[2] Spare2; + SYSTEM_POWER_STATE MinSleep; + SYSTEM_POWER_STATE MaxSleep; + SYSTEM_POWER_STATE ReducedLatencySleep; + ULONG WinLogonFlags; + ULONG Spare3; + ULONG DozeS4Timeout; + ULONG BroadcastCapacityResolution; + SYSTEM_POWER_LEVEL[NUM_DISCHARGE_POLICIES] DischargePolicy; + ULONG VideoTimeout; + BOOLEAN VideoDimDisplay; + ULONG[3] VideoReserved; + ULONG SpindownTimeout; + BOOLEAN OptimizeForPower; + UCHAR FanThrottleTolerance; + UCHAR ForcedThrottle; + UCHAR MinThrottle; + POWER_ACTION_POLICY OverThrottled; +} +alias SYSTEM_POWER_POLICY* PSYSTEM_POWER_POLICY; + +struct SYSTEM_POWER_CAPABILITIES { + BOOLEAN PowerButtonPresent; + BOOLEAN SleepButtonPresent; + BOOLEAN LidPresent; + BOOLEAN SystemS1; + BOOLEAN SystemS2; + BOOLEAN SystemS3; + BOOLEAN SystemS4; + BOOLEAN SystemS5; + BOOLEAN HiberFilePresent; + BOOLEAN FullWake; + BOOLEAN VideoDimPresent; + BOOLEAN ApmPresent; + BOOLEAN UpsPresent; + BOOLEAN ThermalControl; + BOOLEAN ProcessorThrottle; + UCHAR ProcessorMinThrottle; + UCHAR ProcessorMaxThrottle; + UCHAR[4] spare2; + BOOLEAN DiskSpinDown; + UCHAR[8] spare3; + BOOLEAN SystemBatteriesPresent; + BOOLEAN BatteriesAreShortTerm; + BATTERY_REPORTING_SCALE[3] BatteryScale; + SYSTEM_POWER_STATE AcOnLineWake; + SYSTEM_POWER_STATE SoftLidWake; + SYSTEM_POWER_STATE RtcWake; + SYSTEM_POWER_STATE MinDeviceWakeState; + SYSTEM_POWER_STATE DefaultLowLatencyWake; +} +alias SYSTEM_POWER_CAPABILITIES* PSYSTEM_POWER_CAPABILITIES; + +struct SYSTEM_BATTERY_STATE { + BOOLEAN AcOnLine; + BOOLEAN BatteryPresent; + BOOLEAN Charging; + BOOLEAN Discharging; + BOOLEAN[4] Spare1; + ULONG MaxCapacity; + ULONG RemainingCapacity; + ULONG Rate; + ULONG EstimatedTime; + ULONG DefaultAlert1; + ULONG DefaultAlert2; +} +alias SYSTEM_BATTERY_STATE* PSYSTEM_BATTERY_STATE; + +enum POWER_INFORMATION_LEVEL { + SystemPowerPolicyAc, + SystemPowerPolicyDc, + VerifySystemPolicyAc, + VerifySystemPolicyDc, + SystemPowerCapabilities, + SystemBatteryState, + SystemPowerStateHandler, + ProcessorStateHandler, + SystemPowerPolicyCurrent, + AdministratorPowerPolicy, + SystemReserveHiberFile, + ProcessorInformation, + SystemPowerInformation, + ProcessorStateHandler2, + LastWakeTime, + LastSleepTime, + SystemExecutionState, + SystemPowerStateNotifyHandler, + ProcessorPowerPolicyAc, + ProcessorPowerPolicyDc, + VerifyProcessorPowerPolicyAc, + VerifyProcessorPowerPolicyDc, + ProcessorPowerPolicyCurrent +} + +//#if 1 /* (WIN32_WINNT >= 0x0500) */ +struct SYSTEM_POWER_INFORMATION { + ULONG MaxIdlenessAllowed; + ULONG Idleness; + ULONG TimeRemaining; + UCHAR CoolingMode; +} +alias SYSTEM_POWER_INFORMATION* PSYSTEM_POWER_INFORMATION; +//#endif + +struct PROCESSOR_POWER_POLICY_INFO { + ULONG TimeCheck; + ULONG DemoteLimit; + ULONG PromoteLimit; + UCHAR DemotePercent; + UCHAR PromotePercent; + UCHAR[2] Spare; + uint _bf; + + bool AllowDemotion() { return cast(bool)(_bf & 1); } + bool AllowPromotion()() { return cast(bool)(_bf & 2); } + + bool AllowDemotion(bool a) { _bf = (_bf & ~1) | a; return a; } + bool AllowPromotion(bool a) { _bf = (_bf & ~2) | (a << 1); return a; } +/+ + ULONG AllowDemotion : 1; + ULONG AllowPromotion : 1; + ULONG Reserved : 30; ++/ +} +alias PROCESSOR_POWER_POLICY_INFO* PPROCESSOR_POWER_POLICY_INFO; + +struct PROCESSOR_POWER_POLICY { + ULONG Revision; + UCHAR DynamicThrottle; + UCHAR[3] Spare; + ULONG Reserved; + ULONG PolicyCount; + PROCESSOR_POWER_POLICY_INFO[3] Policy; +} +alias PROCESSOR_POWER_POLICY* PPROCESSOR_POWER_POLICY; + +struct ADMINISTRATOR_POWER_POLICY { + SYSTEM_POWER_STATE MinSleep; + SYSTEM_POWER_STATE MaxSleep; + ULONG MinVideoTimeout; + ULONG MaxVideoTimeout; + ULONG MinSpindownTimeout; + ULONG MaxSpindownTimeout; +} +alias ADMINISTRATOR_POWER_POLICY* PADMINISTRATOR_POWER_POLICY; + +//}//#endif /* _WIN32_WINNT >= 0x500 */ + +extern (Windows) { + alias void function(PVOID, DWORD, PVOID) PIMAGE_TLS_CALLBACK; + + static if (_WIN32_WINNT >= 0x500) { + alias LONG function(PEXCEPTION_POINTERS) PVECTORED_EXCEPTION_HANDLER; + alias void function(PVOID, BOOLEAN) WAITORTIMERCALLBACKFUNC; + } +} + +static if (_WIN32_WINNT >= 0x501) { + enum HEAP_INFORMATION_CLASS { + HeapCompatibilityInformation + } + + enum ACTIVATION_CONTEXT_INFO_CLASS { + ActivationContextBasicInformation = 1, + ActivationContextDetailedInformation, + AssemblyDetailedInformationInActivationContext, + FileInformationInAssemblyOfAssemblyInActivationContext + } + + align struct ACTIVATION_CONTEXT_ASSEMBLY_DETAILED_INFORMATION { + DWORD ulFlags; + DWORD ulEncodedAssemblyIdentityLength; + DWORD ulManifestPathType; + DWORD ulManifestPathLength; + LARGE_INTEGER liManifestLastWriteTime; + DWORD ulPolicyPathType; + DWORD ulPolicyPathLength; + LARGE_INTEGER liPolicyLastWriteTime; + DWORD ulMetadataSatelliteRosterIndex; + DWORD ulManifestVersionMajor; + DWORD ulManifestVersionMinor; + DWORD ulPolicyVersionMajor; + DWORD ulPolicyVersionMinor; + DWORD ulAssemblyDirectoryNameLength; + PCWSTR lpAssemblyEncodedAssemblyIdentity; + PCWSTR lpAssemblyManifestPath; + PCWSTR lpAssemblyPolicyPath; + PCWSTR lpAssemblyDirectoryName; + } + alias ACTIVATION_CONTEXT_ASSEMBLY_DETAILED_INFORMATION* + PACTIVATION_CONTEXT_ASSEMBLY_DETAILED_INFORMATION; + alias const(ACTIVATION_CONTEXT_ASSEMBLY_DETAILED_INFORMATION)* + PCACTIVATION_CONTEXT_ASSEMBLY_DETAILED_INFORMATION; + + struct ACTIVATION_CONTEXT_DETAILED_INFORMATION { + DWORD dwFlags; + DWORD ulFormatVersion; + DWORD ulAssemblyCount; + DWORD ulRootManifestPathType; + DWORD ulRootManifestPathChars; + DWORD ulRootConfigurationPathType; + DWORD ulRootConfigurationPathChars; + DWORD ulAppDirPathType; + DWORD ulAppDirPathChars; + PCWSTR lpRootManifestPath; + PCWSTR lpRootConfigurationPath; + PCWSTR lpAppDirPath; + } + alias ACTIVATION_CONTEXT_DETAILED_INFORMATION* + PACTIVATION_CONTEXT_DETAILED_INFORMATION; + alias const(ACTIVATION_CONTEXT_DETAILED_INFORMATION)* + PCACTIVATION_CONTEXT_DETAILED_INFORMATION; + + struct ACTIVATION_CONTEXT_QUERY_INDEX { + ULONG ulAssemblyIndex; + ULONG ulFileIndexInAssembly; + } + alias ACTIVATION_CONTEXT_QUERY_INDEX* PACTIVATION_CONTEXT_QUERY_INDEX; + alias const(ACTIVATION_CONTEXT_QUERY_INDEX)* PCACTIVATION_CONTEXT_QUERY_INDEX; + + struct ASSEMBLY_FILE_DETAILED_INFORMATION { + DWORD ulFlags; + DWORD ulFilenameLength; + DWORD ulPathLength; + PCWSTR lpFileName; + PCWSTR lpFilePath; + } + alias ASSEMBLY_FILE_DETAILED_INFORMATION* + PASSEMBLY_FILE_DETAILED_INFORMATION; + alias const(ASSEMBLY_FILE_DETAILED_INFORMATION)* + PCASSEMBLY_FILE_DETAILED_INFORMATION; +} + +version (Unicode) { + alias OSVERSIONINFOW OSVERSIONINFO; + alias OSVERSIONINFOEXW OSVERSIONINFOEX; +} else { + alias OSVERSIONINFOA OSVERSIONINFO; + alias OSVERSIONINFOEXA OSVERSIONINFOEX; +} + +alias OSVERSIONINFO* POSVERSIONINFO, LPOSVERSIONINFO; +alias OSVERSIONINFOEX* POSVERSIONINFOEX, LPOSVERSIONINFOEX; + + +static if (_WIN32_WINNT >= 0x500) { + extern (Windows) ULONGLONG VerSetConditionMask(ULONGLONG, DWORD, BYTE); +} + +version (Win64) { +enum WORD IMAGE_NT_OPTIONAL_HDR_MAGIC = IMAGE_NT_OPTIONAL_HDR64_MAGIC; + + alias IMAGE_ORDINAL_FLAG64 IMAGE_ORDINAL_FLAG; + alias IMAGE_SNAP_BY_ORDINAL64 IMAGE_SNAP_BY_ORDINAL; + alias IMAGE_ORDINAL64 IMAGE_ORDINAL; + alias IMAGE_OPTIONAL_HEADER64 IMAGE_OPTIONAL_HEADER; + alias IMAGE_NT_HEADERS64 IMAGE_NT_HEADERS; + alias IMAGE_THUNK_DATA64 IMAGE_THUNK_DATA; + alias IMAGE_TLS_DIRECTORY64 IMAGE_TLS_DIRECTORY; +} else { +enum WORD IMAGE_NT_OPTIONAL_HDR_MAGIC = IMAGE_NT_OPTIONAL_HDR32_MAGIC; + + alias IMAGE_ORDINAL_FLAG32 IMAGE_ORDINAL_FLAG; + alias IMAGE_ORDINAL32 IMAGE_ORDINAL; + alias IMAGE_SNAP_BY_ORDINAL32 IMAGE_SNAP_BY_ORDINAL; + alias IMAGE_OPTIONAL_HEADER32 IMAGE_OPTIONAL_HEADER; + alias IMAGE_NT_HEADERS32 IMAGE_NT_HEADERS; + alias IMAGE_THUNK_DATA32 IMAGE_THUNK_DATA; + alias IMAGE_TLS_DIRECTORY32 IMAGE_TLS_DIRECTORY; +} + +alias IMAGE_OPTIONAL_HEADER* PIMAGE_OPTIONAL_HEADER; +alias IMAGE_NT_HEADERS* PIMAGE_NT_HEADERS; +alias IMAGE_THUNK_DATA* PIMAGE_THUNK_DATA; +alias IMAGE_TLS_DIRECTORY* PIMAGE_TLS_DIRECTORY; + +// TODO: MinGW implements these in assembly. How to translate? +PVOID GetCurrentFiber(); +PVOID GetFiberData(); diff --git a/src/urt/internal/sys/windows/winsock2.d b/src/urt/internal/sys/windows/winsock2.d new file mode 100644 index 0000000..5ab3a7c --- /dev/null +++ b/src/urt/internal/sys/windows/winsock2.d @@ -0,0 +1,768 @@ +/* + Written by Christopher E. Miller + $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0). +*/ + + +module urt.internal.sys.windows.winsock2; +version (Windows): + +pragma(lib, "ws2_32"); + +extern(Windows): +nothrow: + +alias SOCKET = size_t; +alias socklen_t = int; + +enum SOCKET INVALID_SOCKET = cast(SOCKET)~0; +enum int SOCKET_ERROR = -1; + +enum WSADESCRIPTION_LEN = 256; +enum WSASYS_STATUS_LEN = 128; + +struct WSADATA +{ + ushort wVersion; + ushort wHighVersion; + char[WSADESCRIPTION_LEN + 1] szDescription = 0; + char[WSASYS_STATUS_LEN + 1] szSystemStatus = 0; + ushort iMaxSockets; + ushort iMaxUdpDg; + char* lpVendorInfo; +} +alias LPWSADATA = WSADATA*; + + +enum int IOCPARM_MASK = 0x7F; +enum int IOC_IN = cast(int)0x80000000; +enum int IOC_OUT = cast(int)0x40000000; +enum int FIONBIO = cast(int)(IOC_IN | ((uint.sizeof & IOCPARM_MASK) << 16) | (102 << 8) | 126); +enum int FIONREAD = cast(int)(IOC_OUT | ((uint.sizeof & IOCPARM_MASK) << 16) | (102 << 8) | 127); + +enum NI_MAXHOST = 1025; +enum NI_MAXSERV = 32; + +@nogc +{ +int WSAStartup(ushort wVersionRequested, LPWSADATA lpWSAData); +@trusted int WSACleanup(); +@trusted SOCKET socket(int af, int type, int protocol); +int ioctlsocket(SOCKET s, int cmd, uint* argp); +int bind(SOCKET s, const(sockaddr)* name, socklen_t namelen); +int connect(SOCKET s, const(sockaddr)* name, socklen_t namelen); +@trusted int listen(SOCKET s, int backlog); +SOCKET accept(SOCKET s, sockaddr* addr, socklen_t* addrlen); +@trusted int closesocket(SOCKET s); +@trusted int shutdown(SOCKET s, int how); +int getpeername(SOCKET s, sockaddr* name, socklen_t* namelen); +int getsockname(SOCKET s, sockaddr* name, socklen_t* namelen); +int send(SOCKET s, const(void)* buf, int len, int flags); +int sendto(SOCKET s, const(void)* buf, int len, int flags, const(sockaddr)* to, socklen_t tolen); +int recv(SOCKET s, void* buf, int len, int flags); +int recvfrom(SOCKET s, void* buf, int len, int flags, sockaddr* from, socklen_t* fromlen); +int getsockopt(SOCKET s, int level, int optname, void* optval, socklen_t* optlen); +int setsockopt(SOCKET s, int level, int optname, const(void)* optval, socklen_t optlen); +uint inet_addr(const char* cp); +int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* errorfds, const(timeval)* timeout); +char* inet_ntoa(in_addr ina); +hostent* gethostbyname(const char* name); +hostent* gethostbyaddr(const(void)* addr, int len, int type); +protoent* getprotobyname(const char* name); +protoent* getprotobynumber(int number); +servent* getservbyname(const char* name, const char* proto); +servent* getservbyport(int port, const char* proto); +} + +enum: int +{ + NI_NOFQDN = 0x01, + NI_NUMERICHOST = 0x02, + NI_NAMEREQD = 0x04, + NI_NUMERICSERV = 0x08, + NI_DGRAM = 0x10, +} + +@nogc +{ +int gethostname(const char* name, int namelen); +int getaddrinfo(const(char)* nodename, const(char)* servname, const(addrinfo)* hints, addrinfo** res); +void freeaddrinfo(addrinfo* ai); +int getnameinfo(const(sockaddr)* sa, socklen_t salen, char* host, uint hostlen, char* serv, uint servlen, int flags); +} + +enum WSABASEERR = 10000; + +enum: int +{ + /* + * Windows Sockets definitions of regular Microsoft C error constants + */ + WSAEINTR = (WSABASEERR+4), + WSAEBADF = (WSABASEERR+9), + WSAEACCES = (WSABASEERR+13), + WSAEFAULT = (WSABASEERR+14), + WSAEINVAL = (WSABASEERR+22), + WSAEMFILE = (WSABASEERR+24), + + /* + * Windows Sockets definitions of regular Berkeley error constants + */ + WSAEWOULDBLOCK = (WSABASEERR+35), + WSAEINPROGRESS = (WSABASEERR+36), + WSAEALREADY = (WSABASEERR+37), + WSAENOTSOCK = (WSABASEERR+38), + WSAEDESTADDRREQ = (WSABASEERR+39), + WSAEMSGSIZE = (WSABASEERR+40), + WSAEPROTOTYPE = (WSABASEERR+41), + WSAENOPROTOOPT = (WSABASEERR+42), + WSAEPROTONOSUPPORT = (WSABASEERR+43), + WSAESOCKTNOSUPPORT = (WSABASEERR+44), + WSAEOPNOTSUPP = (WSABASEERR+45), + WSAEPFNOSUPPORT = (WSABASEERR+46), + WSAEAFNOSUPPORT = (WSABASEERR+47), + WSAEADDRINUSE = (WSABASEERR+48), + WSAEADDRNOTAVAIL = (WSABASEERR+49), + WSAENETDOWN = (WSABASEERR+50), + WSAENETUNREACH = (WSABASEERR+51), + WSAENETRESET = (WSABASEERR+52), + WSAECONNABORTED = (WSABASEERR+53), + WSAECONNRESET = (WSABASEERR+54), + WSAENOBUFS = (WSABASEERR+55), + WSAEISCONN = (WSABASEERR+56), + WSAENOTCONN = (WSABASEERR+57), + WSAESHUTDOWN = (WSABASEERR+58), + WSAETOOMANYREFS = (WSABASEERR+59), + WSAETIMEDOUT = (WSABASEERR+60), + WSAECONNREFUSED = (WSABASEERR+61), + WSAELOOP = (WSABASEERR+62), + WSAENAMETOOLONG = (WSABASEERR+63), + WSAEHOSTDOWN = (WSABASEERR+64), + WSAEHOSTUNREACH = (WSABASEERR+65), + WSAENOTEMPTY = (WSABASEERR+66), + WSAEPROCLIM = (WSABASEERR+67), + WSAEUSERS = (WSABASEERR+68), + WSAEDQUOT = (WSABASEERR+69), + WSAESTALE = (WSABASEERR+70), + WSAEREMOTE = (WSABASEERR+71), + + /* + * Extended Windows Sockets error constant definitions + */ + WSASYSNOTREADY = (WSABASEERR+91), + WSAVERNOTSUPPORTED = (WSABASEERR+92), + WSANOTINITIALISED = (WSABASEERR+93), + + /* Authoritative Answer: Host not found */ + WSAHOST_NOT_FOUND = (WSABASEERR+1001), + HOST_NOT_FOUND = WSAHOST_NOT_FOUND, + + /* Non-Authoritative: Host not found, or SERVERFAIL */ + WSATRY_AGAIN = (WSABASEERR+1002), + TRY_AGAIN = WSATRY_AGAIN, + + /* Non recoverable errors, FORMERR, REFUSED, NOTIMP */ + WSANO_RECOVERY = (WSABASEERR+1003), + NO_RECOVERY = WSANO_RECOVERY, + + /* Valid name, no data record of requested type */ + WSANO_DATA = (WSABASEERR+1004), + NO_DATA = WSANO_DATA, + + /* no address, look for MX record */ + WSANO_ADDRESS = WSANO_DATA, + NO_ADDRESS = WSANO_ADDRESS +} + +/* + * Windows Sockets errors redefined as regular Berkeley error constants + */ +enum: int +{ + EWOULDBLOCK = WSAEWOULDBLOCK, + EINPROGRESS = WSAEINPROGRESS, + EALREADY = WSAEALREADY, + ENOTSOCK = WSAENOTSOCK, + EDESTADDRREQ = WSAEDESTADDRREQ, + EMSGSIZE = WSAEMSGSIZE, + EPROTOTYPE = WSAEPROTOTYPE, + ENOPROTOOPT = WSAENOPROTOOPT, + EPROTONOSUPPORT = WSAEPROTONOSUPPORT, + ESOCKTNOSUPPORT = WSAESOCKTNOSUPPORT, + EOPNOTSUPP = WSAEOPNOTSUPP, + EPFNOSUPPORT = WSAEPFNOSUPPORT, + EAFNOSUPPORT = WSAEAFNOSUPPORT, + EADDRINUSE = WSAEADDRINUSE, + EADDRNOTAVAIL = WSAEADDRNOTAVAIL, + ENETDOWN = WSAENETDOWN, + ENETUNREACH = WSAENETUNREACH, + ENETRESET = WSAENETRESET, + ECONNABORTED = WSAECONNABORTED, + ECONNRESET = WSAECONNRESET, + ENOBUFS = WSAENOBUFS, + EISCONN = WSAEISCONN, + ENOTCONN = WSAENOTCONN, + ESHUTDOWN = WSAESHUTDOWN, + ETOOMANYREFS = WSAETOOMANYREFS, + ETIMEDOUT = WSAETIMEDOUT, + ECONNREFUSED = WSAECONNREFUSED, + ELOOP = WSAELOOP, + ENAMETOOLONG = WSAENAMETOOLONG, + EHOSTDOWN = WSAEHOSTDOWN, + EHOSTUNREACH = WSAEHOSTUNREACH, + ENOTEMPTY = WSAENOTEMPTY, + EPROCLIM = WSAEPROCLIM, + EUSERS = WSAEUSERS, + EDQUOT = WSAEDQUOT, + ESTALE = WSAESTALE, + EREMOTE = WSAEREMOTE +} + +enum: int +{ + EAI_NONAME = WSAHOST_NOT_FOUND, +} + +int WSAGetLastError() @nogc @trusted; + + +enum: int +{ + AF_UNSPEC = 0, + + AF_UNIX = 1, + AF_INET = 2, + AF_IMPLINK = 3, + AF_PUP = 4, + AF_CHAOS = 5, + AF_NS = 6, + AF_IPX = AF_NS, + AF_ISO = 7, + AF_OSI = AF_ISO, + AF_ECMA = 8, + AF_DATAKIT = 9, + AF_CCITT = 10, + AF_SNA = 11, + AF_DECnet = 12, + AF_DLI = 13, + AF_LAT = 14, + AF_HYLINK = 15, + AF_APPLETALK = 16, + AF_NETBIOS = 17, + AF_VOICEVIEW = 18, + AF_FIREFOX = 19, + AF_UNKNOWN1 = 20, + AF_BAN = 21, + AF_ATM = 22, + AF_INET6 = 23, + AF_CLUSTER = 24, + AF_12844 = 25, + AF_IRDA = 26, + AF_NETDES = 28, + + AF_MAX = 29, + + + PF_UNSPEC = AF_UNSPEC, + + PF_UNIX = AF_UNIX, + PF_INET = AF_INET, + PF_IMPLINK = AF_IMPLINK, + PF_PUP = AF_PUP, + PF_CHAOS = AF_CHAOS, + PF_NS = AF_NS, + PF_IPX = AF_IPX, + PF_ISO = AF_ISO, + PF_OSI = AF_OSI, + PF_ECMA = AF_ECMA, + PF_DATAKIT = AF_DATAKIT, + PF_CCITT = AF_CCITT, + PF_SNA = AF_SNA, + PF_DECnet = AF_DECnet, + PF_DLI = AF_DLI, + PF_LAT = AF_LAT, + PF_HYLINK = AF_HYLINK, + PF_APPLETALK = AF_APPLETALK, + PF_VOICEVIEW = AF_VOICEVIEW, + PF_FIREFOX = AF_FIREFOX, + PF_UNKNOWN1 = AF_UNKNOWN1, + PF_BAN = AF_BAN, + PF_INET6 = AF_INET6, + + PF_MAX = AF_MAX, +} + + +enum: int +{ + SOL_SOCKET = 0xFFFF, +} + + +enum: int +{ + SO_DEBUG = 0x0001, + SO_ACCEPTCONN = 0x0002, + SO_REUSEADDR = 0x0004, + SO_KEEPALIVE = 0x0008, + SO_DONTROUTE = 0x0010, + SO_BROADCAST = 0x0020, + SO_USELOOPBACK = 0x0040, + SO_LINGER = 0x0080, + SO_DONTLINGER = ~SO_LINGER, + SO_OOBINLINE = 0x0100, + SO_SNDBUF = 0x1001, + SO_RCVBUF = 0x1002, + SO_SNDLOWAT = 0x1003, + SO_RCVLOWAT = 0x1004, + SO_SNDTIMEO = 0x1005, + SO_RCVTIMEO = 0x1006, + SO_ERROR = 0x1007, + SO_TYPE = 0x1008, + SO_EXCLUSIVEADDRUSE = ~SO_REUSEADDR, + + TCP_NODELAY = 1, + + IP_OPTIONS = 1, + + IP_HDRINCL = 2, + IP_TOS = 3, + IP_TTL = 4, + IP_MULTICAST_IF = 9, + IP_MULTICAST_TTL = 10, + IP_MULTICAST_LOOP = 11, + IP_ADD_MEMBERSHIP = 12, + IP_DROP_MEMBERSHIP = 13, + IP_DONTFRAGMENT = 14, + IP_ADD_SOURCE_MEMBERSHIP = 15, + IP_DROP_SOURCE_MEMBERSHIP = 16, + IP_BLOCK_SOURCE = 17, + IP_UNBLOCK_SOURCE = 18, + IP_PKTINFO = 19, + + IPV6_UNICAST_HOPS = 4, + IPV6_MULTICAST_IF = 9, + IPV6_MULTICAST_HOPS = 10, + IPV6_MULTICAST_LOOP = 11, + IPV6_ADD_MEMBERSHIP = 12, + IPV6_DROP_MEMBERSHIP = 13, + IPV6_JOIN_GROUP = IPV6_ADD_MEMBERSHIP, + IPV6_LEAVE_GROUP = IPV6_DROP_MEMBERSHIP, + IPV6_V6ONLY = 27, +} + + +/// Default FD_SETSIZE value. +/// In C/C++, it is redefinable by #define-ing the macro before #include-ing +/// winsock.h. In D, use the $(D FD_CREATE) function to allocate a $(D fd_set) +/// of an arbitrary size. +enum int FD_SETSIZE = 64; + + +struct fd_set_custom(uint SETSIZE) +{ + uint fd_count; + SOCKET[SETSIZE] fd_array; +} + +alias fd_set = fd_set_custom!FD_SETSIZE; + +// Removes. +void FD_CLR(SOCKET fd, fd_set* set) pure @nogc +{ + uint c = set.fd_count; + SOCKET* start = set.fd_array.ptr; + SOCKET* stop = start + c; + + for (; start != stop; start++) + { + if (*start == fd) + goto found; + } + return; //not found + + found: + for (++start; start != stop; start++) + { + *(start - 1) = *start; + } + + set.fd_count = c - 1; +} + + +// Tests. +int FD_ISSET(SOCKET fd, const(fd_set)* set) pure @nogc +{ +const(SOCKET)* start = set.fd_array.ptr; +const(SOCKET)* stop = start + set.fd_count; + + for (; start != stop; start++) + { + if (*start == fd) + return true; + } + return false; +} + + +// Adds. +void FD_SET(SOCKET fd, fd_set* set) pure @nogc +{ + uint c = set.fd_count; + set.fd_array.ptr[c] = fd; + set.fd_count = c + 1; +} + + +// Resets to zero. +void FD_ZERO(fd_set* set) pure @nogc +{ + set.fd_count = 0; +} + + +/// Creates a new $(D fd_set) with the specified capacity. +fd_set* FD_CREATE(uint capacity) pure +{ + // Take into account alignment (SOCKET may be 64-bit and require 64-bit alignment on 64-bit systems) + size_t size = (fd_set_custom!1).sizeof - SOCKET.sizeof + (SOCKET.sizeof * capacity); + auto data = new ubyte[size]; + auto set = cast(fd_set*)data.ptr; + FD_ZERO(set); + return set; +} + +struct linger +{ + ushort l_onoff; + ushort l_linger; +} + + +struct protoent +{ + char* p_name; + char** p_aliases; + short p_proto; +} + + +struct servent +{ + char* s_name; + char** s_aliases; + + version (Win64) + { + char* s_proto; + short s_port; + } + else version (Win32) + { + short s_port; + char* s_proto; + } +} + + +/+ +union in6_addr +{ + private union _u_t + { + ubyte[16] Byte; + ushort[8] Word; + } + _u_t u; +} + + +struct in_addr6 +{ + ubyte[16] s6_addr; +} ++/ + +@safe pure @nogc +{ + ushort htons(ushort x); + uint htonl(uint x); + ushort ntohs(ushort x); + uint ntohl(uint x); +} + + +enum: int +{ + SOCK_STREAM = 1, + SOCK_DGRAM = 2, + SOCK_RAW = 3, + SOCK_RDM = 4, + SOCK_SEQPACKET = 5, +} + + +enum: int +{ + IPPROTO_IP = 0, + IPPROTO_ICMP = 1, + IPPROTO_IGMP = 2, + IPPROTO_GGP = 3, + IPPROTO_TCP = 6, + IPPROTO_PUP = 12, + IPPROTO_UDP = 17, + IPPROTO_IDP = 22, + IPPROTO_IPV6 = 41, + IPPROTO_ND = 77, + IPPROTO_RAW = 255, + + IPPROTO_MAX = 256, +} + + +enum: int +{ + MSG_OOB = 0x1, + MSG_PEEK = 0x2, + MSG_DONTROUTE = 0x4 +} + + +enum: int +{ + SD_RECEIVE = 0, + SD_SEND = 1, + SD_BOTH = 2, +} + + +enum: uint +{ + INADDR_ANY = 0, + INADDR_LOOPBACK = 0x7F000001, + INADDR_BROADCAST = 0xFFFFFFFF, + INADDR_NONE = 0xFFFFFFFF, + ADDR_ANY = INADDR_ANY, +} + + +enum: int +{ + AI_PASSIVE = 0x1, // Socket address will be used in bind() call + AI_CANONNAME = 0x2, // Return canonical name in first ai_canonname + AI_NUMERICHOST = 0x4, // Nodename must be a numeric address string + AI_NUMERICSERV = 0x8, // Servicename must be a numeric port number + AI_DNS_ONLY = 0x10, // Restrict queries to unicast DNS only (no LLMNR, netbios, etc.) + AI_FORCE_CLEAR_TEXT = 0x20, // Force clear text DNS query + AI_BYPASS_DNS_CACHE = 0x40, // Bypass DNS cache + AI_RETURN_TTL = 0x80, // Return record TTL + AI_ALL = 0x0100, // Query both IP6 and IP4 with AI_V4MAPPED + AI_ADDRCONFIG = 0x0400, // Resolution only if global address configured + AI_V4MAPPED = 0x0800, // On v6 failure, query v4 and convert to V4MAPPED format + AI_NON_AUTHORITATIVE = 0x04000, // LUP_NON_AUTHORITATIVE + AI_SECURE = 0x08000, // LUP_SECURE + AI_RETURN_PREFERRED_NAMES = 0x010000, // LUP_RETURN_PREFERRED_NAMES + AI_FQDN = 0x00020000, // Return the FQDN in ai_canonname + AI_FILESERVER = 0x00040000, // Resolving fileserver name resolution + AI_DISABLE_IDN_ENCODING = 0x00080000, // Disable Internationalized Domain Names handling + AI_SECURE_WITH_FALLBACK = 0x00100000, // Forces clear text fallback if the secure DNS query fails + AI_EXCLUSIVE_CUSTOM_SERVERS = 0x00200000, // Use exclusively the custom DNS servers + AI_RETURN_RESPONSE_FLAGS = 0x10000000, // Requests extra information about the DNS results + AI_REQUIRE_SECURE = 0x20000000, // Forces the DNS query to be done over seucre protocols + AI_RESOLUTION_HANDLE = 0x40000000, // Request resolution handle + AI_EXTENDED = 0x80000000, // Indicates this is extended ADDRINFOEX(2/..) struct +} + + +struct timeval +{ + int tv_sec; + int tv_usec; +} + + +union in_addr +{ + private union _S_un_t + { + private struct _S_un_b_t + { + ubyte s_b1, s_b2, s_b3, s_b4; + } + _S_un_b_t S_un_b; + + private struct _S_un_w_t + { + ushort s_w1, s_w2; + } + _S_un_w_t S_un_w; + + uint S_addr; + } + _S_un_t S_un; + + uint s_addr; + + struct + { + ubyte s_net, s_host; + + union + { + ushort s_imp; + + struct + { + ubyte s_lh, s_impno; + } + } + } +} + + +union in6_addr +{ + private union _in6_u_t + { + ubyte[16] u6_addr8; + ushort[8] u6_addr16; + uint[4] u6_addr32; + } + _in6_u_t in6_u; + + ubyte[16] s6_addr8; + ushort[8] s6_addr16; + uint[4] s6_addr32; + + alias s6_addr = s6_addr8; +} + + +enum in6_addr IN6ADDR_ANY = { s6_addr8: [0] }; +enum in6_addr IN6ADDR_LOOPBACK = { s6_addr8: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1] }; +//alias IN6ADDR_ANY_INIT = IN6ADDR_ANY; +//alias IN6ADDR_LOOPBACK_INIT = IN6ADDR_LOOPBACK; + +enum int INET_ADDRSTRLEN = 16; +enum int INET6_ADDRSTRLEN = 46; + + + + +struct sockaddr +{ + short sa_family; + ubyte[14] sa_data; +} +alias sockaddr SOCKADDR; +alias SOCKADDR* PSOCKADDR, LPSOCKADDR; + +struct sockaddr_storage +{ + short ss_family; + char[6] __ss_pad1 = void; + long __ss_align; + char[112] __ss_pad2 = void; +} +alias sockaddr_storage SOCKADDR_STORAGE; +alias SOCKADDR_STORAGE* PSOCKADDR_STORAGE; + +struct sockaddr_in +{ + short sin_family = AF_INET; + ushort sin_port; + in_addr sin_addr; + ubyte[8] sin_zero; +} +alias sockaddr_in SOCKADDR_IN; +alias SOCKADDR_IN* PSOCKADDR_IN, LPSOCKADDR_IN; + + +struct sockaddr_in6 +{ + short sin6_family = AF_INET6; + ushort sin6_port; + uint sin6_flowinfo; + in6_addr sin6_addr; + uint sin6_scope_id; +} + + +struct addrinfo +{ + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + size_t ai_addrlen; + char* ai_canonname; + sockaddr* ai_addr; + addrinfo* ai_next; +} + + +struct hostent +{ + char* h_name; + char** h_aliases; + short h_addrtype; + short h_length; + char** h_addr_list; + + + char* h_addr() @safe pure nothrow @nogc + { + return h_addr_list[0]; + } +} + +// Note: These are Winsock2!! +struct WSAOVERLAPPED; +alias LPWSAOVERLAPPED = WSAOVERLAPPED*; +alias LPWSAOVERLAPPED_COMPLETION_ROUTINE = void function(uint, uint, LPWSAOVERLAPPED, uint) nothrow @nogc; +int WSAIoctl(SOCKET s, uint dwIoControlCode, + void* lpvInBuffer, uint cbInBuffer, + void* lpvOutBuffer, uint cbOutBuffer, + uint* lpcbBytesReturned, + LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine) @nogc; + + +enum IOC_VENDOR = 0x18000000; +enum SIO_KEEPALIVE_VALS = IOC_IN | IOC_VENDOR | 4; + +/* Argument structure for SIO_KEEPALIVE_VALS */ +struct tcp_keepalive +{ + uint onoff; + uint keepalivetime; + uint keepaliveinterval; +} + + +struct pollfd +{ + SOCKET fd; // Socket handle + short events; // Requested events to monitor + short revents; // Returned events indicating status +} +alias WSAPOLLFD = pollfd; +alias PWSAPOLLFD = pollfd*; +alias LPWSAPOLLFD = pollfd*; + +enum: short { + POLLRDNORM = 0x0100, + POLLRDBAND = 0x0200, + POLLIN = (POLLRDNORM | POLLRDBAND), + POLLPRI = 0x0400, + + POLLWRNORM = 0x0010, + POLLOUT = (POLLWRNORM), + POLLWRBAND = 0x0020, + + POLLERR = 0x0001, + POLLHUP = 0x0002, + POLLNVAL = 0x0004 +} + +int WSAPoll(LPWSAPOLLFD fdArray, uint fds, int timeout) @nogc; diff --git a/src/urt/internal/sys/windows/winuser.d b/src/urt/internal/sys/windows/winuser.d new file mode 100644 index 0000000..f542160 --- /dev/null +++ b/src/urt/internal/sys/windows/winuser.d @@ -0,0 +1,144 @@ +/// Minimal winuser bindings - only virtual key codes. +/// Full winuser.d was trimmed; GUI/display/font APIs removed. +module urt.internal.sys.windows.winuser; +version (Windows): + +enum { + VK_LBUTTON = 0x01, + VK_RBUTTON = 0x02, + VK_CANCEL = 0x03, + VK_MBUTTON = 0x04, + VK_XBUTTON1 = 0x05, + VK_XBUTTON2 = 0x06, + VK_BACK = 0x08, + VK_TAB = 0x09, + VK_CLEAR = 0x0C, + VK_RETURN = 0x0D, + VK_SHIFT = 0x10, + VK_CONTROL = 0x11, + VK_MENU = 0x12, + VK_PAUSE = 0x13, + VK_CAPITAL = 0x14, + VK_KANA = 0x15, + VK_HANGEUL = 0x15, + VK_HANGUL = 0x15, + VK_JUNJA = 0x17, + VK_FINAL = 0x18, + VK_HANJA = 0x19, + VK_KANJI = 0x19, + VK_ESCAPE = 0x1B, + VK_CONVERT = 0x1C, + VK_NONCONVERT = 0x1D, + VK_ACCEPT = 0x1E, + VK_MODECHANGE = 0x1F, + VK_SPACE = 0x20, + VK_PRIOR = 0x21, + VK_NEXT = 0x22, + VK_END = 0x23, + VK_HOME = 0x24, + VK_LEFT = 0x25, + VK_UP = 0x26, + VK_RIGHT = 0x27, + VK_DOWN = 0x28, + VK_SELECT = 0x29, + VK_PRINT = 0x2A, + VK_EXECUTE = 0x2B, + VK_SNAPSHOT = 0x2C, + VK_INSERT = 0x2D, + VK_DELETE = 0x2E, + VK_HELP = 0x2F, + VK_LWIN = 0x5B, + VK_RWIN = 0x5C, + VK_APPS = 0x5D, + VK_SLEEP = 0x5F, + VK_NUMPAD0 = 0x60, + VK_NUMPAD1 = 0x61, + VK_NUMPAD2 = 0x62, + VK_NUMPAD3 = 0x63, + VK_NUMPAD4 = 0x64, + VK_NUMPAD5 = 0x65, + VK_NUMPAD6 = 0x66, + VK_NUMPAD7 = 0x67, + VK_NUMPAD8 = 0x68, + VK_NUMPAD9 = 0x69, + VK_MULTIPLY = 0x6A, + VK_ADD = 0x6B, + VK_SEPARATOR = 0x6C, + VK_SUBTRACT = 0x6D, + VK_DECIMAL = 0x6E, + VK_DIVIDE = 0x6F, + VK_F1 = 0x70, + VK_F2 = 0x71, + VK_F3 = 0x72, + VK_F4 = 0x73, + VK_F5 = 0x74, + VK_F6 = 0x75, + VK_F7 = 0x76, + VK_F8 = 0x77, + VK_F9 = 0x78, + VK_F10 = 0x79, + VK_F11 = 0x7A, + VK_F12 = 0x7B, + VK_F13 = 0x7C, + VK_F14 = 0x7D, + VK_F15 = 0x7E, + VK_F16 = 0x7F, + VK_F17 = 0x80, + VK_F18 = 0x81, + VK_F19 = 0x82, + VK_F20 = 0x83, + VK_F21 = 0x84, + VK_F22 = 0x85, + VK_F23 = 0x86, + VK_F24 = 0x87, + VK_NUMLOCK = 0x90, + VK_SCROLL = 0x91, + VK_LSHIFT = 0xA0, + VK_RSHIFT = 0xA1, + VK_LCONTROL = 0xA2, + VK_RCONTROL = 0xA3, + VK_LMENU = 0xA4, + VK_RMENU = 0xA5, + VK_BROWSER_BACK = 0xA6, + VK_BROWSER_FORWARD = 0xA7, + VK_BROWSER_REFRESH = 0xA8, + VK_BROWSER_STOP = 0xA9, + VK_BROWSER_SEARCH = 0xAA, + VK_BROWSER_FAVORITES = 0xAB, + VK_BROWSER_HOME = 0xAC, + VK_VOLUME_MUTE = 0xAD, + VK_VOLUME_DOWN = 0xAE, + VK_VOLUME_UP = 0xAF, + VK_MEDIA_NEXT_TRACK = 0xB0, + VK_MEDIA_PREV_TRACK = 0xB1, + VK_MEDIA_STOP = 0xB2, + VK_MEDIA_PLAY_PAUSE = 0xB3, + VK_LAUNCH_MAIL = 0xB4, + VK_LAUNCH_MEDIA_SELECT = 0xB5, + VK_LAUNCH_APP1 = 0xB6, + VK_LAUNCH_APP2 = 0xB7, + VK_OEM_1 = 0xBA, + VK_OEM_PLUS = 0xBB, + VK_OEM_COMMA = 0xBC, + VK_OEM_MINUS = 0xBD, + VK_OEM_PERIOD = 0xBE, + VK_OEM_2 = 0xBF, + VK_OEM_3 = 0xC0, + VK_OEM_4 = 0xDB, + VK_OEM_5 = 0xDC, + VK_OEM_6 = 0xDD, + VK_OEM_7 = 0xDE, + VK_OEM_8 = 0xDF, + VK_OEM_102 = 0xE2, + VK_PROCESSKEY = 0xE5, + VK_PACKET = 0xE7, + VK_ATTN = 0xF6, + VK_CRSEL = 0xF7, + VK_EXSEL = 0xF8, + VK_EREOF = 0xF9, + VK_PLAY = 0xFA, + VK_ZOOM = 0xFB, + VK_NONAME = 0xFC, + VK_PA1 = 0xFD, + VK_OEM_CLEAR = 0xFE, +} diff --git a/src/urt/internal/traits.d b/src/urt/internal/traits.d new file mode 100644 index 0000000..cf52442 --- /dev/null +++ b/src/urt/internal/traits.d @@ -0,0 +1,1227 @@ +// TODO: THIS NEEDS TO BE DISSOLVED... +module urt.internal.traits; + +import urt.traits; + +template Fields(T) +{ + static if (is(T == struct) || is(T == union)) + alias Fields = typeof(T.tupleof[0 .. $ - __traits(isNested, T)]); + else static if (is(T == class) || is(T == interface)) + alias Fields = typeof(T.tupleof); + else + alias Fields = AliasSeq!T; +} + +T trustedCast(T, U)(auto ref U u) @trusted pure nothrow +{ + return cast(T)u; +} + +template BaseElemOf(T) +{ + static if (is(OriginalType!T == E[N], E, size_t N)) + alias BaseElemOf = BaseElemOf!E; + else + alias BaseElemOf = T; +} + +unittest +{ + static assert(is(BaseElemOf!(int) == int)); + static assert(is(BaseElemOf!(int[1]) == int)); + static assert(is(BaseElemOf!(int[1][2]) == int)); + static assert(is(BaseElemOf!(int[1][]) == int[1][])); + static assert(is(BaseElemOf!(int[][1]) == int[])); + enum E : int[2]{ test = [0, 1] } + static assert(is(BaseElemOf!(E) == int)); +} + +// [For internal use] +template ModifyTypePreservingTQ(alias Modifier, T) +{ + static if (is(T U == immutable U)) alias ModifyTypePreservingTQ = immutable Modifier!U; + else static if (is(T U == shared inout const U)) alias ModifyTypePreservingTQ = shared inout const Modifier!U; + else static if (is(T U == shared inout U)) alias ModifyTypePreservingTQ = shared inout Modifier!U; + else static if (is(T U == shared const U)) alias ModifyTypePreservingTQ = shared const Modifier!U; + else static if (is(T U == shared U)) alias ModifyTypePreservingTQ = shared Modifier!U; + else static if (is(T U == inout const U)) alias ModifyTypePreservingTQ = inout const Modifier!U; + else static if (is(T U == inout U)) alias ModifyTypePreservingTQ = inout Modifier!U; + else static if (is(T U == const U)) alias ModifyTypePreservingTQ = const Modifier!U; + else alias ModifyTypePreservingTQ = Modifier!T; +} +@safe unittest +{ + alias Intify(T) = int; + static assert(is(ModifyTypePreservingTQ!(Intify, real) == int)); + static assert(is(ModifyTypePreservingTQ!(Intify, const real) == const int)); + static assert(is(ModifyTypePreservingTQ!(Intify, inout real) == inout int)); + static assert(is(ModifyTypePreservingTQ!(Intify, inout const real) == inout const int)); + static assert(is(ModifyTypePreservingTQ!(Intify, shared real) == shared int)); + static assert(is(ModifyTypePreservingTQ!(Intify, shared const real) == shared const int)); + static assert(is(ModifyTypePreservingTQ!(Intify, shared inout real) == shared inout int)); + static assert(is(ModifyTypePreservingTQ!(Intify, shared inout const real) == shared inout const int)); + static assert(is(ModifyTypePreservingTQ!(Intify, immutable real) == immutable int)); +} + +// Substitute all `inout` qualifiers that appears in T to `const` +template substInout(T) +{ + static if (is(T == immutable)) + { + alias substInout = T; + } + else static if (is(T : shared const U, U) || is(T : const U, U)) + { + // U is top-unqualified + mixin("alias substInout = " + ~ (is(T == shared) ? "shared " : "") + ~ (is(T == const) || is(T == inout) ? "const " : "") // substitute inout to const + ~ "substInoutForm!U;"); + } + else + static assert(0); +} + +private template substInoutForm(T) +{ + static if (is(T == struct) || is(T == class) || is(T == union) || is(T == interface)) + { + alias substInoutForm = T; // prevent matching to the form of alias-this-ed type + } + else static if (is(T == V[K], K, V)) alias substInoutForm = substInout!V[substInout!K]; + else static if (is(T == U[n], U, size_t n)) alias substInoutForm = substInout!U[n]; + else static if (is(T == U[], U)) alias substInoutForm = substInout!U[]; + else static if (is(T == U*, U)) alias substInoutForm = substInout!U*; + else alias substInoutForm = T; +} + +unittest +{ + // https://github.com/dlang/dmd/issues/21452 + struct S { int x; } + struct T { int x; alias x this; } + + enum EnumInt { a = 123 } + enum EnumUInt : uint { a = 123 } + enum EnumFloat : float { a = 123 } + enum EnumString : string { a = "123" } + enum EnumStringW : wstring { a = "123" } + enum EnumStruct : S { a = S(7) } + enum EnumAliasThis : T { a = T(7) } + enum EnumDArray : int[] { a = [1] } + enum EnumAArray : int[int] { a = [0 : 1] } + + static assert(substInout!(EnumInt).stringof == "EnumInt"); + static assert(substInout!(inout(EnumUInt)).stringof == "const(EnumUInt)"); + static assert(substInout!(EnumFloat).stringof == "EnumFloat"); + static assert(substInout!(EnumString).stringof == "EnumString"); + static assert(substInout!(inout(EnumStringW)).stringof == "const(EnumStringW)"); + static assert(substInout!(EnumStruct).stringof == "EnumStruct"); + static assert(substInout!(EnumAliasThis).stringof == "EnumAliasThis"); + static assert(substInout!(EnumDArray).stringof == "EnumDArray"); + static assert(substInout!(inout(EnumAArray)[int]).stringof == "const(EnumAArray)[int]"); +} + +/// used to declare an extern(D) function that is defined in a different module +template externDFunc(string fqn, T:FT*, FT) if (is(FT == function)) +{ + static if (is(FT RT == return) && is(FT Args == function)) + { + import core.demangle : mangleFunc; + enum decl = { + string s = "extern(D) RT externDFunc(Args)"; + foreach (attr; __traits(getFunctionAttributes, FT)) + s ~= " " ~ attr; + return s ~ ";"; + }(); + pragma(mangle, mangleFunc!T(fqn)) mixin(decl); + } + else + static assert(0); +} + +template staticIota(int beg, int end) +{ + static if (beg + 1 >= end) + { + static if (beg >= end) + { + alias staticIota = AliasSeq!(); + } + else + { + alias staticIota = AliasSeq!(+beg); + } + } + else + { + enum mid = beg + (end - beg) / 2; + alias staticIota = AliasSeq!(staticIota!(beg, mid), staticIota!(mid, end)); + } +} + +private struct __InoutWorkaroundStruct {} +@property T rvalueOf(T)(T val) { return val; } +@property T rvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init); +@property ref T lvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init); + +// taken from std.traits.isAssignable +template isAssignable(Lhs, Rhs = Lhs) +{ + enum isAssignable = __traits(compiles, lvalueOf!Lhs = rvalueOf!Rhs) && __traits(compiles, lvalueOf!Lhs = lvalueOf!Rhs); +} + +// taken from std.traits.isInnerClass +template isInnerClass(T) if (is(T == class)) +{ + static if (is(typeof(T.outer))) + { + template hasOuterMember(T...) + { + static if (T.length == 0) + enum hasOuterMember = false; + else + enum hasOuterMember = T[0] == "outer" || hasOuterMember!(T[1 .. $]); + } + enum isInnerClass = __traits(isSame, typeof(T.outer), __traits(parent, T)) && !hasOuterMember!(__traits(allMembers, T)); + } + else + enum isInnerClass = false; +} + +template dtorIsNothrow(T) +{ + enum dtorIsNothrow = is(typeof(function{T t=void;}) : void function() nothrow); +} + +// taken from std.meta.allSatisfy +template allSatisfy(alias F, T...) +{ + static foreach (Ti; T) + { + static if (!is(typeof(allSatisfy) == bool) && // not yet defined + !F!(Ti)) + { + enum allSatisfy = false; + } + } + static if (!is(typeof(allSatisfy) == bool)) // if not yet defined + { + enum allSatisfy = true; + } +} + +// taken from std.meta.anySatisfy +template anySatisfy(alias F, Ts...) +{ + static foreach (T; Ts) + { + static if (!is(typeof(anySatisfy) == bool) && // not yet defined + F!T) + { + enum anySatisfy = true; + } + } + static if (!is(typeof(anySatisfy) == bool)) // if not yet defined + { + enum anySatisfy = false; + } +} + +// simplified from std.traits.maxAlignment +template maxAlignment(Ts...) +if (Ts.length > 0) +{ + enum maxAlignment = + { + size_t result = 0; + static foreach (T; Ts) + if (T.alignof > result) result = T.alignof; + return result; + }(); +} + +template classInstanceAlignment(T) +if (is(T == class)) +{ + enum classInstanceAlignment = __traits(classInstanceAlignment, T); +} + +/// See $(REF hasElaborateMove, std,traits) +template hasElaborateMove(S) +{ + static if (__traits(isStaticArray, S)) + { + enum bool hasElaborateMove = S.sizeof && hasElaborateMove!(BaseElemOf!S); + } + else static if (is(S == struct)) + { + enum hasElaborateMove = (is(typeof(S.init.opPostMove(lvalueOf!S))) && + !is(typeof(S.init.opPostMove(rvalueOf!S)))) || + anySatisfy!(.hasElaborateMove, Fields!S); + } + else + { + enum bool hasElaborateMove = false; + } +} + +// std.traits.hasElaborateDestructor +template hasElaborateDestructor(S) +{ + static if (__traits(isStaticArray, S)) + { + enum bool hasElaborateDestructor = S.sizeof && hasElaborateDestructor!(BaseElemOf!S); + } + else static if (is(S == struct)) + { + // Once https://issues.dlang.org/show_bug.cgi?id=24865 is fixed, then + // this should be the implementation, but until that's fixed, we need the + // uncommented code. + // enum hasElaborateDestructor = __traits(hasMember, S, "__xdtor"); + + enum hasElaborateDestructor = hasDtor([__traits(allMembers, S)]); + } + else + { + enum bool hasElaborateDestructor = false; + } +} + +private bool hasDtor(string[] members) +{ + foreach (name; members) + { + if (name == "__xdtor") + return true; + } + + return false; +} + +@safe unittest +{ + static struct NoDestructor {} + static assert(!hasElaborateDestructor!NoDestructor); + static assert(!hasElaborateDestructor!(NoDestructor[42])); + static assert(!hasElaborateDestructor!(NoDestructor[0])); + static assert(!hasElaborateDestructor!(NoDestructor[])); + + static struct HasDestructor { ~this() {} } + static assert( hasElaborateDestructor!HasDestructor); + static assert( hasElaborateDestructor!(HasDestructor[42])); + static assert(!hasElaborateDestructor!(HasDestructor[0])); + static assert(!hasElaborateDestructor!(HasDestructor[])); + + static struct HasDestructor2 { HasDestructor s; } + static assert( hasElaborateDestructor!HasDestructor2); + static assert( hasElaborateDestructor!(HasDestructor2[42])); + static assert(!hasElaborateDestructor!(HasDestructor2[0])); + static assert(!hasElaborateDestructor!(HasDestructor2[])); + + static class HasFinalizer { ~this() {} } + static assert(!hasElaborateDestructor!HasFinalizer); + + static struct HasUnion { union { HasDestructor s; } } + static assert(!hasElaborateDestructor!HasUnion); + static assert(!hasElaborateDestructor!(HasUnion[42])); + static assert(!hasElaborateDestructor!(HasUnion[0])); + static assert(!hasElaborateDestructor!(HasUnion[])); + + static assert(!hasElaborateDestructor!int); + static assert(!hasElaborateDestructor!(int[0])); + static assert(!hasElaborateDestructor!(int[42])); + static assert(!hasElaborateDestructor!(int[])); +} + +// https://issues.dlang.org/show_bug.cgi?id=24865 +@safe unittest +{ + static struct S2 { ~this() {} } + static struct S3 { S2 field; } + static struct S6 { S3[0] field; } + + static assert( hasElaborateDestructor!S2); + static assert( hasElaborateDestructor!S3); + static assert(!hasElaborateDestructor!S6); +} + +// std.traits.hasElaborateCopyDestructor +template hasElaborateCopyConstructor(S) +{ + static if (__traits(isStaticArray, S)) + { + enum bool hasElaborateCopyConstructor = S.sizeof && hasElaborateCopyConstructor!(BaseElemOf!S); + } + else static if (is(S == struct)) + { + enum hasElaborateCopyConstructor = __traits(hasCopyConstructor, S) || __traits(hasPostblit, S); + } + else + { + enum bool hasElaborateCopyConstructor = false; + } +} + +@safe unittest +{ + static struct S + { + int x; + this(return scope ref typeof(this) rhs) { } + this(int x, int y) {} + } + + static assert( hasElaborateCopyConstructor!S); + static assert(!hasElaborateCopyConstructor!(S[0][1])); + + static struct S2 + { + int x; + this(int x, int y) {} + } + + static assert(!hasElaborateCopyConstructor!S2); + + static struct S3 + { + int x; + this(return scope ref typeof(this) rhs, int x = 42) { } + this(int x, int y) {} + } + + static assert( hasElaborateCopyConstructor!S3); + + static struct S4 { union { S s; } } + + static assert(!hasElaborateCopyConstructor!S4); +} + +template hasElaborateAssign(S) +{ + static if (__traits(isStaticArray, S)) + { + enum bool hasElaborateAssign = S.sizeof && hasElaborateAssign!(BaseElemOf!S); + } + else static if (is(S == struct)) + { + enum hasElaborateAssign = is(typeof(S.init.opAssign(rvalueOf!S))) || + is(typeof(S.init.opAssign(lvalueOf!S))); + } + else + { + enum bool hasElaborateAssign = false; + } +} + +unittest +{ + { + static struct S {} + static assert(!hasElaborateAssign!S); + static assert(!hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + { + static struct S { int i; } + static assert(!hasElaborateAssign!S); + static assert(!hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + { + static struct S { void opAssign(S) {} } + static assert( hasElaborateAssign!S); + static assert( hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + { + static struct S { void opAssign(ref S) {} } + static assert( hasElaborateAssign!S); + static assert( hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + { + static struct S { void opAssign(int) {} } + static assert(!hasElaborateAssign!S); + static assert(!hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + { + static struct S { this(this) {} } + static assert( hasElaborateAssign!S); + static assert( hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + // https://issues.dlang.org/show_bug.cgi?id=24834 + /+ + { + static struct S { this(ref S) {} } + static assert( hasElaborateAssign!S); + static assert( hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + +/ + { + static struct S { ~this() {} } + static assert( hasElaborateAssign!S); + static assert( hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + { + static struct S { @disable void opAssign(S); } + static assert(!hasElaborateAssign!S); + static assert(!hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + { + static struct Member {} + static struct S { Member member; } + static assert(!hasElaborateAssign!S); + static assert(!hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + { + static struct Member { void opAssign(Member) {} } + static struct S { Member member; } + static assert( hasElaborateAssign!S); + static assert( hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + { + static struct Member {} + static struct S { Member member; void opAssign(S) {} } + static assert( hasElaborateAssign!S); + static assert( hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + { + static struct Member { @disable void opAssign(Member); } + static struct S { Member member; } + static assert(!hasElaborateAssign!S); + static assert(!hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + { + static struct Member { @disable void opAssign(Member); } + static struct S { Member member; void opAssign(S) {} } + static assert( hasElaborateAssign!S); + static assert( hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + { + static struct Member { void opAssign(Member) {} } + static struct S { Member member; @disable void opAssign(S); } + static assert(!hasElaborateAssign!S); + static assert(!hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + { + static struct Member { void opAssign(Member) {} } + static struct S { union { Member member; } } + static assert(!hasElaborateAssign!S); + static assert(!hasElaborateAssign!(S[10])); + static assert(!hasElaborateAssign!(S[0])); + static assert(!hasElaborateAssign!(S[])); + } + + static assert(!hasElaborateAssign!int); + static assert(!hasElaborateAssign!(string[])); + static assert(!hasElaborateAssign!Object); +} + +template hasIndirections(T) +{ + static if (is(T == enum)) + enum hasIndirections = hasIndirections!(OriginalType!T); + else static if (is(T == struct) || is(T == union)) + enum hasIndirections = anySatisfy!(.hasIndirections, typeof(T.tupleof)); + else static if (__traits(isAssociativeArray, T) || is(T == class) || is(T == interface)) + enum hasIndirections = true; + else static if (is(T == E[N], E, size_t N)) + enum hasIndirections = T.sizeof && (is(immutable E == immutable void) || hasIndirections!(BaseElemOf!E)); + else static if (isFunctionPointer!T) + enum hasIndirections = false; + else + enum hasIndirections = isPointer!T || isDelegate!T || isDynamicArray!T; +} + +@safe unittest +{ + static assert(!hasIndirections!int); + static assert(!hasIndirections!(const int)); + + static assert( hasIndirections!(int*)); + static assert( hasIndirections!(const int*)); + + static assert( hasIndirections!(int[])); + static assert(!hasIndirections!(int[42])); + static assert(!hasIndirections!(int[0])); + + static assert( hasIndirections!(int*)); + static assert( hasIndirections!(int*[])); + static assert( hasIndirections!(int*[42])); + static assert(!hasIndirections!(int*[0])); + + static assert( hasIndirections!string); + static assert( hasIndirections!(string[])); + static assert( hasIndirections!(string[42])); + static assert(!hasIndirections!(string[0])); + + static assert( hasIndirections!(void[])); + static assert( hasIndirections!(void[17])); + static assert(!hasIndirections!(void[0])); + + static assert( hasIndirections!(string[int])); + static assert( hasIndirections!(string[int]*)); + static assert( hasIndirections!(string[int][])); + static assert( hasIndirections!(string[int][12])); + static assert(!hasIndirections!(string[int][0])); + + static assert(!hasIndirections!(int function(string))); + static assert( hasIndirections!(int delegate(string))); + static assert(!hasIndirections!(const(int function(string)))); + static assert( hasIndirections!(const(int delegate(string)))); + static assert(!hasIndirections!(immutable(int function(string)))); + static assert( hasIndirections!(immutable(int delegate(string)))); + + static class C {} + static assert( hasIndirections!C); + + static interface I {} + static assert( hasIndirections!I); + + { + enum E : int { a } + static assert(!hasIndirections!E); + } + { + enum E : int* { a } + static assert( hasIndirections!E); + } + { + enum E : string { a = "" } + static assert( hasIndirections!E); + } + { + enum E : int[] { a = null } + static assert( hasIndirections!E); + } + { + enum E : int[3] { a = [1, 2, 3] } + static assert(!hasIndirections!E); + } + { + enum E : int*[3] { a = [null, null, null] } + static assert( hasIndirections!E); + } + { + enum E : int*[0] { a = int*[0].init } + static assert(!hasIndirections!E); + } + { + enum E : C { a = null } + static assert( hasIndirections!E); + } + { + enum E : I { a = null } + static assert( hasIndirections!E); + } + + { + static struct S {} + static assert(!hasIndirections!S); + + enum E : S { a = S.init } + static assert(!hasIndirections!S); + } + { + static struct S { int i; } + static assert(!hasIndirections!S); + + enum E : S { a = S.init } + static assert(!hasIndirections!S); + } + { + static struct S { C c; } + static assert( hasIndirections!S); + + enum E : S { a = S.init } + static assert( hasIndirections!S); + } + { + static struct S { int[] arr; } + static assert( hasIndirections!S); + + enum E : S { a = S.init } + static assert( hasIndirections!S); + } + { + int local; + struct S { void foo() { ++local; } } + static assert( hasIndirections!S); + + enum E : S { a = S.init } + static assert( hasIndirections!S); + } + + { + static union U {} + static assert(!hasIndirections!U); + } + { + static union U { int i; } + static assert(!hasIndirections!U); + } + { + static union U { C c; } + static assert( hasIndirections!U); + } + { + static union U { int[] arr; } + static assert( hasIndirections!U); + } +} + +// https://issues.dlang.org/show_bug.cgi?id=12000 +@safe unittest +{ + static struct S(T) + { + static assert(hasIndirections!T); + } + + static class A(T) + { + S!A a; + } + + A!int dummy; +} + +// https://github.com/dlang/dmd/issues/20812 +@safe unittest +{ + static assert(!hasIndirections!void); + static assert(!hasIndirections!(const void)); + static assert(!hasIndirections!(inout void)); + static assert(!hasIndirections!(immutable void)); + static assert(!hasIndirections!(shared void)); + + static assert( hasIndirections!(void*)); + static assert( hasIndirections!(const void*)); + static assert( hasIndirections!(inout void*)); + static assert( hasIndirections!(immutable void*)); + static assert( hasIndirections!(shared void*)); + + static assert( hasIndirections!(void[])); + static assert( hasIndirections!(const void[])); + static assert( hasIndirections!(inout void[])); + static assert( hasIndirections!(immutable void[])); + static assert( hasIndirections!(shared void[])); + + static assert( hasIndirections!(void[42])); + static assert( hasIndirections!(const void[42])); + static assert( hasIndirections!(inout void[42])); + static assert( hasIndirections!(immutable void[42])); + static assert( hasIndirections!(shared void[42])); + + static assert(!hasIndirections!(void[0])); + static assert(!hasIndirections!(const void[0])); + static assert(!hasIndirections!(inout void[0])); + static assert(!hasIndirections!(immutable void[0])); + static assert(!hasIndirections!(shared void[0])); +} + +template hasUnsharedIndirections(T) +{ + static if (is(T == immutable)) + enum hasUnsharedIndirections = false; + else static if (is(T == struct) || is(T == union)) + enum hasUnsharedIndirections = anySatisfy!(.hasUnsharedIndirections, Fields!T); + else static if (is(T : E[N], E, size_t N)) + enum hasUnsharedIndirections = is(E == void) ? false : hasUnsharedIndirections!E; + else static if (isFunctionPointer!T) + enum hasUnsharedIndirections = false; + else static if (isPointer!T) + enum hasUnsharedIndirections = !is(T : shared(U)*, U) && !is(T : immutable(U)*, U); + else static if (isDynamicArray!T) + enum hasUnsharedIndirections = !is(T : shared(V)[], V) && !is(T : immutable(V)[], V); + else static if (is(T == class) || is(T == interface)) + enum hasUnsharedIndirections = !is(T : shared(W), W); + else + enum hasUnsharedIndirections = isDelegate!T || __traits(isAssociativeArray, T); // TODO: how to handle these? +} + +unittest +{ + static struct Foo { shared(int)* val; } + + static assert(!hasUnsharedIndirections!(immutable(char)*)); + static assert(!hasUnsharedIndirections!(string)); + + static assert(!hasUnsharedIndirections!(Foo)); + static assert( hasUnsharedIndirections!(Foo*)); + static assert(!hasUnsharedIndirections!(shared(Foo)*)); + static assert(!hasUnsharedIndirections!(immutable(Foo)*)); + + int local; + struct HasContextPointer { int opCall() { return ++local; } } + static assert(hasIndirections!HasContextPointer); +} + +enum bool isAggregateType(T) = is(T == struct) || is(T == union) || + is(T == class) || is(T == interface); + +enum bool isPointer(T) = is(T == U*, U) && !isAggregateType!T; + +enum bool isDynamicArray(T) = is(DynamicArrayTypeOf!T) && !isAggregateType!T; + +template OriginalType(T) +{ + template Impl(T) + { + static if (is(T U == enum)) alias Impl = OriginalType!U; + else alias Impl = T; + } + + alias OriginalType = ModifyTypePreservingTQ!(Impl, T); +} + +template DynamicArrayTypeOf(T) +{ + static if (is(AliasThisTypeOf!T AT) && !is(AT[] == AT)) + alias X = DynamicArrayTypeOf!AT; + else + alias X = OriginalType!T; + + static if (is(Unqual!X : E[], E) && !is(typeof({ enum n = X.length; }))) + alias DynamicArrayTypeOf = X; + else + static assert(0, T.stringof ~ " is not a dynamic array"); +} + +private template AliasThisTypeOf(T) + if (isAggregateType!T) +{ + alias members = __traits(getAliasThis, T); + + static if (members.length == 1) + alias AliasThisTypeOf = typeof(__traits(getMember, T.init, members[0])); + else + static assert(0, T.stringof~" does not have alias this type"); +} + +template isFunctionPointer(T...) + if (T.length == 1) +{ + static if (is(T[0] U) || is(typeof(T[0]) U)) + { + static if (is(U F : F*) && is(F == function)) + enum bool isFunctionPointer = true; + else + enum bool isFunctionPointer = false; + } + else + enum bool isFunctionPointer = false; +} + +template isDelegate(T...) + if (T.length == 1) +{ + static if (is(typeof(& T[0]) U : U*) && is(typeof(& T[0]) U == delegate)) + { + // T is a (nested) function symbol. + enum bool isDelegate = true; + } + else static if (is(T[0] W) || is(typeof(T[0]) W)) + { + // T is an expression or a type. Take the type of it and examine. + enum bool isDelegate = is(W == delegate); + } + else + enum bool isDelegate = false; +} + +// std.meta.Filter +template Filter(alias pred, TList...) +{ + static if (TList.length == 0) + { + alias Filter = AliasSeq!(); + } + else static if (TList.length == 1) + { + static if (pred!(TList[0])) + alias Filter = AliasSeq!(TList[0]); + else + alias Filter = AliasSeq!(); + } + /* The next case speeds up compilation by reducing + * the number of Filter instantiations + */ + else static if (TList.length == 2) + { + static if (pred!(TList[0])) + { + static if (pred!(TList[1])) + alias Filter = AliasSeq!(TList[0], TList[1]); + else + alias Filter = AliasSeq!(TList[0]); + } + else + { + static if (pred!(TList[1])) + alias Filter = AliasSeq!(TList[1]); + else + alias Filter = AliasSeq!(); + } + } + else + { + alias Filter = + AliasSeq!( + Filter!(pred, TList[ 0 .. $/2]), + Filter!(pred, TList[$/2 .. $ ])); + } +} + +// std.meta.staticMap +template staticMap(alias F, T...) +{ + static if (T.length == 0) + { + alias staticMap = AliasSeq!(); + } + else static if (T.length == 1) + { + alias staticMap = AliasSeq!(F!(T[0])); + } + /* Cases 2 to 8 improve compile performance by reducing + * the number of recursive instantiations of staticMap + */ + else static if (T.length == 2) + { + alias staticMap = AliasSeq!(F!(T[0]), F!(T[1])); + } + else static if (T.length == 3) + { + alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2])); + } + else static if (T.length == 4) + { + alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]), F!(T[3])); + } + else static if (T.length == 5) + { + alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]), F!(T[3]), F!(T[4])); + } + else static if (T.length == 6) + { + alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]), F!(T[3]), F!(T[4]), F!(T[5])); + } + else static if (T.length == 7) + { + alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]), F!(T[3]), F!(T[4]), F!(T[5]), F!(T[6])); + } + else static if (T.length == 8) + { + alias staticMap = AliasSeq!(F!(T[0]), F!(T[1]), F!(T[2]), F!(T[3]), F!(T[4]), F!(T[5]), F!(T[6]), F!(T[7])); + } + else + { + alias staticMap = + AliasSeq!( + staticMap!(F, T[ 0 .. $/2]), + staticMap!(F, T[$/2 .. $ ])); + } +} + +// std.exception.assertCTFEable +version (CoreUnittest) package(core) +void assertCTFEable(alias dg)() +{ + static assert({ cast(void) dg(); return true; }()); + cast(void) dg(); +} + +// std.traits.FunctionTypeOf +/* +Get the function type from a callable object `func`. + +Using builtin `typeof` on a property function yields the types of the +property value, not of the property function itself. Still, +`FunctionTypeOf` is able to obtain function types of properties. + +Note: +Do not confuse function types with function pointer types; function types are +usually used for compile-time reflection purposes. + */ +template FunctionTypeOf(func...) +if (func.length == 1 /*&& isCallable!func*/) +{ + static if (is(typeof(& func[0]) Fsym : Fsym*) && is(Fsym == function) || is(typeof(& func[0]) Fsym == delegate)) + { + alias FunctionTypeOf = Fsym; // HIT: (nested) function symbol + } + else static if (is(typeof(& func[0].opCall) Fobj == delegate)) + { + alias FunctionTypeOf = Fobj; // HIT: callable object + } + else static if (is(typeof(& func[0].opCall) Ftyp : Ftyp*) && is(Ftyp == function)) + { + alias FunctionTypeOf = Ftyp; // HIT: callable type + } + else static if (is(func[0] T) || is(typeof(func[0]) T)) + { + static if (is(T == function)) + alias FunctionTypeOf = T; // HIT: function + else static if (is(T Fptr : Fptr*) && is(Fptr == function)) + alias FunctionTypeOf = Fptr; // HIT: function pointer + else static if (is(T Fdlg == delegate)) + alias FunctionTypeOf = Fdlg; // HIT: delegate + else + static assert(0); + } + else + static assert(0); +} + +@safe unittest +{ + class C + { + int value() @property { return 0; } + } + static assert(is( typeof(C.value) == int )); + static assert(is( FunctionTypeOf!(C.value) == function )); +} + +// Disabled: uses `new Callable` (GC allocation) which is unsupported in @nogc uRT build +version (none) +@system unittest +{ + int test(int a); + int propGet() @property; + int propSet(int a) @property; + int function(int) test_fp; + int delegate(int) test_dg; + static assert(is( typeof(test) == FunctionTypeOf!(typeof(test)) )); + static assert(is( typeof(test) == FunctionTypeOf!test )); + static assert(is( typeof(test) == FunctionTypeOf!test_fp )); + static assert(is( typeof(test) == FunctionTypeOf!test_dg )); + alias int GetterType() @property; + alias int SetterType(int) @property; + static assert(is( FunctionTypeOf!propGet == GetterType )); + static assert(is( FunctionTypeOf!propSet == SetterType )); + + interface Prop { int prop() @property; } + Prop prop; + static assert(is( FunctionTypeOf!(Prop.prop) == GetterType )); + static assert(is( FunctionTypeOf!(prop.prop) == GetterType )); + + class Callable { int opCall(int) { return 0; } } + auto call = new Callable; + static assert(is( FunctionTypeOf!call == typeof(test) )); + + struct StaticCallable { static int opCall(int) { return 0; } } + StaticCallable stcall_val; + StaticCallable* stcall_ptr; + static assert(is( FunctionTypeOf!stcall_val == typeof(test) )); + static assert(is( FunctionTypeOf!stcall_ptr == typeof(test) )); + + interface Overloads + { + void test(string); + real test(real); + int test(int); + int test() @property; + } + alias ov = __traits(getVirtualMethods, Overloads, "test"); + alias F_ov0 = FunctionTypeOf!(ov[0]); + alias F_ov1 = FunctionTypeOf!(ov[1]); + alias F_ov2 = FunctionTypeOf!(ov[2]); + alias F_ov3 = FunctionTypeOf!(ov[3]); + static assert(is(F_ov0* == void function(string))); + static assert(is(F_ov1* == real function(real))); + static assert(is(F_ov2* == int function(int))); + static assert(is(F_ov3* == int function() @property)); + + alias F_dglit = FunctionTypeOf!((int a){ return a; }); + static assert(is(F_dglit* : int function(int))); +} + +// std.traits.ReturnType +/* +Get the type of the return value from a function, +a pointer to function, a delegate, a struct +with an opCall, a pointer to a struct with an opCall, +or a class with an `opCall`. Please note that $(D_KEYWORD ref) +is not part of a type, but the attribute of the function +(see template $(LREF functionAttributes)). +*/ +template ReturnType(func...) +if (func.length == 1 /*&& isCallable!func*/) +{ + static if (is(FunctionTypeOf!func R == return)) + alias ReturnType = R; + else + static assert(0, "argument has no return type"); +} + +// +@safe unittest +{ + int foo(); + ReturnType!foo x; // x is declared as int +} + +@safe unittest +{ + struct G + { + int opCall (int i) { return 1;} + } + + alias ShouldBeInt = ReturnType!G; + static assert(is(ShouldBeInt == int)); + + G g; + static assert(is(ReturnType!g == int)); + + G* p; + alias pg = ReturnType!p; + static assert(is(pg == int)); + + class C + { + int opCall (int i) { return 1;} + } + + static assert(is(ReturnType!C == int)); + + C c; + static assert(is(ReturnType!c == int)); + + class Test + { + int prop() @property { return 0; } + } + alias R_Test_prop = ReturnType!(Test.prop); + static assert(is(R_Test_prop == int)); + + alias R_dglit = ReturnType!((int a) { return a; }); + static assert(is(R_dglit == int)); +} + +// std.traits.Parameters +/* +Get, as a tuple, the types of the parameters to a function, a pointer +to function, a delegate, a struct with an `opCall`, a pointer to a +struct with an `opCall`, or a class with an `opCall`. +*/ +template Parameters(func...) +if (func.length == 1 /*&& isCallable!func*/) +{ + static if (is(FunctionTypeOf!func P == function)) + alias Parameters = P; + else + static assert(0, "argument has no parameters"); +} + +// +@safe unittest +{ + int foo(int, long); + void bar(Parameters!foo); // declares void bar(int, long); + void abc(Parameters!foo[1]); // declares void abc(long); +} + +@safe unittest +{ + int foo(int i, bool b) { return 0; } + static assert(is(Parameters!foo == AliasSeq!(int, bool))); + static assert(is(Parameters!(typeof(&foo)) == AliasSeq!(int, bool))); + + struct S { real opCall(real r, int i) { return 0.0; } } + S s; + static assert(is(Parameters!S == AliasSeq!(real, int))); + static assert(is(Parameters!(S*) == AliasSeq!(real, int))); + static assert(is(Parameters!s == AliasSeq!(real, int))); + + class Test + { + int prop() @property { return 0; } + } + alias P_Test_prop = Parameters!(Test.prop); + static assert(P_Test_prop.length == 0); + + alias P_dglit = Parameters!((int a){}); + static assert(P_dglit.length == 1); + static assert(is(P_dglit[0] == int)); +} + +// Return `true` if `Type` has `member` that evaluates to `true` in a static if condition +enum isTrue(Type, string member) = __traits(compiles, { static if (__traits(getMember, Type, member)) {} else static assert(0); }); + +unittest +{ + static struct T + { + enum a = true; + enum b = false; + enum c = 1; + enum d = 45; + enum e = "true"; + enum f = ""; + enum g = null; + alias h = bool; + } + + static assert( isTrue!(T, "a")); + static assert(!isTrue!(T, "b")); + static assert( isTrue!(T, "c")); + static assert( isTrue!(T, "d")); + static assert( isTrue!(T, "e")); + static assert( isTrue!(T, "f")); + static assert(!isTrue!(T, "g")); + static assert(!isTrue!(T, "h")); +} + +template hasUDA(alias symbol, alias attribute) +{ + enum isAttr(T) = is(T == attribute); + + enum hasUDA = anySatisfy!(isAttr, __traits(getAttributes, symbol)); +} + +unittest +{ + enum SomeUDA; + + struct Test + { + int woUDA; + @SomeUDA int oneUDA; + @SomeUDA @SomeUDA int twoUDAs; + } + + static assert(hasUDA!(Test.oneUDA, SomeUDA)); + static assert(hasUDA!(Test.twoUDAs, SomeUDA)); + static assert(!hasUDA!(Test.woUDA, SomeUDA)); +} diff --git a/src/urt/io.d b/src/urt/io.d index 73fba66..4a23337 100644 --- a/src/urt/io.d +++ b/src/urt/io.d @@ -1,40 +1,77 @@ module urt.io; -import core.stdc.stdio; - - nothrow @nogc: -int write(const(char)[] str) -{ - return printf("%.*s", cast(int)str.length, str.ptr); -} -int writeln(const(char)[] str) +enum WriteTarget : ubyte { - return printf("%.*s\n", cast(int)str.length, str.ptr); + stdout = 0, + stderr = 1, + debugstring = 2, // Windows OutputDebugStringA } -int write(Args...)(ref Args args) - if (Args.length != 1 || !is(Args[0] : const(char)[])) +template write_to(WriteTarget target, bool newline = false) { - import urt.string.format; - import urt.mem.temp; + int write_to(const(char)[] str) + { + static if (target == WriteTarget.stdout || target == WriteTarget.stderr) + { + version (Espressif) + { + foreach (ch; str) + esp_rom_uart_putc(ch); + static if (newline) + esp_rom_uart_putc('\n'); + return cast(int) str.length; + } + else version (FreeStanding) + { + import urt.driver.uart : uart0_puts; + uart0_puts(str); + static if (newline) + uart0_puts("\n"); + return cast(int) str.length; + } + else + { + fwrite(str.ptr, 1, str.length, target == WriteTarget.stdout ? stdout : stderr); + if (newline) + fwrite("\n".ptr, 1, "\n".length, target == WriteTarget.stdout ? stdout : stderr); + return cast(int)(str.length + "\n".length); + } + } + else static if (target == WriteTarget.debugstring) + { + version (Windows) + { + import core.sys.windows.windows; + OutputDebugStringA(str.ptr); + static if (newline) + OutputDebugStringA("\n"); + return cast(int)str.length + newline; + } + else + { + // is stderr the best analogy on other platforms? + return write_to!(WriteTarget.stderr, newline)(str); + } + } + else + static assert(0, "Invalid WriteTarget"); + } - size_t len = concat(null, args).length; - const(char)[] t = concat(cast(char[])talloc(len), args); - return write(t); -} - -int writeln(Args...)(ref Args args) - if (Args.length != 1 || !is(Args[0] : const(char)[])) -{ - import urt.string.format; - import urt.mem.temp; + int write_to(Args...)(ref Args args) + if (Args.length != 1 || !is(Args[0] : const(char)[])) + { + import urt.string.format; + import urt.mem.temp; - return tconcat(args).writeln; + size_t len = concat(null, args).length; + const(char)[] t = concat(cast(char[])talloc(len), args); + return write_to(t); + } } -int writef(Args...)(const(char)[] fmt, ref Args args) +int writef_to(WriteTarget target, bool newline = false, Args...)(const(char)[] fmt, ref Args args) if (Args.length > 0) { import urt.string.format; @@ -42,20 +79,38 @@ int writef(Args...)(const(char)[] fmt, ref Args args) size_t len = format(null, fmt, args).length; const(char)[] t = format(cast(char[])talloc(len), fmt, args); - return write(t); + return write_to!(target, newline)(t); } -int writelnf(Args...)(const(char)[] fmt, ref Args args) - if (Args.length > 0) -{ - import urt.string.format; - import urt.mem.temp; +alias write = write_to!(WriteTarget.stdout, false); +alias writeln = write_to!(WriteTarget.stdout, true); +alias write_err = write_to!(WriteTarget.stderr, false); +alias writeln_err = write_to!(WriteTarget.stderr, true); +alias write_debug = write_to!(WriteTarget.debugstring, false); +alias writeln_debug = write_to!(WriteTarget.debugstring, true); - size_t len = format(null, fmt, args).length; - const(char)[] t = format(cast(char[])talloc(len), fmt, args); - return writeln(t); +void flush(WriteTarget target = WriteTarget.stdout)() nothrow @nogc +{ + version (Espressif) + { + // ROM UART writes are unbuffered + } + else version (FreeStanding) + { + // UART writes are unbuffered - nothing to flush + } + else + { + static if (target == WriteTarget.stdout || target == WriteTarget.stderr) + fflush(target == WriteTarget.stdout ? stdout : stderr); + } } +int writef(Args...)(ref Args args) + => writef_to!(WriteTarget.stdout, false)(args); +int writelnf(Args...)(ref Args args) + => writef_to!(WriteTarget.stdout, true)(args); + unittest { writeln("Hello, World!"); @@ -69,3 +124,15 @@ unittest write("mister ", "robot "); writef("how do {0} do?\n", "you"); } + + +private: + +version (Espressif) +{ + // ROM UART putc -- in mask ROM, zero code size + extern(C) void esp_rom_uart_putc(char c) nothrow @nogc; +} +else version (FreeStanding) {} +else + import urt.internal.stdc.stdio : stdout, stderr, fwrite, fflush; diff --git a/src/urt/lifetime.d b/src/urt/lifetime.d index acb04d1..d08eb32 100644 --- a/src/urt/lifetime.d +++ b/src/urt/lifetime.d @@ -1,10 +1,9 @@ module urt.lifetime; +import urt.internal.lifetime : emplaceRef; // TODO: DESTROY THIS! T* emplace(T)(T* chunk) @safe pure { - import core.internal.lifetime : emplaceRef; - emplaceRef!T(*chunk); return chunk; } @@ -12,8 +11,6 @@ T* emplace(T)(T* chunk) @safe pure T* emplace(T, Args...)(T* chunk, auto ref Args args) if (is(T == struct) || Args.length == 1) { - import core.internal.lifetime : emplaceRef; - emplaceRef!T(*chunk, forward!args); return chunk; } @@ -21,7 +18,7 @@ T* emplace(T, Args...)(T* chunk, auto ref Args args) T emplace(T, Args...)(T chunk, auto ref Args args) if (is(T == class)) { - import core.internal.traits : isInnerClass; + import urt.internal.traits : isInnerClass; static assert(!__traits(isAbstractClass, T), T.stringof ~ " is abstract and it can't be emplaced"); @@ -73,8 +70,7 @@ T emplace(T, Args...)(void[] chunk, auto ref Args args) T* emplace(T, Args...)(void[] chunk, auto ref Args args) if (!is(T == class)) { - import core.internal.traits : Unqual; - import core.internal.lifetime : emplaceRef; + import urt.traits : Unqual; assert(chunk.length >= T.sizeof, "chunk size too small."); assert((cast(size_t) chunk.ptr) % T.alignof == 0, "emplace: Chunk is not aligned."); @@ -83,12 +79,11 @@ T* emplace(T, Args...)(void[] chunk, auto ref Args args) return cast(T*) chunk.ptr; } - /+ void copyEmplace(S, T)(ref S source, ref T target) @system if (is(immutable S == immutable T)) { - import core.internal.traits : BaseElemOf, hasElaborateCopyConstructor, Unconst, Unqual; + import urt.internal.traits : BaseElemOf, hasElaborateCopyConstructor, Unconst, Unqual; // cannot have the following as simple template constraint due to nested-struct special case... static if (!__traits(compiles, (ref S src) { T tgt = src; })) @@ -100,7 +95,7 @@ void copyEmplace(S, T)(ref S source, ref T target) @system void blit() { - import core.stdc.string : memcpy; + import urt.mem : memcpy; memcpy(cast(Unqual!(T)*) &target, cast(Unqual!(T)*) &source, T.sizeof); } @@ -203,7 +198,7 @@ T move(T)(return scope ref T source) nothrow @nogc private void moveImpl(T)(scope ref T target, return scope ref T source) nothrow @nogc { - import core.internal.traits : hasElaborateDestructor; + import urt.internal.traits : hasElaborateDestructor; static if (is(T == struct)) { @@ -225,7 +220,7 @@ private T moveImpl(T)(return scope ref T source) nothrow @nogc return trustedMoveImpl(source); } -private T trustedMoveImpl(T)(return scope ref T source) @trusted nothrow @nogc +private T trustedMoveImpl(T)(return scope ref T source) nothrow @nogc @trusted { T result = void; moveEmplaceImpl(result, source); @@ -242,7 +237,7 @@ private enum bool hasContextPointers(T) = { } else static if (is(T == struct)) { - import core.internal.traits : anySatisfy; + import urt.internal.traits : anySatisfy; return __traits(isNested, T) || anySatisfy!(hasContextPointers, typeof(T.tupleof)); } else return false; @@ -259,8 +254,8 @@ private void moveEmplaceImpl(T)(scope ref T target, return scope ref T source) @ // "Cannot move object with internal pointer unless `opPostMove` is defined."); // } - import core.internal.traits : hasElaborateAssign, isAssignable, hasElaborateMove, - hasElaborateDestructor, hasElaborateCopyConstructor; + import urt.internal.traits : hasElaborateAssign, isAssignable, hasElaborateMove, + hasElaborateDestructor, hasElaborateCopyConstructor; static if (is(T == struct)) { @@ -269,7 +264,7 @@ private void moveEmplaceImpl(T)(scope ref T target, return scope ref T source) @ static if (hasElaborateAssign!T || !isAssignable!T) { - import core.stdc.string : memcpy; + import urt.mem : memcpy; () @trusted { memcpy(&target, &source, T.sizeof); }(); } else @@ -327,20 +322,11 @@ void moveEmplace(T)(ref T source, ref T target) @system @nogc -//debug = PRINTF; - -debug(PRINTF) -{ - import core.stdc.stdio; -} - /// Implementation of `_d_delstruct` and `_d_delstructTrace` template _d_delstructImpl(T) { private void _d_delstructImpure(ref T p) { - debug(PRINTF) printf("_d_delstruct(%p)\n", p); - destroy(*p); p = null; } @@ -360,7 +346,7 @@ template _d_delstructImpl(T) * `@trusted` until the implementation can be brought up to modern D * expectations. */ - void _d_delstruct(ref T p) @trusted @nogc pure nothrow + void _d_delstruct(ref T p) nothrow @nogc pure @trusted { if (p) { @@ -392,7 +378,7 @@ template _d_delstructImpl(T) // wipes source after moving pragma(inline, true) -private void wipe(T, Init...)(return scope ref T source, ref const scope Init initializer) @trusted nothrow @nogc +private void wipe(T, Init...)(return scope ref T source, ref const scope Init initializer) nothrow @nogc @trusted if (!Init.length || ((Init.length == 1) && (is(immutable T == immutable Init[0])))) { @@ -406,7 +392,7 @@ if (!Init.length || } else static if (is(T == struct) && hasContextPointers!T) { - import core.internal.traits : anySatisfy; + import urt.internal.traits : anySatisfy; static if (anySatisfy!(hasContextPointers, typeof(T.tupleof))) { static foreach (i; 0 .. T.tupleof.length - __traits(isNested, T)) @@ -430,7 +416,7 @@ if (!Init.length || } else { - import core.internal.traits : hasElaborateAssign, isAssignable; + import urt.internal.traits : hasElaborateAssign, isAssignable; static if (Init.length) { static if (hasElaborateAssign!T || !isAssignable!T) @@ -449,7 +435,7 @@ if (!Init.length || T _d_newclassT(T)() @trusted if (is(T == class)) { - import core.internal.traits : hasIndirections; + import urt.internal.traits : hasIndirections; import core.exception : onOutOfMemoryError; import core.memory : pureMalloc; import core.memory : GC; @@ -478,27 +464,11 @@ T _d_newclassT(T)() @trusted attr |= BlkAttr.NO_SCAN; p = GC.malloc(init.length, attr, typeid(T)); - debug(PRINTF) printf(" p = %p\n", p); - } - - debug(PRINTF) - { - printf("p = %p\n", p); - printf("init.ptr = %p, len = %llu\n", init.ptr, cast(ulong)init.length); - printf("vptr = %p\n", *cast(void**) init); - printf("vtbl[0] = %p\n", (*cast(void***) init)[0]); - printf("vtbl[1] = %p\n", (*cast(void***) init)[1]); - printf("init[0] = %x\n", (cast(uint*) init)[0]); - printf("init[1] = %x\n", (cast(uint*) init)[1]); - printf("init[2] = %x\n", (cast(uint*) init)[2]); - printf("init[3] = %x\n", (cast(uint*) init)[3]); - printf("init[4] = %x\n", (cast(uint*) init)[4]); } // initialize it p[0 .. init.length] = init[]; - debug(PRINTF) printf("initialization done\n"); return cast(T) p; } @@ -541,7 +511,7 @@ T _d_newclassTTrace(T)(string file, int line, string funcname) @trusted T* _d_newitemT(T)() @trusted { import core.internal.lifetime : emplaceInitializer; - import core.internal.traits : hasIndirections; + import urt.internal.traits : hasIndirections; import core.memory : GC; auto flags = !hasIndirections!T ? GC.BlkAttr.NO_SCAN : GC.BlkAttr.NONE; @@ -587,7 +557,7 @@ version (D_ProfileGC) template TypeInfoSize(T) { - import core.internal.traits : hasElaborateDestructor; + import urt.internal.traits : hasElaborateDestructor; enum TypeInfoSize = hasElaborateDestructor!T ? size_t.sizeof : 0; } +/ diff --git a/src/urt/log.d b/src/urt/log.d index 71af7fd..e7a3bb7 100644 --- a/src/urt/log.d +++ b/src/urt/log.d @@ -1,8 +1,183 @@ module urt.log; -import urt.io; +import urt.mem.temp : tconcat, tconcat_impl, tformat; +import urt.string.format : normalise_args; +import urt.time; -enum Level +nothrow @nogc: + + +enum Severity : ubyte +{ + emergency = 0, + alert = 1, + critical = 2, + error = 3, + warning = 4, + notice = 5, + info = 6, + debug_ = 7, + trace = 8, +} + +immutable string[9] severity_names = [ + "Emergency", "Alert", "Critical", "Error", "Warning", "Notice", "Info", "Debug", "Trace" +]; + +struct LogMessage +{ + Severity severity; + const(char)[] tag; + const(char)[] object_name; + const(char)[] message; + MonoTime timestamp; +} + +struct LogFilter +{ + Severity max_severity = Severity.info; + const(char)[] tag_prefix; +} + +alias SinkOutputFn = void function(void* context, scope ref const LogMessage msg) nothrow @nogc; + +struct LogSinkHandle +{ + int index = -1; + bool valid() const pure nothrow @nogc + => index >= 0 && index < max_sinks; +} + +LogSinkHandle register_log_sink(SinkOutputFn output, void* context = null, LogFilter filter = LogFilter.init) +{ + foreach (i, ref sink; g_sinks) + { + if (!sink.active) + { + sink.output = output; + sink.context = context; + sink.filter = filter; + sink.enabled = true; + sink.active = true; + recalc_max_severity(); + return LogSinkHandle(cast(int)i); + } + } + return LogSinkHandle(-1); +} + +void unregister_log_sink(LogSinkHandle handle) +{ + if (!handle.valid) + return; + g_sinks[handle.index] = SinkSlot.init; + recalc_max_severity(); +} + +void set_sink_filter(LogSinkHandle handle, LogFilter filter) +{ + if (!handle.valid) + return; + g_sinks[handle.index].filter = filter; + recalc_max_severity(); +} + +void set_sink_enabled(LogSinkHandle handle, bool enabled) +{ + if (!handle.valid) + return; + g_sinks[handle.index].enabled = enabled; + recalc_max_severity(); +} + +void log_emergency(T...)(const(char)[] tag, ref T args) { write_log(Severity.emergency, tag, null, args); } +void log_alert(T...)(const(char)[] tag, ref T args) { write_log(Severity.alert, tag, null, args); } +void log_critical(T...)(const(char)[] tag, ref T args) { write_log(Severity.critical, tag, null, args); } +void log_error(T...)(const(char)[] tag, ref T args) { write_log(Severity.error, tag, null, args); } +void log_warning(T...)(const(char)[] tag, ref T args) { write_log(Severity.warning, tag, null, args); } +void log_notice(T...)(const(char)[] tag, ref T args) { write_log(Severity.notice, tag, null, args); } +void log_info(T...)(const(char)[] tag, ref T args) { write_log(Severity.info, tag, null, args); } + +void log_emergencyf(T...)(const(char)[] tag, const(char)[] fmt, ref T args) { write_logf(Severity.emergency, tag, null, fmt, args); } +void log_alertf(T...)(const(char)[] tag, const(char)[] fmt, ref T args) { write_logf(Severity.alert, tag, null, fmt, args); } +void log_criticalf(T...)(const(char)[] tag, const(char)[] fmt, ref T args) { write_logf(Severity.critical, tag, null, fmt, args); } +void log_errorf(T...)(const(char)[] tag, const(char)[] fmt, ref T args) { write_logf(Severity.error, tag, null, fmt, args); } +void log_warningf(T...)(const(char)[] tag, const(char)[] fmt, ref T args) { write_logf(Severity.warning, tag, null, fmt, args); } +void log_noticef(T...)(const(char)[] tag, const(char)[] fmt, ref T args) { write_logf(Severity.notice, tag, null, fmt, args); } +void log_infof(T...)(const(char)[] tag, const(char)[] fmt, ref T args) { write_logf(Severity.info, tag, null, fmt, args); } + +void log_debug(T...)(const(char)[] tag, ref T args) { write_log(Severity.debug_, tag, null, args); } +void log_trace(T...)(const(char)[] tag, ref T args) { write_log(Severity.trace, tag, null, args); } +void log_debugf(T...)(const(char)[] tag, const(char)[] fmt, ref T args) { write_logf(Severity.debug_, tag, null, fmt, args); } +void log_tracef(T...)(const(char)[] tag, const(char)[] fmt, ref T args) { write_logf(Severity.trace, tag, null, fmt, args); } + +// this can be declared in any scope to automatically prefix log messages with a tag (e.g. module name) +// eg: alias log = Log!"my.module"; +// log.warn("oh no!"); +template Log(string tag) +{ + void info(T...)(ref T args) { write_log(Severity.info, tag, null, args); } + void warning(T...)(ref T args) { write_log(Severity.warning, tag, null, args); } + void error(T...)(ref T args) { write_log(Severity.error, tag, null, args); } + void notice(T...)(ref T args) { write_log(Severity.notice, tag, null, args); } + void critical(T...)(ref T args) { write_log(Severity.critical, tag, null, args); } + void alert(T...)(ref T args) { write_log(Severity.alert, tag, null, args); } + void emergency(T...)(ref T args) { write_log(Severity.emergency, tag, null, args); } + + void infof(T...)(const(char)[] fmt, ref T args) { write_logf(Severity.info, tag, null, fmt, args); } + void warningf(T...)(const(char)[] fmt, ref T args) { write_logf(Severity.warning, tag, null, fmt, args); } + void errorf(T...)(const(char)[] fmt, ref T args) { write_logf(Severity.error, tag, null, fmt, args); } + void noticef(T...)(const(char)[] fmt, ref T args) { write_logf(Severity.notice, tag, null, fmt, args); } + void criticalf(T...)(const(char)[] fmt, ref T args) { write_logf(Severity.critical, tag, null, fmt, args); } + void alertf(T...)(const(char)[] fmt, ref T args) { write_logf(Severity.alert, tag, null, fmt, args); } + void emergencyf(T...)(const(char)[] fmt, ref T args) { write_logf(Severity.emergency, tag, null, fmt, args); } + + void debug_(T...)(ref T args) { write_log(Severity.debug_, tag, null, args); } + void trace(T...)(ref T args) { write_log(Severity.trace, tag, null, args); } + void debugf(T...)(const(char)[] fmt, ref T args) { write_logf(Severity.debug_, tag, null, fmt, args); } + void tracef(T...)(const(char)[] fmt, ref T args) { write_logf(Severity.trace, tag, null, fmt, args); } +} + +void write_log(T...)(Severity severity, const(char)[] tag, const(char)[] object_name, ref T args) +{ + if (severity > g_max_severity) + return; + import urt.string, urt.array; + static if (T.length == 1 && (is(T[0] : const(char)[]) || is(T[0] : const String) || is(T[0] : const MutableString!N, size_t N) || is(T[0] : const Array!char))) + auto msg = LogMessage(severity, tag, object_name, args[0][], getTime()); + else + auto msg = LogMessage(severity, tag, object_name, tconcat_impl(normalise_args(args)), getTime()); + write_log(msg); +} + +void write_logf(T...)(Severity severity, const(char)[] tag, const(char)[] object_name, const(char)[] fmt, ref T args) +{ + if (severity > g_max_severity) + return; + auto msg = LogMessage(severity, tag, object_name, tformat(fmt, args), getTime()); + write_log(msg); +} + +void write_log(scope ref const LogMessage msg) +{ + import urt.string : startsWith; + + foreach (ref sink; g_sinks) + { + if (!sink.active || !sink.enabled) + continue; + if (msg.severity > sink.filter.max_severity) + continue; + if (sink.filter.tag_prefix.length > 0 && !msg.tag.startsWith(sink.filter.tag_prefix)) + continue; + sink.output(sink.context, msg); + } +} + + +// --- backward compatibility (deprecated) --- + +enum Level : ubyte { Error = 0, Warning, @@ -10,7 +185,18 @@ enum Level Debug } -immutable string[] levelNames = [ "Error", "Warning", "Info", "Debug" ]; +immutable string[] levelNames = ["Error", "Warning", "Info", "Debug"]; + +Severity level_to_severity(Level level) +{ + final switch (level) + { + case Level.Error: return Severity.error; + case Level.Warning: return Severity.warning; + case Level.Info: return Severity.info; + case Level.Debug: return Severity.debug_; + } +} __gshared Level logLevel = Level.Info; @@ -26,14 +212,52 @@ void writeErrorf(T...)(const(char)[] format, ref T things) { writeLogf(Level.Err void writeLog(T...)(Level level, ref T things) { - if (level > logLevel) + Severity sev = level_to_severity(level); + if (sev > g_max_severity) return; - writeln(levelNames[level], ": ", things); + write_log(sev, null, null, things); } void writeLogf(T...)(Level level, const(char)[] format, ref T things) { - if (level > logLevel) - return; - writelnf("{-2}: {@-1}", things, levelNames[level], format); + write_logf(level_to_severity(level), null, null, format, things); +} + +alias LegacyLogSink = void function(Level level, scope const(char)[] message) nothrow @nogc; + +private void legacy_sink_adapter(void* context, scope ref const LogMessage msg) nothrow @nogc +{ + __gshared immutable Level[9] map = [Level.Error, Level.Error, Level.Error, Level.Error, Level.Warning, Level.Info, Level.Info, Level.Debug, Level.Debug]; + (cast(LegacyLogSink)context)(map[msg.severity], msg.message); +} + +LogSinkHandle register_log_sink(LegacyLogSink sink) + => register_log_sink(&legacy_sink_adapter, cast(void*)sink); + + +private: + +enum max_sinks = 16; + +struct SinkSlot +{ + SinkOutputFn output; + void* context; + LogFilter filter; + bool enabled; + bool active; +} + +__gshared SinkSlot[max_sinks] g_sinks; +__gshared Severity g_max_severity = Severity.info; + +void recalc_max_severity() +{ + Severity max_sev = Severity.emergency; + foreach (ref sink; g_sinks) + { + if (sink.active && sink.enabled && sink.filter.max_severity > max_sev) + max_sev = sink.filter.max_severity; + } + g_max_severity = max_sev; } diff --git a/src/urt/map.d b/src/urt/map.d index 732fc79..325641d 100644 --- a/src/urt/map.d +++ b/src/urt/map.d @@ -42,14 +42,14 @@ struct AVLTree(K, V, alias Pred = DefCmp!K, Allocator = Mallocator) } size_t length() const nothrow - => numNodes; + => _num_modes; bool empty() const nothrow - => numNodes == 0; + => _num_modes == 0; void clear() nothrow { - destroy(pRoot); - pRoot = null; + destroy(_root); + _root = null; } V* insert(_K, _V)(auto ref _K key, auto ref _V val) @@ -167,11 +167,12 @@ struct AVLTree(K, V, alias Pred = DefCmp!K, Allocator = Mallocator) { Node* node = cast(Node*)Allocator.instance.alloc(Node.sizeof); emplace(&node.kvp, forward!key, forward!val); - node.left = node.right = null; - node.height = 1; - pRoot = insert(pRoot, node); + node._base.left = node._base.right = null; + node._base.height = 1; + _root = insert(_root, node); return node.kvp.value; } + /+ V& replace(K &&key, V &&val) { @@ -180,7 +181,7 @@ struct AVLTree(K, V, alias Pred = DefCmp!K, Allocator = Mallocator) epConstruct(&node.kvp) KVP(std::move(key), std::move(val)); node.left = node.right = null; node.height = 1; - pRoot = insert(pRoot, node); + _root = insert(_root, node); return node.kvp.value; } V& replace(const K &key, V &&val) @@ -190,7 +191,7 @@ struct AVLTree(K, V, alias Pred = DefCmp!K, Allocator = Mallocator) epConstruct(&node.kvp) KVP(key, std::move(val)); node.left = node.right = null; node.height = 1; - pRoot = insert(pRoot, node); + _root = insert(_root, node); return node.kvp.value; } V& replace(K &&key, const V &val) @@ -200,7 +201,7 @@ struct AVLTree(K, V, alias Pred = DefCmp!K, Allocator = Mallocator) epConstruct(&node.kvp) KVP(std::move(key), val); node.left = node.right = null; node.height = 1; - pRoot = insert(pRoot, node); + _root = insert(_root, node); return node.kvp.value; } V& replace(const K &key, const V &val) @@ -210,7 +211,7 @@ struct AVLTree(K, V, alias Pred = DefCmp!K, Allocator = Mallocator) epConstruct(&node.kvp) KVP(key, val); node.left = node.right = null; node.height = 1; - pRoot = insert(pRoot, node); + _root = insert(_root, node); return node.kvp.value; } @@ -221,7 +222,7 @@ struct AVLTree(K, V, alias Pred = DefCmp!K, Allocator = Mallocator) epConstruct(&node.kvp) KVP(std::move(kvp)); node.left = node.right = null; node.height = 1; - pRoot = insert(pRoot, node); + _root = insert(_root, node); return node.kvp.value; } V& replace(const KVP &kvp) @@ -231,19 +232,19 @@ struct AVLTree(K, V, alias Pred = DefCmp!K, Allocator = Mallocator) epConstruct(&node.kvp) KVP(kvp); node.left = node.right = null; node.height = 1; - pRoot = insert(pRoot, node); + _root = insert(_root, node); return node.kvp.value; } +/ void remove(_K)(ref const _K key) { - pRoot = deleteNode(pRoot, key); + _root = delete_node(_root, key); } inout(V)* get(_K)(ref const _K key) inout { - inout(Node)* n = find(pRoot, key); + inout(Node)* n = find(_root, key); return n ? &n.kvp.value : null; } @@ -289,288 +290,117 @@ struct AVLTree(K, V, alias Pred = DefCmp!K, Allocator = Mallocator) // TODO: why don't the const overloads work properly? auto keys() const nothrow - => Range!(IterateBy.Keys, true)(pRoot); + => Range!(IterateBy.Keys, true)(_root); auto values() nothrow - => Range!(IterateBy.Values)(pRoot); -// auto values() const nothrow -// => Range!(IterateBy.Values, true)(pRoot); + => Range!(IterateBy.Values)(_root); + auto values() const nothrow + => Range!(IterateBy.Values, true)(_root); auto opIndex() nothrow - => Range!(IterateBy.KVP)(pRoot); -// auto opIndex() const nothrow -// => Range!(IterateBy.KVP, true)(pRoot); - -private: -nothrow: - alias Node = AVLTreeNode!(K, V); + => Range!(IterateBy.KVP)(_root); + auto opIndex() const nothrow + => Range!(IterateBy.KVP, true)(_root); - size_t numNodes = 0; - Node* pRoot = null; - - static int height(const(Node)* n) pure + import urt.string.format : FormatArg, formatValue; + ptrdiff_t toString()(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const { - return n ? n.height : 0; - } + if (buffer.ptr is null) + { + // count the buffer size + size_t size = 2, comma = 0; + foreach (kvp; this) + { + size += comma; + comma = 1; + ptrdiff_t len = formatValue(kvp.key, buffer, format, formatArgs); + if (len < 0) + return len; + size += len + 1; + len = formatValue(kvp.value, buffer, format, formatArgs); + if (len < 0) + return len; + size += len; + } + return size; + } - static int maxHeight(const(Node)* n) pure - { - if (!n) - return 0; - if (n.left) + if (buffer.length < 2) + return -1; + buffer[0] = '{'; + + size_t offset = 1; + bool add_comma = false; + foreach (kvp; this) { - if (n.right) - return max(n.left.height, n.right.height); + if (add_comma) + { + if (offset >= buffer.length) + return -1; + buffer[offset++] = ','; + } else - return n.left.height; + add_comma = true; + ptrdiff_t len = formatValue(kvp.key, buffer[offset .. $], format, formatArgs); + if (len < 0) + return len; + offset += len; + if (offset >= buffer.length) + return -1; + buffer[offset++] = ':'; + len = formatValue(kvp.value, buffer[offset .. $], format, formatArgs); + if (len < 0) + return len; + offset += len; } - if (n.right) - return n.right.height; - return 0; - } - static int getBalance(Node* n) pure - { - return n ? height(n.left) - height(n.right) : 0; + if (offset >= buffer.length) + return -1; + buffer[offset++] = '}'; + return offset; } - static Node* rightRotate(Node* y) pure + ptrdiff_t fromString()(const(char)[] s) { - Node* x = y.left; - Node* T2 = x.right; - - // Perform rotation - x.right = y; - y.left = T2; - - // Update heights - y.height = maxHeight(y) + 1; - x.height = maxHeight(x) + 1; - - // Return new root - return x; + assert(false, "TODO"); } - static Node* leftRotate(Node* x) pure - { - Node* y = x.right; - Node* T2 = y.left; - - // Perform rotation - y.left = x; - x.right = T2; +private: +nothrow: + alias Node = AVLTreeNode!(K, V); - // Update heights - x.height = maxHeight(x) + 1; - y.height = maxHeight(y) + 1; + size_t _num_modes = 0; + Node* _root = null; - // Return new root - return y; - } + static ptrdiff_t compare_node(const void* a, const void* b) pure + => Pred(*cast(K*)a, *cast(K*)b); - static inout(Node)* find(_K)(inout(Node)* n, ref const _K key) + static void free_node(void* p) { - if (n is null) - return null; - ptrdiff_t c = Pred(n.kvp.key, key); - if (c > 0) - return find(n.left, key); - if (c < 0) - return find(n.right, key); - return n; + Allocator.instance.freeT(cast(Node*)p); } + static inout(Node)* find(_K)(inout(Node)* n, ref const _K key) pure + => cast(inout(Node)*)find_node(n.base, (a, b) => Pred(*cast(K*)a, *cast(_K*)b), &key); + void destroy(Node* n) { - if (n is null) - return; - - destroy(n.left); - destroy(n.right); - - Allocator.instance.freeT(n); - - --numNodes; + _num_modes -= destroy_node(n.base, &free_node); } Node* insert(Node* n, Node* newnode) { - // 1. Perform the normal BST rotation - if (n is null) - { - ++numNodes; - return newnode; - } - - ptrdiff_t c = Pred(newnode.kvp.key, n.kvp.key); - if (c < 0) - n.left = insert(n.left, newnode); - else if (c > 0) - n.right = insert(n.right, newnode); - else - { - newnode.left = n.left; - newnode.right = n.right; - newnode.height = n.height; - - Allocator.instance.freeT(n); - - return newnode; - } - - // 2. Update height of this ancestor Node - n.height = maxHeight(n) + 1; - - // 3. get the balance factor of this ancestor Node to check whether - // this Node became unbalanced - int balance = getBalance(n); - - // If this Node becomes unbalanced, then there are 4 cases - - if (balance > 1) - { - ptrdiff_t lc = Pred(newnode.kvp.key, n.left.kvp.key); - // Left Left Case - if (lc < 0) - return rightRotate(n); - - // Left Right Case - if (lc > 0) - { - n.left = leftRotate(n.left); - return rightRotate(n); - } - } - - if (balance < -1) - { - ptrdiff_t rc = Pred(newnode.kvp.key, n.right.kvp.key); - - // Right Right Case - if (rc > 0) - return leftRotate(n); - - // Right Left Case - if (rc < 0) - { - n.right = rightRotate(n.right); - return leftRotate(n); - } - } - - // return the (unchanged) Node pointer - return n; - } - - Node* deleteNode(_K)(Node* _pRoot, ref const _K key) - { - // STEP 1: PERFORM STANDARD BST DELETE - - if (_pRoot is null) - return _pRoot; - - ptrdiff_t c = Pred(_pRoot.kvp.key, key); - - // If the key to be deleted is smaller than the _pRoot's key, - // then it lies in left subtree - if (c > 0) - _pRoot.left = deleteNode(_pRoot.left, key); - - // If the key to be deleted is greater than the _pRoot's key, - // then it lies in right subtree - else if (c < 0) - _pRoot.right = deleteNode(_pRoot.right, key); - - // if key is same as _pRoot's key, then this is the Node - // to be deleted - else - _pRoot = doDelete(_pRoot); - - return rebalance(_pRoot); + return cast(Node*)insert_node(n.base, newnode.base, _num_modes, + &compare_node, + &free_node); } - Node* doDelete(Node* _pRoot) + Node* delete_node(_K)(Node* _pRoot, ref const _K key) { - // Node with only one child or no child - if ((_pRoot.left is null) || (_pRoot.right is null)) - { - Node* temp = _pRoot.left ? _pRoot.left : _pRoot.right; - - // No child case - if (temp is null) - { - temp = _pRoot; - _pRoot = null; - } - else // One child case - { - // TODO: FIX THIS!! - // this is copying the child node into the parent node because there is no parent pointer - // DO: add parent pointer, then fix up the parent's child pointer to the child, and do away with this pointless copy! - *_pRoot = (*temp).move; // Copy the contents of the non-empty child - } - - Allocator.instance.freeT(temp); - - --numNodes; - } - else - { - // Node with two children: we replace 'this' node with the next one in sequence... - - // get the in-order successor: the 'next' item is the far left node on the right hand side) - Node* next = _pRoot.right; - while (next.left !is null) // find the leftmost leaf - next = next.left; - + return cast(Node*).delete_node(_pRoot.base, &key, _num_modes, &compare_node, (void* from, void* to) { // Copy the in-order successor's data to this Node - _pRoot.kvp.key = next.kvp.key; // we can't move the key, because deleteNode still needs to be able to find it - _pRoot.kvp.value = next.kvp.value.move; - - // Delete the node we just shifted - _pRoot.right = deleteNode(_pRoot.right, next.kvp.key); - } - - return _pRoot; - } - - Node* rebalance(Node* _pRoot) - { - // If the tree had only one Node then return - if (_pRoot is null) - return null; - - // STEP 2: UPDATE HEIGHT OF THE CURRENT NODE - _pRoot.height = max(height(_pRoot.left), height(_pRoot.right)) + 1; - - // STEP 3: GET THE BALANCE FACTOR OF THIS NODE (to check whether - // this Node became unbalanced) - int balance = getBalance(_pRoot); - - // If this Node becomes unbalanced, then there are 4 cases - - // Left Left Case - if (balance > 1 && getBalance(_pRoot.left) >= 0) - return rightRotate(_pRoot); - - // Left Right Case - if (balance > 1 && getBalance(_pRoot.left) < 0) - { - _pRoot.left = leftRotate(_pRoot.left); - return rightRotate(_pRoot); - } - - // Right Right Case - if (balance < -1 && getBalance(_pRoot.right) <= 0) - return leftRotate(_pRoot); - - // Right Left Case - if (balance < -1 && getBalance(_pRoot.right) > 0) - { - _pRoot.right = rightRotate(_pRoot.right); - return leftRotate(_pRoot); - } - - return _pRoot; + (cast(Node*)to).kvp.key = (cast(Node*)from).kvp.key; // we can't move the key, because delete_node still needs to be able to find it + (cast(Node*)to).kvp.value = (cast(Node*)from).kvp.value.move; + }, &free_node); } // static Node* clone(Node* pOld) @@ -657,9 +487,7 @@ public: PN n; ref const(K) key() @property const pure => n.kvp.key; -// ref const(V) value() @property const pure -// => n.kvp.value; - ref V value() @property pure + ref inout(V) value() @property inout pure => n.kvp.value; } return KV(n); @@ -688,14 +516,36 @@ public: } } } +struct BaseNode +{ + BaseNode* left, right; + int height; +} struct AVLTreeNode(K, V) { nothrow @nogc: - AVLTreeNode* left, right; + alias _base this; + + BaseNode _base; KVP!(K, V) kvp; - int height; + + inout(BaseNode)* base() inout pure @property + => &_base; + + inout(AVLTreeNode)* left() inout pure @property + => cast(inout(AVLTreeNode)*)_base.left; + void left(AVLTreeNode* node) pure @property + { + _base.left = node.base; + } + inout(AVLTreeNode)* right() inout pure @property + => cast(inout(AVLTreeNode)*)_base.right; + void right(AVLTreeNode* node) pure @property + { + _base.right = node.base; + } this() @disable; @@ -711,7 +561,7 @@ nothrow @nogc: left = rh.left; right = rh.right; kvp = rh.kvp; - height = rh.height; + _base.height = rh._base.height; } ref AVLTreeNode opAssign(ref AVLTreeNode rh) @@ -729,49 +579,6 @@ nothrow @nogc: } } -/+ -template -ptrdiff_t epStringify(Slice buffer, String epUnusedParam(format), const AVLTree &tree, const VarArg* epUnusedParam(pArgs)) -{ - size_t offset = 0; - if (buffer) - offset += String("{ ").copyTo(buffer); - else - offset += String("{ ").length; - - bool bFirst = true; - for (auto &&kvp : tree) - { - if (!bFirst) - { - if (buffer) - offset += String(", ").copyTo(buffer.drop(offset)); - else - offset += String(", ").length; - } - else - bFirst = false; - - if (buffer) - offset += epStringify(buffer.drop(offset), null, kvp, null); - else - offset += epStringify(null, null, kvp, null); - } - - if (buffer) - offset += String(" }").copyTo(buffer.drop(offset)); - else - offset += String(" }").length; - - return offset; -} -+/ - -//// Range retrieval -//template -//TreeRange> range(const AVLTree &input) { return TreeRange>(input); } - - unittest { @@ -900,7 +707,7 @@ unittest assert(map.get(2) is null); } - // Iteration (opApply) + // Iteration (range) { TestAVLTree map; map.insert(3, 30); @@ -908,25 +715,43 @@ unittest map.insert(2, 20); map.insert(4, 40); - int sumKeys = 0; - int sumValues = 0; - int count = 0; - // Iterate key-value pairs + int sumKeys = 0, sumValues = 0, count = 0; foreach (kv; map) { sumKeys += kv.key; sumValues += kv.value; count++; } + assert(count == 4); + assert(sumKeys == 1 + 2 + 3 + 4); + assert(sumValues == 10 + 20 + 30 + 40); + // Iterate const key-value pairs + ref const cmap = map; + sumKeys = sumValues = count = 0; + foreach (kv; cmap) + { + sumKeys += kv.key; + sumValues += kv.value; + count++; + } assert(count == 4); assert(sumKeys == 1 + 2 + 3 + 4); assert(sumValues == 10 + 20 + 30 + 40); - sumValues = 0; - count = 0; + // Iterate keys only + sumKeys = 0, count = 0; + foreach (v; map.keys) + { + sumKeys += v; + count++; + } + assert(count == 4); + assert(sumKeys == 1 + 2 + 3 + 4); + // Iterate values only + sumValues = 0, count = 0; foreach (v; map.values) { sumValues += v; @@ -935,6 +760,16 @@ unittest assert(count == 4); assert(sumValues == 10 + 20 + 30 + 40); + // Iterate const values only + sumValues = 0, count = 0; + foreach (v; cmap.values) + { + sumValues += v; + count++; + } + assert(count == 4); + assert(sumValues == 10 + 20 + 30 + 40); + // Test stopping iteration count = 0; foreach (k; map.keys) @@ -946,27 +781,6 @@ unittest assert(count == 2); // Should stop after 1 and 2 } - // Iteration (Iterator struct) - { - TestAVLTree map; - map.insert(3, 30); - map.insert(1, 10); - map.insert(2, 20); - map.insert(4, 40); - - foreach (kvp; map) // Uses Iterator internally - { - // Note: D's foreach over structs with opApply might not directly use the Iterator struct - // but opApply tests cover the iteration logic. - // This loop tests if the basic range primitives work. - // A direct Iterator test: - } - - // Test empty map iteration - TestAVLTree emptyMap; - assert(emptyMap[].empty); - } - // Test with string keys { alias StringMap = AVLTree!(const(char)[], int); @@ -997,3 +811,273 @@ unittest assert(count == 2); } } + + +private: + +alias CompFn = ptrdiff_t function(const void* a, const void* b) pure nothrow @nogc; +alias MoveFn = void function(void* from, void* to) nothrow @nogc; +alias DestroyFn = void function(void* a) nothrow @nogc; + +int height(const(BaseNode)* n) pure +{ + return n ? n.height : 0; +} + +int max_height(const(BaseNode)* n) pure +{ + if (!n) + return 0; + if (n.left) + { + if (n.right) + return max(n.left.height, n.right.height); + else + return n.left.height; + } + if (n.right) + return n.right.height; + return 0; +} + +int get_balance(BaseNode* n) pure +{ + return n ? height(n.left) - height(n.right) : 0; +} + +BaseNode* right_rotate(BaseNode* y) pure +{ + BaseNode* x = y.left; + BaseNode* T2 = x.right; + + // Perform rotation + x.right = y; + y.left = T2; + + // Update heights + y.height = max_height(y) + 1; + x.height = max_height(x) + 1; + + // Return new root + return x; +} + +BaseNode* left_rotate(BaseNode* x) pure +{ + BaseNode* y = x.right; + BaseNode* T2 = y.left; + + // Perform rotation + y.left = x; + x.right = T2; + + // Update heights + x.height = max_height(x) + 1; + y.height = max_height(y) + 1; + + // Return new root + return y; +} + +BaseNode* rebalance(BaseNode* root) pure +{ + // If the tree had only one Node then return + if (root is null) + return null; + + // STEP 2: UPDATE HEIGHT OF THE CURRENT NODE + root.height = max(height(root.left), height(root.right)) + 1; + + // STEP 3: GET THE BALANCE FACTOR OF THIS NODE (to check whether + // this Node became unbalanced) + int balance = get_balance(root); + + // If this Node becomes unbalanced, then there are 4 cases + + // Left Left Case + if (balance > 1 && get_balance(root.left) >= 0) + return right_rotate(root); + + // Left Right Case + if (balance > 1 && get_balance(root.left) < 0) + { + root.left = left_rotate(root.left); + return right_rotate(root); + } + + // Right Right Case + if (balance < -1 && get_balance(root.right) <= 0) + return left_rotate(root); + + // Right Left Case + if (balance < -1 && get_balance(root.right) > 0) + { + root.right = right_rotate(root.right); + return left_rotate(root); + } + + return root; +} + +inout(BaseNode)* find_node(inout(BaseNode)* n, CompFn pred, const void* key) pure +{ + if (n is null) + return null; + ptrdiff_t c = pred(&n[1], key); + if (c > 0) + return find_node(n.left, pred, key); + if (c < 0) + return find_node(n.right, pred, key); + return n; +} + +size_t destroy_node(BaseNode* n, DestroyFn free_fun) +{ + if (n is null) + return 0; + + size_t count = destroy_node(n.left, free_fun); + count += destroy_node(n.right, free_fun); + free_fun(n); + return count + 1; +} + +BaseNode* insert_node(BaseNode* n, BaseNode* newnode, ref size_t num_nodes, CompFn pred, DestroyFn free_fun) +{ + // 1. Perform the normal BST rotation + if (n is null) + { + ++num_nodes; + return newnode; + } + + ptrdiff_t c = pred(&newnode[1], &n[1]); + if (c < 0) + n.left = insert_node(n.left, newnode, num_nodes, pred, free_fun); + else if (c > 0) + n.right = insert_node(n.right, newnode, num_nodes, pred, free_fun); + else + { + newnode.left = n.left; + newnode.right = n.right; + newnode.height = n.height; + + free_fun(n); + + return newnode; + } + + // 2. Update height of this ancestor Node + n.height = max_height(n) + 1; + + // 3. get the balance factor of this ancestor Node to check whether + // this Node became unbalanced + int balance = get_balance(n); + + // If this Node becomes unbalanced, then there are 4 cases + + if (balance > 1) + { + ptrdiff_t lc = pred(&newnode[1], &n.left[1]); + // Left Left Case + if (lc < 0) + return right_rotate(n); + + // Left Right Case + if (lc > 0) + { + n.left = left_rotate(n.left); + return right_rotate(n); + } + } + + if (balance < -1) + { + ptrdiff_t rc = pred(&newnode[1], &n.right[1]); + + // Right Right Case + if (rc > 0) + return left_rotate(n); + + // Right Left Case + if (rc < 0) + { + n.right = right_rotate(n.right); + return left_rotate(n); + } + } + + // return the (unchanged) Node pointer + return n; +} + +BaseNode* delete_node(BaseNode* root, const void* key, ref size_t num_nodes, CompFn pred, MoveFn move_fun, DestroyFn free_fun) +{ + // STEP 1: PERFORM STANDARD BST DELETE + + if (root is null) + return root; + + ptrdiff_t c = pred(&root[1], key); + + // If the key to be deleted is smaller than the root's key, + // then it lies in left subtree + if (c > 0) + root.left = delete_node(root.left, key, num_nodes, pred, move_fun, free_fun); + + // If the key to be deleted is greater than the root's key, + // then it lies in right subtree + else if (c < 0) + root.right = delete_node(root.right, key, num_nodes, pred, move_fun, free_fun); + + // if key is same as root's key, then this is the Node + // to be deleted + else + root = do_delete(root, num_nodes, pred, move_fun, free_fun); + + return rebalance(root); +} + +BaseNode* do_delete(BaseNode* root, ref size_t num_nodes, CompFn pred, MoveFn move_fun, DestroyFn free_fun) +{ + // Node with only one child or no child + if ((root.left is null) || (root.right is null)) + { + BaseNode* temp = root.left ? root.left : root.right; + + // No child case + if (temp is null) + { + temp = root; + root = null; + } + else // One child case + { + // TODO: FIX THIS!! + // this is copying the child node into the parent node because there is no parent pointer + // DO: add parent pointer, then fix up the parent's child pointer to the child, and do away with this pointless copy! + *root = (*temp).move; // Copy the tree structure (BaseNode fields) + move_fun(temp, root); // Copy the key/value data + } + + free_fun(temp); + + --num_nodes; + } + else + { + // Node with two children: we replace 'this' node with the next one in sequence... + + // get the in-order successor: the 'next' item is the far left node on the right hand side) + BaseNode* next = root.right; + while (next.left !is null) // find the leftmost leaf + next = next.left; + + move_fun(next, root); + + // Delete the node we just shifted + root.right = delete_node(root.right, &next[1], num_nodes, pred, move_fun, free_fun); + } + + return root; +} diff --git a/src/urt/math.d b/src/urt/math.d index 236c867..d53626f 100644 --- a/src/urt/math.d +++ b/src/urt/math.d @@ -1,8 +1,9 @@ module urt.math; import urt.intrinsic; -import core.stdc.stdio; // For writeDebugf -import std.format; // For format + +// for arch where using FPU for int<->float conversions is preferred +//version = PreferFPUIntConv; version (LDC) version = GDC_OR_LDC; version (GNU) version = GDC_OR_LDC; @@ -62,8 +63,153 @@ extern(C) double exp(double x); double log(double x); double acos(double x); +// double pow(double x, double e); +} + +int float_is_integer(double f, out ulong i) +{ + version (PreferFPUIntConv) + { + if (!(f == f)) + return 0; // NaN + if (f < 0) + { + if (f < long.min) + return 0; // out of range + long t = cast(long)f; + if (cast(double)t != f) + return 0; // not an integer + i = cast(ulong)t; + return -1; + } + if (f >= ulong.max) + return 0; // out of range + ulong t = cast(ulong)f; + if (cast(double)t != f) + return 0; // not an integer + i = t; + return 1; + } + else + { + import urt.meta : bit_mask; + enum M = 52, E = 11, B = 1023; + + ulong u = *cast(const(ulong)*)&f; + int e = (u >> M) & bit_mask!E; + ulong m = u & bit_mask!M; + + if (e == bit_mask!E) + return 0; // NaN/Inf + if (e == 0) + { + if (m) + return 0; // denormal + i = 0; + return 1; // +/- 0 + } + int shift = e - B; + if (shift < 0) + return 0; // |f| < 1 + bool integral = shift >= M || (m & bit_mask(M - shift)) == 0; + if (!integral) + return 0; // not an integer + if (f < 0) + { + if (f < long.min) + return 0; // out of range + i = cast(ulong)cast(long)f; + return -1; + } + if (f >= ulong.max) + return 0; // out of range + i = cast(ulong)f; + return 1; + } +} + +unittest +{ + // this covers all the branches, but maybe test some extreme cases? + ulong i; + assert(float_is_integer(double.nan, i) == 0); + assert(float_is_integer(double.infinity, i) == 0); + assert(float_is_integer(-double.infinity, i) == 0); + assert(float_is_integer(double.max, i) == 0); + assert(float_is_integer(-double.max, i) == 0); + assert(float_is_integer(0.5, i) == 0); + assert(float_is_integer(1.5, i) == 0); + assert(float_is_integer(cast(double)ulong.max, i) == 0); + assert(float_is_integer(0.0, i) == 1 && i == 0); + assert(float_is_integer(-0.0, i) == 1 && i == 0); + assert(float_is_integer(200, i) == 1 && i == 200); + assert(float_is_integer(-200, i) == -1 && cast(long)i == -200); } +auto pow(B, E)(B base, E exp) @trusted +{ + enum isFloatB = is(B == float) || is(B == double) || is(B == real); + enum isFloatE = is(E == float) || is(E == double) || is(E == real); + + static if (isFloatB) + { + // Floating-point base + B result = B(1); + B b = base; + + static if (isFloatE) + { + // F ^^ F - handle integer-valued exponents (covers 99% of + // real-world `^^` uses: value^^2, 10.0^^e, etc.) + if (exp == 0) + return B(1); + long iexp = cast(long)exp; + if (cast(E)iexp == exp) + return _powfi!B(b, iexp); + // True non-integer exponent: not supported without libm. + assert(false, "Non-integer float exponent needs libm"); + } + else + { + // F ^^ I - binary exponentiation + return _powfi!B(b, long(exp)); + } + } + else + { + // I ^^ I - integer power + if (exp == 0) + return B(1); + B result = B(1); + B b = base; + auto e = cast(ulong) exp; + while (e > 0) + { + if (e & 1) + result *= b; + b *= b; + e >>= 1; + } + return result; + } +} +// binary exponentiation: float base, integer exponent. +private F _powfi(F)(F base, long exp) @trusted +{ + if (exp == 0) + return F(1); + bool neg = exp < 0; + ulong e = neg ? cast(ulong)(-exp) : cast(ulong) exp; + F result = F(1); + while (e > 0) + { + if (e & 1) + result *= base; + base *= base; + e >>= 1; + } + return neg ? F(1) / result : result; +} pragma(inline, true) bool addc(T = uint)(T a, T b, out T r, bool c_in) @@ -591,7 +737,10 @@ private T _divrem2x1_impl(T)(T[2] a, T b, out T rem) static if (is(T == uint)) { - static assert(false, "TODO!"); + // 32-bit: use 64-bit arithmetic directly + ulong dividend = (cast(ulong)a[1] << 32) | a[0]; + rem = cast(uint)(dividend % b); + return cast(uint)(dividend / b); } else { diff --git a/src/urt/mem/alloc.d b/src/urt/mem/alloc.d index 7408808..f915b86 100644 --- a/src/urt/mem/alloc.d +++ b/src/urt/mem/alloc.d @@ -1,160 +1,235 @@ module urt.mem.alloc; -import core.stdc.stdlib; +import urt.mem; nothrow @nogc: -void[] alloc(size_t size) nothrow @nogc + +enum MemFlags : ubyte { + none = 0, + + fast = 1, // internal SRAM, lowest latency + slow = 2, // external PSRAM, bulk storage + fastest = 3, // TCM / tightly-coupled, single cycle - // TODO: we might pin the length to a debug table somewhere... - return malloc(size)[0 .. size]; + dma = 0x4, // DMA-accessible } -void[] allocAligned(size_t size, size_t alignment) nothrow @nogc -{ - import urt.util : isPowerOf2, max; - alignment = max(alignment, (void*).sizeof); - assert(isPowerOf2(alignment), "Alignment must be a power of two!"); +MemFlags mem_speed(MemFlags flags) pure => cast(MemFlags)(flags & 3); +bool mem_is_dma(MemFlags flags) pure => (flags & MemFlags.dma) != 0; - version (Windows) - { - import urt.util : alignDown; - // This is how Visual Studio's _aligned_malloc works... - // see C:\Program Files (x86)\Windows Kits\10\Source\10.0.15063.0\ucrt\heap\align.cpp - // - // This is implemented so memsize() can return the correct result. - // - size_t header_size = (void*).sizeof + alignment; - size_t total = header_size + size; +void[] alloc(size_t size, MemFlags flags = MemFlags.none) pure + => alloc(size, size_t.sizeof, flags); - void* mem = malloc(total); - if (mem is null) - return null; +void[] alloc(size_t size, size_t alignment, MemFlags flags = MemFlags.none) pure +{ + import urt.util : is_power_of_2; - size_t ptr = cast(size_t)mem; - size_t allocptr = alignDown(ptr + header_size, alignment); - (cast(void**)allocptr)[-1] = mem; + assert(is_power_of_2(alignment), "Alignment must be a power of two!"); - return (cast(void*)allocptr)[0 .. size]; - } - else version (Posix) + void[] mem = _alloc(size, alignment, flags); + version (AllocTracking) { - import core.sys.posix.stdlib; - void* mem; - return posix_memalign(&mem, alignment, size) ? null : mem[0 .. size]; + import urt.mem.tracking : track_alloc; + if (mem.ptr !is null) + { + alias TrackFn = void function(void*, size_t) pure nothrow @nogc; + (cast(TrackFn) &track_alloc)(mem.ptr, mem.length); + } } - else - { - void[] mem = malloc(size)[0 .. size]; - // HACK: just for now... - assert((cast(size_t)mem.ptr & (alignment - 1)) == 0, "Memory not aligned!"); - return mem; - } -} - -void[] realloc(void[] mem, size_t newSize) nothrow @nogc -{ - // TODO: we might pin the length to a debug table somewhere... - return core.stdc.stdlib.realloc(mem.ptr, newSize)[0 .. newSize]; + return mem; } -void[] reallocAligned(void[] mem, size_t newSize, size_t alignment) nothrow @nogc +void[] realloc(void[] mem, size_t new_size, size_t alignment = 8, MemFlags flags = MemFlags.none) pure { - import urt.util : isPowerOf2, min, max; - - alignment = max(alignment, (void*).sizeof); - assert(isPowerOf2(alignment), "Alignment must be a power of two!"); + import urt.util : min; - void[] newAlloc = newSize > 0 ? allocAligned(newSize, alignment) : null; - if (newAlloc !is null && mem !is null) + if (new_size == 0) { - size_t toCopy = min(mem.length, newSize); - newAlloc[0 .. toCopy] = mem[0 .. toCopy]; + free(mem); + return null; } - freeAligned(mem); - return newAlloc; -} + if (mem.ptr is null) + return alloc(new_size, alignment, flags); -// NOTE: This function is only compatible with allocAligned! -void[] expand(void[] mem, size_t newSize) nothrow @nogc -{ - version (Windows) + static if (has_realloc) { - if (mem.ptr is null) - return null; - void* ptr = (cast(void**)mem.ptr)[-1]; - size_t head = (cast(size_t)mem.ptr - cast(size_t)ptr); - void* r = _expand(ptr, head + newSize); - if (r is null) - return null; - return mem.ptr[0 .. newSize]; + void* old_ptr = mem.ptr; + void[] new_mem = _realloc(mem, new_size, alignment, flags); + version (AllocTracking) + { + import urt.mem.tracking : track_realloc; + if (new_mem.ptr !is null) + { + alias TrackFn = void function(void*, void*, size_t) pure nothrow @nogc; + (cast(TrackFn) &track_realloc)(old_ptr, new_mem.ptr, new_mem.length); + } + } + return new_mem; } else { - if (newSize <= memsize(mem.ptr)) - return mem.ptr[0 .. newSize]; - return null; + // Fallback path uses nested alloc/free, which are already hooked. + void[] new_mem = alloc(new_size, alignment, flags); + if (new_mem.ptr !is null) + { + size_t copy = min(mem.length, new_size); + new_mem[0 .. copy] = mem[0 .. copy]; + } + free(mem); + return new_mem; } } -void free(void[] mem) nothrow @nogc +void free(void[] mem) pure { - // maybe check the length passed to free matches the alloc? - // ... or you know, just don't do that. - - core.stdc.stdlib.free(mem.ptr); + if (mem.ptr is null) + return; + version (AllocTracking) + { + import urt.mem.tracking : untrack_alloc; + alias UntrackFn = void function(void*) pure nothrow @nogc; + (cast(UntrackFn) &untrack_alloc)(mem.ptr); + } + _free(mem.ptr); } -void freeAligned(void[] mem) nothrow @nogc +void[] expand(void[] mem, size_t new_size) pure { - version (Windows) + if (mem.ptr is null) + return null; + static if (has_expand) + return _expand(mem, new_size); + else static if (has_memsize) { - if (mem.ptr is null) - return; - void* p = (cast(void**)mem.ptr)[-1]; - core.stdc.stdlib.free(p); + if (new_size <= _memsize(mem.ptr)) + return mem.ptr[0 .. new_size]; + return null; } else - core.stdc.stdlib.free(mem.ptr); + assert(false, "unsupported"); } -size_t memsize(void* ptr) nothrow @nogc +size_t memsize(void* ptr) pure { - version (Windows) - { - if (ptr is null) - return 0; - void* mem = (cast(void**)ptr)[-1]; - return _msize(mem) - (cast(size_t)ptr - cast(size_t)mem); - } - else version (Posix) - return malloc_usable_size(ptr); - else version (Darwin) - return malloc_size(ptr); + if (ptr is null) + return 0; + static if (has_memsize) + return _memsize(ptr); else - assert(false, "Unsupported platform"); + assert(false, "unsupported"); } +void[] alloc_exec(size_t size) pure +{ + static if (has_exec) + return _alloc_exec(size); + else + return null; +} -unittest +void free_exec(void[] mem) pure { - void[] mem = allocAligned(16, 8); - size_t s = memsize(mem.ptr); - mem = expand(mem, 8); - mem = expand(mem, 16); - freeAligned(mem); + static if (has_exec) + { + if (mem.ptr !is null) + _free_exec(mem); + } } +void[] alloc_retain(size_t size) pure +{ + static if (has_retain) + return _alloc_retain(size); + else + return null; +} -version (Windows) +void free_retain(void[] mem) pure { - extern(C) void* _expand(void* memblock, size_t size) nothrow @nogc; - extern(C) size_t _msize(void* _Block); + static if (has_retain) + { + if (mem.ptr !is null) + _free_retain(mem); + } } -version (Posix) + +// pointer tagging utilities -- for containers to store flags in low 3 bits +// of 8-byte aligned pointers. the allocator itself returns clean pointers. +T* tag(T)(T* ptr, MemFlags flags) pure + => cast(T*)(cast(size_t)ptr | flags); + +T* untag(T)(T* ptr) pure + => cast(T*)(cast(size_t)ptr & ~cast(size_t)0x7); + +MemFlags get_flags(void* ptr) pure + => cast(MemFlags)(cast(size_t)ptr & 0x7); + + +version (Espressif) + public import urt.driver.esp32.alloc; +else version (BL808_M0) + public import urt.driver.bl618.alloc; +else version (BL808) + public import urt.driver.bl808.alloc; +else version (BL618) + public import urt.driver.bl618.alloc; +else version (RP2350) + public import urt.driver.rp2350.alloc; +else version (BK7231N) + public import urt.driver.bk7231.alloc; +else version (BK7231T) + public import urt.driver.bk7231.alloc; +else version (STM32F4) + public import urt.driver.stm32.alloc; +else version (STM32F7) + public import urt.driver.stm32.alloc; +else version (Windows) + public import urt.driver.windows.alloc; +else version (Posix) + public import urt.driver.posix.alloc; +else + static assert(false, "No alloc driver for this platform"); + + +unittest { - extern(C) size_t malloc_usable_size(void *__ptr); + // basic alloc/free + void[] mem = alloc(32, 8); + assert(mem !is null); + assert((cast(size_t)mem.ptr & 0x7) == 0); // 8-byte aligned + assert(mem.length == 32); + free(mem); + + // alloc with flags (on desktop, flags are ignored but API works) + mem = alloc(64, 8, MemFlags.fast); + assert(mem !is null); + size_t s = memsize(mem.ptr); + assert(s >= 64); + free(mem); + + // realloc preserves data + mem = alloc(16, 8); + (cast(ubyte*)mem.ptr)[0 .. 16] = 0xAB; + mem = realloc(mem, 64); + assert(mem !is null); + assert((cast(ubyte*)mem.ptr)[0] == 0xAB); + free(mem); + + // expand + mem = alloc(16, 8); + void[] expanded = expand(mem, 8); + if (expanded !is null) + assert(expanded.ptr is mem.ptr); + free(mem); + + // pointer tagging utilities + void* p = mem.ptr; + enum test_flags = cast(MemFlags)(MemFlags.fast | MemFlags.dma); + void* tagged = tag(p, test_flags); + assert(get_flags(tagged) == test_flags); + assert(untag(tagged) is p); } diff --git a/src/urt/mem/allocator.d b/src/urt/mem/allocator.d index 75cadff..7c1ad50 100644 --- a/src/urt/mem/allocator.d +++ b/src/urt/mem/allocator.d @@ -2,28 +2,30 @@ module urt.mem.allocator; import urt.lifetime; +nothrow: + // TODO: this should be defined by platform/compiler/etc... enum DefaultAlign = size_t.sizeof; -NoGCAllocator defaultAllocator() nothrow @nogc +NoGCAllocator defaultAllocator() pure @nogc { - return Mallocator.instance; + return Mallocator.instance(); } -NoGCAllocator tempAllocator() nothrow @nogc +NoGCAllocator tempAllocator() pure @nogc { import urt.mem.temp; - - return TempAllocator.instance; + return TempAllocator.instance(); } class Allocator { - abstract void[] alloc(size_t bytes, size_t alignment = DefaultAlign) nothrow; +nothrow: + abstract void[] alloc(size_t bytes, size_t alignment = DefaultAlign) pure; - void[] realloc(void[] mem, size_t newSize, size_t alignment = DefaultAlign) nothrow + void[] realloc(void[] mem, size_t newSize, size_t alignment = DefaultAlign) pure { void[] newMem = alloc(newSize, alignment); if (newMem != null) @@ -37,16 +39,16 @@ class Allocator return newMem; } - void[] expand(void[] mem, size_t newSize) nothrow + void[] expand(void[] mem, size_t newSize) pure { return null; } - abstract void free(void[] mem) nothrow; + abstract void free(void[] mem) pure; // abstract size_t getUsableSize(void* p); // get the usable size for an allocation... - final T* allocT(T, Args...)(auto ref Args args) nothrow + final T* allocT(T, Args...)(auto ref Args args) if (!is(T == class)) { T* item = cast(T*)alloc(T.sizeof, T.alignof).ptr; @@ -59,7 +61,7 @@ class Allocator return item; } - final T allocT(T, Args...)(auto ref Args args) nothrow + final T allocT(T, Args...)(auto ref Args args) if (is(T == class)) { T item = cast(T)alloc(__traits(classInstanceSize, T), __traits(classInstanceAlignment, T)).ptr; @@ -72,7 +74,7 @@ class Allocator return item; } - final void freeT(T)(T* item) nothrow + final void freeT(T)(T* item) if (!is(T == class)) { try @@ -84,7 +86,7 @@ class Allocator free((cast(void*)item)[0..T.sizeof]); } - final void freeT(T)(T item) nothrow + final void freeT(T)(T item) if (is(T == class)) { try @@ -96,7 +98,7 @@ class Allocator free((cast(void*)item)[0..__traits(classInstanceSize, T)]); } - final T[] allocArray(T, Args...)(size_t count, auto ref Args args) nothrow + final T[] allocArray(T, Args...)(size_t count, auto ref Args args) if (!is(T == class)) { if (count == 0) @@ -114,7 +116,7 @@ class Allocator return items; } - final T[] reallocArray(T, Args...)(T[] arr, size_t newCount, auto ref Args args) nothrow + final T[] reallocArray(T, Args...)(T[] arr, size_t newCount, auto ref Args args) if (!is(T == class)) { if (newCount < arr.length) @@ -148,7 +150,7 @@ class Allocator return arr; } - final void freeArray(T)(T[] items) nothrow + final void freeArray(T)(T[] items) if (!is(T == class)) { try @@ -166,15 +168,21 @@ class Allocator class GCAllocator : Allocator { - static GCAllocator instance() nothrow @nogc => _instance; +nothrow: + static GCAllocator instance() pure @nogc + { + alias PureHack = GCAllocator function() pure nothrow @nogc; + static GCAllocator hack() nothrow @nogc => _instance; + return (cast(PureHack)&hack)(); + } - override void[] alloc(size_t bytes, size_t alignment = DefaultAlign) nothrow + override void[] alloc(size_t bytes, size_t alignment = DefaultAlign) pure { // TODO: can/should we enforce alignment? return new void[bytes]; } - override void free(void[] mem) nothrow + override void free(void[] mem) pure { // GC will take care of it... } @@ -185,9 +193,10 @@ private: class NoGCAllocator : Allocator { - abstract override void[] alloc(size_t bytes, size_t alignment = DefaultAlign) nothrow @nogc; +nothrow @nogc: + abstract override void[] alloc(size_t bytes, size_t alignment = DefaultAlign) pure; - override void[] realloc(void[] mem, size_t newSize, size_t alignment = DefaultAlign) nothrow @nogc + override void[] realloc(void[] mem, size_t newSize, size_t alignment = DefaultAlign) pure { void[] newMem = alloc(newSize, alignment); if (newMem != null) @@ -201,14 +210,14 @@ class NoGCAllocator : Allocator return newMem; } - override void[] expand(void[] mem, size_t newSize) nothrow @nogc + override void[] expand(void[] mem, size_t newSize) pure { return null; } - abstract override void free(void[] mem) nothrow @nogc; + abstract override void free(void[] mem) pure; - final T* allocT(T, Args...)(auto ref Args args) nothrow @nogc + final T* allocT(T, Args...)(auto ref Args args) if (!is(T == class)) { T* item = cast(T*)alloc(T.sizeof, T.alignof).ptr; @@ -221,7 +230,7 @@ class NoGCAllocator : Allocator return item; } - final T allocT(T, Args...)(auto ref Args args) nothrow @nogc + final T allocT(T, Args...)(auto ref Args args) if (is(T == class)) { T item = cast(T)alloc(__traits(classInstanceSize, T), __traits(classInstanceAlignment, T)).ptr; @@ -234,7 +243,7 @@ class NoGCAllocator : Allocator return item; } - final void freeT(T)(T* item) nothrow @nogc + final void freeT(T)(T* item) if (!is(T == class)) { try @@ -246,7 +255,7 @@ class NoGCAllocator : Allocator free((cast(void*)item)[0..T.sizeof]); } - final void freeT(T)(T item) nothrow @nogc + final void freeT(T)(T item) if (is(T == class)) { // HACK: since druntime can't actually destroy a @nogc class! @@ -262,7 +271,7 @@ class NoGCAllocator : Allocator free((cast(void*)item)[0..__traits(classInstanceSize, T)]); } - final T[] allocArray(T, Args...)(size_t count, auto ref Args args) nothrow @nogc + final T[] allocArray(T, Args...)(size_t count, auto ref Args args) if (!is(T == class)) { if (count == 0) @@ -279,7 +288,7 @@ class NoGCAllocator : Allocator return items; } - final T[] allocArray(T)(size_t count) nothrow @nogc + final T[] allocArray(T)(size_t count) if (is(T == class)) { if (count == 0) @@ -291,7 +300,7 @@ class NoGCAllocator : Allocator return items; } - final T[] reallocArray(T, Args...)(T[] arr, size_t newCount, auto ref Args args) nothrow @nogc + final T[] reallocArray(T, Args...)(T[] arr, size_t newCount, auto ref Args args) if (!is(T == class)) { if (newCount < arr.length) @@ -326,7 +335,7 @@ class NoGCAllocator : Allocator return arr; } - final void freeArray(T)(T[] items) nothrow @nogc + final void freeArray(T)(T[] items) if (!is(T == class)) { try @@ -341,7 +350,7 @@ class NoGCAllocator : Allocator free(cast(void[])items[]); } - final void freeArray(T)(T[] items) nothrow @nogc + final void freeArray(T)(T[] items) if (is(T == class)) { free(cast(void[])items[]); @@ -351,27 +360,33 @@ class NoGCAllocator : Allocator class Mallocator : NoGCAllocator { static import urt.mem.alloc; +nothrow @nogc: - static Mallocator instance() nothrow @nogc => _instance; + static Mallocator instance() pure + { + alias PureHack = Mallocator function() pure nothrow @nogc; + static Mallocator hack() nothrow @nogc => _instance; + return (cast(PureHack)&hack)(); + } - override void[] alloc(size_t size, size_t alignment = DefaultAlign) nothrow @nogc + override void[] alloc(size_t size, size_t alignment = DefaultAlign) pure { - return urt.mem.alloc.allocAligned(size, alignment); + return urt.mem.alloc.alloc(size, alignment); } - override void[] realloc(void[] mem, size_t newSize, size_t alignment = DefaultAlign) nothrow @nogc + override void[] realloc(void[] mem, size_t newSize, size_t alignment = DefaultAlign) pure { - return urt.mem.alloc.reallocAligned(mem, newSize, alignment); + return urt.mem.alloc.realloc(mem, newSize, alignment); } - override void[] expand(void[] mem, size_t newSize) nothrow + override void[] expand(void[] mem, size_t newSize) pure { return urt.mem.alloc.expand(mem, newSize); } - override void free(void[] mem) nothrow @nogc + override void free(void[] mem) pure { - urt.mem.alloc.freeAligned(mem); + urt.mem.alloc.free(mem); } private: @@ -381,25 +396,25 @@ private: class RegionAllocator : NoGCAllocator { import urt.mem.region; +nothrow @nogc: - static RegionAllocator getRegionAllocator(void[] region) pure nothrow @nogc + static RegionAllocator get_region_allocator(void[] region) pure { Region* r = makeRegion(region); return r.alloc!RegionAllocator(r); } - - this(Region* region) pure nothrow @nogc + this(Region* region) pure { this.region = region; } - override void[] alloc(size_t bytes, size_t alignment = DefaultAlign) pure nothrow @nogc + override void[] alloc(size_t bytes, size_t alignment = DefaultAlign) pure { return region.alloc(bytes, alignment); } - override void free(void[] mem) pure nothrow @nogc + override void free(void[] mem) pure { } @@ -434,7 +449,10 @@ unittest } } - Allocator a = new Mallocator; + import urt.util; + auto mallocator = InPlace!Mallocator(Default); + + Allocator a = mallocator; S* s = a.allocT!S(10); a.freeT(s); C c = a.allocT!C(); diff --git a/src/urt/mem/package.d b/src/urt/mem/package.d index 795a4e3..f187ef8 100644 --- a/src/urt/mem/package.d +++ b/src/urt/mem/package.d @@ -1,29 +1,220 @@ module urt.mem; -public import core.stdc.stddef : wchar_t; - +// TODO: remove these public imports, because this is pulled by object.d! public import urt.lifetime : emplace, moveEmplace, forward, move; +public import urt.mem.alloc; public import urt.mem.allocator; +nothrow @nogc: + + +version (LDC) + pragma(LDC_alloca) void* alloca(size_t size) pure @safe; +else + extern(C) void* alloca(size_t size) pure @trusted; extern(C) { nothrow @nogc: - void* alloca(size_t size); - void* memcpy(void* dest, const void* src, size_t n); - void* memmove(void* dest, const void* src, size_t n); - void* memset(void* s, int c, size_t n); - void* memzero(void* s, size_t n) => memset(s, 0, n); + void* memcpy(void* dest, const void* src, size_t n) pure; + void* memmove(void* dest, const void* src, size_t n) pure; + void* memset(void* s, int c, size_t n) pure; + void* memzero(void* s, size_t n) pure => memset(s, 0, n); + int memcmp(const void *s1, const void *s2, size_t n) pure; - size_t strlen(const char* s); - int strcmp(const char* s1, const char* s2); - char* strcpy(char* dest, const char* src); + size_t strlen(const char* s) pure; + int strcmp(const char* s1, const char* s2) pure; + char* strcpy(char* dest, const char* src) pure; char* strcat(char* dest, const char* src); - size_t wcslen(const wchar_t* s); -// wchar_t* wcscpy(wchar_t* dest, const wchar_t* src); + size_t wcslen(const wchar_t* s) pure; +// wchar_t* wcscpy(wchar_t* dest, const wchar_t* src) pure; // wchar_t* wcscat(wchar_t* dest, const wchar_t* src); -// wchar_t* wcsncpy(wchar_t* dest, const wchar_t* src, size_t n); +// wchar_t* wcsncpy(wchar_t* dest, const wchar_t* src, size_t n) pure; // wchar_t* wcsncat(wchar_t* dest, const wchar_t* src, size_t n); } + +T debug_alloc(T = void)() pure @trusted + if (is(T == class)) +{ + static T gc_alloc(size_t size) pure nothrow => new T; + return (cast(T function(size_t) pure nothrow @nogc)&gc_alloc)(size); +} +T* debug_alloc(T = void)() pure @trusted + if (!is(T == class)) +{ + static T* gc_alloc(size_t size) pure nothrow => new T; + return (cast(T* function(size_t) pure nothrow @nogc)&gc_alloc)(size); +} +T[] debug_alloc(T = void)(size_t size) pure @trusted +{ + static T[] gc_alloc(size_t size) pure nothrow => new T[size]; + return (cast(T[] function(size_t) pure nothrow @nogc)&gc_alloc)(size); +} + + +private: + +version(DigitalMars) +{ + // DMD lowers alloca(n) calls to __alloca(n) + extern(C) void* __alloca(int nbytes) + { + version (D_InlineAsm_X86) + { + asm nothrow @nogc + { + naked ; + mov EDX,ECX ; + mov EAX,4[ESP] ; // get nbytes + push EBX ; + push EDI ; + push ESI ; + + add EAX,15 ; + and EAX,0xFFFFFFF0 ; // round up to 16 byte boundary + jnz Abegin ; + mov EAX,16 ; // minimum allocation is 16 + Abegin: + mov ESI,EAX ; // ESI = nbytes + neg EAX ; + add EAX,ESP ; // EAX is now what the new ESP will be. + jae Aoverflow ; + } + version (Win32) + { + asm nothrow @nogc + { + // Touch guard pages to commit stack memory + mov ECX,EAX ; + mov EBX,ESI ; + L1: + test [ECX+EBX],EBX ; + sub EBX,0x1000 ; + jae L1 ; + test [ECX],EBX ; + } + } + asm nothrow @nogc + { + mov ECX,EBP ; + sub ECX,ESP ; + sub ECX,[EDX] ; + add [EDX],ESI ; + mov ESP,EAX ; + add EAX,ECX ; + mov EDI,ESP ; + add ESI,ESP ; + shr ECX,2 ; + rep ; + movsd ; + jmp done ; + + Aoverflow: + xor EAX,EAX ; + + done: + pop ESI ; + pop EDI ; + pop EBX ; + ret ; + } + } + else version (D_InlineAsm_X86_64) + { + version (Win64) + { + asm nothrow @nogc + { + naked ; + push RBX ; + push RDI ; + push RSI ; + mov RAX,RCX ; + add RAX,15 ; + and AL,0xF0 ; + test RAX,RAX ; + jnz Abegin ; + mov RAX,16 ; + Abegin: + mov RSI,RAX ; + neg RAX ; + add RAX,RSP ; + jae Aoverflow ; + + // Touch guard pages + mov RCX,RAX ; + mov RBX,RSI ; + L1: + test [RCX+RBX],RBX ; + sub RBX,0x1000 ; + jae L1 ; + test [RCX],RBX ; + + mov RCX,RBP ; + sub RCX,RSP ; + sub RCX,[RDX] ; + add [RDX],RSI ; + mov RSP,RAX ; + add RAX,RCX ; + mov RDI,RSP ; + add RSI,RSP ; + shr RCX,3 ; + rep ; + movsq ; + jmp done ; + + Aoverflow: + xor RAX,RAX ; + + done: + pop RSI ; + pop RDI ; + pop RBX ; + ret ; + } + } + else + { + asm nothrow @nogc + { + naked ; + mov RDX,RCX ; + mov RAX,RDI ; + add RAX,15 ; + and AL,0xF0 ; + test RAX,RAX ; + jnz Abegin ; + mov RAX,16 ; + Abegin: + mov RSI,RAX ; + neg RAX ; + add RAX,RSP ; + jae Aoverflow ; + + mov RCX,RBP ; + sub RCX,RSP ; + sub RCX,[RDX] ; + add [RDX],RSI ; + mov RSP,RAX ; + add RAX,RCX ; + mov RDI,RSP ; + add RSI,RSP ; + shr RCX,3 ; + rep ; + movsq ; + jmp done ; + + Aoverflow: + xor RAX,RAX ; + + done: + ret ; + } + } + } + else + static assert(0, "Unsupported architecture for __alloca"); + } +} diff --git a/src/urt/mem/region.d b/src/urt/mem/region.d index 9a4a228..386e661 100644 --- a/src/urt/mem/region.d +++ b/src/urt/mem/region.d @@ -2,11 +2,13 @@ module urt.mem.region; import urt.util; +nothrow @nogc: -static Region* makeRegion(void[] mem) pure nothrow @nogc + +static Region* makeRegion(void[] mem) pure { assert(mem.length >= Region.sizeof, "Memory block too small"); - Region* region = cast(Region*)mem.ptr.alignUp(Region.alignof); + Region* region = cast(Region*)mem.ptr.align_up(Region.alignof); size_t alignBytes = cast(void*)region - mem.ptr; if (size_t.sizeof > 4 && mem.length > uint.max + alignBytes + Region.sizeof) region.length = uint.max; @@ -21,7 +23,7 @@ struct Region void[] alloc(size_t size, size_t alignment = size_t.sizeof) pure nothrow @nogc { size_t ptr = cast(size_t)&this + Region.sizeof + offset; - size_t alignedPtr = ptr.alignUp(alignment); + size_t alignedPtr = ptr.align_up(alignment); size_t alignBytes = alignedPtr - ptr; if (offset + alignBytes + size > length) return null; diff --git a/src/urt/mem/ring.d b/src/urt/mem/ring.d index 2bfdb48..6396eab 100644 --- a/src/urt/mem/ring.d +++ b/src/urt/mem/ring.d @@ -84,7 +84,7 @@ private: // declare some stuff outside the template... -size_t read_ring(void[] buffer, const void[] data, ref uint readCur, const int writeCur, const size_t capacity) +size_t read_ring(void[] buffer, const void[] data, ref uint readCur, const uint writeCur, const size_t capacity) { if (writeCur >= readCur) { diff --git a/src/urt/mem/scratchpad.d b/src/urt/mem/scratchpad.d index 83a3abb..2a20c30 100644 --- a/src/urt/mem/scratchpad.d +++ b/src/urt/mem/scratchpad.d @@ -8,10 +8,10 @@ nothrow @nogc: enum size_t MaxScratchpadSize = 2048; enum size_t NumScratchBuffers = 4; -static assert(MaxScratchpadSize.isPowerOf2, "Scratchpad size must be a power of 2"); +static assert(MaxScratchpadSize.is_power_of_2, "Scratchpad size must be a power of 2"); -void[] allocScratchpad(size_t size = MaxScratchpadSize) +void[] alloc_scratchpad(size_t size = MaxScratchpadSize) { if (size > MaxScratchpadSize) { @@ -19,17 +19,17 @@ void[] allocScratchpad(size_t size = MaxScratchpadSize) return null; } - size = max(size.nextPowerOf2, WindowSize); + size = max(size.next_power_of_2, WindowSize); size_t maskBits = size / WindowSize; size_t mask = (1 << maskBits) - 1; - for (size_t page = 0; page < scratchpadAlloc.length; ++page) + for (size_t page = 0; page < scratchpad_alloc.length; ++page) { for (size_t window = 0; window < 8; window += maskBits) { - if ((scratchpadAlloc[page] & (mask << window)) == 0) + if ((scratchpad_alloc[page] & (mask << window)) == 0) { - scratchpadAlloc[page] |= mask << window; + scratchpad_alloc[page] |= mask << window; return scratchpad[page*MaxScratchpadSize + window*WindowSize .. page*MaxScratchpadSize + window*WindowSize + size]; } } @@ -40,7 +40,7 @@ void[] allocScratchpad(size_t size = MaxScratchpadSize) return null; } -void freeScratchpad(void[] mem) +void free_scratchpad(void[] mem) { size_t page = (cast(size_t)mem.ptr - cast(size_t)scratchpad.ptr) / MaxScratchpadSize; size_t window = (cast(size_t)mem.ptr - cast(size_t)scratchpad.ptr) % MaxScratchpadSize / WindowSize; @@ -48,8 +48,8 @@ void freeScratchpad(void[] mem) size_t maskBits = mem.length / WindowSize; size_t mask = (1 << maskBits) - 1; - assert((scratchpadAlloc[page] & (mask << window)) == (mask << window), "Freeing unallocated scratchpad memory!"); - scratchpadAlloc[page] &= ~(mask << window); + assert((scratchpad_alloc[page] & (mask << window)) == (mask << window), "Freeing unallocated scratchpad memory!"); + scratchpad_alloc[page] &= ~(mask << window); } private: @@ -57,31 +57,31 @@ private: enum WindowSize = MaxScratchpadSize / 8; __gshared ubyte[MaxScratchpadSize*NumScratchBuffers] scratchpad; -__gshared ubyte[NumScratchBuffers] scratchpadAlloc; +__gshared ubyte[NumScratchBuffers] scratchpad_alloc; unittest { - void[] t = allocScratchpad(MaxScratchpadSize); + void[] t = alloc_scratchpad(MaxScratchpadSize); assert(t.length == MaxScratchpadSize); - void[] t2 = allocScratchpad(MaxScratchpadSize / 2); + void[] t2 = alloc_scratchpad(MaxScratchpadSize / 2); assert(t2.length == MaxScratchpadSize / 2); - void[] t3 = allocScratchpad(MaxScratchpadSize / 2); + void[] t3 = alloc_scratchpad(MaxScratchpadSize / 2); assert(t3.length == MaxScratchpadSize / 2); - void[] t4 = allocScratchpad(MaxScratchpadSize / 4); + void[] t4 = alloc_scratchpad(MaxScratchpadSize / 4); assert(t4.length == MaxScratchpadSize / 4); - void[] t5 = allocScratchpad(MaxScratchpadSize / 8); + void[] t5 = alloc_scratchpad(MaxScratchpadSize / 8); assert(t5.length == MaxScratchpadSize / 8); - void[] t6 = allocScratchpad(MaxScratchpadSize / 4); + void[] t6 = alloc_scratchpad(MaxScratchpadSize / 4); assert(t6.length == MaxScratchpadSize / 4); - void[] t7 = allocScratchpad(MaxScratchpadSize / 8); + void[] t7 = alloc_scratchpad(MaxScratchpadSize / 8); assert(t7.length == MaxScratchpadSize / 8); - freeScratchpad(t); - freeScratchpad(t7); - freeScratchpad(t5); - freeScratchpad(t4); - freeScratchpad(t6); - freeScratchpad(t2); - freeScratchpad(t3); + free_scratchpad(t); + free_scratchpad(t7); + free_scratchpad(t5); + free_scratchpad(t4); + free_scratchpad(t6); + free_scratchpad(t2); + free_scratchpad(t3); } diff --git a/src/urt/mem/string.d b/src/urt/mem/string.d index a04f5e9..15ede1d 100644 --- a/src/urt/mem/string.d +++ b/src/urt/mem/string.d @@ -4,61 +4,58 @@ import urt.mem; import urt.string; -// TODO: THIS IS TEMP!! REMOVE ME!! -shared static this() -{ - initStringHeap(ushort.max); -} - - -struct StringCache -{ -} - struct CacheString { +nothrow @nogc: alias toString this; - this(typeof(null)) pure nothrow @nogc + this(typeof(null)) pure { offset = 0; } - string toString() const nothrow @nogc + string toString() const pure { - ushort len = *cast(ushort*)(stringHeap.ptr + offset); - return cast(string)stringHeap[offset + 2 .. offset + 2 + len]; + // HACK: deploy the pure hack! + static char[] pureHack() nothrow @nogc => stringHeap; + string heap = (cast(immutable(char[]) function() pure nothrow @nogc)&pureHack)(); + + ushort len = *cast(ushort*)(heap.ptr + offset); + return heap[offset + 2 .. offset + 2 + len]; } - immutable(char)* ptr() const nothrow @nogc - => cast(immutable(char)*)(stringHeap.ptr + offset + 2); + immutable(char)* ptr() const pure + => toString().ptr; + + size_t length() const pure + => toString().length; - size_t length() const nothrow @nogc - => *cast(ushort*)(stringHeap.ptr + offset); + string opIndex() const pure + => toString(); - bool opCast(T : bool)() const pure nothrow @nogc + bool opCast(T : bool)() const pure => offset != 0; - void opAssign(typeof(null)) pure nothrow @nogc + void opAssign(typeof(null)) pure { offset = 0; } - bool opEquals(const(char)[] rhs) const nothrow @nogc + bool opEquals(const(char)[] rhs) const pure { string s = toString(); - return s.length == rhs.length && (s.ptr == rhs.ptr || s[] == rhs[]); + return s.length == rhs.length && (s.ptr is rhs.ptr || s[] == rhs[]); } - size_t toHash() const nothrow @nogc + size_t toHash() const pure { import urt.hash; static if (size_t.sizeof == 4) - return fnv1aHash(cast(ubyte[])toString()); + return fnv1a(cast(ubyte[])toString()); else - return fnv1aHash64(cast(ubyte[])toString()); + return fnv1a64(cast(ubyte[])toString()); } private: @@ -69,29 +66,31 @@ private: this.offset = offset; } - auto __debugOverview() => toString; - auto __debugExpanded() => toString; - auto __debugStringView() => toString; + version (Windows) + { + auto __debugOverview() => toString; + auto __debugExpanded() => toString; + auto __debugStringView() => toString; + } } -void initStringHeap(uint stringHeapSize) nothrow +void init_string_heap(uint string_heap_size) nothrow @nogc { assert(stringHeapInitialised == false, "String heap already initialised!"); - assert(stringHeapSize <= ushort.max, "String heap too large!"); + assert(string_heap_size <= ushort.max, "String heap too large!"); - stringHeap = new char[stringHeapSize]; + stringHeap = defaultAllocator.allocArray!char(string_heap_size); // write the null string to the start - stringHeapCursor = 0; - stringHeap[stringHeapCursor++] = 0; - stringHeap[stringHeapCursor++] = 0; - numStrings = 1; + stringHeap[0..2] = 0; + stringHeapCursor = 2; stringHeapInitialised = true; } -void deinitStringHeap() nothrow +void deinit_string_heap() nothrow @nogc { + defaultAllocator.freeArray(stringHeap); } uint getStringHeapAllocated() nothrow @nogc @@ -104,44 +103,46 @@ uint getStringHeapRemaining() nothrow @nogc return cast(uint)stringHeap.length - stringHeapCursor; } -CacheString addString(const(char)[] str, bool dedup = true) nothrow @nogc +CacheString addString(const(char)[] str) pure nothrow @nogc { - // null string - if (str.length == 0) - return CacheString(0); + // HACK: even though this mutates global state, the string cache is immutable after it's emplaced + // so, multiple calls with the same source string will always return the same result! + static CacheString impl(const(char)[] str) nothrow @nogc + { + // null string + if (str.length == 0) + return CacheString(0); - assert(str.length < 2^^14, "String longer than max string len (32768 chars)"); + assert(str.length < 2^^14, "String longer than max string len (32768 chars)"); - if (dedup) - { // first we scan to see if it's already in here... - for (ushort i = 1; i < stringHeapCursor;) + for (ushort i = 2; i < stringHeapCursor;) { ushort offset = i; - ushort len = stringHeap[i++]; - if (len >= 128) - len = (len & 0x7F) | ((stringHeap[i++] << 7) & 0x7F); + ushort len = *cast(ushort*)(stringHeap.ptr + i); + i += 2; if (len == str.length && stringHeap[i .. i + len] == str[]) return CacheString(offset); - i += len; + i += len + (len & 1); } - } - if (stringHeapCursor & 1) - stringHeap[stringHeapCursor++] = '\0'; + // add the string to the heap... + assert(stringHeapCursor + str.length < stringHeap.length, "String heap overflow!"); - // add the string to the heap... - assert(stringHeapCursor + str.length < stringHeap.length, "String heap overflow!"); + char[] heap = stringHeap[stringHeapCursor .. $]; + ushort offset = stringHeapCursor; - ushort offset = stringHeapCursor; - str.makeString(stringHeap[stringHeapCursor .. $]); - stringHeapCursor += str.length + 2; - ++numStrings; + *cast(ushort*)heap.ptr = cast(ushort)str.length; + heap[2 .. 2 + str.length] = str[]; + stringHeapCursor += str.length + 2; + if (stringHeapCursor & 1) + stringHeap[stringHeapCursor++] = '\0'; - return CacheString(offset); + return CacheString(offset); + } + return (cast(CacheString function(const(char)[]) pure nothrow @nogc)&impl)(str); } - void* allocWithStringCache(size_t bytes, String[] cachedStrings, const(char[])[] strings) nothrow @nogc { import urt.mem.alloc; @@ -170,15 +171,20 @@ void* allocWithStringCache(size_t bytes, String[] cachedStrings, const(char[])[] class StringAllocator : NoGCAllocator { - override void[] alloc(size_t bytes, size_t alignment = 1) nothrow @nogc + override void[] alloc(size_t bytes, size_t alignment = 1) pure nothrow @nogc { - assert(stringHeapCursor + bytes < stringHeap.length, "String heap overflow!"); + static void[] impl(size_t bytes, size_t alignment) nothrow @nogc + { + assert(stringHeapCursor + bytes < stringHeap.length, "String heap overflow!"); - char[] heap = cast(char[])stringHeap; - return heap[stringHeapCursor .. stringHeapCursor + bytes]; + char[] heap = cast(char[])stringHeap; + return heap[stringHeapCursor .. stringHeapCursor + bytes]; + } + alias PureHack = void[] function(size_t, size_t) pure nothrow @nogc; + return (cast(PureHack)&impl)(bytes, alignment); } - override void free(void[] mem) nothrow @nogc + override void free(void[] mem) pure nothrow @nogc { // you don't free cached strings! } @@ -191,7 +197,6 @@ private: __gshared bool stringHeapInitialised = false; __gshared char[] stringHeap = null; __gshared ushort stringHeapCursor = 0; -__gshared uint numStrings = 0; unittest diff --git a/src/urt/mem/temp.d b/src/urt/mem/temp.d index 6f2d28b..6a17a5e 100644 --- a/src/urt/mem/temp.d +++ b/src/urt/mem/temp.d @@ -4,11 +4,12 @@ import urt.mem; version = DebugTempAlloc; +nothrow @nogc: enum size_t TempMemSize = 4096; -void[] talloc(size_t size) nothrow @nogc +void[] talloc(size_t size) pure { debug version (DebugTempAlloc) { @@ -16,27 +17,21 @@ void[] talloc(size_t size) nothrow @nogc assert(InFormatFunction == false, "It is illegal to use the temp allocator inside string conversion functions. Consider using stack or scratchpad."); } - if (size >= TempMemSize / 2) - { - assert(false, "Requested temp memory size is too large"); - return null; - } + assert(size <= TempMemSize / 2, "Requested temp memory size is too large"); - if (allocOffset + size > TempMemSize) - allocOffset = 0; - - void[] mem = tempMem[allocOffset .. allocOffset + size]; - allocOffset += size; - - return mem; + void[] mem = tmem_tail(); + if (mem.length < size) + mem = tmem_reset(); + tmem_advance(size); + return mem[0 .. size]; } -void[] tallocAligned(size_t size, size_t alignment) nothrow @nogc +void[] talloc_aligned(size_t size, size_t alignment) pure { assert(false); } -void[] trealloc(void[] mem, size_t newSize) nothrow @nogc +void[] trealloc(void[] mem, size_t newSize) pure { if (newSize <= mem.length) return mem[0 .. newSize]; @@ -50,85 +45,142 @@ void[] trealloc(void[] mem, size_t newSize) nothrow @nogc return r; } -void[] treallocAligned(void[] mem, size_t newSize, size_t alignment) nothrow @nogc +void[] trealloc_aligned(void[] mem, size_t newSize, size_t alignment) pure { assert(false); } -void[] texpand(void[] mem, size_t newSize) nothrow @nogc +void[] texpand(void[] mem, size_t newSize) pure { - if (mem.ptr + mem.length != tempMem.ptr + allocOffset) + void[] tmem = tmem_tail(); + if (mem.ptr + mem.length != tmem.ptr) return null; ptrdiff_t grow = newSize - mem.length; - if (cast(size_t)(allocOffset + grow) > TempMemSize) + if (grow > tmem.length) return null; - allocOffset += grow; + tmem_advance(grow); return mem.ptr[0 .. newSize]; } -void tfree(void[] mem) nothrow @nogc +void tfree(void[] mem) pure { // maybe do some debug accounting...? } -char* tstringz(const(char)[] str) nothrow @nogc +char* tstringz(const(char)[] str) pure { - if (str.length > TempMemSize / 2) - return null; - - size_t len = str.length; - if (allocOffset + len + 1 > TempMemSize) - allocOffset = 0; + char* r = cast(char*)talloc(str.length + 1).ptr; + r[0 .. str.length] = str[]; + r[str.length] = '\0'; + return r; +} +char* tstringz(const(wchar)[] str) pure +{ + import urt.string.uni : uni_convert; + char* r = cast(char*)talloc(str.length*3 + 1).ptr; + size_t len = uni_convert(str, r[0 .. str.length*3]); + r[len] = '\0'; + return r; +} - char* r = cast(char*)tempMem.ptr + allocOffset; - r[0 .. len] = str[]; +wchar* twstringz(const(char)[] str) pure +{ + import urt.string.uni : uni_convert; + wchar* r = cast(wchar*)talloc(str.length*2 + 2).ptr; + size_t len = uni_convert(str, r[0 .. str.length]); r[len] = '\0'; - allocOffset += len + 1; + return r; +} +wchar* twstringz(const(wchar)[] str) pure +{ + wchar* r = cast(wchar*)talloc(str.length*2 + 2).ptr; + r[0 .. str.length] = str[]; + r[str.length] = '\0'; return r; } -char[] tstring(T)(auto ref T value) +const(char)[] tstring(T)(auto ref T value) { - import urt.string.format : toString; - ptrdiff_t r = toString(value, cast(char[])tempMem[allocOffset..$]); - if (r < 0) + import urt.string, urt.array; + static if (is(T : const(char)[]) || is(T : const String) || is(T : const MutableString!N, size_t N) || is(T : const Array!char)) { - allocOffset = 0; - r = toString(value, cast(char[])tempMem[0..TempMemSize / 2]); + pragma(inline, true); + return value[]; + } + else + { + import urt.string.format : toString; + char[] tmem = cast(char[])tmem_tail(); + ptrdiff_t r = toString(value, tmem); if (r < 0) { -// assert(false, "Formatted string is too large for the temp buffer!"); - return null; + tmem = cast(char[])tmem_reset(); + r = toString(value, tmem); + if (r < 0) + { +// assert(false, "Formatted string is too large for the temp buffer!"); + return null; + } } + const(char)[] result = tmem[0 .. r]; + tmem_advance(r); + return result; } - char[] result = cast(char[])tempMem[allocOffset .. allocOffset + r]; - allocOffset += r; - return result; } -char[] tconcat(Args...)(ref Args args) +const(dchar)[] tdstring(T)(auto ref T value) { - import urt.string.format : concat; - char[] r = concat(cast(char[])tempMem[allocOffset..$], args); + static if (is(T : const(char)[]) || is(T : const(wchar)[]) || is(T : const(dchar)[])) + alias s = value; + else + char[] s = tstring(value); + import urt.string.uni : uni_convert; + dchar* r = cast(dchar*)talloc(s[].length*4).ptr; + size_t len = uni_convert(s[], r[0 .. s.length]); + return r[0 .. len]; +} + +const(char)[] tconcat(Args...)(ref Args args) +{ + import urt.string, urt.array; + static if (Args.length == 1 && (is(Args[0] : const(char)[]) || is(Args[0] : const String) || is(Args[0] : const MutableString!N, size_t N) || is(Args[0] : const Array!char))) + { + pragma(inline, true); + return args[0][]; + } + else + { + pragma(inline, true); + import urt.string.format : normalise_args; + return tconcat_impl(normalise_args(args)); + } +} + +import urt.meta.tuple : Tuple; +const(char)[] tconcat_impl(Args...)(Tuple!Args args) +{ + import urt.string.format : concat_impl; + const(char)[] r = concat_impl(cast(char[])tempMem[alloc_offset..$], args); if (!r) { - allocOffset = 0; - r = concat(cast(char[])tempMem[0..TempMemSize / 2], args); + alloc_offset = 0; + r = concat_impl(cast(char[])tempMem[0..TempMemSize / 2], args); } - allocOffset += r.length; + alloc_offset += r.length; return r; } + char[] tformat(Args...)(const(char)[] fmt, ref Args args) { import urt.string.format : format; - char[] r = format(cast(char[])tempMem[allocOffset..$], fmt, args); + char[] r = format(cast(char[])tempMem[alloc_offset..$], fmt, args); if (!r) { - allocOffset = 0; + alloc_offset = 0; r = format(cast(char[])tempMem[0..TempMemSize / 2], fmt, args); } - allocOffset += r.length; + alloc_offset += r.length; return r; } @@ -136,15 +188,21 @@ char[] tformat(Args...)(const(char)[] fmt, ref Args args) class TempAllocator : NoGCAllocator { static import urt.mem.alloc; +nothrow @nogc: - static TempAllocator instance() nothrow @nogc => _instance; + static TempAllocator instance() pure + { + alias PureHack = TempAllocator function() pure nothrow @nogc; + static TempAllocator hack() nothrow @nogc => _instance; + return (cast(PureHack)&hack)(); + } - override void[] alloc(size_t bytes, size_t alignment = DefaultAlign) nothrow @nogc + override void[] alloc(size_t bytes, size_t alignment = DefaultAlign) pure { return talloc(bytes); } - override void[] realloc(void[] mem, size_t newSize, size_t alignment = DefaultAlign) nothrow @nogc + override void[] realloc(void[] mem, size_t newSize, size_t alignment = DefaultAlign) pure { return trealloc(mem, newSize); // TODO... @@ -152,12 +210,12 @@ class TempAllocator : NoGCAllocator return null; } - override void[] expand(void[] mem, size_t newSize) nothrow + override void[] expand(void[] mem, size_t newSize) pure { return texpand(mem, newSize); } - override void free(void[] mem) nothrow @nogc + override void free(void[] mem) pure { tfree(mem); } @@ -170,4 +228,33 @@ private: private: static void[TempMemSize] tempMem; -static ushort allocOffset = 0; +static ushort alloc_offset = 0; + +void[] tmem_tail() pure +{ + static void[] impl() nothrow @nogc + => tempMem[alloc_offset..$]; + alias PureHack = void[] function() pure nothrow @nogc; + return (cast(PureHack)&impl)(); +} + +void[] tmem_reset() pure +{ + static void[] impl() nothrow @nogc + { + alloc_offset = 0; + return tempMem[0..TempMemSize / 2]; + } + alias PureHack = void[] function() pure nothrow @nogc; + return (cast(PureHack)&impl)(); +} + +void tmem_advance(size_t n) pure +{ + static void impl(ushort n) nothrow @nogc + { + alloc_offset += n; + } + alias PureHack = void function(ushort) pure nothrow @nogc; + return (cast(PureHack)&impl)(cast(ushort)n); +} diff --git a/src/urt/mem/tracking.d b/src/urt/mem/tracking.d new file mode 100644 index 0000000..cbb679d --- /dev/null +++ b/src/urt/mem/tracking.d @@ -0,0 +1,413 @@ +/** + * Allocation tracking for leak detection. + * + * Enabled with `version = AllocTracking;`. When enabled, the central + * allocator (`urt.mem.alloc`) calls into this module on every + * alloc/realloc/free to record the caller's stack trace in a fixed-size + * open-addressed hash table keyed on pointer. + * + * The table uses static `__gshared` storage (no allocation of its own, + * which would recurse) and is NOT thread-safe. + * + * Typical workflow: + * 1. Boot the system, let all startup/immortal allocations settle. + * 2. Call `alloc_mark_baseline()` (via the console command + * `/system/alloc/mark`). + * 3. After running for a while, call `alloc_print_leaks(min_age, sink)` + * to see allocations made after the baseline that have been alive + * for at least `min_age`, grouped by call site. + */ +module urt.mem.tracking; + +version (AllocTracking): + +import urt.time : MonoTime, getTime, Duration; +import urt.internal.exception : capture_trace, resolve_address, resolve_batch, Resolved; + +nothrow @nogc: + + +// Tuning + +// Number of stack frames captured per allocation. +enum trace_depth = 6; + +// Maximum live allocations tracked. +// ~80 bytes per entry on 64-bit: 16384 * 80 = 1.25 MiB. +enum track_capacity = 16384; +static assert((track_capacity & (track_capacity - 1)) == 0, "track_capacity must be a power of two"); + +// Load factor ceiling (num/den). Linear probing degrades past ~70%. +enum track_max_load_num = 7; +enum track_max_load_den = 10; + +// Max unique call sites shown in a grouped leak dump. +enum max_groups = 256; + + +// Entry / storage + +struct Entry +{ + void* ptr; // null = empty, (void*)1 = tombstone + uint size; + uint serial; + MonoTime time; + void*[trace_depth] pcs; +} + +__gshared Entry[track_capacity] _table; +__gshared uint _live_count; +__gshared uint _serial_counter; +__gshared uint _baseline_serial; +__gshared ulong _tracked_bytes; +__gshared bool _table_full_warned; + + +// Tracking API -- called by urt.mem.alloc hooks + +void track_alloc(void* ptr, size_t size) +{ + if (ptr is null) + return; + + // Refuse inserts past the load factor -- linear probing gets pathological. + if (_live_count * track_max_load_den >= track_capacity * track_max_load_num) + { + warn_table_full(); + return; + } + + size_t slot = find_insert(ptr); + if (slot == size_t.max) + { + warn_table_full(); + return; + } + + bool was_live = _table[slot].ptr !is null && _table[slot].ptr !is tombstone; + if (was_live) + _tracked_bytes -= _table[slot].size; + else + _live_count++; + + _table[slot].ptr = ptr; + _table[slot].size = cast(uint) size; + _table[slot].serial = ++_serial_counter; + _table[slot].time = getTime(); + _table[slot].pcs[] = null; + capture_trace(_table[slot].pcs[]); + + _tracked_bytes += size; +} + +void untrack_alloc(void* ptr) +{ + if (ptr is null) + return; + size_t slot = find(ptr); + if (slot == size_t.max) + return; + _tracked_bytes -= _table[slot].size; + _table[slot].ptr = tombstone; + _table[slot].size = 0; + _live_count--; +} + +void track_realloc(void* old_ptr, void* new_ptr, size_t new_size) +{ + if (old_ptr is new_ptr) + { + if (new_ptr is null) + return; + size_t slot = find(new_ptr); + if (slot != size_t.max) + { + _tracked_bytes = _tracked_bytes - _table[slot].size + new_size; + _table[slot].size = cast(uint) new_size; + // Keep original serial/time/trace -- the allocation's identity hasn't changed. + } + else + track_alloc(new_ptr, new_size); + return; + } + untrack_alloc(old_ptr); + track_alloc(new_ptr, new_size); +} + + +// Baseline / stats + +void alloc_mark_baseline() +{ + _baseline_serial = _serial_counter; +} + +uint alloc_baseline() => _baseline_serial; + +void alloc_stats(out uint live_count, out ulong live_bytes, out uint capacity, out uint total_serial) +{ + live_count = _live_count; + live_bytes = _tracked_bytes; + capacity = track_capacity; + total_serial = _serial_counter; +} + + +// Report helpers + +// Return the first frame in `pcs` whose resolved symbol is NOT a known +// allocator wrapper. Falls back to the last non-null frame if every +// frame resolves to a wrapper. +void* top_user_pc(const(void*)[] pcs) +{ + void* fallback = null; + foreach (addr; pcs) + { + if (addr is null) + continue; + fallback = cast(void*) addr; + + Resolved r; + if (!resolve_address(cast(void*) addr, r)) + return cast(void*) addr; // unresolved -> assume user code + if (!is_wrapper_name(r.name)) + return cast(void*) addr; + } + return fallback; +} + +alias Sink = void delegate(const(char)[]) nothrow @nogc; + +// Sink-based stats dump. One sink call per line, no trailing newline. +void alloc_print_stats(scope Sink sink) +{ + import urt.mem.temp : tformat; + + sink(tformat("Live allocations: {0}", _live_count)); + sink(tformat("Live bytes: {0}", _tracked_bytes)); + sink(tformat("Table capacity: {0}", cast(uint) track_capacity)); + sink(tformat("Total allocs: {0} (serial)", _serial_counter)); + sink(tformat("Baseline: {0}", _baseline_serial)); + if (_table_full_warned) + sink("WARNING: tracking table has overflowed at least once -- some allocations untracked"); +} + +// Grouped leak dump. Shows allocations with `serial > baseline` that +// have been alive at least `min_age`, grouped by top-user PC. +void alloc_print_leaks(Duration min_age, scope Sink sink) +{ + print_candidates(_baseline_serial, min_age, "Leak candidates", sink); +} + +// Grouped dump of every currently-live allocation, ignoring baseline +// and age. Intended for shutdown/exit leak reports -- at shutdown +// anything still alive is effectively a leak. +void alloc_print_live(scope Sink sink) +{ + print_candidates(0, Duration.init, "Live allocations", sink); +} + +private void print_candidates(uint min_serial, Duration min_age, string kind, scope Sink sink) +{ + import urt.mem.temp : tformat; + + struct Site + { + void* pc; + uint count; + ulong bytes; + MonoTime oldest; + } + + Site[max_groups] groups = void; + size_t ngroups = 0; + Site overflow; + overflow.pc = null; + overflow.count = 0; + overflow.bytes = 0; + + MonoTime now = getTime(); + + uint total_count = 0; + ulong total_bytes = 0; + + foreach (ref e; _table) + { + if (e.ptr is null || e.ptr is tombstone) + continue; + if (e.serial <= min_serial) + continue; + if (now - e.time < min_age) + continue; + + void* top = top_user_pc(e.pcs[]); + + size_t g = size_t.max; + foreach (i; 0 .. ngroups) + if (groups[i].pc is top) { g = i; break; } + + Site* s; + if (g == size_t.max) + { + if (ngroups < max_groups) + { + groups[ngroups].pc = top; + groups[ngroups].count = 0; + groups[ngroups].bytes = 0; + groups[ngroups].oldest = e.time; + s = &groups[ngroups]; + ngroups++; + } + else + s = &overflow; + } + else + s = &groups[g]; + + s.count++; + s.bytes += e.size; + if (e.time < s.oldest || s.oldest == MonoTime()) + s.oldest = e.time; + + total_count++; + total_bytes += e.size; + } + + if (total_count == 0) + { + sink(tformat("{0}: none (min serial {1}, min age {2})", kind, min_serial, min_age)); + return; + } + + // Sort by bytes descending -- insertion sort, ngroups is small. + foreach (i; 1 .. ngroups) + { + Site tmp = groups[i]; + size_t j = i; + while (j > 0 && groups[j - 1].bytes < tmp.bytes) + { + groups[j] = groups[j - 1]; + --j; + } + groups[j] = tmp; + } + + sink(tformat("{0}: {1} allocations, {2} bytes, {3} unique sites", + kind, total_count, total_bytes, + cast(uint) ngroups + (overflow.count > 0 ? 1 : 0))); + sink(""); + + // One batched resolve for all unique sites - on POSIX this folds + // what was N full DWARF .debug_line scans into a single scan. + void*[max_groups] addrs = void; + Resolved[max_groups] resolved; + foreach (i; 0 .. ngroups) + addrs[i] = groups[i].pc; + resolve_batch(addrs[0 .. ngroups], resolved[0 .. ngroups]); + + foreach (i; 0 .. ngroups) + { + Site s = groups[i]; + Duration age = now - s.oldest; + const r = &resolved[i]; + + sink(tformat(" {0} allocs, {1} bytes, oldest {2}", s.count, s.bytes, age)); + if (r.file.length > 0 && r.line > 0) + sink(tformat(" {0}({1}): {2}", r.file, r.line, r.name)); + else if (r.name.length > 0) + sink(tformat(" {0}", r.name)); + else + sink(tformat(" 0x{0:016x}", cast(size_t) s.pc)); + } + + if (overflow.count > 0) + { + Duration age = now - overflow.oldest; + sink(tformat(" [overflow] {0} allocs, {1} bytes, oldest {2} (from > {3} unique sites)", + overflow.count, overflow.bytes, age, cast(uint) max_groups)); + } +} + + +private: + +enum void* tombstone = cast(void*) 1; + +// Prefixes of symbol names considered "allocator plumbing" and skipped +// when computing the top user frame. Extend as needed. +immutable string[] WRAPPER_PREFIXES = [ + "urt.mem.", + "urt.internal.exception.", // capture_trace itself lives here + "_d_new", + "_d_alloc", + "_d_array", + "_D3urt3mem", +]; + +bool is_wrapper_name(const(char)[] name) +{ + foreach (p; WRAPPER_PREFIXES) + { + if (name.length >= p.length && name[0 .. p.length] == p) + return true; + } + return false; +} + +void warn_table_full() +{ + if (_table_full_warned) + return; + _table_full_warned = true; + + import urt.io : writeln_err; + writeln_err("urt.mem.tracking: allocation table at capacity -- subsequent allocs untracked! Increase track_capacity in urt.mem.tracking."); +} + + +// Hash table primitives + +size_t hash_ptr(void* p) pure +{ + size_t x = cast(size_t) p >> 3; + return cast(size_t)(cast(ulong) x * 0x9E3779B97F4A7C15UL); +} + +size_t find(void* ptr) +{ + enum mask = cast(size_t)(track_capacity - 1); + size_t i = hash_ptr(ptr) & mask; + foreach (_; 0 .. track_capacity) + { + void* p = _table[i].ptr; + if (p is null) + return size_t.max; + if (p is ptr) + return i; + i = (i + 1) & mask; + } + return size_t.max; +} + +size_t find_insert(void* ptr) +{ + enum mask = cast(size_t)(track_capacity - 1); + size_t i = hash_ptr(ptr) & mask; + size_t first_tomb = size_t.max; + foreach (_; 0 .. track_capacity) + { + void* p = _table[i].ptr; + if (p is null) + return first_tomb != size_t.max ? first_tomb : i; + if (p is tombstone) + { + if (first_tomb == size_t.max) + first_tomb = i; + } + else if (p is ptr) + return i; + i = (i + 1) & mask; + } + return first_tomb; +} diff --git a/src/urt/meta/enuminfo.d b/src/urt/meta/enuminfo.d new file mode 100644 index 0000000..1a1ef5e --- /dev/null +++ b/src/urt/meta/enuminfo.d @@ -0,0 +1,394 @@ +module urt.meta.enuminfo; + +import urt.algorithm : binary_search, qsort; +import urt.traits :EnumType, is_enum, Unqual; +import urt.meta : Iota, STATIC_MAP; +import urt.variant; + +nothrow @nogc: + + +const(E)* enum_from_key(E)(const(char)[] key) pure + if (is_enum!E) + => enum_info!E.value_for(key); + +const(char)[] enum_key_from_value(E)(EnumType!E value) pure + if (is_enum!E) + => enum_info!E.key_for(value); + +const(char)[] enum_key_by_decl_index(E)(size_t value) pure + if (is_enum!E) + => enum_info!E.key_by_decl_index(value); + +struct VoidEnumInfo +{ + import urt.algorithm : binary_search; +nothrow @nogc: + + // keys and values are sorted for binary search + ushort count; + ushort stride; + uint type_hash; + + const(char)[] key_for(const void* value, int function(const void* a, const void* b) pure nothrow @nogc pred) const pure + { + size_t i = binary_search(_values[0 .. count*stride], stride, value, pred); + if (i < count) + return get_key(_lookup_tables[count + i]); + return null; + } + + const(char)[] key_for(const void* value, int delegate(const void* a, const void* b) pure nothrow @nogc pred) const pure + { + size_t i = binary_search(_values[0 .. count*stride], stride, value, pred); + if (i < count) + return get_key(_lookup_tables[count + i]); + return null; + } + + const(char)[] key_by_decl_index(size_t i) const pure + { + assert(i < count, "Declaration index out of range"); + return get_key(_lookup_tables[count*2 + i]); + } + + const(char)[] key_by_sorted_index(size_t i) const pure + { + assert(i < count, "Declaration index out of range"); + return get_key(i); + } + + Variant value_for(const(char)[] key) const pure + { + size_t i = binary_search!key_compare(_keys[0 .. count], key, _string_buffer); + if (i == count) + return Variant(); + i = _lookup_tables[i]; + return _get_value(_values + i*stride, &this); + } + + bool contains(const(char)[] key) const pure + { + size_t i = binary_search!key_compare(_keys[0 .. count], key, _string_buffer); + return i < count; + } + +private: + const void* _values; + const ushort* _keys; + const char* _string_buffer; + + // these tables map between indices of keys and values + const ubyte* _lookup_tables; + + const GetFun _get_value; + + this(ubyte count, ushort stride, uint type_hash, inout void* values, inout ushort* keys, inout char* strings, inout ubyte* lookup, GetFun get_value) inout pure + { + this.count = count; + this.stride = stride; + this.type_hash = type_hash; + this._keys = keys; + this._values = values; + this._string_buffer = strings; + this._lookup_tables = lookup; + this._get_value = get_value; + } + + const(char)[] get_key(size_t i) const pure + { + const(char)* s = _string_buffer + _keys[i]; + return s[0 .. s.key_length]; + } +} + +template EnumInfo(E) +{ + static assert (is(E == Unqual!E), "EnumInfo can only be instantiated with unqualified types!"); + + static if (is(E == void)) + alias EnumInfo = VoidEnumInfo; + else + { + struct EnumInfo + { + import urt.algorithm : binary_search; + nothrow @nogc: + + static assert (EnumInfo.sizeof == EnumInfo.sizeof, "Template EnumInfo must not add any members!"); + + static if (is(E T == enum)) + alias V = T; + else + static assert(false, E.string ~ " is not an enum type!"); + + // keys and values are sorted for binary search + union { + VoidEnumInfo _base; + struct { + ubyte[VoidEnumInfo._values.offsetof] _pad; + const E* _values; // shadows the _values in _base with a typed version + } + } + alias _base this; + + inout(VoidEnumInfo*) make_void() inout pure + => &_base; + + this(ubyte count, uint type_hash, inout(E)* values, inout ushort* keys, inout char* strings, inout ubyte* lookup) inout pure + { + _base = inout(VoidEnumInfo)(count, E.sizeof, type_hash, values, keys, strings, lookup, cast(GetFun)&get_value!V); + } + + const(E)[] values() const pure + => _values[0 .. count]; + + const(char)[] key_for(V value) const pure + { + size_t i = binary_search(values[0 .. count], value); + if (i < count) + return get_key(_lookup_tables[count + i]); + return null; + } + + const(char)[] key_by_decl_index(size_t i) const pure + => _base.key_by_decl_index(i); + + const(char)[] key_by_sorted_index(size_t i) const pure + => _base.key_by_sorted_index(i); + + const(E)* value_for(const(char)[] key) const pure + { + size_t i = binary_search!key_compare(_keys[0 .. count], key, _string_buffer); + if (i == count) + return null; + return _values + _lookup_tables[i]; + } + + bool contains(const(char)[] key) const pure + => _base.contains(key); + } + } +} + +template enum_info(E) + if (is(E == enum)) +{ + static assert (is(E == Unqual!E), "EnumInfo can only be instantiated with unqualified types!"); + + enum ubyte num_items = enum_members.length; + static assert(num_items <= ubyte.max, "Too many enum items!"); + + __gshared immutable enum_info = immutable(EnumInfo!E)( + num_items, + fnv1a(cast(ubyte[])E.stringof), + _values.ptr, + _keys.ptr, + _strings.ptr, + _lookup.ptr + ); + +private: + import urt.algorithm : binary_search, compare, qsort; + import urt.hash : fnv1a; + import urt.string.uni : uni_compare; + + // keys and values are sorted for binary search + __gshared immutable E[num_items] _values = [ STATIC_MAP!(GetValue, iota) ]; + + // keys are stored as offsets info the string buffer + __gshared immutable ushort[num_items] _keys = () { + ushort[num_items] key_offsets; + size_t offset = 2; + foreach (i; 0 .. num_items) + { + const(char)[] key = by_key[i].k; + key_offsets[i] = cast(ushort)offset; + offset += 2 + key.length; + if (key.length & 1) + offset += 1; // align to 2 bytes + } + return key_offsets; + }(); + + // build the string buffer + __gshared immutable char[total_strings] _strings = () { + char[total_strings] str_data; + char* ptr = str_data.ptr; + foreach (i; 0 .. num_items) + { + const(char)[] key = by_key[i].k; + version (LittleEndian) + { + *ptr++ = key.length & 0xFF; + *ptr++ = (key.length >> 8) & 0xFF; + } + else + { + *ptr++ = (key.length >> 8) & 0xFF; + *ptr++ = key.length & 0xFF; + } + ptr[0 .. key.length] = key[]; + ptr += key.length; + if (key.length & 1) + *ptr++ = 0; // align to 2 bytes + } + return str_data; + }(); + + // these tables map between indices of keys and values + __gshared immutable ubyte[num_items * 3] _lookup = [ STATIC_MAP!(GetKeyRedirect, iota), + STATIC_MAP!(GetValRedirect, iota), + STATIC_MAP!(GetKeyOrig, iota) ]; + + // a whole bunch of nonsense to build the tables... + struct KI + { + string k; + ubyte i; + } + struct VI + { + E v; + ubyte i; + } + + alias iota = Iota!(enum_members.length); + enum enum_members = __traits(allMembers, E); + enum by_key = (){ KI[num_items] r = [ STATIC_MAP!(MakeKI, iota) ]; r.qsort!((ref a, ref b) => uni_compare(a.k, b.k)); return r; }(); + enum by_value = (){ VI[num_items] r = [ STATIC_MAP!(MakeVI, iota) ]; r.qsort!((ref a, ref b) => compare(a.v, b.v)); return r; }(); + enum inv_key = (){ KI[num_items] bk = by_key; ubyte[num_items] r; foreach (ubyte i, ref ki; bk) r[ki.i] = i; return r; }(); + enum inv_val = (){ VI[num_items] bv = by_value; ubyte[num_items] r; foreach (ubyte i, ref vi; bv) r[vi.i] = i; return r; }(); + + // calculate the total size of the string buffer + enum total_strings = () { + size_t total = 0; + static foreach (k; enum_members) + total += 2 + k.length + (k.length & 1); + return total; + }(); + + enum MakeKI(ushort i) = KI(trim_key!(enum_members[i]), i); + enum MakeVI(ushort i) = VI(__traits(getMember, E, enum_members[i]), i); + enum GetValue(size_t i) = by_value[i].v; + enum GetKeyRedirect(size_t i) = inv_val[by_key[i].i]; + enum GetValRedirect(size_t i) = inv_key[by_value[i].i]; + enum GetKeyOrig(size_t i) = inv_key[i]; +} + +VoidEnumInfo* make_enum_info(T)(const(char)[] name, const(char)[][] keys, T[] values) +{ + import urt.algorithm; + import urt.hash : fnv1a; + import urt.mem.allocator; + import urt.string; + import urt.string.uni; + import urt.util; + + assert(keys.length == values.length, "keys and values must have the same length"); + assert(keys.length <= ubyte.max, "Too many enum items!"); + + size_t count = keys.length; + + struct VI(T) + { + T v; + ubyte i; + } + + // first we'll sort the keys and values for binary searching + // we need to associate their original indices for the lookup tables + auto ksort = tempAllocator().allocArray!(VI!(const(char)[]))(count); + auto vsort = tempAllocator().allocArray!(VI!T)(count); + foreach (i; 0 .. count) + { + ksort[i] = VI!(const(char)[])(keys[i], cast(ubyte)i); + vsort[i] = VI!T(values[i], cast(ubyte)i); + } + ksort.qsort!((ref a, ref b) => uni_compare(a.v, b.v)); + vsort.qsort!((ref a, ref b) => compare(a.v, b.v)); + + // build the reverse lookup tables + ubyte[] inv_k = tempAllocator().allocArray!ubyte(count); + ubyte[] inv_v = tempAllocator().allocArray!ubyte(count); + foreach (i, ref ki; ksort) + inv_k[ki.i] = cast(ubyte)i; + foreach (i, ref vi; vsort) + inv_v[vi.i] = cast(ubyte)i; + + // count the string memory + size_t total_string; + foreach (i; 0 .. count) + total_string += 2 + keys[i].length + (keys[i].length & 1); + + // calculate the total size + size_t total_size = VoidEnumInfo.sizeof + T.sizeof*count; + total_size += (total_size & 1) + ushort.sizeof*count + count*3; + total_size += (total_size & 1) + total_string; + + // allocate a buffer and assign all the sub-buffers + void[] info = defaultAllocator().alloc(total_size); + VoidEnumInfo* result = cast(VoidEnumInfo*)info.ptr; + T* value_ptr = cast(T*)&result[1]; + char* str_data = cast(char*)&value_ptr[count]; + if (cast(size_t)str_data & 1) + *str_data++ = 0; // align to 2 bytes + ushort* key_ptr = cast(ushort*)str_data; + ubyte* lookup = cast(ubyte*)&key_ptr[count]; + str_data = cast(char*)&lookup[count*3]; + if (cast(size_t)str_data & 1) + *str_data++ = 0; // align to 2 bytes + char* str_ptr = str_data + 2; + + // populate the enum info data + foreach (i; 0 .. count) + { + value_ptr[i] = vsort[i].v; + + // write the string data and store the key offset + const(char)[] key = ksort[i].v; + key_ptr[i] = cast(ushort)(str_ptr - str_data); + writeString(str_ptr, key); + if (key.length & 1) + (str_ptr++)[key.length] = 0; // align to 2 bytes + str_ptr += 2 + key.length; + + lookup[i] = inv_v[ksort[i].i]; + lookup[count + i] = inv_k[vsort[i].i]; + lookup[count*2 + i] = inv_k[i]; + } + + // build and return the object + return new(*result) VoidEnumInfo(cast(ubyte)keys.length, cast(ushort)T.sizeof, fnv1a(cast(ubyte[])name), value_ptr, key_ptr, str_data, lookup, cast(GetFun)&get_value!T); +} + + +private: + +alias GetFun = Variant function(const(void)*, const(VoidEnumInfo)*) pure; + +Variant get_value(T)(const(void)* ptr, const(VoidEnumInfo)* info) + => Variant(*cast(T*)ptr, info); + +import urt.string : trim; +enum trim_key(string key) = key.trim!(c => c == '_'); + +ushort key_length(const(char)* key) pure +{ + if (__ctfe) + { + version (LittleEndian) + return key[-2] | cast(ushort)(key[-1] << 8); + else + return key[-1] | cast(ushort)(key[-2] << 8); + } + else + return *cast(ushort*)(key - 2); +} + +int key_compare(ushort a, const(char)[] b, const(char)* strings) pure +{ + import urt.string.uni : uni_compare; + const(char)* s = strings + a; + return uni_compare(s[0 .. s.key_length], b); +} diff --git a/src/urt/meta/nullable.d b/src/urt/meta/nullable.d index 3d981cc..98f29fd 100644 --- a/src/urt/meta/nullable.d +++ b/src/urt/meta/nullable.d @@ -9,8 +9,8 @@ template Nullable(T) { struct Nullable { - enum T NullValue = null; - T value = NullValue; + enum T null_value = null; + T value = null_value; this(T v) { @@ -18,7 +18,7 @@ template Nullable(T) } bool opCast(T : bool)() const - => value !is NullValue; + => value !is null_value; bool opEquals(typeof(null)) const => value is null; @@ -42,16 +42,16 @@ template Nullable(T) } template Nullable(T) - if (isBoolean!T) + if (is_boolean!T) { struct Nullable { - enum ubyte NullValue = 0xFF; - private ubyte _value = NullValue; + enum ubyte null_value = 0xFF; + private ubyte _value = null_value; this(typeof(null)) { - _value = NullValue; + _value = null_value; } this(T v) { @@ -62,27 +62,27 @@ template Nullable(T) => _value == 1; bool opCast(T : bool)() const - => _value != NullValue; + => _value != null_value; bool opEquals(typeof(null)) const - => _value == NullValue; + => _value == null_value; bool opEquals(T v) const => _value == cast(ubyte)v; void opAssign(typeof(null)) { - _value = NullValue; + _value = null_value; } void opAssign(U)(U v) if (is(U : T)) { - assert(v != NullValue); + assert(v != null_value); _value = cast(ubyte)v; } ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const nothrow @nogc { - if (value == NullValue) + if (value == null_value) return formatValue(null, buffer, format, formatArgs); else return formatValue(value, buffer, format, formatArgs); @@ -91,16 +91,16 @@ template Nullable(T) } template Nullable(T) - if (isSomeInt!T) + if (is_some_int!T) { struct Nullable { - enum T NullValue = isSignedInt!T ? T.min : T.max; - T value = NullValue; + enum T null_value = is_signed_int!T ? T.min : T.max; + T value = null_value; this(typeof(null)) { - value = NullValue; + value = null_value; } this(T v) { @@ -108,27 +108,27 @@ template Nullable(T) } bool opCast(T : bool)() const - => value != NullValue; + => value != null_value; bool opEquals(typeof(null)) const - => value == NullValue; + => value == null_value; bool opEquals(T v) const - => value != NullValue && value == v; + => value != null_value && value == v; void opAssign(typeof(null)) { - value = NullValue; + value = null_value; } void opAssign(U)(U v) if (is(U : T)) { - assert(v != NullValue); + assert(v != null_value); value = v; } ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const nothrow @nogc { - if (value == NullValue) + if (value == null_value) return formatValue(null, buffer, format, formatArgs); else return formatValue(value, buffer, format, formatArgs); @@ -137,16 +137,16 @@ template Nullable(T) } template Nullable(T) - if (isSomeFloat!T) + if (is_some_float!T) { struct Nullable { - enum T NullValue = T.nan; - T value = NullValue; + enum T null_value = T.nan; + T value = null_value; this(typeof(null)) { - value = NullValue; + value = null_value; } this(T v) { @@ -154,16 +154,16 @@ template Nullable(T) } bool opCast(T : bool)() const - => value !is NullValue; + => value !is null_value; bool opEquals(typeof(null)) const - => value is NullValue; + => value is null_value; bool opEquals(T v) const => value == v; // because nan doesn't compare with anything void opAssign(typeof(null)) { - value = NullValue; + value = null_value; } void opAssign(U)(U v) if (is(U : T)) @@ -173,7 +173,7 @@ template Nullable(T) ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const nothrow @nogc { - if (value is NullValue) + if (value is null_value) return formatValue(null, buffer, format, formatArgs); else return formatValue(value, buffer, format, formatArgs); @@ -189,42 +189,42 @@ template Nullable(T) struct Nullable { T value = void; - bool isValue = false; + bool is_value = false; this(typeof(null)) { - isValue = false; + is_value = false; } this(T v) { moveEmplace(v, value); - isValue = true; + is_value = true; } ~this() { - if (isValue) + if (is_value) value.destroy(); } bool opCast(T : bool)() const - => isValue; + => is_value; bool opEquals(typeof(null)) const - => !isValue; + => !is_value; bool opEquals(T v) const - => isValue && value == v; + => is_value && value == v; void opAssign(typeof(null)) { - if (isValue) + if (is_value) value.destroy(); - isValue = false; + is_value = false; } void opAssign(U)(U v) if (is(U : T)) { - if (!isValue) + if (!is_value) moveEmplace(v, value); else value = v; @@ -232,7 +232,7 @@ template Nullable(T) ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const nothrow @nogc { - if (!isValue) + if (!is_value) return formatValue(null, buffer, format, formatArgs); else return formatValue(value, buffer, format, formatArgs); @@ -246,39 +246,39 @@ template Nullable(T) struct Nullable { T value; - bool isValue; + bool is_value; this(typeof(null)) { - isValue = false; + is_value = false; } this(T v) { value = v; - isValue = true; + is_value = true; } bool opCast(T : bool)() const - => isValue; + => is_value; bool opEquals(typeof(null)) const - => !isValue; + => !is_value; bool opEquals(T v) const - => isValue && value == v; + => is_value && value == v; void opAssign(typeof(null)) { - isValue = false; + is_value = false; } void opAssign(T v) { value = v; - isValue = true; + is_value = true; } ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const nothrow @nogc { - if (!isValue) + if (!is_value) return formatValue(null, buffer, format, formatArgs); else return formatValue(value, buffer, format, formatArgs); diff --git a/src/urt/meta/package.d b/src/urt/meta/package.d index faa2941..62bf1a2 100644 --- a/src/urt/meta/package.d +++ b/src/urt/meta/package.d @@ -1,90 +1,160 @@ module urt.meta; +import urt.traits : is_callable, is_enum, EnumType, Unqual; + +nothrow @nogc: alias Alias(alias a) = a; alias Alias(T) = T; alias AliasSeq(TList...) = TList; -template intForWidth(size_t width, bool signed = false) +template Iota(ptrdiff_t start, ptrdiff_t end) +{ + static assert(start <= end, "start must be less than or equal to end"); + alias Iota = AliasSeq!(); + static foreach (i; start .. end) + Iota = AliasSeq!(Iota, i); +} +alias Iota(size_t end) = Iota!(0, end); + +auto make_delegate(Fun)(Fun fun) pure + if (is_callable!Fun) +{ + import urt.traits : is_function_pointer, ReturnType, Parameters; + + static if (is_function_pointer!fun) + { + struct Hack + { + static if (is(Fun : R function(Args) pure nothrow @nogc, R, Args...)) + ReturnType!Fun call(Parameters!Fun args) pure nothrow @nogc => (cast(ReturnType!Fun function(Parameters!Fun args) pure nothrow @nogc)&this)(args); + else static if (is(Fun : R function(Args) nothrow @nogc, R, Args...)) + ReturnType!Fun call(Parameters!Fun args) nothrow @nogc => (cast(ReturnType!Fun function(Parameters!Fun args) nothrow @nogc)&this)(args); + else static if (is(Fun : R function(Args) pure @nogc, R, Args...)) + ReturnType!Fun call(Parameters!Fun args) pure @nogc => (cast(ReturnType!Fun function(Parameters!Fun args) pure @nogc)&this)(args); + else static if (is(Fun : R function(Args) pure nothrow, R, Args...)) + ReturnType!Fun call(Parameters!Fun args) pure nothrow => (cast(ReturnType!Fun function(Parameters!Fun args) pure nothrow)&this)(args); + else static if (is(Fun : R function(Args) pure, R, Args...)) + ReturnType!Fun call(Parameters!Fun args) pure => (cast(ReturnType!Fun function(Parameters!Fun args) pure)&this)(args); + else static if (is(Fun : R function(Args) nothrow, R, Args...)) + ReturnType!Fun call(Parameters!Fun args) nothrow => (cast(ReturnType!Fun function(Parameters!Fun args) nothrow)&this)(args); + else static if (is(Fun : R function(Args) @nogc, R, Args...)) + ReturnType!Fun call(Parameters!Fun args) @nogc => (cast(ReturnType!Fun function(Parameters!Fun args) @nogc)&this)(args); + else static if (is(Fun : R function(Args), R, Args...)) + ReturnType!Fun call(Parameters!Fun args) => (cast(ReturnType!Fun function(Parameters!Fun args))&this)(args); + } + Hack hack; + auto dg = &hack.call; + dg.ptr = fun; + return dg; + } + else static if (is(Fun == delegate)) + return fun; + else + static assert(false, "Unsupported type for make_delegate"); +} + +ulong bit_mask(size_t bits) pure +{ + return (1UL << bits) - 1; +} + +template bit_mask(size_t bits, bool signed = false) { - static if (width <= 8 && !signed) - alias intForWidth = ubyte; - else static if (width <= 8 && signed) - alias intForWidth = byte; - else static if (width <= 16 && !signed) - alias intForWidth = ushort; - else static if (width <= 16 && signed) - alias intForWidth = short; - else static if (width <= 32 && !signed) - alias intForWidth = uint; - else static if (width <= 32 && signed) - alias intForWidth = int; - else static if (width <= 64 && !signed) - alias intForWidth = ulong; - else static if (width <= 64 && signed) - alias intForWidth = long; + static assert(bits <= 64, "bit_mask only supports up to 64 bits"); + static if (bits == 64) + enum IntForWidth!(64, signed) bit_mask = ~0UL; + else + enum IntForWidth!(bits, signed) bit_mask = (1UL << bits) - 1; } -template staticMap(alias fun, args...) +template IntForWidth(size_t bits, bool signed = false) { - alias staticMap = AliasSeq!(); + static if (bits <= 8 && !signed) + alias IntForWidth = ubyte; + else static if (bits <= 8 && signed) + alias IntForWidth = byte; + else static if (bits <= 16 && !signed) + alias IntForWidth = ushort; + else static if (bits <= 16 && signed) + alias IntForWidth = short; + else static if (bits <= 32 && !signed) + alias IntForWidth = uint; + else static if (bits <= 32 && signed) + alias IntForWidth = int; + else static if (bits <= 64 && !signed) + alias IntForWidth = ulong; + else static if (bits <= 64 && signed) + alias IntForWidth = long; +} + +alias TypeForOp(string op, U) = typeof(mixin(op ~ "U()")); +alias TypeForOp(string op, A, B) = typeof(mixin("A()" ~ op ~ "B()")); + +template STATIC_MAP(alias fun, args...) +{ + alias STATIC_MAP = AliasSeq!(); static foreach (arg; args) - staticMap = AliasSeq!(staticMap, fun!arg); + STATIC_MAP = AliasSeq!(STATIC_MAP, fun!arg); +} + +template STATIC_UNROLL(alias array) +{ + static if (is(typeof(array) : T[], T)) + { + alias STATIC_UNROLL = AliasSeq!(); + static foreach (i; 0 .. array.length) + STATIC_UNROLL = AliasSeq!(STATIC_UNROLL, array[i]); + } + else + static assert(false, "STATIC_UNROLL requires an array"); } -template staticIndexOf(args...) +template STATIC_FILTER(alias filter, args...) +{ + alias STATIC_FILTER = AliasSeq!(); + static foreach (arg; args) + static if (filter!arg) + STATIC_FILTER = AliasSeq!(STATIC_FILTER, arg); +} + +template static_index_of(args...) if (args.length >= 1) { - enum staticIndexOf = { + enum static_index_of = { static foreach (idx, arg; args[1 .. $]) - static if (isSame!(args[0], arg)) + static if (is_same!(args[0], arg)) // `if (__ctfe)` is redundant here but avoids the "Unreachable code" warning. if (__ctfe) return idx; return -1; }(); } -template InterleaveSeparator(alias sep, Args...) +template INTERLEAVE_SEPARATOR(alias sep, Args...) { - alias InterleaveSeparator = AliasSeq!(); + alias INTERLEAVE_SEPARATOR = AliasSeq!(); static foreach (i, A; Args) static if (i > 0) - InterleaveSeparator = AliasSeq!(InterleaveSeparator, sep, A); + INTERLEAVE_SEPARATOR = AliasSeq!(INTERLEAVE_SEPARATOR, sep, A); else - InterleaveSeparator = AliasSeq!(A); + INTERLEAVE_SEPARATOR = AliasSeq!(A); } -template EnumKeys(E) -{ - static assert(is(E == enum), "EnumKeys only works with enums!"); - __gshared immutable string[EnumStrings.length] EnumKeys = [ EnumStrings ]; - private alias EnumStrings = __traits(allMembers, E); -} - -E enumFromString(E)(const(char)[] key) -if (is(E == enum)) -{ - foreach (i, k; EnumKeys!E) - if (key[] == k[]) - return cast(E)i; - return cast(E)-1; -} - private: -template isSame(alias a, alias b) +template is_same(alias a, alias b) { static if (!is(typeof(&a && &b)) // at least one is an rvalue - && __traits(compiles, { enum isSame = a == b; })) // c-t comparable - enum isSame = a == b; + && __traits(compiles, { enum is_same = a == b; })) // c-t comparable + enum is_same = a == b; else - enum isSame = __traits(isSame, a, b); + enum is_same = __traits(isSame, a, b); } // TODO: remove after https://github.com/dlang/dmd/pull/11320 and https://issues.dlang.org/show_bug.cgi?id=21889 are fixed -template isSame(A, B) +template is_same(A, B) { - enum isSame = is(A == B); + enum is_same = is(A == B); } diff --git a/src/urt/meta/tuple.d b/src/urt/meta/tuple.d index a2fe66f..f29e758 100644 --- a/src/urt/meta/tuple.d +++ b/src/urt/meta/tuple.d @@ -64,20 +64,20 @@ template Tuple(Specs...) // Returns Specs for a subtuple this[from .. to] preserving field // names if any. - alias sliceSpecs(size_t from, size_t to) = staticMap!(expandSpec, fieldSpecs[from .. to]); + alias sliceSpecs(size_t from, size_t to) = STATIC_MAP!(expandSpec, fieldSpecs[from .. to]); struct Tuple { nothrow @nogc: - alias Types = staticMap!(extractType, fieldSpecs); + alias Types = STATIC_MAP!(extractType, fieldSpecs); private alias _Fields = Specs; /** * The names of the `Tuple`'s components. Unnamed fields have empty names. */ - alias fieldNames = staticMap!(extractName, fieldSpecs); + alias fieldNames = STATIC_MAP!(extractName, fieldSpecs); /** * Use `t.expand` for a `Tuple` `t` to expand it into its @@ -369,7 +369,7 @@ template Tuple(Specs...) } import std.range : roundRobin, iota; - alias NewTupleT = Tuple!(staticMap!(GetItem, aliasSeqOf!( + alias NewTupleT = Tuple!(STATIC_MAP!(GetItem, aliasSeqOf!( roundRobin(iota(nT), iota(nT, 2*nT))))); return *(() @trusted => cast(NewTupleT*)&this)(); } @@ -545,7 +545,7 @@ template Tuple(Specs...) } else { - formattedWrite(sink, fmt.nested, staticMap!(sharedToString, this.expand)); + formattedWrite(sink, fmt.nested, STATIC_MAP!(sharedToString, this.expand)); } } else if (fmt.spec == 's') diff --git a/src/urt/package.d b/src/urt/package.d index 354632b..5ac2f72 100644 --- a/src/urt/package.d +++ b/src/urt/package.d @@ -16,20 +16,328 @@ public import urt.processor; public import urt.meta : Alias, AliasSeq; public import urt.util : min, max, swap; +import urt.io; + +version (Windows) +{ + private enum crt = __traits(getTargetInfo, "cppRuntimeLibrary"); + static if (crt) + pragma(lib, crt); + pragma(lib, "kernel32"); +} + private: -pragma(crt_constructor) -void crt_bootup() +// ---------------------------------------------------------------------- +// C entry point - replaces druntime's rt/dmain2.d +// +// We initialize uRT subsystems, scan the PE .minfo section for +// ModuleInfo records, run module constructors, call the D main +// (_Dmain), then run destructors. +// ---------------------------------------------------------------------- + +extern(C) int main(int argc, char** argv) nothrow @nogc @trusted { - import urt.time : initClock; - initClock(); + import urt.mem; - import urt.rand; - initRand(); + import urt.mem.string : init_string_heap, deinit_string_heap; + init_string_heap(ushort.max); + + import urt.time : init_clock; + init_clock(); + + import urt.driver.uart : uart_init, uart_deinit; + uart_init(); - import urt.dbg : setupAssertHandler; - setupAssertHandler(); + import urt.rand; + init_rand(); import urt.string.string : initStringAllocators; initStringAllocators(); + + string* args = cast(string*)alloca(string.sizeof * argc); + string[] d_args = args[0 .. argc]; + foreach (i; 0 .. argc) + d_args[i] = cast(string)argv[i][0 .. argv[i].strlen]; + + auto modules = get_module_infos(); + run_module_ctors(modules); + + version (unittest) + { + import urt.internal.stdc.stdlib : exit; + + size_t executed, passed; + foreach (m; modules) + { + if (m is null) continue; + if (auto fp = cast(void function() nothrow @nogc) m.unitTest) + { + write_err(" running: ", m.name, " ... "); + flush!(WriteTarget.stderr)(); + ++executed; + if (run_test(fp)) + ++passed; + else + writeln_err("FAIL"); + } + } + + if (executed > 0) + writeln_err(passed, '/', executed, " modules passed unittests", ); + else + writeln_err("No unittest functions found!"); + + flush!(WriteTarget.stderr)(); + run_module_dtors(modules); + int result = executed > 0 && passed == executed ? 0 : 1; + + version (FreeStanding) + { + import urt.internal.stdc.stdlib : abort; + writeln_err("Process restarting..."); + abort(); + } + } + else + { + int result = call_dmain(d_args); + + flush!(WriteTarget.stdout)(); + } + + run_module_dtors(modules); + + uart_deinit(); + + deinit_string_heap(); + + return result; +} + +version (unittest) +{ + // separated from main() because DMD cannot mix alloca() and exception handling + bool run_test(void function() nothrow @nogc test) nothrow @nogc @trusted + { + try + { + test(); + writeln_err("ok"); + return true; + } + catch (Throwable t) + { + writeln_err(t.msg); + return false; + } + } +} +else +{ + extern(C) int _Dmain(scope string[] args) @nogc; + + int call_dmain(scope string[] args) nothrow @nogc @trusted + { + int result; + try + result = _Dmain(args); + catch (Throwable t) + { + writeln_err("Uncaught exception: ", t.msg); + result = 1; + } + return result; + } +} + +// ---------------------------------------------------------------------- +// Module constructor/destructor execution +// ---------------------------------------------------------------------- + +alias Fn = void function() nothrow @nogc; + +void run_module_ctors(immutable(ModuleInfo*)[] modules) nothrow @nogc @trusted +{ + // order-independent constructors first + foreach (m; modules) + { + if (m is null) + continue; + if (auto fp = cast(Fn) m.ictor) + fp(); + } + + // then regular constructors + foreach (m; modules) + { + if (m is null) + continue; + if (auto fp = cast(Fn) m.ctor) + fp(); + } + + // TLS constructors + foreach (m; modules) + { + if (m is null) + continue; + if (auto fp = cast(Fn) m.tlsctor) + fp(); + } +} + +void run_module_dtors(immutable(ModuleInfo*)[] modules) nothrow @nogc @trusted +{ + // TLS destructors + foreach_reverse (m; modules) + { + if (m is null) + continue; + if (auto fp = cast(Fn) m.tlsdtor) + fp(); + } + + // regular destructors + foreach_reverse (m; modules) + { + if (m is null) + continue; + if (auto fp = cast(Fn) m.dtor) + fp(); + } +} + +immutable(ModuleInfo*)[] get_module_infos() nothrow @nogc @trusted +{ + version (Windows) + { + auto section = find_pe_section(cast(void*)&__ImageBase, ".minfo"); + if (!section.length) + return null; + return (cast(immutable(ModuleInfo*)*)section.ptr)[0 .. section.length / (void*).sizeof]; + } + else version (linux) + { + // DMD path: _d_dso_registry stashed the .minfo section boundaries + if (_elf_minfo_beg !is null) + return _elf_minfo_beg[0 .. _elf_minfo_end - _elf_minfo_beg]; + + // LDC path: read __minfo section via linker-generated symbols + version (LDC) + { + if (&__start___minfo !is null && &__stop___minfo !is null) + return (&__start___minfo)[0 .. &__stop___minfo - &__start___minfo]; + } + + return null; + } + else + { + // Freestanding/bare-metal: walk _Dmodule_ref linked list. + // Populated by .init_array at startup. + if (_Dmodule_ref is null) + return null; + + size_t count = 0; + for (auto p = _Dmodule_ref; p !is null; p = p.next) + ++count; + + import urt.mem.allocator : Mallocator; + auto arr = Mallocator.instance.allocArray!(void*)(count); + auto p = _Dmodule_ref; + foreach (i; 0 .. count) { arr[i] = cast(void*)p.mod; p = p.next; } + return cast(immutable(ModuleInfo*)[])arr; + } +} + +// ---------------------------------------------------------------------- +// PE .minfo section scanning - finds compiler-generated ModuleInfo pointers. +// Inline PE parsing to avoid core.sys.windows struct __init dependencies. +// ---------------------------------------------------------------------- + +version (Windows) +{ + extern(C) extern __gshared ubyte __ImageBase; + + void[] find_pe_section(void* image_base, string name) nothrow @nogc @trusted + { + if (name.length > 8) return null; + + auto base = cast(ubyte*) image_base; + + // DOS header: e_magic at offset 0 (2 bytes), e_lfanew at offset 0x3C (4 bytes) + if (base[0] != 0x4D || base[1] != 0x5A) // 'MZ' + return null; + + auto lfanew = *cast(int*)(base + 0x3C); + auto pe = base + lfanew; + + // PE signature check + if (pe[0] != 'P' || pe[1] != 'E' || pe[2] != 0 || pe[3] != 0) + return null; + + // COFF file header starts at pe+4 + // NumberOfSections at offset 2 (2 bytes) + // SizeOfOptionalHeader at offset 16 (2 bytes) + auto file_header = pe + 4; + ushort num_sections = *cast(ushort*)(file_header + 2); + ushort opt_header_size = *cast(ushort*)(file_header + 16); + + // Section headers start after optional header + auto sections = file_header + 20 + opt_header_size; + + // Each IMAGE_SECTION_HEADER is 40 bytes: + // Name[8] at offset 0 + // VirtualSize at offset 8 + // VirtualAddress at offset 12 + foreach (i; 0 .. num_sections) + { + auto sec = sections + i * 40; + auto sec_name = (cast(char*) sec)[0 .. 8]; + + bool match = true; + foreach (j; 0 .. name.length) + { + if (sec_name[j] != name[j]) + { + match = false; + break; + } + } + if (match && (name.length == 8 || sec_name[name.length] == 0)) + { + auto virtual_size = *cast(uint*)(sec + 8); + auto virtual_address = *cast(uint*)(sec + 12); + return (base + virtual_address)[0 .. virtual_size]; + } + } + return null; + } +} +else version (linux) +{ + // Stashed by _d_dso_registry in object.d from ELF .init_array callback (DMD) + extern(C) extern __gshared immutable(ModuleInfo*)* _elf_minfo_beg; + extern(C) extern __gshared immutable(ModuleInfo*)* _elf_minfo_end; + + version (LDC) + { + // LDC emits ModuleInfo pointers into the __minfo ELF section but does + // not generate .init_array calls to _d_dso_registry. The linker + // generates __start___minfo / __stop___minfo boundary symbols for us. + extern(C) extern __gshared immutable(ModuleInfo*) __start___minfo; + extern(C) extern __gshared immutable(ModuleInfo*) __stop___minfo; + } +} +else +{ + // Freestanding/bare-metal: LDC chains ModuleReference structs into + // this linked list via .init_array at startup. + struct ModuleReference + { + ModuleReference* next; + immutable(ModuleInfo)* mod; + } + extern(C) extern __gshared ModuleReference* _Dmodule_ref; } diff --git a/src/urt/platform.d b/src/urt/platform.d index 6db5f2c..ca97907 100644 --- a/src/urt/platform.d +++ b/src/urt/platform.d @@ -17,15 +17,31 @@ version (visionOS) version = Darwin; -version (Windows) - enum string Platform = "Windows"; -else version (linux) - enum string Platform = "Linux"; -else version (Darwin) - enum string Platform = "Darwin"; -else version (FreeBSD) - enum string Platform = "FreeBSD"; -else version (FreeStanding) - enum string Platform = "Bare-metal"; -else - static assert(0, "Unsupported platform"); +// Platform identity -- specific chip name where known, generic fallback otherwise + +version (ESP8266) enum string Platform = "ESP8266"; +else version (ESP32) enum string Platform = "ESP32"; +else version (ESP32_S2) enum string Platform = "ESP32-S2"; +else version (ESP32_S3) enum string Platform = "ESP32-S3"; +else version (ESP32_C2) enum string Platform = "ESP32-C2"; +else version (ESP32_C3) enum string Platform = "ESP32-C3"; +else version (ESP32_C5) enum string Platform = "ESP32-C5"; +else version (ESP32_C6) enum string Platform = "ESP32-C6"; +else version (ESP32_H2) enum string Platform = "ESP32-H2"; +else version (ESP32_P4) enum string Platform = "ESP32-P4"; +else version (BL808_M0) enum string Platform = "BL808-M0"; +else version (BL808) enum string Platform = "BL808"; +else version (BL618) enum string Platform = "BL618"; +else version (BK7231N) enum string Platform = "BK7231N"; +else version (BK7231T) enum string Platform = "BK7231T"; +else version (RP2350) enum string Platform = "RP2350"; +else version (STM32F4) enum string Platform = "STM32F4"; +else version (STM32F7) enum string Platform = "STM32F7"; +else version (Windows) enum string Platform = "Windows"; +else version (linux) enum string Platform = "Linux"; +else version (Darwin) enum string Platform = "macOS"; +else version (FreeBSD) enum string Platform = "FreeBSD"; +else version (FreeRTOS) enum string Platform = "FreeRTOS"; +else version (BareMetal) enum string Platform = "bare-metal"; +else version (FreeStanding) enum string Platform = "bare-metal"; +else enum string Platform = "unknown"; diff --git a/src/urt/processor.d b/src/urt/processor.d index 4acc356..0f7ba27 100644 --- a/src/urt/processor.d +++ b/src/urt/processor.d @@ -1,22 +1,45 @@ module urt.processor; +enum Endian : byte +{ + Native = -1, // specifies the native/working endian + Little = 0, + Big = 1 +} + version (LittleEndian) +{ enum LittleEndian = true; + enum BigEndian = false; + enum Endian proc_endian = Endian.Little; +} else +{ enum LittleEndian = false; + enum BigEndian = true; + enum Endian proc_endian = Endian.Big; +} version (X86_64) { version = Intel; enum string ProcessorFamily = "x86_64"; + enum string ProcessorName = "x86_64"; } else version (X86) { version = Intel; enum string ProcessorFamily = "x86"; + enum string ProcessorName = "x86"; } else version (AArch64) +{ enum string ProcessorFamily = "ARM64"; + version (LDC) + enum string ProcessorName = __traits(targetCPU); + else + enum string ProcessorName = "aarch64"; +} else version (ARM) { enum string ProcessorFamily = "ARM"; @@ -63,11 +86,125 @@ else version (ARM) enum ProcFeatures = ProcFeaturesT(); } else version (RISCV64) +{ enum string ProcessorFamily = "RISCV64"; + + // Synthesize ISA string: "RV64I" + single-letter extensions in canonical order + enum string ProcessorName = "RV64I" + ~ (ProcFeatures.m ? "M" : "") + ~ (ProcFeatures.a ? "A" : "") + ~ (ProcFeatures.f ? "F" : "") + ~ (ProcFeatures.d ? "D" : "") + ~ (ProcFeatures.c ? "C" : "") + ~ (ProcFeatures.v ? "V" : "") + ~ (ProcFeatures.h ? "H" : "") + ~ (ProcFeatures.xtheadba ? " (T-Head)" : ""); + + struct ProcFeaturesT + { + // Standard extensions + bool a = __traits(targetHasFeature, "a"); // Atomic instructions + bool c = __traits(targetHasFeature, "c"); // Compressed instructions + bool d = __traits(targetHasFeature, "d"); // Double-precision float + bool f = __traits(targetHasFeature, "f"); // Single-precision float + bool h = __traits(targetHasFeature, "h"); // Hypervisor + bool m = __traits(targetHasFeature, "m"); // Integer multiply/divide + bool v = __traits(targetHasFeature, "v"); // Vector extension (RVV 1.0) + // Bit manipulation + bool zba = __traits(targetHasFeature, "zba"); // Address generation + bool zbb = __traits(targetHasFeature, "zbb"); // Basic bit manipulation + bool zbs = __traits(targetHasFeature, "zbs"); // Single-bit instructions + // System + bool zicsr = __traits(targetHasFeature, "zicsr"); // CSR instructions + bool zifencei = __traits(targetHasFeature, "zifencei"); // Instruction-fetch fence + // T-Head vendor extensions (BL808 C906, etc.) + bool xtheadba = __traits(targetHasFeature, "xtheadba"); // Address calculation + bool xtheadbb = __traits(targetHasFeature, "xtheadbb"); // Basic bit manipulation + bool xtheadbs = __traits(targetHasFeature, "xtheadbs"); // Single-bit instructions + bool xtheadcmo = __traits(targetHasFeature, "xtheadcmo"); // Cache management + bool xtheadcondmov = __traits(targetHasFeature, "xtheadcondmov"); // Conditional move + bool xtheadmac = __traits(targetHasFeature, "xtheadmac"); // Multiply-accumulate + bool xtheadmemidx = __traits(targetHasFeature, "xtheadmemidx"); // Indexed memory ops + bool xtheadmempair = __traits(targetHasFeature, "xtheadmempair"); // Paired memory ops + bool xtheadsync = __traits(targetHasFeature, "xtheadsync"); // Multicore sync + bool xtheadvdot = __traits(targetHasFeature, "xtheadvdot"); // Vector dot product + } + enum ProcFeatures = ProcFeaturesT(); +} else version (RISCV32) +{ enum string ProcessorFamily = "RISCV"; + + // Synthesize ISA string: "RV32I" or "RV32E" + extensions + enum string ProcessorName = "RV32" + ~ (ProcFeatures.e ? 'E' : 'I') + ~ (ProcFeatures.m ? "M" : "") + ~ (ProcFeatures.a ? "A" : "") + ~ (ProcFeatures.f ? "F" : "") + ~ (ProcFeatures.d ? "D" : "") + ~ (ProcFeatures.c ? "C" : "") + ~ (ProcFeatures.v ? "V" : ""); + + struct ProcFeaturesT + { + // Standard extensions + bool e = __traits(targetHasFeature, "e"); // RV32E: 16 registers only + bool a = __traits(targetHasFeature, "a"); // Atomic instructions + bool c = __traits(targetHasFeature, "c"); // Compressed instructions + bool d = __traits(targetHasFeature, "d"); // Double-precision float + bool f = __traits(targetHasFeature, "f"); // Single-precision float + bool m = __traits(targetHasFeature, "m"); // Integer multiply/divide + bool v = __traits(targetHasFeature, "v"); // Vector extension (RVV 1.0) + // Bit manipulation + bool zba = __traits(targetHasFeature, "zba"); // Address generation + bool zbb = __traits(targetHasFeature, "zbb"); // Basic bit manipulation + bool zbs = __traits(targetHasFeature, "zbs"); // Single-bit instructions + // System + bool zicsr = __traits(targetHasFeature, "zicsr"); // CSR instructions + bool zifencei = __traits(targetHasFeature, "zifencei"); // Instruction-fetch fence + } + enum ProcFeatures = ProcFeaturesT(); +} else version (Xtensa) +{ enum string ProcessorFamily = "Xtensa"; + + // Synthesize name from key features + enum string ProcessorName = "Xtensa" + ~ (ProcFeatures.windowed ? " Windowed" : " Call0") + ~ (ProcFeatures.fp ? (ProcFeatures.dfpaccel ? " DP" : " SP") : "") + ~ (ProcFeatures.mac16 ? " MAC16" : ""); + + struct ProcFeaturesT + { + // Core ISA options + bool density = __traits(targetHasFeature, "density"); // Density (16-bit) instructions + bool loop = __traits(targetHasFeature, "loop"); // Zero-overhead loops + bool windowed = __traits(targetHasFeature, "windowed"); // Windowed registers (vs call0 ABI) + bool boolean_ = __traits(targetHasFeature, "bool"); // Boolean registers + bool sext = __traits(targetHasFeature, "sext"); // Sign extend instruction + bool nsa = __traits(targetHasFeature, "nsa"); // Normalization shift amount + bool clamps = __traits(targetHasFeature, "clamps"); // Clamp signed + bool minmax = __traits(targetHasFeature, "minmax"); // Min/max instructions + // Multiply/divide + bool mul16 = __traits(targetHasFeature, "mul16"); // 16-bit multiply + bool mul32 = __traits(targetHasFeature, "mul32"); // 32-bit multiply + bool mul32high = __traits(targetHasFeature, "mul32high"); // 32-bit multiply high + bool div32 = __traits(targetHasFeature, "div32"); // 32-bit divide + bool mac16 = __traits(targetHasFeature, "mac16"); // 16-bit MAC (ESP32 LX6) + // Floating point + bool fp = __traits(targetHasFeature, "fp"); // Single-precision float + bool dfpaccel = __traits(targetHasFeature, "dfpaccel"); // Double-precision FP acceleration + // System + bool exception_ = __traits(targetHasFeature, "exception"); // Exception handling + bool interrupt = __traits(targetHasFeature, "interrupt"); // Interrupt handling + bool highpriinterrupts = __traits(targetHasFeature, "highpriinterrupts"); // High-priority interrupts + bool debug_ = __traits(targetHasFeature, "debug"); // Debug support + bool threadptr = __traits(targetHasFeature, "threadptr"); // Thread pointer register + bool coprocessor = __traits(targetHasFeature, "coprocessor"); // Coprocessor interface + } + enum ProcFeatures = ProcFeaturesT(); +} else static assert(0, "Unsupported processor"); @@ -81,10 +218,23 @@ else version (ARM) { enum SupportUnalignedLoadStore = !ProcFeatures.strict_align; } +else version (RISCV64) +{ + enum SupportUnalignedLoadStore = __traits(targetHasFeature, "unaligned-scalar-mem"); +} +else version (RISCV32) +{ + enum SupportUnalignedLoadStore = __traits(targetHasFeature, "unaligned-scalar-mem"); +} else { - // TODO: I think MIPS R6 can do native unalogned loads/stores - enum SupportUnalignedLoadStore = false; + // No arch-level feature flag available (Xtensa, MIPS, etc.) + // Platforms that support unaligned access set -d-version=SupportUnaligned in Makefile + // (e.g., ESP32-S3 Xtensa LX7 has hardware unaligned load/store) + version (SupportUnaligned) + enum SupportUnalignedLoadStore = true; + else + enum SupportUnalignedLoadStore = false; } // Different arch may define this differently... diff --git a/src/urt/rand.d b/src/urt/rand.d index b4b0f1e..c54cd25 100644 --- a/src/urt/rand.d +++ b/src/urt/rand.d @@ -55,7 +55,7 @@ private: rng.state = rng.state * PCG_DEFAULT_MULTIPLIER_64 + rng.inc; } - package void initRand() + package void init_rand() { import urt.time; srand(getTime().ticks, cast(size_t)&globalRand); diff --git a/src/urt/range/package.d b/src/urt/range/package.d index 06de7f0..94af160 100644 --- a/src/urt/range/package.d +++ b/src/urt/range/package.d @@ -8,23 +8,23 @@ template map(fun...) { /** Params: - r = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + r = an $(REF_ALTTEXT input range, is_input_range, std,range,primitives) Returns: A range with each fun applied to all the elements. If there is more than one fun, the element type will be `Tuple` containing one element for each fun. */ auto map(Range)(Range r) - if (isInputRange!(Unqual!Range)) + if (is_input_range!(Unqual!Range)) { - import urt.meta : AliasSeq, staticMap; + import urt.meta : AliasSeq, STATIC_MAP; alias RE = ElementType!(Range); static if (fun.length > 1) { import std.functional : adjoin; - import urt.meta : staticIndexOf; + import urt.meta : static_index_of; - alias _funs = staticMap!(unaryFun, fun); + alias _funs = STATIC_MAP!(unaryFun, fun); alias _fun = adjoin!_funs; // Once https://issues.dlang.org/show_bug.cgi?id=5710 is fixed @@ -56,7 +56,7 @@ private struct MapResult(alias fun, Range) alias R = Unqual!Range; R _input; - static if (isBidirectionalRange!R) + static if (is_bidirectional_range!R) { @property auto ref back()() { @@ -101,7 +101,7 @@ private struct MapResult(alias fun, Range) return fun(_input.front); } - static if (isRandomAccessRange!R) + static if (is_random_access_range!R) { static if (is(typeof(Range.init[ulong.max]))) private alias opIndex_t = ulong; @@ -147,7 +147,7 @@ private struct MapResult(alias fun, Range) } } - static if (isForwardRange!R) + static if (is_forward_range!R) { @property auto save() { @@ -160,9 +160,9 @@ private struct MapResult(alias fun, Range) template reduce(fun...) if (fun.length >= 1) { - import urt.meta : staticMap; + import urt.meta : STATIC_MAP; - alias binfuns = staticMap!(binaryFun, fun); + alias binfuns = STATIC_MAP!(binaryFun, fun); static if (fun.length > 1) import urt.meta.tuple : tuple, isTuple; @@ -182,24 +182,21 @@ template reduce(fun...) Returns: the final result of the accumulator applied to the iterable - - Throws: `Exception` if `r` is empty +/ auto reduce(R)(R r) if (isIterable!R) { - import std.exception : enforce; - alias E = Select!(isInputRange!R, ElementType!R, ForeachType!R); - alias Args = staticMap!(ReduceSeedType!E, binfuns); + alias E = Select!(is_input_range!R, ElementType!R, ForeachType!R); + alias Args = STATIC_MAP!(ReduceSeedType!E, binfuns); - static if (isInputRange!R) + static if (is_input_range!R) { // no need to throw if range is statically known to be non-empty static if (!__traits(compiles, { static assert(r.length > 0); })) - enforce(!r.empty, "Cannot reduce an empty input range w/o an explicit seed value."); + assert(!r.empty, "Cannot reduce an empty input range w/o an explicit seed value."); Args result = r.front; r.popFront(); @@ -245,7 +242,7 @@ template reduce(fun...) private auto reducePreImpl(R, Args...)(R r, ref Args args) { - alias Result = staticMap!(Unqual, Args); + alias Result = STATIC_MAP!(Unqual, Args); static if (is(Result == Args)) alias result = args; else @@ -259,7 +256,7 @@ template reduce(fun...) import std.algorithm.internal : algoFormat; static assert(Args.length == fun.length, algoFormat("Seed %s does not have the correct amount of fields (should be %s)", Args.stringof, fun.length)); - alias E = Select!(isInputRange!R, ElementType!R, ForeachType!R); + alias E = Select!(is_input_range!R, ElementType!R, ForeachType!R); static if (mustInitialize) bool initialized = false; @@ -279,7 +276,7 @@ template reduce(fun...) static if (mustInitialize) if (initialized == false) { - import core.internal.lifetime : emplaceRef; + import urt.lifetime : emplaceRef; foreach (i, f; binfuns) emplaceRef!(Args[i])(args[i], e); initialized = true; @@ -309,7 +306,7 @@ template fold(fun...) { /** Params: - r = the $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to fold + r = the $(REF_ALTTEXT input range, is_input_range, std,range,primitives) to fold seeds = the initial values of each accumulator (optional), one for each predicate Returns: Either the accumulated result for a single predicate, or a @@ -323,7 +320,7 @@ template fold(fun...) } else { - import std.typecons : tuple; + import urt.meta : tuple; return reduce!fun(tuple(seeds), r); } } diff --git a/src/urt/range/primitives.d b/src/urt/range/primitives.d index b9789cd..f5ddec8 100644 --- a/src/urt/range/primitives.d +++ b/src/urt/range/primitives.d @@ -3,44 +3,44 @@ module urt.range.primitives; import urt.traits; -enum bool isInputRange(R) = +enum bool is_input_range(R) = is(typeof(R.init) == R) && is(typeof((R r) { return r.empty; } (R.init)) == bool) && (is(typeof((return ref R r) => r.front)) || is(typeof(ref (return ref R r) => r.front))) && !is(typeof((R r) { return r.front; } (R.init)) == void) && is(typeof((R r) => r.popFront)); -enum bool isInputRange(R, E) = - .isInputRange!R && isQualifierConvertible!(ElementType!R, E); +enum bool is_input_range(R, E) = + .is_input_range!R && isQualifierConvertible!(ElementType!R, E); -enum bool isForwardRange(R) = isInputRange!R +enum bool is_forward_range(R) = is_input_range!R && is(typeof((R r) { return r.save; } (R.init)) == R); -enum bool isForwardRange(R, E) = - .isForwardRange!R && isQualifierConvertible!(ElementType!R, E); +enum bool is_forward_range(R, E) = + .is_forward_range!R && isQualifierConvertible!(ElementType!R, E); -enum bool isBidirectionalRange(R) = isForwardRange!R +enum bool is_bidirectional_range(R) = is_forward_range!R && is(typeof((R r) => r.popBack)) && (is(typeof((return ref R r) => r.back)) || is(typeof(ref (return ref R r) => r.back))) && is(typeof(R.init.back.init) == ElementType!R); -enum bool isBidirectionalRange(R, E) = - .isBidirectionalRange!R && isQualifierConvertible!(ElementType!R, E); +enum bool is_bidirectional_range(R, E) = + .is_bidirectional_range!R && isQualifierConvertible!(ElementType!R, E); -enum bool isRandomAccessRange(R) = - is(typeof(lvalueOf!R[1]) == ElementType!R) +enum bool is_random_access_range(R) = + is(typeof(lvalue_of!R[1]) == ElementType!R) && !(isAutodecodableString!R && !isAggregateType!R) - && isForwardRange!R - && (isBidirectionalRange!R || isInfinite!R) + && is_forward_range!R + && (is_bidirectional_range!R || isInfinite!R) && (hasLength!R || isInfinite!R) - && (isInfinite!R || !is(typeof(lvalueOf!R[$ - 1])) - || is(typeof(lvalueOf!R[$ - 1]) == ElementType!R)); -enum bool isRandomAccessRange(R, E) = - .isRandomAccessRange!R && isQualifierConvertible!(ElementType!R, E); + && (isInfinite!R || !is(typeof(lvalue_of!R[$ - 1])) + || is(typeof(lvalue_of!R[$ - 1]) == ElementType!R)); +enum bool is_random_access_range(R, E) = + .is_random_access_range!R && isQualifierConvertible!(ElementType!R, E); // is this in the wrong place? should this be a general traits for arrays and stuff too? template ElementType(R) { - static if (is(typeof(lvalueOf!R.front))) - alias ElementType = typeof(lvalueOf!R.front); + static if (is(typeof(lvalue_of!R.front))) + alias ElementType = typeof(lvalue_of!R.front); else static if (is(R : T[], T)) alias ElementType = T; else diff --git a/src/urt/result.d b/src/urt/result.d index 564b15c..d39959c 100644 --- a/src/urt/result.d +++ b/src/urt/result.d @@ -3,73 +3,136 @@ module urt.result; nothrow @nogc: -enum InternalCode +struct Result { - Success = 0, - BufferTooSmall, - InvalidParameter, - Unsupported +nothrow @nogc: + enum success = Result(); + + uint system_code = 0; + + bool opCast(T : bool)() const + => system_code == 0; + + bool succeeded() const + => system_code == 0; + bool failed() const + => system_code != 0; } -struct Result +struct SizeResult { nothrow @nogc: - enum Success = Result(); + this(size_t size) + { + debug assert(size <= ptrdiff_t.max, "Size too large to fit in a signed integer!"); + this.size = size; + } + this(Result result) + { + static if (size_t.sizeof == 4) + assert(cast(int)result.system_code >= 0, "Negative result codes not supported on 32-bit machines!"); + this.size = -cast(ptrdiff_t)result.system_code; + } - uint systemCode = 0; + ptrdiff_t size = 0; bool opCast(T : bool)() const - => systemCode == 0; + => size >= 0; bool succeeded() const - => systemCode == 0; + => size >= 0; bool failed() const - => systemCode != 0; + => size < 0; + + Result result() const + => size >= 0 ? Result.success : Result(cast(uint)-size); } +struct StringResult +{ +nothrow @nogc: + enum success = StringResult(); + + const(char)[] message = null; + + bool opCast(T : bool)() const + => message is null; + + bool succeeded() const + => message is null; + bool failed() const + => message !is null; +} + +// TODO: should we have a way to convert Result to StringResult, so we can format error messages? + + +version (Posix) version = Errno; +version (CRuntime_Picolibc) version = Errno; +version (CRuntime_Newlib) version = Errno; version (Windows) { - import core.sys.windows.windows; + import urt.internal.sys.windows; - Result InternalResult(InternalCode code) + enum InternalResult : Result { - switch (code) - { - case InternalCode.Success: - return Result(); - case InternalCode.BufferTooSmall: - return Result(ERROR_INSUFFICIENT_BUFFER); - case InternalCode.InvalidParameter: - return Result(ERROR_INVALID_PARAMETER); - default: - return Result(ERROR_INVALID_FUNCTION); // InternalCode.Unsupported - } + success = Result.success, + failed = Result(ERROR_GEN_FAILURE), + buffer_too_small = Result(ERROR_INSUFFICIENT_BUFFER), + invalid_parameter = Result(ERROR_INVALID_PARAMETER), + data_error = Result(ERROR_INVALID_DATA), + unsupported = Result(ERROR_NOT_SUPPORTED), + out_of_range = Result(ERROR_ARITHMETIC_OVERFLOW), + already_exists = Result(ERROR_ALREADY_EXISTS), + timeout = Result(ERROR_TIMEOUT), + aborted = Result(ERROR_OPERATION_ABORTED), + no_memory = Result(ERROR_NOT_ENOUGH_MEMORY), } - Result Win32Result(uint err) + Result win32_result(uint err) => Result(err); + Result getlasterror_result() + => Result(GetLastError()); } -else version (Posix) +else version (Errno) { - import core.stdc.errno; - Result InternalResult(InternalCode code) + import urt.internal.stdc.errno; + + enum InternalResult : Result { - switch (code) - { - case InternalCode.Success: - return Result(); - case InternalCode.BufferTooSmall: - return Result(ERANGE); - case InternalCode.InvalidParameter: - return Result(EINVAL); - default: - return Result(ENOTSUP); // InternalCode.Unsupported - } + success = Result.success, + failed = Result(EIO), + buffer_too_small = Result(ERANGE), + invalid_parameter = Result(EINVAL), + data_error = Result(EILSEQ), + unsupported = Result(ENOTSUP), + out_of_range = Result(ERANGE), + already_exists = Result(EEXIST), + timeout = Result(ETIMEDOUT), + aborted = Result(EINTR), + no_memory = Result(ENOMEM), } - Result PosixResult(int err) + Result posix_result(int err) => Result(err); - Result ErrnoResult() + Result errno_result() => Result(errno); } +else version (FreeStanding) +{ + enum InternalResult : Result + { + success = Result.success, + failed = Result(1), + buffer_too_small = Result(2), + invalid_parameter = Result(3), + data_error = Result(4), + unsupported = Result(5), + out_of_range = Result(6), + already_exists = Result(7), + timeout = Result(8), + aborted = Result(9), + no_memory = Result(10), + } +} diff --git a/src/urt/si/quantity.d b/src/urt/si/quantity.d index 33462df..1ec1c61 100644 --- a/src/urt/si/quantity.d +++ b/src/urt/si/quantity.d @@ -1,14 +1,22 @@ module urt.si.quantity; +import urt.meta : TypeForOp; import urt.si.unit; +import urt.traits; nothrow @nogc: -alias VarQuantity = Quantity!(double); +alias VarQuantity = Quantity!double; alias Scalar = Quantity!(double, ScaledUnit()); alias Metres = Quantity!(double, ScaledUnit(Metre)); alias Seconds = Quantity!(double, ScaledUnit(Second)); +alias Volts = Quantity!(double, ScaledUnit(Volt)); +alias Amps = Quantity!(double, ScaledUnit(Ampere)); +alias AmpHours = Quantity!(double, AmpereHour); +alias Watts = Quantity!(double, ScaledUnit(Watt)); +alias Kilowatts = Quantity!(double, Kilowatt); +alias WattHours = Quantity!(double, WattHour); struct Quantity(T, ScaledUnit _unit = ScaledUnit(uint.max)) @@ -20,7 +28,7 @@ nothrow @nogc: enum Dynamic = _unit.pack == uint.max; enum IsCompatible(ScaledUnit U) = _unit.unit == U.unit; - T value; + T value = 0; static if (Dynamic) ScaledUnit unit; @@ -31,11 +39,20 @@ nothrow @nogc: if (is(U : T)) => unit.unit == compatibleWith.unit.unit; - this(T value) pure + static if (Dynamic) { - static if (Dynamic) - this.unit = ScaledUnit(); - this.value = value; + this(T value, ScaledUnit unit = ScaledUnit()) pure + { + this.unit = unit; + this.value = value; + } + } + else + { + this(T value) pure + { + this.value = value; + } } this(U, ScaledUnit _U)(Quantity!(U, _U) b) pure @@ -49,10 +66,10 @@ nothrow @nogc: else { static if (b.Dynamic) - assert(isCompatible(b), "Incompatible unit!"); + assert(isCompatible(b), "Incompatible units!"); else - static assert(IsCompatible!_U, "Incompatible unit: ", unit, " and ", b.unit); - value = adjustScale(b); + static assert(IsCompatible!_U, "Incompatible units: ", unit.toString, " and ", b.unit.toString); + value = adjust_scale(b); } } @@ -61,13 +78,14 @@ nothrow @nogc: static if (Dynamic) unit = Scalar; else - static assert(unit == Unit(), "Incompatible unit: ", unit, " and Scalar"); + static assert(unit == Unit(), "Incompatible units: ", unit.toString, " and Scalar"); this.value = value; } void opAssign(U, ScaledUnit _U)(Quantity!(U, _U) b) pure - if (is(U : T)) { + static assert(__traits(compiles, value = b.value), "cannot implicitly convert ScaledUnit of type `", U, "` to `", T, "`"); + static if (Dynamic) { unit = b.unit; @@ -76,37 +94,45 @@ nothrow @nogc: else { static if (b.Dynamic) - assert(isCompatible(b), "Incompatible unit!"); + assert(isCompatible(b), "Incompatible units!"); else - static assert(IsCompatible!_U, "Incompatible unit: ", unit, " and ", b.unit); - value = adjustScale(b); + static assert(IsCompatible!_U, "Incompatible units: ", unit.toString, " and ", b.unit.toString); + value = adjust_scale(b); } } - auto opBinary(string op, U)(U value) const pure - if ((op == "+" || op == "-") && is(U : T)) + auto opUnary(string op)() const pure + if (op == "+" || op == "-") { + alias RT = Quantity!(TypeForOp!(op, T), _unit); static if (Dynamic) - assert(unit == Scalar); + return RT(mixin(op ~ "value"), unit); else - static assert(unit == Unit(), "Incompatible unit: ", unit, " and Scalar"); - return mixin("this.value " ~ op ~ " value"); + return RT(mixin(op ~ "value")); } + auto opBinary(string op, U)(U value) const pure + if ((op == "+" || op == "-") && is(U : T)) + => opBinary!op(Quantity!(U, ScaledUnit())(value)); + auto opBinary(string op, U, ScaledUnit _U)(Quantity!(U, _U) b) const pure if ((op == "+" || op == "-") && is(U : T)) { + // TODO: what unit should be result take? + // for float T, I reckon maybe the MAX exponent? + // for int types... we need to do some special shit to manage overflows! + // HACK: for now, we just scale to the left-hand size... :/ static if (!Dynamic && !b.Dynamic && unit == Unit() && b.unit == Unit()) return mixin("value " ~ op ~ " b.value"); else { static if (Dynamic || b.Dynamic) - assert(isCompatible(b), "Incompatible unit!"); + assert(isCompatible(b), "Incompatible units!"); else - static assert(IsCompatible!_U, "Incompatible unit: ", unit, " and ", b.unit); + static assert(IsCompatible!_U, "Incompatible units: ", unit.toString, " and ", b.unit.toString); - This r; - r.value = mixin("value " ~ op ~ " adjustScale(b)"); + Quantity!(TypeForOp!(op, T, U), _unit) r; + r.value = mixin("value " ~ op ~ " adjust_scale(b)"); static if (Dynamic) r.unit = unit; return r; @@ -120,11 +146,12 @@ nothrow @nogc: return mixin("this.value " ~ op ~ " value"); else { - This r; - r.value = mixin("this.value " ~ op ~ " value"); + alias RT = TypeForOp!(op, T, U); + RT v = mixin("this.value " ~ op ~ " value"); static if (Dynamic) - r.unit = unit; - return r; + return Quantity!RT(v, unit); + else + return Quantity!(RT, unit)(v); } } @@ -135,33 +162,63 @@ nothrow @nogc: return mixin("value " ~ op ~ " b.value"); else { + // TODO: if the unit product is invalid, then we need to decide a target scaling factor... static if (Dynamic || b.Dynamic) - { - Quantity!T r; - r.unit = mixin("unit " ~ op ~ " b.unit"); - } + const u = mixin("unit " ~ op ~ " b.unit"); else - Quantity!(T, mixin("unit " ~ op ~ " b.unit")) r; + enum u = mixin("unit " ~ op ~ " b.unit"); - // TODO: if the unit product is invalid, then we apply the scaling factor... - // ... but which side should we scale to? probably the left I guess... + alias RT = TypeForOp!(op, T, U); + RT v = mixin("value " ~ op ~ " b.value"); - r.value = mixin("value " ~ op ~ " b.value"); - return r; + static if (Dynamic || b.Dynamic) + return Quantity!RT(v, u); + else + return Quantity!(RT, u)(v); } } - void opOpAssign(string op, U)(U value) pure - if (is(U : T)) + void opOpAssign(string op)(T value) pure { + // TODO: in D; ubyte += int is allowed, so we should cast the result to T this = opBinary!op(value); } void opOpAssign(string op, U, ScaledUnit _U)(Quantity!(U, _U) b) pure { + // TODO: in D; ubyte += int is allowed, so we should cast the result to T this = opBinary!op(b); } + bool opCast(T : bool)() const pure + => value != 0; + + // not clear if this should return the raw value, or the normalised value...? +// T opCast(T)() const pure +// if (is_some_float!T || is_some_int!T) +// { +// assert(unit.pack == 0, "Non-scalar unit can't cast to scalar"); +// assert(false, "TODO: should we be applying the scale to this result?"); +// return cast(T)value; +// } + + T opCast(T)() const pure + if (is(T == Quantity!(U, _U), U, ScaledUnit _U)) + { + static if (is(T == Quantity!(U, _U), U, ScaledUnit _U)) + { + T r; + static if (Dynamic || T.Dynamic) + assert(isCompatible(r), "Incompatible units!"); + else + static assert(IsCompatible!_U, "Incompatible units: ", r.unit.toString, " and ", unit.toString); + r.value = cast(U)r.adjust_scale(this); + static if (T.Dynamic) + r.unit = unit; + return r; + } + } + bool opEquals(U)(U value) const pure if (is(U : T)) { @@ -185,14 +242,14 @@ nothrow @nogc: // can't compare mismatch unit types... i think? static if (Dynamic || rh.Dynamic) - assert(isCompatible(rh), "Incompatible unit!"); + assert(isCompatible(rh), "Incompatible units!"); else - static assert(IsCompatible!_U, "Incompatible unit: ", unit, " and ", rh.unit); + static assert(IsCompatible!_U, "Incompatible units: ", unit.toString, " and ", rh.unit.toString); // TODO: meeting in the middle is only better if the signs are opposite // otherwise we should just scale to the left... static if (Dynamic && rh.Dynamic) - { + {{ // if the scale values are both dynamic, it should be more precise if we meet in the middle... auto lScale = unit.scale(); auto lTrans = unit.offset(); @@ -200,9 +257,9 @@ nothrow @nogc: auto rTrans = rh.unit.offset(); lhs = lhs*lScale + lTrans; rhs = rhs*rScale + rTrans; - } + }} else - rhs = adjustScale(rh); + rhs = adjust_scale(rh); compare: static if (epsilon == 0) @@ -222,41 +279,136 @@ nothrow @nogc: } } -private: - T adjustScale(U, ScaledUnit _U)(Quantity!(U, _U) b) const pure + auto normalise() const pure { static if (Dynamic) { - auto lScale = unit.scale!true(); - auto lTrans = unit.offset!true(); + Quantity!T r; + r.unit = ScaledUnit(unit.unit); } else - { - enum lScale = unit.scale!true(); - enum lTrans = unit.offset!true(); - } - static if (b.Dynamic) - { - auto rScale = b.unit.scale(); - auto rTrans = b.unit.offset(); - } + Quantity!(T, ScaledUnit(unit.unit)) r; + r.value = r.adjust_scale(this); + return r; + } + + Quantity!Ty adjust_scale(Ty = T)(ScaledUnit su) const pure + { + Quantity!Ty r; + r.unit = su; + assert(r.isCompatible(this), "Incompatible units!"); + if (su == unit) + r.value = cast(Ty)this.value; else + r.value = r.adjust_scale(this); + return r; + } + + import urt.string.format : FormatArg; + ptrdiff_t toString(char[] buffer, const(char)[], const(FormatArg)[]) const + { + import urt.conv : format_float; + + double v = value; + ScaledUnit u = unit; + + if (u.pack) { - enum rScale = b.unit.scale(); - enum rTrans = b.unit.offset(); + // round upward to the nearest ^3 + if (u.siScale) + { + int x = u.exp; + if (u.unit.pack == 0) + { + if (x == -3) + { + v *= 0.1; + u = ScaledUnit(Unit(), x + 1); + } + else if (x != -2) + { + v *= u.scale(); + u = ScaledUnit(); + } + } + else + { + x = (x + 33) % 3; + if (x != 0) + { + u = ScaledUnit(u.unit, u.exp + (3 - x)); + if (x == 1) + v *= 0.01; + else + v *= 0.1; + } + } + } } - static if (Dynamic || b.Dynamic) + ptrdiff_t l = format_float(v, buffer); + if (l < 0) + return l; + + if (u.pack) { - auto scale = lScale*rScale; - auto trans = lTrans + lScale*rTrans; + ptrdiff_t l2 = u.toString(buffer.ptr ? buffer.ptr[l .. buffer.length] : null, null, null); + if (l2 < 0) + return l2; + l += l2; } + + return l; + } + + ptrdiff_t fromString(const(char)[] s) + { + return -1; + } + +private: + T adjust_scale(U, ScaledUnit _U)(Quantity!(U, _U) b) const pure + { + static if (!Dynamic && !b.Dynamic && unit == b.unit) + return cast(T)b.value; else { - enum scale = lScale*rScale; - enum trans = lTrans + lScale*rTrans; + if (unit == b.unit) + return cast(T)b.value; + + static if (Dynamic) + { + auto lScale = unit.scale!true(); + auto lTrans = unit.offset!true(); + } + else + { + enum lScale = unit.scale!true(); + enum lTrans = unit.offset!true(); + } + static if (b.Dynamic) + { + auto rScale = b.unit.scale(); + auto rTrans = b.unit.offset(); + } + else + { + enum rScale = b.unit.scale(); + enum rTrans = b.unit.offset(); + } + + static if (Dynamic || b.Dynamic) + { + auto scale = lScale*rScale; + auto trans = lTrans + lScale*rTrans; + } + else + { + enum scale = lScale*rScale; + enum trans = lTrans + lScale*rTrans; + } + return cast(T)(b.value*scale + trans); } - return cast(T)(b.value*scale + trans); } } @@ -345,3 +497,49 @@ unittest assert(DegreesC(100).opEquals!epsilon(DegreesF(212))); assert(DegreesF(100).opEquals!epsilon(DegreesC(37.77777777777777))); } + + +VarQuantity parse_quantity(const(char)[] text, size_t* bytes_taken = null) nothrow +{ + import urt.si.unit; + import urt.conv; + + int e; + uint base; + size_t taken; + long raw_value = text.parse_int_with_exponent_and_base(e, base, &taken); + if (taken == 0) + { + if (bytes_taken) + *bytes_taken = 0; + return VarQuantity(double.nan); + } + + // we parsed a number! + auto r = VarQuantity(e == 0 ? raw_value : raw_value * double(base)^^e); + + if (taken < text.length) + { + // try and parse a unit... + ScaledUnit su; + float pre_scale; + ptrdiff_t unit_taken = su.parse_unit(text[taken .. $], pre_scale, false); + if (unit_taken > 0) + { + taken += unit_taken; + r = VarQuantity(r.value * pre_scale, su); + } + } + if (bytes_taken) + *bytes_taken = taken; + return r; +} + +unittest +{ + import urt.si.unit; + + size_t taken; + assert("10V".parse_quantity(&taken) == Volts(10) && taken == 3); + assert("10.2e+2Wh".parse_quantity(&taken) == WattHours(1020) && taken == 9); +} diff --git a/src/urt/si/unit.d b/src/urt/si/unit.d index cf40a9e..1fdbe2c 100644 --- a/src/urt/si/unit.d +++ b/src/urt/si/unit.d @@ -1,5 +1,8 @@ module urt.si.unit; +import urt.array; +import urt.string; + nothrow @nogc: @@ -29,6 +32,9 @@ nothrow @nogc: // +enum ScaledUnit unit(const(char)[] desc) = () { ScaledUnit r; float f; ptrdiff_t e = r.parse_unit(desc, f); assert(e > 0, "Invalid unit"); assert(f == 1, "Unit requires pre-scale"); return r; }(); + + // base units enum Metre = Unit(UnitType.Length); enum Kilogram = Unit(UnitType.Mass); @@ -39,6 +45,9 @@ enum Candela = Unit(UnitType.Luma); enum Radian = Unit(UnitType.Angle); // non-si units +enum Minute = ScaledUnit(Second, ScaleFactor.Minute); +enum Hour = ScaledUnit(Second, ScaleFactor.Hour); +enum Day = ScaledUnit(Second, ScaleFactor.Day); enum Inch = ScaledUnit(Metre, ScaleFactor.Inch); enum Foot = ScaledUnit(Metre, ScaleFactor.Foot); enum Mile = ScaledUnit(Metre, ScaleFactor.Mile); @@ -60,7 +69,9 @@ enum CubicMetre = Metre^^3; enum Litre = ScaledUnit(CubicMetre, -3); enum Gram = ScaledUnit(Kilogram, -3); enum Milligram = ScaledUnit(Kilogram, -6); +enum Nanosecond = ScaledUnit(Second, -9); enum Hertz = Cycle / Second; +//enum Kilohertz = TODO: ONOES! Our system can't encode kilohertz! This is disaster!! enum Newton = Kilogram * Metre / Second^^2; enum Pascal = Newton / Metre^^2; enum PSI = ScaledUnit(Pascal, ScaleFactor.PSI); @@ -96,7 +107,18 @@ enum UnitType : ubyte struct Unit { -nothrow @nogc: +nothrow: + // debug/ctfe helper + string toString() pure + { + char[32] t = void; + ptrdiff_t l = toString(t, null, null); + if (l < 0) + return "Invalid unit"; // OR JUST COULDN'T STRINGIFY! + return t[0..l].idup; + } + +@nogc: uint pack; @@ -225,82 +247,40 @@ nothrow @nogc: this = this.opBinary!op(rh); } - ptrdiff_t toString(char[] buffer) const + import urt.string.format : FormatArg; + ptrdiff_t toString(char[] buffer, const(char)[], const(FormatArg)[]) const pure { - immutable string[24] si = [ -// "da", "h", "k", "10k", "100k", "M", "10M", "100M", "G", "10G", "100G", "T", "10T", "100T", "P", "10P", "100P", "E", "10E", "100E", "Z", "10Z", "100Z", "Y" - "10", "100", "k", "10k", "100k", "M", "10M", "100M", "G", "10G", "100G", "T", "10T", "100T", "P", "10P", "100P", "E", "10E", "100E", "Z", "10Z", "100Z", "Y" - ]; - immutable string[24] si_inv = [ -// "d", "c", "m", "100μ", "10μ", "μ", "100n", "10n", "n", "100p", "10p", "p", "100f", "10f", "f", "100a", "10a", "a", "100z", "10z", "z", "100y", "10y", "y" - "100m", "10m", "m", "100μ", "10μ", "μ", "100n", "10n", "n", "100p", "10p", "p", "100f", "10f", "f", "100a", "10a", "a", "100z", "10z", "z", "100y", "10y", "y" - ]; - - ptrdiff_t len = 0; - - // ⁰¹⁻²³⁴⁵⁶⁷⁸⁹·° - -// if (q(0).exponent == 0) -// { -// // if we have a scaling factor... what do we write? -//// if (sf) ... -// len = 0; -// return len; -// } -// -// auto name = this in unitNames; -// if (name) -// { -// len = name.length; -// buffer[0 .. len] = *name; -// return len; -// } -// -// int f = sf(); -// name = unit() in unitNames; -// -// if ((name || q(1).exponent == 0) && f <= 24) -// { -// if (f > 0) -// { -// ref immutable string[24] prefix = inv ? si_inv : si; -// -// uint scale = f - 1; -// if (q(0).measure == UnitType.Mass) -// scale += 3; -// -// len = prefix[scale].length; -// buffer[0 .. len] = prefix[scale][]; -// } -// -// if (name) -// { -// buffer[len .. len + name.length] = *name; -// len += name.length; -// } -// else -// { -// immutable string[UnitType.max + 1] unit_name = [ "g", "m", "s", "A", "K", "cd", "cy" ]; -// -// string uname = unit_name[q(0).measure]; -// buffer[len .. len + uname.length] = uname[]; -// len += uname.length; -// } -// -// if (q(0).measure != 1) -// { -// buffer[len++] = '^'; -// len += q(0).exponent.toString(buffer[len .. $]); -// } -// -// } -// else -// { -// // multiple terms, or an odd scale factor... -// // TODO... -// assert(false); -// } + assert(false, "TODO"); + } + + ptrdiff_t fromString(const(char)[] s) pure + { + if (s.length == 0) + { + pack = 0; + return 0; + } + + Unit r; + size_t len = s.length; + bool invert; + char sep; + while (const(char)[] unit = s.split!('/', '*')(sep)) + { + int p = unit.take_power(); + if (p == 0) + return -1; // invalid power + if (const Unit* u = unit in unitMap) + r *= (*u) ^^ (invert ? -p : p); + else + { + assert(false, "TODO?"); + } + if (sep == '/') + invert = true; + } + this = r; return len; } @@ -381,7 +361,18 @@ enum ExtendedScaleFactor : ubyte struct ScaledUnit { -nothrow @nogc: +nothrow: + // debug/ctfe helper + string toString() pure + { + char[32] t = void; + ptrdiff_t l = toString(t, null, null); + if (l < 0) + return "Invalid unit"; // OR JUST COULDN'T STRINGIFY! + return t[0..l].idup; + } + +@nogc: uint pack; @@ -432,12 +423,12 @@ nothrow @nogc: return unit == b.unit; } - double scale(bool inv = false)() const pure + double scale(bool invert = false)() const pure { if (siScale) { int e = exp(); - if (inv) + if (invert) e = -e; if (uint(e + 9) < 19) return sciScaleFactor[e + 9]; @@ -445,9 +436,9 @@ nothrow @nogc: } if (isExtended()) - return extScaleFactor[(pack >> 29) ^ (inv << 2)]; + return extScaleFactor[(pack >> 29) ^ (invert << 2)]; - double s = scaleFactor[(pack >> 31) ^ inv][sf()]; + double s = scaleFactor[(pack >> 31) ^ invert][sf()]; for (uint i = ((pack >> 29) & 3); i > 0; --i) s *= s; return s; @@ -574,9 +565,278 @@ nothrow @nogc: bool opEquals(Unit rh) const pure => (pack & 0xFF000000) ? false : unit == rh; + alias parseUnit = parse_unit; // TODO: DELETE ME!!! + ptrdiff_t parse_unit(const(char)[] s, out float pre_scale, bool allow_unit_scale = true) pure + { + import urt.conv : parse_uint_with_exponent; + + pre_scale = 1; + + if (s.length == 0) + { + pack = 0; + return 0; + } + + size_t len = s.length; + if (s[0] == '-') + { + if (s.length == 1) + return -1; + pre_scale = -1; + s = s[1 .. $]; + } + + ScaledUnit r; + bool invert; + char sep; + while (const(char)[] term = s.split!(['/', '*'], false, false)(&sep)) + { + int p = term.take_power(); + if (p == 0) + return -1; // invalid exponent + if (term.length == 0) + return -1; + + size_t offset = 0; + + // parse the scale factor + int e = 0; + if (term[0].is_numeric) + { + if (!allow_unit_scale) + return -1; // no numeric scale factor allowed + ulong sf = term.parse_uint_with_exponent(e, &offset); + pre_scale *= sf; + } + + if (offset == term.length) + r *= ScaledUnit(Unit(), e); + else if (const ScaledUnit* su = term[offset .. $] in noScaleUnitMap) + { + r *= (*su) ^^ (invert ? -p : p); + pre_scale *= 10.0^^e; + } + else + { + // try and parse SI prefix... + switch (term[offset]) + { + case 'Y': e += 24; ++offset; break; + case 'Z': e += 21; ++offset; break; + case 'E': e += 18; ++offset; break; + case 'P': e += 15; ++offset; break; + case 'T': e += 12; ++offset; break; + case 'G': e += 9; ++offset; break; + case 'M': e += 6; ++offset; break; + case 'k': e += 3; ++offset; break; + case 'h': e += 2; ++offset; break; + case 'c': e -= 2; ++offset; break; + case 'u': e -= 6; ++offset; break; + case 'n': e -= 9; ++offset; break; + case 'p': e -= 12; ++offset; break; + case 'f': e -= 15; ++offset; break; + case 'a': e -= 18; ++offset; break; + case 'z': e -= 21; ++offset; break; + case 'y': e -= 24; ++offset; break; + case 'm': + // can confuse with metres... so gotta check... + if (offset + 1 < term.length) + e -= 3, ++offset; + break; + case 'd': + if (offset + 1 < term.length && term[offset + 1] == 'a') + { + e += 1, offset += 2; + break; + } + e -= 1, ++offset; + break; + default: + if (offset + "µ".length < term.length && term[offset .. offset + "µ".length] == "µ") + e -= 6, offset += "µ".length; + break; + } + if (offset == term.length) + return -1; + + term = term[offset .. $]; + if (const Unit* u = term in unitMap) + { + if (term == "kg") + { + // we alrady parsed the 'k', so this string must have been "kkg", which is nonsense + return -1; + } + r *= ScaledUnit((*u) ^^ (invert ? -p : p), e); + } + else if (const ScaledUnit* su = term in scaledUnitMap) + r *= ScaledUnit(su.unit, su.exp + e) ^^ (invert ? -p : p); + else if (const ScaledUnit* su = term in noScaleUnitMapSI) + { + r *= (*su) ^^ (invert ? -p : p); + pre_scale *= 10.0^^e; + } + else + return -1; // string was not taken? + } + + if (sep == '/') + invert = true; + } + this = r; + return len; + } + + import urt.string.format : FormatArg; + ptrdiff_t format_unit(char[] buffer, out float pre_scale, bool allow_unit_scale = true) const pure + { + assert(allow_unit_scale == true, "TODO: support for no-scale formatting (require pre-scale)"); + pre_scale = 1; + + if (!unit.pack) + { + if (siScale && exp == -2) + { + if (buffer.ptr) + { + if (buffer.length == 0) + return -1; + buffer[0] = '%'; + } + return 1; + } + else if (siScale && exp == -3) + { + enum pm_len = "‰".length; + if (buffer.ptr) + { + if (buffer.length < pm_len) + return -1; + buffer[0..pm_len] = "‰"; + } + return pm_len; + } + else + assert(false, "TODO!"); // how (or should?) we encode a scale as a unit type? + } + + size_t len = 0; + if (siScale) + { + int x = exp; + if (x != 0) + { + // for scale factors between SI units, we'll normalise to the next higher unit... + int y = (x + 33) % 3; + if (y != 0) + { + if (y == 1) + { + if (buffer.ptr) + { + if (buffer.length < 2) + return -1; + buffer[0..2] = "10"; + } + --x; + len += 2; + } + else + { + if (buffer.ptr) + { + if (buffer.length < 3) + return -1; + buffer[0..3] = "100"; + } + x -= 2; + len += 3; + } + } + assert(x >= -30, "TODO: handle this very small case"); + + if (x != 0) + { + if (buffer.ptr) + { + if (buffer.length <= len) + return -1; + buffer[len] = "qryzafpnum kMGTPEZYRQ"[x/3 + 10]; + } + ++len; + } + } + + if (const string* name = unit in unitNames) + { + if (buffer.ptr) + { + if (buffer.length < len + name.length) + return -1; + buffer[len .. len + name.length] = *name; + } + len += name.length; + } + else + { + // synth a unit name... + assert(false, "TODO"); + } + } + else + { + if (const string* name = this in scaledUnitNames) + { + if (buffer.ptr) + { + if (buffer.length < len + name.length) + return -1; + buffer[len .. len + name.length] = *name; + } + len += name.length; + } + else + { + // what now? + assert(false, "TODO"); + } + } + return len; + } + + ptrdiff_t toString(char[] buffer, const(char)[], const(FormatArg)[]) const pure + { + float pre_scale; + ptrdiff_t r = format_unit(buffer, pre_scale, true); + if (pre_scale != 1) + return -1; + return r; + } + + ptrdiff_t fromString(const(char)[] s) pure + { + float scale; + ptrdiff_t r = parse_unit(s, scale); + if (scale != 1) + return -1; + return r; + } + size_t toHash() const pure => pack; + version (Windows) + { + auto __debugOverview() + { + import urt.mem; + char[] buffer = debug_alloc!char(32); + ptrdiff_t len = toString(buffer, null, null); + return buffer[0 .. len]; + } + } + package: this(uint pack) pure { @@ -649,9 +909,9 @@ immutable double[16][2] scaleFactor = [ [ PI/180, // Degrees double.nan // extended... ], [ - 1/60, // Minute - 1/3600, // Hour - 1/86400, // Day + 1/60.0, // Minute + 1/3600.0, // Hour + 1/86400.0, // Day 1/0.0254, // Inch 1/0.3048, // Foot 1/1609.344, // Mile @@ -688,39 +948,41 @@ immutable double[4] tempOffsets = [ (-273.15*9)/5 + 32 // K -> F ]; -immutable string[ScaledUnit] unitNames = [ +immutable string[Unit] unitNames = [ + Metre : "m", + Metre^^2 : "m²", + Metre^^3 : "m³", + Kilogram : "kg", + Second : "s", + Ampere : "A", + Kelvin : "K", + Candela : "cd", + Radian : "rad", - // ⁰¹⁻²³⁴⁵⁶⁷⁸⁹·° + // derived units + Newton : "N", + Pascal : "Pa", + Joule : "J", + Watt : "W", + Coulomb : "C", + Volt : "V", + Ohm : "Ω", + Farad : "F", + Siemens : "S", + Weber : "Wb", + Tesla : "T", + Henry : "H", + Lumen : "lm", + Lux : "lx", +]; - // base units - ScaledUnit() : "", - ScaledUnit(Metre) : "m", - ScaledUnit(Kilogram) : "kg", - ScaledUnit(Second) : "s", - ScaledUnit(Ampere) : "A", - ScaledUnit(Kelvin) : "°K", - ScaledUnit(Candela) : "cd", - ScaledUnit(Radian) : "rad", +immutable string[ScaledUnit] scaledUnitNames = [ + Minute : "min", +// Minute : "mins", + Hour : "hr", +// Hour : "hrs", + Day : "day", - // derived units - ScaledUnit(SquareMetre) : "m²", - ScaledUnit(CubicMetre) : "m³", - ScaledUnit(Newton) : "N", - ScaledUnit(Pascal) : "Pa", - ScaledUnit(Joule) : "J", - ScaledUnit(Watt) : "W", - ScaledUnit(Coulomb) : "C", - ScaledUnit(Volt) : "V", - ScaledUnit(Ohm) : "Ω", - ScaledUnit(Farad) : "F", - ScaledUnit(Siemens) : "S", - ScaledUnit(Weber) : "Wb", - ScaledUnit(Tesla) : "T", - ScaledUnit(Henry) : "H", - ScaledUnit(Lumen) : "lm", - ScaledUnit(Lux) : "lx", - - // scaled units Inch : "in", Foot : "ft", Mile : "mi", @@ -745,3 +1007,143 @@ immutable string[ScaledUnit] unitNames = [ AmpereHour : "Ah", WattHour : "Wh", ]; + +immutable Unit[string] unitMap = [ + // base units + "m" : Metre, + "kg" : Kilogram, + "s" : Second, + "A" : Ampere, + "K" : Kelvin, + "cd" : Candela, + "rad" : Radian, + + // derived units + "N" : Newton, + "Pa" : Pascal, + "J" : Joule, + "W" : Watt, + "C" : Coulomb, + "V" : Volt, + "Ω" : Ohm, + "F" : Farad, + "S" : Siemens, + "Wb" : Weber, + "T" : Tesla, + "H" : Henry, + "lm" : Lumen, + "lx" : Lux, + + // questionable... :/ + "VA" : Watt, + "var" : Watt, +]; + +immutable ScaledUnit[string] noScaleUnitMap = [ + "min" : Minute, + "mins" : Minute, + "hr" : Hour, + "hrs" : Hour, + "day" : Day, + "days" : Day, + "'" : Inch, + "in" : Inch, + "\"" : Foot, + "ft" : Foot, + "mi" : Mile, + "oz" : Ounce, + // TODO: us/uk floz/gallon? + "lb" : Pound, + "°" : Degree, + "deg" : Degree, + "°C" : Celsius, + "°F" : Fahrenheit, + "cy" : Cycle, + "psi" : PSI, + "%" : Percent, + "‰" : Permille, + "‱" : ScaledUnit(Unit(), -4), + "ppm" : ScaledUnit(Unit(), -6), +]; + +// these can have SI prefixes +immutable ScaledUnit[string] scaledUnitMap = [ + "l" : Litre, + "g" : Gram, +]; + +// these can have SI prefixes, but scale must be converted to coefficient +immutable ScaledUnit[string] noScaleUnitMapSI = [ + "Ah" : AmpereHour, + "Wh" : WattHour, + "Hz" : Hertz, + + // questionable... :/ + "VAh" : WattHour, + "varh" : WattHour, +]; + +int take_power(ref const(char)[] s) pure +{ + size_t e = s.findFirst('^'); + if (e < s.length) + { + const(char)[] p = s[e+1..$]; + s = s[0..e]; + if (s.length == 0 || p.length == 0) + return 0; + if (p[0] == '-') + { + if (p.length != 2 || uint(p[2] - '0') > 4) + return 0; + return -(p[2] - '0'); + } + if (p.length != 1 || uint(p[1] - '0') > 4) + return 0; + return p[2] - '0'; + } + else if (s.length > 2) + { + if (s[$-2..$] == "¹") + { + if (s.length > 5 && s[$-5..$-2] == "⁻") + { + s = s[0..$-5]; + return -1; + } + s = s[0..$-2]; + return 1; + } + if (s[$-2..$] == "²") + { + if (s.length > 5 && s[$-5..$-2] == "⁻") + { + s = s[0..$-5]; + return -2; + } + s = s[0..$-2]; + return 2; + } + if (s[$-2..$] == "³") + { + if (s.length > 5 && s[$-5..$-2] == "⁻") + { + s = s[0..$-5]; + return -3; + } + s = s[0..$-2]; + return 3; + } + } + else if (s.length > 3 && s[$-3..$] == "⁴") + { + if (s.length > 6 && s[$-6..$-3] == "⁻") + { + s = s[0..$-6]; + return -4; + } + s = s[0..$-3]; + return 4; + } + return 1; +} diff --git a/src/urt/socket.d b/src/urt/socket.d index 800f72d..3a4d6a5 100644 --- a/src/urt/socket.d +++ b/src/urt/socket.d @@ -6,13 +6,53 @@ public import urt.mem; public import urt.result; public import urt.time; -version (Windows) +version (BareMetal) + version = SocketCallbacks; + +version (SocketCallbacks) +{ + alias SocketHandle = int; + enum INVALID_SOCKET = -1; + + struct SocketBackend + { + nothrow @nogc: + SocketResult function(AddressFamily, SocketType, Protocol, out Socket) create; + SocketResult function(Socket) close; + SocketResult function(Socket, ref const InetAddress) bind; + SocketResult function(Socket, uint) listen; + SocketResult function(Socket, ref const InetAddress) connect; + SocketResult function(Socket, out Socket, InetAddress*) accept; + SocketResult function(Socket, SocketShutdownMode) shutdown; + SocketResult function(Socket, const(InetAddress)*, MsgFlags, const(void[])[], size_t*) sendmsg; + SocketResult function(Socket, void[], MsgFlags, size_t*) recv; + SocketResult function(Socket, void[], MsgFlags, InetAddress*, size_t*) recvfrom; + SocketResult function(Socket, out size_t) pending; + SocketResult function(PollFd[], Duration, out uint) poll; + SocketResult function(Socket, SocketOption, const(void)*, size_t) set_option; + SocketResult function(Socket, SocketOption, void*, size_t) get_option; + SocketResult function(Socket, out InetAddress) get_peer_name; + SocketResult function(Socket, out InetAddress) get_socket_name; + SocketResult function(char*, size_t) get_hostname; + SocketResult function(const(char)[], const(char)[], AddressInfo*, AddressInfoResolver*) get_address_info; + bool function(AddressInfoResolver*, out AddressInfo) next_address; + void function(AddressInfoResolver*) free_address_info; + } + + __gshared SocketBackend* _socket_backend; + + void register_socket_backend(SocketBackend* backend) nothrow @nogc + { + _socket_backend = backend; + } +} +else version (Windows) { // TODO: this is in core.sys.windows.winsock2; why do I need it here? pragma(lib, "ws2_32"); - import core.sys.windows.windows; - import core.sys.windows.winsock2 : + import urt.internal.sys.windows; + import urt.internal.sys.windows.winsock2 : _bind = bind, _listen = listen, _connect = connect, _accept = accept, _send = send, _sendto = sendto, _recv = recv, _recvfrom = recvfrom, _shutdown = shutdown; @@ -20,23 +60,23 @@ version (Windows) version = HasIPv6; alias SocketHandle = SOCKET; + + enum IPV6_RECVPKTINFO = 49; + enum IPV6_PKTINFO = 50; } else version (Posix) { - import core.stdc.errno; - import core.sys.posix.fcntl; - import core.sys.posix.poll; - import core.sys.posix.unistd : close, gethostname; import urt.internal.os; // use ImportC to import system C headers... - import core.sys.posix.netinet.in_ : sockaddr_in6; alias _bind = urt.internal.os.bind, _listen = urt.internal.os.listen, _connect = urt.internal.os.connect, - _accept = urt.internal.os.accept, _send = urt.internal.os.send, _sendto = urt.internal.os.sendto, - _recv = urt.internal.os.recv, _recvfrom = urt.internal.os.recvfrom, _shutdown = urt.internal.os.shutdown; - alias _poll = core.sys.posix.poll.poll; + _accept = urt.internal.os.accept, _send = urt.internal.os.send, _sendto = urt.internal.os.sendto, _sendmsg = urt.internal.os.sendmsg, + _recv = urt.internal.os.recv, _recvfrom = urt.internal.os.recvfrom, _shutdown = urt.internal.os.shutdown, + _close = urt.internal.os.close, _poll = urt.internal.os.poll; + version = BSDSockets; version = HasUnixSocket; version = HasIPv6; + version = Errno; alias SocketHandle = int; enum INVALID_SOCKET = -1; @@ -49,6 +89,143 @@ else version (Posix) enum AF_BRIDGE = 7; // Multiprotocol bridge enum AF_INET6 = 10; // IP version 6 } +else version (lwIP) +{ + // lwIP BSD socket API -- constants and structs match lwIP defaults + version = BSDSockets; + version = HasIPv6; + version = Errno; + + alias SocketHandle = int; + enum INVALID_SOCKET = -1; + + enum AF_UNSPEC = 0; + enum AF_UNIX = 1; + enum AF_INET = 2; + enum AF_INET6 = 10; + + enum SOCK_STREAM = 1; + enum SOCK_DGRAM = 2; + enum SOCK_RAW = 3; + + enum IPPROTO_IP = 0; + enum IPPROTO_ICMP = 1; + enum IPPROTO_TCP = 6; + enum IPPROTO_UDP = 17; + enum IPPROTO_IPV6 = 41; + enum IPPROTO_RAW = 255; + + enum SOL_SOCKET = 0xFFF; + + enum MSG_PEEK = 0x01; + + enum SHUT_RD = 0; + enum SHUT_WR = 1; + enum SHUT_RDWR = 2; + + // fcntl constants for non-blocking mode + enum F_GETFL = 3; + enum F_SETFL = 4; + enum O_NONBLOCK = 1; + + // socket options + enum SO_REUSEADDR = 0x0004; + enum SO_KEEPALIVE = 0x0008; + enum SO_LINGER = 0x0080; + enum SO_SNDBUF = 0x1001; + enum SO_RCVBUF = 0x1002; + enum SO_ERROR = 0x1007; + enum TCP_NODELAY = 0x01; + enum TCP_KEEPIDLE = 0x03; + enum TCP_KEEPINTVL = 0x04; + enum TCP_KEEPCNT = 0x05; + enum IP_ADD_MEMBERSHIP = 3; + enum IP_MULTICAST_TTL = 5; + enum IP_MULTICAST_LOOP = 7; + + alias socklen_t = uint; + struct in_addr { uint s_addr; } + struct in6_addr { ubyte[16] s6_addr; } + struct sockaddr { ubyte sa_len; ubyte sa_family; ubyte[14] sa_data; } + struct sockaddr_in { ubyte sin_len; ubyte sin_family; ushort sin_port; in_addr sin_addr; ubyte[8] sin_zero; } + struct sockaddr_in6 { ubyte sin6_len; ubyte sin6_family; ushort sin6_port; uint sin6_flowinfo; in6_addr sin6_addr; uint sin6_scope_id; } + struct sockaddr_storage { ubyte s2_len; ubyte ss_family; ubyte[2] s2_data1; uint[3] s2_data2; uint[3] s2_data3; } + struct linger { int l_onoff; int l_linger; } + struct ip_mreq { in_addr imr_multiaddr; in_addr imr_interface; } + struct iovec { void* iov_base; size_t iov_len; } + struct msghdr { void* msg_name; socklen_t msg_namelen; iovec* msg_iov; int msg_iovlen; void* msg_control; socklen_t msg_controllen; int msg_flags; } + + enum POLLRDNORM = 0x10; + enum POLLWRNORM = 0x80; + enum POLLERR = 0x04; + enum POLLHUP = 0x200; + enum POLLNVAL = 0x08; + + enum AI_PASSIVE = 0x01; + enum AI_CANONNAME = 0x02; + enum AI_NUMERICHOST = 0x04; + enum AI_NUMERICSERV = 0x08; + enum AI_V4MAPPED = 0x10; + enum AI_ALL = 0x20; + enum AI_ADDRCONFIG = 0x40; + + struct pollfd { int fd; short events; short revents; } + struct addrinfo { int ai_flags; int ai_family; int ai_socktype; int ai_protocol; socklen_t ai_addrlen; sockaddr* ai_addr; char* ai_canonname; addrinfo* ai_next; } + + // lwIP socket functions -- actual symbol names are lwip_* prefixed + extern(C) nothrow @nogc + { + int lwip_poll(pollfd*, uint, int); + SocketHandle lwip_socket(int, int, int); + int lwip_bind(SocketHandle, const(sockaddr)*, socklen_t); + int lwip_listen(SocketHandle, int); + int lwip_connect(SocketHandle, const(sockaddr)*, socklen_t); + SocketHandle lwip_accept(SocketHandle, sockaddr*, socklen_t*); + ptrdiff_t lwip_send(SocketHandle, const(void)*, size_t, int); + ptrdiff_t lwip_sendto(SocketHandle, const(void)*, size_t, int, const(sockaddr)*, socklen_t); + ptrdiff_t lwip_sendmsg(SocketHandle, const(msghdr)*, int); + ptrdiff_t lwip_recv(SocketHandle, void*, size_t, int); + ptrdiff_t lwip_recvfrom(SocketHandle, void*, size_t, int, sockaddr*, socklen_t*); + int lwip_shutdown(SocketHandle, int); + int lwip_setsockopt(SocketHandle, int, int, const(void)*, socklen_t); + int lwip_getsockopt(SocketHandle, int, int, void*, socklen_t*); + int lwip_getsockname(SocketHandle, sockaddr*, socklen_t*); + int lwip_getpeername(SocketHandle, sockaddr*, socklen_t*); + int ow_lwip_getaddrinfo(const(char)*, const(char)*, const(addrinfo)*, addrinfo**); + void ow_lwip_freeaddrinfo(addrinfo*); + int lwip_close(int); + int lwip_fcntl(int, int, int); + int lwip_ioctl(int, int, void*); + } + + enum FIONBIO = 0x8004667e; // _IOW('f', 126, unsigned long) + enum FIONREAD = 0x4004667f; // _IOR('f', 127, unsigned long) + + // Aliases so the rest of the codebase uses POSIX names + alias _poll = lwip_poll; + alias socket = lwip_socket; + alias _bind = lwip_bind; + alias _listen = lwip_listen; + alias _connect = lwip_connect; + alias _accept = lwip_accept; + alias _send = lwip_send; + alias _sendto = lwip_sendto; + alias _sendmsg = lwip_sendmsg; + alias _recv = lwip_recv; + alias _recvfrom = lwip_recvfrom; + alias _shutdown = lwip_shutdown; + alias setsockopt = lwip_setsockopt; + alias getsockopt = lwip_getsockopt; + alias getsockname = lwip_getsockname; + alias getpeername = lwip_getpeername; + alias getaddrinfo = ow_lwip_getaddrinfo; + alias freeaddrinfo = ow_lwip_freeaddrinfo; + alias _close = lwip_close; + alias fcntl = lwip_fcntl; + alias ioctlsocket = lwip_ioctl; + + int gethostname(char*, size_t) nothrow @nogc { return -1; } +} else static assert(false, "Platform not supported"); @@ -57,119 +234,119 @@ nothrow @nogc: enum SocketResult { - Success, - Failure, - WouldBlock, - NoBuffer, - NetworkDown, - ConnectionRefused, - ConnectionReset, - ConnectionAborted, - ConnectionClosed, - Interrupted, - InvalidSocket, - InvalidArgument, + success, + failure, + would_block, + no_buffer, + network_down, + connection_refused, + connection_reset, + connection_aborted, + connection_closed, + interrupted, + invalid_socket, + invalid_argument, } enum SocketType : byte { - Unknown = -1, - Stream = 0, - Datagram, - Raw, + unknown = -1, + stream = 0, + datagram, + raw, } enum Protocol : byte { - Unknown = -1, - TCP = 0, - UDP, - IP, - ICMP, - Raw, + unknown = -1, + tcp = 0, + udp, + ip, + icmp, + raw, } enum SocketShutdownMode : ubyte { - Read, - Write, - ReadWrite + read, + write, + read_write } enum SocketOption : ubyte { // not traditionally a 'socket option', but this is way more convenient - NonBlocking, + non_blocking, // Socket options - KeepAlive, - Linger, - RandomizePort, - SendBufferLength, - RecvBufferLength, - ReuseAddress, - NoSigPipe, - Error, + keep_alive, + linger, + randomize_port, + send_buffer_length, + recv_buffer_length, + reuse_address, + no_sig_pPipe, + error, // IP options - FirstIpOption, - Multicast = FirstIpOption, - MulticastLoopback, - MulticastTTL, + first_ip_option, + multicast = first_ip_option, + multicast_loopback, + multicast_ttl, + ip_pktinfo, // IPv6 options - FirstIpv6Option, + first_ipv6_option, + ipv6_pktinfo = first_ipv6_option, // ICMP options - FirstIcmpOption = FirstIpv6Option, + first_icmp_option, // ICMPv6 options - FirstIcmpv6Option = FirstIcmpOption, + first_icmpv6_option = first_icmp_option, // TCP options - FirstTcpOption = FirstIcmpv6Option, - TCP_KeepIdle = FirstTcpOption, - TCP_KeepIntvl, - TCP_KeepCnt, - TCP_KeepAlive, // Apple: similar to KeepIdle - TCP_NoDelay, - + first_tcp_option = first_icmpv6_option, + tcp_keep_idle = first_tcp_option, + tcp_keep_intvl, + tcp_keep_cnt, + tcp_keep_alive, // Apple: similar to KeepIdle + tcp_no_delay, // UDP options - FirstUdpOption, + first_udp_option, } enum MsgFlags : ubyte { - None = 0, - OOB = 1 << 0, - Peek = 1 << 1, - Confirm = 1 << 2, - NoSig = 1 << 3, + none = 0, + peek = 1 << 0, + confirm = 1 << 1, + no_sig = 1 << 2, //... } enum AddressInfoFlags : ubyte { - None = 0, - Passive = 1 << 0, - CanonName = 1 << 1, - NumericHost = 1 << 2, - NumericServ = 1 << 3, - All = 1 << 4, - AddrConfig = 1 << 5, - V4Mapped = 1 << 6, - FQDN = 1 << 7, + none = 0, + passive = 1 << 0, + canon_name = 1 << 1, + numeric_host = 1 << 2, + numeric_serv = 1 << 3, + all = 1 << 4, + addr_config = 1 << 5, + v4_mapped = 1 << 6, + fqdn = 1 << 7, } enum PollEvents : ubyte { - None = 0, - Read = 1 << 0, - Write = 1 << 1, - Error = 1 << 2, - HangUp = 1 << 3, - Invalid = 1 << 4, + none = 0, + read = 1 << 0, + write = 1 << 1, + error = 1 << 2, + hangup = 1 << 3, + invalid = 1 << 4, } @@ -189,483 +366,839 @@ private: Result create_socket(AddressFamily af, SocketType type, Protocol proto, out Socket socket) { - version (HasUnixSocket) {} else - assert(af != AddressFamily.Unix, "Unix sockets not supported on this platform!"); + version (SocketCallbacks) + return Result(_socket_backend.create(af, type, proto, socket)); + else + { + version (HasUnixSocket) {} else + assert(af != AddressFamily.unix, "Unix sockets not supported on this platform!"); + + socket.handle = .socket(s_addressFamily[af], s_socketType[type], s_protocol[proto]); + if (socket == Socket.invalid) + return socket_getlasterror(); - socket.handle = .socket(s_addressFamily[af], s_socketType[type], s_protocol[proto]); - if (socket == Socket.invalid) - return socket_getlasterror(); - return Result.Success; + return Result.success; + } } Result close(Socket socket) { - version (Windows) - int result = closesocket(socket.handle); - else version (Posix) - int result = close(socket.handle); + version (SocketCallbacks) + return Result(_socket_backend.close(socket)); else - assert(false, "Not implemented!"); - if (result < 0) - return socket_getlasterror(); - -// { -// LockGuard lock(s_noSignalMut); -// s_noSignal.Erase(socket); -// } - - return Result.Success; + { + int result; + version (Windows) + result = closesocket(socket.handle); + else version (BSDSockets) + result = _close(socket.handle); + else + assert(false, "Not implemented!"); + if (result < 0) + return socket_getlasterror(); + return Result.success; + } } Result shutdown(Socket socket, SocketShutdownMode how) { - int t = int(how); - switch (how) + version (SocketCallbacks) + return Result(_socket_backend.shutdown(socket, how)); + else { - version (Windows) - { - case SocketShutdownMode.Read: t = SD_RECEIVE; break; - case SocketShutdownMode.Write: t = SD_SEND; break; - case SocketShutdownMode.ReadWrite: t = SD_BOTH; break; - } - else version (Posix) + int t = int(how); + switch (how) { - case SocketShutdownMode.Read: t = SHUT_RD; break; - case SocketShutdownMode.Write: t = SHUT_WR; break; - case SocketShutdownMode.ReadWrite: t = SHUT_RDWR; break; + version (Windows) + { + case SocketShutdownMode.read: t = SD_RECEIVE; break; + case SocketShutdownMode.write: t = SD_SEND; break; + case SocketShutdownMode.read_write: t = SD_BOTH; break; + } + else version (BSDSockets) + { + case SocketShutdownMode.read: t = SHUT_RD; break; + case SocketShutdownMode.write: t = SHUT_WR; break; + case SocketShutdownMode.read_write: t = SHUT_RDWR; break; + } + default: + assert(false, "Invalid `how`"); } - default: - assert(false, "Invalid `how`"); - } - if (_shutdown(socket.handle, t) < 0) - return socket_getlasterror(); - return Result.Success; + if (_shutdown(socket.handle, t) < 0) + return socket_getlasterror(); + return Result.success; + } } Result bind(Socket socket, ref const InetAddress address) { - ubyte[512] buffer = void; - size_t addrLen; - sockaddr* sockAddr = make_sockaddr(address, buffer, addrLen); - assert(sockAddr, "Invalid socket address"); + version (SocketCallbacks) + return Result(_socket_backend.bind(socket, address)); + else + { + ubyte[512] buffer = void; + size_t addr_len; + sockaddr* sock_addr = make_sockaddr(address, buffer, addr_len); + assert(sock_addr, "Invalid socket address"); - if (_bind(socket.handle, sockAddr, cast(int)addrLen) < 0) - return socket_getlasterror(); - return Result.Success; + if (_bind(socket.handle, sock_addr, cast(int)addr_len) < 0) + return socket_getlasterror(); + return Result.success; + } } Result listen(Socket socket, uint backlog = -1) { - if (_listen(socket.handle, int(backlog & 0x7FFFFFFF)) < 0) - return socket_getlasterror(); - return Result.Success; + version (SocketCallbacks) + return Result(_socket_backend.listen(socket, backlog)); + else + { + if (_listen(socket.handle, int(backlog & 0x7FFFFFFF)) < 0) + return socket_getlasterror(); + return Result.success; + } } Result connect(Socket socket, ref const InetAddress address) { - ubyte[512] buffer = void; - size_t addrLen; - sockaddr* sockAddr = make_sockaddr(address, buffer, addrLen); - assert(sockAddr, "Invalid socket address"); + version (SocketCallbacks) + return Result(_socket_backend.connect(socket, address)); + else + { + ubyte[512] buffer = void; + size_t addr_len; + sockaddr* sock_addr = make_sockaddr(address, buffer, addr_len); + assert(sock_addr, "Invalid socket address"); - if (_connect(socket.handle, sockAddr, cast(int)addrLen) < 0) - return socket_getlasterror(); - return Result.Success; + if (_connect(socket.handle, sock_addr, cast(int)addr_len) < 0) + return socket_getlasterror(); + return Result.success; + } } -Result accept(Socket socket, out Socket connection, InetAddress* connectingSocketAddress = null) +Result accept(Socket socket, out Socket connection, InetAddress* remote_address = null, InetAddress* local_address = null) { - char[sockaddr_storage.sizeof] buffer = void; - sockaddr* addr = cast(sockaddr*)buffer.ptr; - socklen_t size = buffer.sizeof; + version (SocketCallbacks) + { + Result r = Result(_socket_backend.accept(socket, connection, remote_address)); + if (!r) + return r; + if (local_address) + return get_socket_name(connection, *local_address); + return Result.success; + } + else + { + char[sockaddr_storage.sizeof] buffer = void; + sockaddr* addr = cast(sockaddr*)buffer.ptr; + socklen_t size = buffer.sizeof; - connection.handle = _accept(socket.handle, addr, &size); - if (connection == Socket.invalid) - return socket_getlasterror(); - else if (connectingSocketAddress) - *connectingSocketAddress = make_InetAddress(addr); - return Result.Success; + connection.handle = _accept(socket.handle, addr, &size); + if (connection == Socket.invalid) + return socket_getlasterror(); + if (remote_address) + *remote_address = make_InetAddress(addr); + if (local_address) + { + if (getsockname(connection.handle, addr, &size) < 0) + return socket_getlasterror(); + *local_address = make_InetAddress(addr); + } + // platforms are inconsistent regarding whether accept inherits the listening socket's blocking mode + // for consistentency, we always set blocking on the accepted socket + connection.set_socket_option(SocketOption.non_blocking, false); + return Result.success; + } } -Result send(Socket socket, const(void)[] message, MsgFlags flags = MsgFlags.None, size_t* bytesSent = null) +Result send(Socket socket, const(void)[] message, MsgFlags flags = MsgFlags.none, size_t* bytes_sent = null) { - Result r = Result.Success; + version (SocketCallbacks) + return sendmsg(socket, null, flags, null, bytes_sent, (&message)[0..1]); + else + return send(socket, flags, bytes_sent, (&message)[0..1]); +} - ptrdiff_t sent = _send(socket.handle, message.ptr, cast(int)message.length, map_message_flags(flags)); - if (sent < 0) +Result send(Socket socket, MsgFlags flags, size_t* bytes_sent, const void[][] buffers...) +{ + version (SocketCallbacks) + return sendmsg(socket, null, flags, null, bytes_sent, buffers); + else version (Windows) { - r = socket_getlasterror(); - sent = 0; + uint sent; + WSABUF[32] bufs = void; + assert(buffers.length <= bufs.length, "Too many buffers!"); + + uint n = 0; + foreach(buffer; buffers) + { + if (buffer.length == 0) + continue; + assert(buffer.length <= uint.max, "Buffer too large!"); + bufs[n].buf = cast(char*)buffer.ptr; + bufs[n++].len = cast(uint)buffer.length; + } + if (n > 0) + { + int rc = WSASend(socket.handle, bufs.ptr, n, &sent, /+map_message_flags(flags)+/ 0, null, null); // there are no meaningful flags on Windows + if (rc == SOCKET_ERROR) + return socket_getlasterror(); + } + if (bytes_sent) + *bytes_sent = sent; + return Result.success; } - if (bytesSent) - *bytesSent = sent; - return r; + else + return sendmsg(socket, null, flags, null, bytes_sent, buffers); } -Result sendto(Socket socket, const(void)[] message, MsgFlags flags = MsgFlags.None, const InetAddress* address = null, size_t* bytesSent = null) +Result sendto(Socket socket, const(void)[] message, MsgFlags flags = MsgFlags.none, const InetAddress* address = null, size_t* bytes_sent = null) { - ubyte[sockaddr_storage.sizeof] tmp = void; - size_t addrLen; - sockaddr* sockAddr = null; - if (address) - { - sockAddr = make_sockaddr(*address, tmp, addrLen); - assert(sockAddr, "Invalid socket address"); - } + version (SocketCallbacks) + return sendmsg(socket, address, flags, null, bytes_sent, (&message)[0..1]); + else version (Windows) + return sendto(socket, address, bytes_sent, (&message)[0..1]); + else + return sendmsg(socket, address, flags, null, bytes_sent, (&message)[0..1]); +} - Result r = Result.Success; - ptrdiff_t sent = _sendto(socket.handle, message.ptr, cast(int)message.length, map_message_flags(flags), sockAddr, cast(int)addrLen); - if (sent < 0) +Result sendto(Socket socket, const InetAddress* address, size_t* bytes_sent, const void[][] buffers...) +{ + version (SocketCallbacks) + return sendmsg(socket, address, MsgFlags.none, null, bytes_sent, buffers); + else version (Windows) { - r = socket_getlasterror(); - sent = 0; + ubyte[sockaddr_storage.sizeof] tmp = void; + size_t addr_len; + sockaddr* sock_addr = null; + if (address) + { + sock_addr = make_sockaddr(*address, tmp, addr_len); + assert(sock_addr, "Invalid socket address"); + } + + uint sent; + WSABUF[32] bufs = void; + assert(buffers.length <= bufs.length, "Too many buffers!"); + + uint n = 0; + foreach(buffer; buffers) + { + if (buffer.length == 0) + continue; + assert(buffer.length <= uint.max, "Buffer too large!"); + bufs[n].buf = cast(char*)buffer.ptr; + bufs[n++].len = cast(uint)buffer.length; + } + if (n > 0) + { + int r = WSASendTo(socket.handle, bufs.ptr, n, &sent, /+map_message_flags(flags)+/ 0, sock_addr, cast(int)addr_len, null, null); // there are no meaningful flags on Windows + if (r == SOCKET_ERROR) + return socket_getlasterror(); + } + if (bytes_sent) + *bytes_sent = sent; + return Result.success; } - if (bytesSent) - *bytesSent = sent; - return r; + else + return sendmsg(socket, address, MsgFlags.none, null, bytes_sent, buffers); } -Result recv(Socket socket, void[] buffer, MsgFlags flags = MsgFlags.None, size_t* bytesReceived) +Result sendmsg(Socket socket, const InetAddress* address, MsgFlags flags, const(void)[] control, size_t* bytes_sent, const void[][] buffers) { - Result r = Result.Success; - ptrdiff_t bytes = _recv(socket.handle, buffer.ptr, cast(int)buffer.length, map_message_flags(flags)); - if (bytes > 0) - *bytesReceived = bytes; + version (SocketCallbacks) + return Result(_socket_backend.sendmsg(socket, address, flags, buffers, bytes_sent)); else { - *bytesReceived = 0; - if (bytes == 0) + ubyte[sockaddr_storage.sizeof] tmp = void; + size_t addr_len; + sockaddr* sock_addr = null; + if (address) + { + sock_addr = make_sockaddr(*address, tmp, addr_len); + assert(sock_addr, "Invalid socket address"); + } + + version (Windows) { - // if we request 0 bytes, we receive 0 bytes, and it doesn't imply end-of-stream - if (buffer.length > 0) + uint sent; + WSAMSG msg; + WSABUF[32] bufs = void; + assert(buffers.length <= bufs.length, "Too many buffers!"); + + uint n = 0; + foreach(buffer; buffers) { - // a graceful disconnection occurred - // TODO: !!! - r = ConnectionClosedResult; -// r = InternalResult(InternalCode.RemoteDisconnected); + if (buffer.length == 0) + continue; + assert(buffer.length <= uint.max, "Buffer too large!"); + bufs[n].buf = cast(char*)buffer.ptr; + bufs[n++].len = cast(uint)buffer.length; + } + if (n > 0) + { + msg.name = sock_addr; + msg.namelen = cast(int)addr_len; + msg.lpBuffers = bufs.ptr; + msg.dwBufferCount = n; + msg.Control.buf = cast(char*)control.ptr; + msg.Control.len = cast(uint)control.length; + msg.dwFlags = 0; + + int rc = WSASendMsg(socket.handle, &msg, /+map_message_flags(flags)+/ 0, &sent, null, null); // there are no meaningful flags on Windows + if (rc == SOCKET_ERROR) + return socket_getlasterror(); } } else { - Result error = socket_getlasterror(); - // TODO: Do we want a better way to distinguish between receiving a 0-length packet vs no-data (which looks like an error)? - // Is a zero-length packet possible to detect in TCP streams? Makes more sense for recvfrom... - SocketResult sr = get_SocketResult(error); - if (sr != SocketResult.WouldBlock) - r = error; + ptrdiff_t sent; + msghdr hdr; + iovec[32] iov = void; + assert(buffers.length <= iov.length, "Too many buffers!"); + + size_t n = 0; + foreach(buffer; buffers) + { + if (buffer.length == 0) + continue; + assert(buffer.length <= uint.max, "Buffer too large!"); + iov[n].iov_base = cast(void*)buffer.ptr; + iov[n++].iov_len = buffer.length; + } + if (n > 0) + { + hdr.msg_name = sock_addr; + hdr.msg_namelen = cast(socklen_t)addr_len; + hdr.msg_iov = iov.ptr; + hdr.msg_iovlen = cast(typeof(hdr.msg_iovlen))n; + hdr.msg_control = cast(void*)control.ptr; + hdr.msg_controllen = cast(typeof(hdr.msg_controllen))control.length; + hdr.msg_flags = 0; + + sent = _sendmsg(socket.handle, &hdr, map_message_flags(flags)); + if (sent < 0) + return socket_getlasterror(); + } } + if (bytes_sent) + *bytes_sent = sent; + return Result.success; } - return r; } -Result recvfrom(Socket socket, void[] buffer, MsgFlags flags = MsgFlags.None, InetAddress* senderAddress = null, size_t* bytesReceived) +Result pending(Socket socket, out size_t bytes_available) { - char[sockaddr_storage.sizeof] addrBuffer = void; - sockaddr* addr = cast(sockaddr*)addrBuffer.ptr; - socklen_t size = addrBuffer.sizeof; - - Result r = Result.Success; - ptrdiff_t bytes = _recvfrom(socket.handle, buffer.ptr, cast(int)buffer.length, map_message_flags(flags), addr, &size); - if (bytes >= 0) - *bytesReceived = bytes; + version (SocketCallbacks) + return Result(_socket_backend.pending(socket, bytes_available)); else { - *bytesReceived = 0; - - Result error = socket_getlasterror(); - SocketResult sockRes = get_SocketResult(error); - if (sockRes != SocketResult.NoBuffer && // buffers full - sockRes != SocketResult.ConnectionRefused && // posix error - sockRes != SocketResult.ConnectionReset) // !!! windows may report this error, but it appears to mean something different on posix - r = error; + version (Windows) + { + import urt.internal.sys.windows.winsock2 : ioctlsocket, FIONREAD; + uint avail; + if (ioctlsocket(socket.handle, FIONREAD, &avail) != 0) + return socket_getlasterror(); + bytes_available = avail; + } + else version (Posix) + { + import urt.internal.os : ioctl; + int avail; + if (ioctl(socket.handle, 0x541B, &avail) < 0) // FIONREAD + return socket_getlasterror(); + bytes_available = avail; + } + else version (lwIP) + { + uint avail; + if (ioctlsocket(socket.handle, FIONREAD, &avail) != 0) + return socket_getlasterror(); + bytes_available = avail; + } + else + static assert(false, "Platform not supported"); + return Result.success; } - if (r && senderAddress) - *senderAddress = make_InetAddress(addr); - return r; } -Result set_socket_option(Socket socket, SocketOption option, const(void)* optval, size_t optlen) +Result recv(Socket socket, void[] buffer, MsgFlags flags = MsgFlags.none, size_t* bytes_received) { - Result r = Result.Success; - - // check the option appears to be the proper datatype - const OptInfo* optInfo = &s_socketOptions[option]; - assert(optInfo.rtType != OptType.Unsupported, "Socket option is unsupported on this platform!"); - assert(optlen == s_optTypeRtSize[optInfo.rtType], "Socket option has incorrect payload size!"); - - // special case for non-blocking - // this is not strictly a 'socket option', but this rather simplifies our API - if (option == SocketOption.NonBlocking) + version (SocketCallbacks) + return Result(_socket_backend.recv(socket, buffer, flags, bytes_received)); + else { - bool value = *cast(const(bool)*)optval; - version (Windows) + Result r = Result.success; + ptrdiff_t bytes = _recv(socket.handle, buffer.ptr, cast(int)buffer.length, map_message_flags(flags)); + if (bytes > 0) + *bytes_received = bytes; + else { - uint opt = value ? 1 : 0; - r.systemCode = ioctlsocket(socket.handle, FIONBIO, &opt); + *bytes_received = 0; + if (bytes == 0) + { + // if we request 0 bytes, we receive 0 bytes, and it doesn't imply end-of-stream + if (buffer.length > 0) + { + // a graceful disconnection occurred + // TODO: !!! + r = ConnectionClosedResult; +// r = InternalResult(InternalCode.RemoteDisconnected); + } + } + else + { + Result error = socket_getlasterror(); + // TODO: Do we want a better way to distinguish between receiving a 0-length packet vs no-data (which looks like an error)? + // Is a zero-length packet possible to detect in TCP streams? Makes more sense for recvfrom... + SocketResult sr = socket_result(error); + if (sr != SocketResult.would_block) + r = error; + } } - else version (Posix) + return r; + } +} + +Result recvfrom(Socket socket, void[] buffer, MsgFlags flags = MsgFlags.none, InetAddress* sender_address = null, size_t* bytes_received, InetAddress* local_address = null) +{ + version (SocketCallbacks) + { + assert(local_address is null, "local_address not supported on callback backend"); + return Result(_socket_backend.recvfrom(socket, buffer, flags, sender_address, bytes_received)); + } + else + { + char[sockaddr_storage.sizeof] addr_buffer = void; + sockaddr* addr = cast(sockaddr*)addr_buffer.ptr; + + if (local_address) { - int flags = fcntl(socket.handle, F_GETFL, 0); - r.systemCode = fcntl(socket.handle, F_SETFL, value ? (flags | O_NONBLOCK) : (flags & ~O_NONBLOCK)); + version (Windows) + { + assert(WSARecvMsg, "WSARecvMsg not available!"); + + void[1500] ctrl = void; // HUGE BUFFER! + + WSABUF msg_buf; + msg_buf.buf = cast(char*)buffer.ptr; + msg_buf.len = cast(uint)buffer.length; + + WSAMSG msg; + msg.name = addr; + msg.namelen = addr_buffer.sizeof; + msg.lpBuffers = &msg_buf; + msg.dwBufferCount = 1; + msg.Control.buf = cast(char*)ctrl.ptr; + msg.Control.len = cast(uint)ctrl.length; + msg.dwFlags = 0; + uint bytes; + int r = WSARecvMsg(socket.handle, &msg, &bytes, null, null); + if (r == 0) + *bytes_received = bytes; + else + { + *bytes_received = 0; + goto fail; + } + + // parse the control messages + *local_address = InetAddress(); + for (WSACMSGHDR* c = WSA_CMSG_FIRSTHDR(&msg); c != null; c = WSA_CMSG_NXTHDR(&msg, c)) + { + if (c.cmsg_level == IPPROTO_IP && c.cmsg_type == IP_PKTINFO) + { + IN_PKTINFO* pk = cast(IN_PKTINFO*)WSA_CMSG_DATA(c); + *local_address = InetAddress(make_IPAddr(pk.ipi_addr), 0); // TODO: be nice to populate the listening port... + // pk.ipi_ifindex = receiving interface index + } + if (c.cmsg_level == IPPROTO_IPV6 && c.cmsg_type == IPV6_PKTINFO) + { + IN6_PKTINFO* pk6 = cast(IN6_PKTINFO*)WSA_CMSG_DATA(c); + *local_address = InetAddress(make_IPv6Addr(pk6.ipi6_addr), 0); // TODO: be nice to populate the listening port... + // pk6.ipi6_ifindex = receiving interface index + } + } + } + else + { + assert(false, "TODO: call recvmsg and all that..."); + } } else - assert(false, "Not implemented!"); - return r; + { + socklen_t size = addr_buffer.sizeof; + ptrdiff_t bytes = _recvfrom(socket.handle, buffer.ptr, cast(int)buffer.length, map_message_flags(flags), addr, &size); + if (bytes >= 0) + *bytes_received = bytes; + else + { + *bytes_received = 0; + goto fail; + } + } + + if (sender_address) + *sender_address = make_InetAddress(addr); + return Result.success; + + fail: + Result error = socket_getlasterror(); + SocketResult sockRes = socket_result(error); + if (sockRes != SocketResult.no_buffer && // buffers full + sockRes != SocketResult.connection_refused && // posix error + sockRes != SocketResult.connection_reset) // !!! windows may report this error, but it appears to mean something different on posix + return error; + return Result.success; } +} -// // Convenience for socket-level no signal since some platforms only support message flag -// if (option == SocketOption.NoSigPipe) -// { -// LockGuard!SharedMutex lock(s_noSignalMut); -// s_noSignal.InsertOrAssign(socket.handle, *cast(const(bool)*)optval); -// -// if (optInfo.platformType == OptType.Unsupported) -// return r; -// } - - // determine the option 'level' - OptLevel level = get_optlevel(option); - version (HasIPv6) {} else - assert(level != OptLevel.IPv6 && level != OptLevel.ICMPv6, "Platform does not support IPv6!"); - - // platforms don't all agree on option data formats! - const(void)* arg = optval; - int itmp = void; - linger ling = void; - if (optInfo.rtType != optInfo.platformType) +Result set_socket_option(Socket socket, SocketOption option, const(void)* optval, size_t optlen) +{ + version (SocketCallbacks) + return Result(_socket_backend.set_option(socket, option, optval, optlen)); + else { - switch (optInfo.rtType) + Result r = Result.success; + + // check the option appears to be the proper datatype + const OptInfo* opt_info = &s_socketOptions[option]; + assert(opt_info.rt_type != OptType.unsupported, "Socket option is unsupported on this platform!"); + assert(optlen == s_optTypeRtSize[opt_info.rt_type], "Socket option has incorrect payload size!"); + + // special case for non-blocking + // this is not strictly a 'socket option', but this rather simplifies our API + if (option == SocketOption.non_blocking) { - // TODO: there are more converstions necessary as options/platforms are added - case OptType.Bool: + bool value = *cast(const(bool)*)optval; + version (Windows) + { + uint opt = value ? 1 : 0; + r.system_code = ioctlsocket(socket.handle, FIONBIO, &opt); + } + else version (BSDSockets) { - const bool value = *cast(const(bool)*)optval; - switch (optInfo.platformType) + version (lwIP) { - case OptType.Int: - itmp = value ? 1 : 0; - arg = &itmp; - break; - default: assert(false, "Unexpected"); + // lwIP's fcntl has quirks with F_SETFL; use ioctlsocket(FIONBIO) instead. + int opt = value ? 1 : 0; + r.system_code = ioctlsocket(socket.handle, FIONBIO, &opt); + } + else + { + int flags = fcntl(socket.handle, F_GETFL, 0); + r.system_code = fcntl(socket.handle, F_SETFL, value ? (flags | O_NONBLOCK) : (flags & ~O_NONBLOCK)); } - break; } - case OptType.Duration: + else + assert(false, "Not implemented!"); + return r; + } + +// // Convenience for socket-level no signal since some platforms only support message flag +// if (option == SocketOption.NoSigPipe) +// { +// LockGuard!SharedMutex lock(s_noSignalMut); +// s_noSignal.InsertOrAssign(socket.handle, *cast(const(bool)*)optval); +// +// if (opt_info.platform_type == OptType.unsupported) +// return r; +// } + + // determine the option 'level' + OptLevel level = get_optlevel(option); + version (HasIPv6) {} else + assert(level != OptLevel.ipv6 && level != OptLevel.icmpv6, "Platform does not support IPv6!"); + + // platforms don't all agree on option data formats! + const(void)* arg = optval; + int itmp = void; + linger ling = void; + if (opt_info.rt_type != opt_info.platform_type) + { + switch (opt_info.rt_type) { - const Duration value = *cast(const(Duration)*)optval; - switch (optInfo.platformType) + // TODO: there are more converstions necessary as options/platforms are added + case OptType.bool_: { - case OptType.Seconds: - itmp = cast(int)value.as!"seconds"; - arg = &itmp; - break; - case OptType.Milliseconds: - itmp = cast(int)value.as!"msecs"; - arg = &itmp; - break; - case OptType.Linger: - itmp = cast(int)value.as!"seconds"; - ling = linger(!!itmp, cast(ushort)itmp); - arg = &ling; - break; - default: assert(false, "Unexpected"); + const bool value = *cast(const(bool)*)optval; + switch (opt_info.platform_type) + { + case OptType.int_: + itmp = value ? 1 : 0; + arg = &itmp; + break; + default: assert(false, "Unexpected"); + } + break; } - break; + case OptType.duration: + { + const Duration value = *cast(const(Duration)*)optval; + switch (opt_info.platform_type) + { + case OptType.seconds: + itmp = cast(int)value.as!"seconds"; + arg = &itmp; + break; + case OptType.milliseconds: + itmp = cast(int)value.as!"msecs"; + arg = &itmp; + break; + case OptType.linger: + itmp = cast(int)value.as!"seconds"; + ling = linger(!!itmp, cast(typeof(linger.l_linger))itmp); + arg = &ling; + break; + default: assert(false, "Unexpected"); + } + break; + } + default: + assert(false, "Unexpected!"); } - default: - assert(false, "Unexpected!"); } - } - // set the option - r.systemCode = setsockopt(socket.handle, s_sockOptLevel[level], optInfo.option, cast(const(char)*)arg, s_optTypePlatformSize[optInfo.platformType]); + // set the option + r.system_code = setsockopt(socket.handle, s_sockOptLevel[level], opt_info.option, cast(const(char)*)arg, s_optTypePlatformSize[opt_info.platform_type]); - return r; + return r; + } } Result set_socket_option(Socket socket, SocketOption option, bool value) { - const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rtType == OptType.Unsupported) - return InternalResult(InternalCode.Unsupported); - assert(optInfo.rtType == OptType.Bool, "Incorrect value type for option"); - return set_socket_option(socket, option, &value, bool.sizeof); + version (SocketCallbacks) + return set_socket_option(socket, option, &value, bool.sizeof); + else + { + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(opt_info.rt_type == OptType.bool_, "Incorrect value type for option"); + return set_socket_option(socket, option, &value, bool.sizeof); + } } Result set_socket_option(Socket socket, SocketOption option, int value) { - const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rtType == OptType.Unsupported) - return InternalResult(InternalCode.Unsupported); - assert(optInfo.rtType == OptType.Int, "Incorrect value type for option"); - return set_socket_option(socket, option, &value, int.sizeof); + version (SocketCallbacks) + return set_socket_option(socket, option, &value, int.sizeof); + else + { + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(opt_info.rt_type == OptType.int_, "Incorrect value type for option"); + return set_socket_option(socket, option, &value, int.sizeof); + } } Result set_socket_option(Socket socket, SocketOption option, Duration value) { - const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rtType == OptType.Unsupported) - return InternalResult(InternalCode.Unsupported); - assert(optInfo.rtType == OptType.Duration, "Incorrect value type for option"); - return set_socket_option(socket, option, &value, Duration.sizeof); + version (SocketCallbacks) + return set_socket_option(socket, option, &value, Duration.sizeof); + else + { + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(opt_info.rt_type == OptType.duration, "Incorrect value type for option"); + return set_socket_option(socket, option, &value, Duration.sizeof); + } } Result set_socket_option(Socket socket, SocketOption option, IPAddr value) { - const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rtType == OptType.Unsupported) - return InternalResult(InternalCode.Unsupported); - assert(optInfo.rtType == OptType.INAddress, "Incorrect value type for option"); - return set_socket_option(socket, option, &value, IPAddr.sizeof); + version (SocketCallbacks) + return set_socket_option(socket, option, &value, IPAddr.sizeof); + else + { + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(opt_info.rt_type == OptType.inet_addr, "Incorrect value type for option"); + return set_socket_option(socket, option, &value, IPAddr.sizeof); + } } Result set_socket_option(Socket socket, SocketOption option, ref MulticastGroup value) { - const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rtType == OptType.Unsupported) - return InternalResult(InternalCode.Unsupported); - assert(optInfo.rtType == OptType.MulticastGroup, "Incorrect value type for option"); - return set_socket_option(socket, option, &value, MulticastGroup.sizeof); + version (SocketCallbacks) + return set_socket_option(socket, option, &value, MulticastGroup.sizeof); + else + { + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(opt_info.rt_type == OptType.multicast_group, "Incorrect value type for option"); + return set_socket_option(socket, option, &value, MulticastGroup.sizeof); + } } Result get_socket_option(Socket socket, SocketOption option, void* output, size_t outputlen) { - Result r = Result.Success; + version (SocketCallbacks) + return Result(_socket_backend.get_option(socket, option, output, outputlen)); + else + { + Result r = Result.success; - // check the option appears to be the proper datatype - const OptInfo* optInfo = &s_socketOptions[option]; - assert(optInfo.rtType != OptType.Unsupported, "Socket option is unsupported on this platform!"); - assert(outputlen == s_optTypeRtSize[optInfo.rtType], "Socket option has incorrect payload size!"); + // check the option appears to be the proper datatype + const OptInfo* opt_info = &s_socketOptions[option]; + assert(opt_info.rt_type != OptType.unsupported, "Socket option is unsupported on this platform!"); + assert(outputlen == s_optTypeRtSize[opt_info.rt_type], "Socket option has incorrect payload size!"); - assert(option != SocketOption.NonBlocking, "Socket option NonBlocking cannot be get"); + assert(option != SocketOption.non_blocking, "Socket option NonBlocking cannot be get"); - // determine the option 'level' - OptLevel level = get_optlevel(option); - version (HasIPv6) - assert(level != OptLevel.IPv6 && level != OptLevel.ICMPv6, "Platform does not support IPv6!"); + // determine the option 'level' + OptLevel level = get_optlevel(option); + version (HasIPv6) {} else + assert(level != OptLevel.ipv6 && level != OptLevel.icmpv6, "Platform does not support IPv6!"); - // platforms don't all agree on option data formats! - void* arg = output; - int itmp = 0; - linger ling = { 0, 0 }; - if (optInfo.rtType != optInfo.platformType) - { - switch (optInfo.platformType) + // platforms don't all agree on option data formats! + void* arg = output; + int itmp = 0; + linger ling = { 0, 0 }; + if (opt_info.rt_type != opt_info.platform_type) { - case OptType.Int: - case OptType.Seconds: - case OptType.Milliseconds: + switch (opt_info.platform_type) { - arg = &itmp; - break; - } - case OptType.Linger: - { - arg = &ling; - break; + case OptType.int_: + case OptType.seconds: + case OptType.milliseconds: + { + arg = &itmp; + break; + } + case OptType.linger: + { + arg = &ling; + break; + } + default: + assert(false, "Unexpected!"); } - default: - assert(false, "Unexpected!"); } - } - socklen_t writtenLen = s_optTypePlatformSize[optInfo.platformType]; - // get the option - r.systemCode = getsockopt(socket.handle, s_sockOptLevel[level], optInfo.option, cast(char*)arg, &writtenLen); + socklen_t writtenLen = s_optTypePlatformSize[opt_info.platform_type]; + // get the option + r.system_code = getsockopt(socket.handle, s_sockOptLevel[level], opt_info.option, cast(char*)arg, &writtenLen); - if (optInfo.rtType != optInfo.platformType) - { - switch (optInfo.rtType) + if (opt_info.rt_type != opt_info.platform_type) { - // TODO: there are more converstions necessary as options/platforms are added - case OptType.Bool: + switch (opt_info.rt_type) { - bool* value = cast(bool*)output; - switch (optInfo.platformType) + // TODO: there are more converstions necessary as options/platforms are added + case OptType.bool_: { - case OptType.Int: - *value = !!itmp; - break; - default: assert(false, "Unexpected"); + bool* value = cast(bool*)output; + switch (opt_info.platform_type) + { + case OptType.int_: + *value = !!itmp; + break; + default: assert(false, "Unexpected"); + } + break; } - break; - } - case OptType.Duration: - { - Duration* value = cast(Duration*)output; - switch (optInfo.platformType) + case OptType.duration: { - case OptType.Seconds: - *value = seconds(itmp); - break; - case OptType.Milliseconds: - *value = msecs(itmp); - break; - case OptType.Linger: - *value = seconds(ling.l_linger); - break; - default: assert(false, "Unexpected"); + Duration* value = cast(Duration*)output; + switch (opt_info.platform_type) + { + case OptType.seconds: + *value = seconds(itmp); + break; + case OptType.milliseconds: + *value = msecs(itmp); + break; + case OptType.linger: + *value = seconds(ling.l_linger); + break; + default: assert(false, "Unexpected"); + } + break; } - break; + default: + assert(false, "Unexpected!"); } - default: - assert(false, "Unexpected!"); } - } - assert(optInfo.rtType != OptType.INAddress, "TODO: uncomment this block... for some reason, this block causes DMD to do a bad codegen!"); + assert(opt_info.rt_type != OptType.inet_addr, "TODO: uncomment this block... for some reason, this block causes DMD to do a bad codegen!"); /+ - // Options expected in network-byte order - switch (optInfo.rtType) - { - case OptType.INAddress: + // Options expected in network-byte order + switch (opt_info.rt_type) { - IPAddr* addr = cast(IPAddr*)output; - addr.address = loadBigEndian(&addr.address()); - break; + case OptType.INAddress: + { + IPAddr* addr = cast(IPAddr*)output; + addr.address = loadBigEndian(&addr.address()); + break; + } + default: + break; } - default: - break; - } +/ - return r; + return r; + } } Result get_socket_option(Socket socket, SocketOption option, out bool output) { - const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rtType == OptType.Unsupported) - return InternalResult(InternalCode.Unsupported); - assert(optInfo.rtType == OptType.Bool, "Incorrect value type for option"); - return get_socket_option(socket, option, &output, bool.sizeof); + version (SocketCallbacks) + return get_socket_option(socket, option, &output, bool.sizeof); + else + { + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(opt_info.rt_type == OptType.bool_, "Incorrect value type for option"); + return get_socket_option(socket, option, &output, bool.sizeof); + } } Result get_socket_option(Socket socket, SocketOption option, out int output) { - const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rtType == OptType.Unsupported) - return InternalResult(InternalCode.Unsupported); - assert(optInfo.rtType == OptType.Int, "Incorrect value type for option"); - return get_socket_option(socket, option, &output, int.sizeof); + version (SocketCallbacks) + return get_socket_option(socket, option, &output, int.sizeof); + else + { + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(opt_info.rt_type == OptType.int_, "Incorrect value type for option"); + return get_socket_option(socket, option, &output, int.sizeof); + } } Result get_socket_option(Socket socket, SocketOption option, out Duration output) { - const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rtType == OptType.Unsupported) - return InternalResult(InternalCode.Unsupported); - assert(optInfo.rtType == OptType.Duration, "Incorrect value type for option"); - return get_socket_option(socket, option, &output, Duration.sizeof); + version (SocketCallbacks) + return get_socket_option(socket, option, &output, Duration.sizeof); + else + { + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(opt_info.rt_type == OptType.duration, "Incorrect value type for option"); + return get_socket_option(socket, option, &output, Duration.sizeof); + } } Result get_socket_option(Socket socket, SocketOption option, out IPAddr output) { - const OptInfo* optInfo = &s_socketOptions[option]; - if (optInfo.rtType == OptType.Unsupported) - return InternalResult(InternalCode.Unsupported); - assert(optInfo.rtType == OptType.INAddress, "Incorrect value type for option"); - return get_socket_option(socket, option, &output, IPAddr.sizeof); + version (SocketCallbacks) + return get_socket_option(socket, option, &output, IPAddr.sizeof); + else + { + const OptInfo* opt_info = &s_socketOptions[option]; + if (opt_info.rt_type == OptType.unsupported) + return InternalResult.unsupported; + assert(opt_info.rt_type == OptType.inet_addr, "Incorrect value type for option"); + return get_socket_option(socket, option, &output, IPAddr.sizeof); + } } Result set_keepalive(Socket socket, bool enable, Duration keepIdle, Duration keepInterval, int keepCount) @@ -680,71 +1213,85 @@ Result set_keepalive(Socket socket, bool enable, Duration keepIdle, Duration kee uint bytesReturned = 0; if (WSAIoctl(socket.handle, SIO_KEEPALIVE_VALS, &alive, alive.sizeof, null, 0, &bytesReturned, null, null) < 0) return socket_getlasterror(); - return Result.Success; + return Result.success; } else { - Result res = set_socket_option(socket, SocketOption.KeepAlive, enable); - if (!enable || res != Result.Success) + Result res = set_socket_option(socket, SocketOption.keep_alive, enable); + if (!enable || res != Result.success) return res; version (Darwin) { // OSX doesn't support setting keep-alive interval and probe count. - return set_socket_option(socket, SocketOption.TCP_KeepAlive, keepIdle); + return set_socket_option(socket, SocketOption.tcp_keep_alive, keepIdle); } else { - res = set_socket_option(socket, SocketOption.TCP_KeepIdle, keepIdle); - if (res != Result.Success) + res = set_socket_option(socket, SocketOption.tcp_keep_idle, keepIdle); + if (res != Result.success) return res; - res = set_socket_option(socket, SocketOption.TCP_KeepIntvl, keepInterval); - if (res != Result.Success) + res = set_socket_option(socket, SocketOption.tcp_keep_intvl, keepInterval); + if (res != Result.success) return res; - return set_socket_option(socket, SocketOption.TCP_KeepCnt, keepCount); + return set_socket_option(socket, SocketOption.tcp_keep_cnt, keepCount); } } } Result get_peer_name(Socket socket, out InetAddress name) { - char[sockaddr_storage.sizeof] buffer; - sockaddr* addr = cast(sockaddr*)buffer; - socklen_t bufferLen = buffer.sizeof; - - int fail = getpeername(socket.handle, addr, &bufferLen); - if (fail == 0) - name = make_InetAddress(addr); + version (SocketCallbacks) + return Result(_socket_backend.get_peer_name(socket, name)); else - return socket_getlasterror(); - return Result.Success; + { + char[sockaddr_storage.sizeof] buffer; + sockaddr* addr = cast(sockaddr*)buffer; + socklen_t bufferLen = buffer.sizeof; + + int fail = getpeername(socket.handle, addr, &bufferLen); + if (fail == 0) + name = make_InetAddress(addr); + else + return socket_getlasterror(); + return Result.success; + } } Result get_socket_name(Socket socket, out InetAddress name) { - char[sockaddr_storage.sizeof] buffer; - sockaddr* addr = cast(sockaddr*)buffer; - socklen_t bufferLen = buffer.sizeof; - - int fail = getsockname(socket.handle, addr, &bufferLen); - if (fail == 0) - name = make_InetAddress(addr); + version (SocketCallbacks) + return Result(_socket_backend.get_socket_name(socket, name)); else - return socket_getlasterror(); - return Result.Success; + { + char[sockaddr_storage.sizeof] buffer; + sockaddr* addr = cast(sockaddr*)buffer; + socklen_t bufferLen = buffer.sizeof; + + int fail = getsockname(socket.handle, addr, &bufferLen); + if (fail == 0) + name = make_InetAddress(addr); + else + return socket_getlasterror(); + return Result.success; + } } Result get_hostname(char* name, size_t len) { - int fail = gethostname(name, cast(int)len); - if (fail) - return socket_getlasterror(); - return Result.Success; + version (SocketCallbacks) + return Result(_socket_backend.get_hostname(name, len)); + else + { + int fail = gethostname(name, cast(int)len); + if (fail) + return socket_getlasterror(); + return Result.success; + } } Result get_address_info(const(char)[] nodeName, const(char)[] service, AddressInfo* hints, out AddressInfoResolver result) { - import urt.array : findFirst; - import urt.mem.temp : tstringz; + import urt.string : findFirst; size_t colon = nodeName.findFirst(':'); if (colon < nodeName.length) @@ -754,72 +1301,89 @@ Result get_address_info(const(char)[] nodeName, const(char)[] service, AddressIn nodeName = nodeName[0 .. colon]; } - addrinfo tmpHints; - if (hints) + version (SocketCallbacks) { - // translate hints... - tmpHints.ai_flags = map_addrinfo_flags(hints.flags); - tmpHints.ai_family = s_addressFamily[hints.family]; - tmpHints.ai_socktype = s_socketType[hints.sockType]; - tmpHints.ai_protocol = s_protocol[hints.protocol]; - tmpHints.ai_canonname = cast(char*)hints.canonName; // HAX! - tmpHints.ai_addrlen = 0; - tmpHints.ai_addr = null; - tmpHints.ai_next = null; + // Backend fills the resolver with a defaultAllocator-allocated + // array of AddressInfo entries (sentinel-terminated). + return Result(_socket_backend.get_address_info(nodeName, service, hints, &result)); } + else + { + import urt.mem.temp : tstringz; + + addrinfo tmpHints; + if (hints) + { + // translate hints... + tmpHints.ai_flags = map_addrinfo_flags(hints.flags); + tmpHints.ai_family = s_addressFamily[hints.family]; + tmpHints.ai_socktype = s_socketType[hints.sock_type]; + tmpHints.ai_protocol = s_protocol[hints.protocol]; + tmpHints.ai_canonname = cast(char*)hints.canon_name; // HAX! + tmpHints.ai_addrlen = 0; + tmpHints.ai_addr = null; + tmpHints.ai_next = null; + } - addrinfo* res; - int err = getaddrinfo(nodeName.tstringz, service ? service.tstringz : null, hints ? &tmpHints : null, &res); - if (err != 0) - return Result(err); + addrinfo* res; + int err = getaddrinfo(nodeName.tstringz, service ? service.tstringz : null, hints ? &tmpHints : null, &res); + if (err != 0) + return Result(err); - // if it was used previously - if (result.m_internal[0]) - freeaddrinfo(cast(addrinfo*)result.m_internal[0]); + // if it was used previously + if (result.m_internal[0]) + freeaddrinfo(cast(addrinfo*)result.m_internal[0]); - result.m_internal[0] = res; - result.m_internal[1] = res; + result.m_internal[0] = res; + result.m_internal[1] = res; - return Result.Success; + return Result.success; + } } Result poll(PollFd[] pollFds, Duration timeout, out uint numEvents) { - enum MaxFds = 512; - assert(pollFds.length <= MaxFds, "Too many fds!"); - version (Windows) - WSAPOLLFD[MaxFds] fds; - else version (Posix) - pollfd[MaxFds] fds; - for (size_t i = 0; i < pollFds.length; ++i) - { - fds[i].fd = pollFds[i].socket.handle; - fds[i].revents = 0; - fds[i].events = ((pollFds[i].requestEvents & PollEvents.Read) ? POLLRDNORM : 0) | - ((pollFds[i].requestEvents & PollEvents.Write) ? POLLWRNORM : 0); - } - version (Windows) - int r = WSAPoll(fds.ptr, cast(uint)pollFds.length, timeout.ticks < 0 ? -1 : cast(int)timeout.as!"msecs"); - else version (Posix) - int r = _poll(fds.ptr, pollFds.length, timeout.ticks < 0 ? -1 : cast(int)timeout.as!"msecs"); + version (SocketCallbacks) + return Result(_socket_backend.poll(pollFds, timeout, numEvents)); else - assert(false, "Not implemented!"); - if (r < 0) - { - numEvents = 0; - return socket_getlasterror(); - } - numEvents = r; - for (size_t i = 0; i < pollFds.length; ++i) { - pollFds[i].returnEvents = cast(PollEvents)( - ((fds[i].revents & POLLRDNORM) ? PollEvents.Read : 0) | - ((fds[i].revents & POLLWRNORM) ? PollEvents.Write : 0) | - ((fds[i].revents & POLLERR) ? PollEvents.Error : 0) | - ((fds[i].revents & POLLHUP) ? PollEvents.HangUp : 0) | - ((fds[i].revents & POLLNVAL) ? PollEvents.Invalid : 0)); + enum MaxFds = 512; + assert(pollFds.length <= MaxFds, "Too many fds!"); + version (Windows) + WSAPOLLFD[MaxFds] fds; + else + pollfd[MaxFds] fds; + for (size_t i = 0; i < pollFds.length; ++i) + { + fds[i].fd = pollFds[i].socket.handle; + fds[i].revents = 0; + fds[i].events = cast(short)(((pollFds[i].request_events & PollEvents.read) ? POLLRDNORM : 0) | + ((pollFds[i].request_events & PollEvents.write) ? POLLWRNORM : 0)); + } + int r; + version (Windows) + r = WSAPoll(fds.ptr, cast(uint)pollFds.length, timeout.ticks < 0 ? -1 : cast(int)timeout.as!"msecs"); + else version (BSDSockets) + r = _poll(fds.ptr, cast(uint)pollFds.length, timeout.ticks < 0 ? -1 : cast(int)timeout.as!"msecs"); + else + assert(false, "Not implemented!"); + if (r < 0) + { + numEvents = 0; + return socket_getlasterror(); + } + numEvents = r; + for (size_t i = 0; i < pollFds.length; ++i) + { + pollFds[i].return_events = cast(PollEvents)( + ((fds[i].revents & POLLRDNORM) ? PollEvents.read : 0) | + ((fds[i].revents & POLLWRNORM) ? PollEvents.write : 0) | + ((fds[i].revents & POLLERR) ? PollEvents.error : 0) | + ((fds[i].revents & POLLHUP) ? PollEvents.hangup : 0) | + ((fds[i].revents & POLLNVAL) ? PollEvents.invalid : 0)); + } + return Result.success; } - return Result.Success; } Result poll(ref PollFd pollFd, Duration timeout, out uint numEvents) @@ -831,9 +1395,9 @@ struct AddressInfo { AddressInfoFlags flags; AddressFamily family; - SocketType sockType; + SocketType sock_type; Protocol protocol; - const(char)* canonName; // Note: this memory is valid until the next call to `next_address`, or until `AddressInfoResolver` is destroyed + const(char)* canon_name; // Note: this memory is valid until the next call to `next_address`, or until `AddressInfoResolver` is destroyed InetAddress address; } @@ -853,7 +1417,12 @@ nothrow @nogc: ~this() { if (m_internal[0]) - freeaddrinfo(cast(addrinfo*)m_internal[0]); + { + version (SocketCallbacks) + _socket_backend.free_address_info(&this); + else + freeaddrinfo(cast(addrinfo*)m_internal[0]); + } } // TODO: this should be a MOVE ONLY assignment! @@ -875,16 +1444,21 @@ nothrow @nogc: if (!m_internal[1]) return false; - addrinfo* info = cast(addrinfo*)(m_internal[1]); - m_internal[1] = info.ai_next; - - addressInfo.flags = AddressInfoFlags.None; // info.ai_flags is only used for 'hints' - addressInfo.family = map_address_family(info.ai_family); - addressInfo.sockType = cast(int)info.ai_socktype ? map_socket_type(info.ai_socktype) : SocketType.Unknown; - addressInfo.protocol = map_protocol(info.ai_protocol); - addressInfo.canonName = info.ai_canonname; - addressInfo.address = make_InetAddress(info.ai_addr); - return true; + version (SocketCallbacks) + return _socket_backend.next_address(&this, addressInfo); + else + { + addrinfo* info = cast(addrinfo*)(m_internal[1]); + m_internal[1] = info.ai_next; + + addressInfo.flags = AddressInfoFlags.none; // info.ai_flags is only used for 'hints' + addressInfo.family = map_address_family(info.ai_family); + addressInfo.sock_type = cast(int)info.ai_socktype ? map_socket_type(info.ai_socktype) : SocketType.unknown; + addressInfo.protocol = map_protocol(info.ai_protocol); + addressInfo.canon_name = info.ai_canonname; + addressInfo.address = make_InetAddress(info.ai_addr); + return true; + } } void*[2] m_internal = [ null, null ]; @@ -893,101 +1467,125 @@ nothrow @nogc: struct PollFd { Socket socket; - PollEvents requestEvents; - PollEvents returnEvents; - void* userData; + PollEvents request_events; + PollEvents return_events; + void* user_data; } -Result socket_getlasterror() -{ - version (Windows) - return Result(WSAGetLastError()); - else - return Result(errno); -} - Result get_socket_error(Socket socket) { - Result r; - socklen_t optlen = r.systemCode.sizeof; - int callResult = getsockopt(socket.handle, SOL_SOCKET, SO_ERROR, cast(char*)&r.systemCode, &optlen); - if (callResult) - r.systemCode = callResult; - return r; + version (SocketCallbacks) + { + int err; + Result r = get_socket_option(socket, SocketOption.error, err); + if (r) + r.system_code = err; + return r; + } + else + { + Result r; + socklen_t optlen = r.system_code.sizeof; + int callResult = getsockopt(socket.handle, SOL_SOCKET, SO_ERROR, cast(char*)&r.system_code, &optlen); + if (callResult) + r.system_code = callResult; + return r; + } } // TODO: !!! -enum Result ConnectionClosedResult = Result(-12345); -SocketResult get_SocketResult(Result result) +enum Result ConnectionClosedResult = Result(-12345); +SocketResult socket_result(Result result) { - if (result) - return SocketResult.Success; - if (result.systemCode == ConnectionClosedResult.systemCode) - return SocketResult.ConnectionClosed; - version (Windows) - { - if (result.systemCode == WSAEWOULDBLOCK) - return SocketResult.WouldBlock; - if (result.systemCode == WSAEINPROGRESS) - return SocketResult.WouldBlock; - if (result.systemCode == WSAENOBUFS) - return SocketResult.NoBuffer; - if (result.systemCode == WSAENETDOWN) - return SocketResult.NetworkDown; - if (result.systemCode == WSAECONNREFUSED) - return SocketResult.ConnectionRefused; - if (result.systemCode == WSAECONNRESET) - return SocketResult.ConnectionReset; - if (result.systemCode == WSAEINTR) - return SocketResult.Interrupted; - if (result.systemCode == WSAENOTSOCK) - return SocketResult.InvalidSocket; - if (result.systemCode == WSAEINVAL) - return SocketResult.InvalidArgument; - } - else version (Posix) + version (SocketCallbacks) + return cast(SocketResult)result.system_code; + else { - static if (EAGAIN != EWOULDBLOCK) - if (result.systemCode == EAGAIN) - return SocketResult.WouldBlock; - if (result.systemCode == EWOULDBLOCK) - return SocketResult.WouldBlock; - if (result.systemCode == EINPROGRESS) - return SocketResult.WouldBlock; - if (result.systemCode == ENOMEM) - return SocketResult.NoBuffer; - if (result.systemCode == ENETDOWN) - return SocketResult.NetworkDown; - if (result.systemCode == ECONNREFUSED) - return SocketResult.ConnectionRefused; - if (result.systemCode == ECONNRESET) - return SocketResult.ConnectionReset; - if (result.systemCode == EINTR) - return SocketResult.Interrupted; - if (result.systemCode == EINVAL) - return SocketResult.InvalidArgument; + if (result) + return SocketResult.success; + if (result.system_code == ConnectionClosedResult.system_code) + return SocketResult.connection_closed; + else version (Windows) + { + if (result.system_code == WSAEWOULDBLOCK) + return SocketResult.would_block; + if (result.system_code == WSAEINPROGRESS) + return SocketResult.would_block; + if (result.system_code == WSAENOBUFS) + return SocketResult.no_buffer; + if (result.system_code == WSAENETDOWN) + return SocketResult.network_down; + if (result.system_code == WSAECONNREFUSED) + return SocketResult.connection_refused; + if (result.system_code == WSAECONNRESET) + return SocketResult.connection_reset; + if (result.system_code == WSAEINTR) + return SocketResult.interrupted; + if (result.system_code == WSAENOTSOCK) + return SocketResult.invalid_socket; + if (result.system_code == WSAEINVAL) + return SocketResult.invalid_argument; + } + else version (Errno) + { + import urt.internal.stdc.errno; + static if (EAGAIN != EWOULDBLOCK) + if (result.system_code == EAGAIN) + return SocketResult.would_block; + if (result.system_code == EWOULDBLOCK) + return SocketResult.would_block; + if (result.system_code == EINPROGRESS) + return SocketResult.would_block; + if (result.system_code == ENOMEM) + return SocketResult.no_buffer; + if (result.system_code == ENETDOWN) + return SocketResult.network_down; + if (result.system_code == ECONNREFUSED) + return SocketResult.connection_refused; + if (result.system_code == ECONNRESET) + return SocketResult.connection_reset; + if (result.system_code == EINTR) + return SocketResult.interrupted; + if (result.system_code == EINVAL) + return SocketResult.invalid_argument; + } + return SocketResult.failure; } - return SocketResult.Failure; } +private: + +version (SocketCallbacks) {} else { -sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_t addrLen) +Result socket_getlasterror() +{ + version (Windows) + return Result(WSAGetLastError()); + else version (Errno) + return errno_result(); + else + static assert(false, "socket_getlasterror not implemented for this platform"); +} + +sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_t addr_len) { - sockaddr* sockAddr = cast(sockaddr*)buffer.ptr; + sockaddr* sock_addr = cast(sockaddr*)buffer.ptr; switch (address.family) { - case AddressFamily.IPv4: + case AddressFamily.ipv4: { - addrLen = sockaddr_in.sizeof; + addr_len = sockaddr_in.sizeof; if (buffer.length < sockaddr_in.sizeof) return null; - sockaddr_in* ain = cast(sockaddr_in*)sockAddr; + sockaddr_in* ain = cast(sockaddr_in*)sock_addr; memzero(ain, sockaddr_in.sizeof); - ain.sin_family = s_addressFamily[AddressFamily.IPv4]; + version (lwIP) + ain.sin_len = sockaddr_in.sizeof; + ain.sin_family = s_addressFamily[AddressFamily.ipv4]; version (Windows) { ain.sin_addr.S_un.S_un_b.s_b1 = address._a.ipv4.addr.b[0]; @@ -995,33 +1593,37 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ ain.sin_addr.S_un.S_un_b.s_b3 = address._a.ipv4.addr.b[2]; ain.sin_addr.S_un.S_un_b.s_b4 = address._a.ipv4.addr.b[3]; } - else version (Posix) + else version (BSDSockets) ain.sin_addr.s_addr = address._a.ipv4.addr.address; else assert(false, "Not implemented!"); storeBigEndian(&ain.sin_port, ushort(address._a.ipv4.port)); break; } - case AddressFamily.IPv6: + case AddressFamily.ipv6: { version (HasIPv6) { - addrLen = sockaddr_in6.sizeof; + addr_len = sockaddr_in6.sizeof; if (buffer.length < sockaddr_in6.sizeof) return null; - sockaddr_in6* ain6 = cast(sockaddr_in6*)sockAddr; + sockaddr_in6* ain6 = cast(sockaddr_in6*)sock_addr; memzero(ain6, sockaddr_in6.sizeof); - ain6.sin6_family = s_addressFamily[AddressFamily.IPv6]; + version (lwIP) + ain6.sin6_len = sockaddr_in6.sizeof; + ain6.sin6_family = s_addressFamily[AddressFamily.ipv6]; storeBigEndian(&ain6.sin6_port, cast(ushort)address._a.ipv6.port); - storeBigEndian(cast(uint*)&ain6.sin6_flowinfo, address._a.ipv6.flowInfo); + storeBigEndian(cast(uint*)&ain6.sin6_flowinfo, address._a.ipv6.flow_info); storeBigEndian(cast(uint*)&ain6.sin6_scope_id, address._a.ipv6.scopeId); for (int a = 0; a < 8; ++a) { version (Windows) storeBigEndian(&ain6.sin6_addr.in6_u.u6_addr16[a], address._a.ipv6.addr.s[a]); else version (Posix) - storeBigEndian(cast(ushort*)ain6.sin6_addr.s6_addr + a, address._a.ipv6.addr.s[a]); + storeBigEndian(&ain6.sin6_addr.__in6_u.__u6_addr16[a], address._a.ipv6.addr.s[a]); + else version (BSDSockets) + storeBigEndian(cast(ushort*)&ain6.sin6_addr.s6_addr[a * 2], address._a.ipv6.addr.s[a]); else assert(false, "Not implemented!"); } @@ -1030,17 +1632,17 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ assert(false, "Platform does not support IPv6!"); break; } - case AddressFamily.Unix: + case AddressFamily.unix: { // version (HasUnixSocket) // { -// addrLen = sockaddr_un.sizeof; +// addr_len = sockaddr_un.sizeof; // if (buffer.length < sockaddr_un.sizeof) // return null; // -// sockaddr_un* aun = cast(sockaddr_un*)sockAddr; +// sockaddr_un* aun = cast(sockaddr_un*)sock_addr; // memzero(aun, sockaddr_un.sizeof); -// aun.sun_family = s_addressFamily[AddressFamily.Unix]; +// aun.sun_family = s_addressFamily[AddressFamily.unix]; // // memcpy(aun.sun_path, address.un.path, UNIX_PATH_LEN); // break; @@ -1050,70 +1652,51 @@ sockaddr* make_sockaddr(ref const InetAddress address, ubyte[] buffer, out size_ } default: { - sockAddr = null; - addrLen = 0; + sock_addr = null; + addr_len = 0; assert(false, "Unsupported address family"); break; } } - return sockAddr; + return sock_addr; } -InetAddress make_InetAddress(const(sockaddr)* sockAddress) +InetAddress make_InetAddress(const(sockaddr)* sock_address) { InetAddress addr; - addr.family = map_address_family(sockAddress.sa_family); + addr.family = map_address_family(sock_address.sa_family); switch (addr.family) { - case AddressFamily.IPv4: + case AddressFamily.ipv4: { - const sockaddr_in* ain = cast(const(sockaddr_in)*)sockAddress; + const sockaddr_in* ain = cast(const(sockaddr_in)*)sock_address; addr._a.ipv4.port = loadBigEndian(&ain.sin_port); - version (Windows) - { - addr._a.ipv4.addr.b[0] = ain.sin_addr.S_un.S_un_b.s_b1; - addr._a.ipv4.addr.b[1] = ain.sin_addr.S_un.S_un_b.s_b2; - addr._a.ipv4.addr.b[2] = ain.sin_addr.S_un.S_un_b.s_b3; - addr._a.ipv4.addr.b[3] = ain.sin_addr.S_un.S_un_b.s_b4; - } - else version (Posix) - addr._a.ipv4.addr.address = ain.sin_addr.s_addr; - else - assert(false, "Not implemented!"); + addr._a.ipv4.addr = make_IPAddr(ain.sin_addr); break; } - case AddressFamily.IPv6: + case AddressFamily.ipv6: { version (HasIPv6) { - const sockaddr_in6* ain6 = cast(const(sockaddr_in6)*)sockAddress; + const sockaddr_in6* ain6 = cast(const(sockaddr_in6)*)sock_address; addr._a.ipv6.port = loadBigEndian(&ain6.sin6_port); - addr._a.ipv6.flowInfo = loadBigEndian(cast(const(uint)*)&ain6.sin6_flowinfo); + addr._a.ipv6.flow_info = loadBigEndian(cast(const(uint)*)&ain6.sin6_flowinfo); addr._a.ipv6.scopeId = loadBigEndian(cast(const(uint)*)&ain6.sin6_scope_id); - - for (int a = 0; a < 8; ++a) - { - version (Windows) - addr._a.ipv6.addr.s[a] = loadBigEndian(&ain6.sin6_addr.in6_u.u6_addr16[a]); - else version (Posix) - addr._a.ipv6.addr.s[a] = loadBigEndian(cast(const(ushort)*)ain6.sin6_addr.s6_addr + a); - else - assert(false, "Not implemented!"); - } + addr._a.ipv6.addr = make_IPv6Addr(ain6.sin6_addr); } else assert(false, "Platform does not support IPv6!"); break; } - case AddressFamily.Unix: + case AddressFamily.unix: { // version (HasUnixSocket) // { -// const sockaddr_un* aun = cast(const(sockaddr_un)*)sockAddress; +// const sockaddr_un* aun = cast(const(sockaddr_un)*)sock_address; // // memcpy(addr.un.path, aun.sun_path, UNIX_PATH_LEN); // if (UNIX_PATH_LEN < UnixPathLen) @@ -1131,33 +1714,64 @@ InetAddress make_InetAddress(const(sockaddr)* sockAddress) return addr; } +IPAddr make_IPAddr(ref const in_addr in4) +{ + IPAddr addr; + version (Windows) + { + addr.b[0] = in4.S_un.S_un_b.s_b1; + addr.b[1] = in4.S_un.S_un_b.s_b2; + addr.b[2] = in4.S_un.S_un_b.s_b3; + addr.b[3] = in4.S_un.S_un_b.s_b4; + } + else version (BSDSockets) + addr.address = in4.s_addr; + else + assert(false, "Not implemented!"); + return addr; +} -private: +IPv6Addr make_IPv6Addr(ref const in6_addr in6) +{ + IPv6Addr addr; + for (int a = 0; a < 8; ++a) + { + version (Windows) + addr.s[a] = loadBigEndian(&in6.in6_u.u6_addr16[a]); + else version (Posix) + addr.s[a] = loadBigEndian(&in6.__in6_u.__u6_addr16[a]); + else version (BSDSockets) + addr.s[a] = loadBigEndian(cast(const(ushort)*)&in6.s6_addr[a * 2]); + else + assert(false, "Not implemented!"); + } + return addr; +} enum OptLevel : ubyte { - Socket, - IP, - IPv6, - ICMP, - ICMPv6, - TCP, - UDP, + socket, + ip, + ipv6, + icmp, + icmpv6, + tcp, + udp, } enum OptType : ubyte { - Unsupported, - Bool, - Int, - Seconds, - Milliseconds, - Duration, - INAddress, // IPAddr + in_addr - //IN6Address, // IPv6Addr + in6_addr - MulticastGroup, // MulticastGroup + ip_mreq - //MulticastGroupIPv6, // MulticastGroupIPv6? + ipv6_mreq - Linger, + unsupported, + bool_, + int_, + seconds, + milliseconds, + duration, + inet_addr, // IPAddr + in_addr + //inet6_addr, // IPv6Addr + in6_addr + multicast_group, // MulticastGroup + ip_mreq + //multicast_group_ipv6, // MulticastGroupIPv6? + ipv6_mreq + linger, // etc... } @@ -1169,11 +1783,16 @@ __gshared immutable ubyte[] s_optTypePlatformSize = [ 0, 0, int.sizeof, int.size struct OptInfo { short option; - OptType rtType; - OptType platformType; + OptType rt_type; + OptType platform_type; } -__gshared immutable ushort[AddressFamily.max+1] s_addressFamily = [ +version (lwIP) + alias sa_family_t = ubyte; +else + alias sa_family_t = ushort; + +__gshared immutable sa_family_t[AddressFamily.max+1] s_addressFamily = [ AF_UNSPEC, AF_UNIX, AF_INET, @@ -1182,15 +1801,15 @@ __gshared immutable ushort[AddressFamily.max+1] s_addressFamily = [ AddressFamily map_address_family(int addressFamily) { if (addressFamily == AF_INET) - return AddressFamily.IPv4; + return AddressFamily.ipv4; else if (addressFamily == AF_INET6) - return AddressFamily.IPv6; + return AddressFamily.ipv6; else if (addressFamily == AF_UNIX) - return AddressFamily.Unix; + return AddressFamily.unix; else if (addressFamily == AF_UNSPEC) - return AddressFamily.Unspecified; + return AddressFamily.unspecified; assert(false, "Unsupported address family"); - return AddressFamily.Unknown; + return AddressFamily.unknown; } __gshared immutable int[SocketType.max+1] s_socketType = [ @@ -1201,13 +1820,13 @@ __gshared immutable int[SocketType.max+1] s_socketType = [ SocketType map_socket_type(int sockType) { if (sockType == SOCK_STREAM) - return SocketType.Stream; + return SocketType.stream; else if (sockType == SOCK_DGRAM) - return SocketType.Datagram; + return SocketType.datagram; else if (sockType == SOCK_RAW) - return SocketType.Raw; + return SocketType.raw; assert(false, "Unsupported socket type"); - return SocketType.Unknown; + return SocketType.unknown; } __gshared immutable int[Protocol.max+1] s_protocol = [ @@ -1220,17 +1839,17 @@ __gshared immutable int[Protocol.max+1] s_protocol = [ Protocol map_protocol(int protocol) { if (protocol == IPPROTO_TCP) - return Protocol.TCP; + return Protocol.tcp; else if (protocol == IPPROTO_UDP) - return Protocol.UDP; + return Protocol.udp; else if (protocol == IPPROTO_IP) - return Protocol.IP; + return Protocol.ip; else if (protocol == IPPROTO_ICMP) - return Protocol.ICMP; + return Protocol.icmp; else if (protocol == IPPROTO_RAW) - return Protocol.Raw; + return Protocol.raw; assert(false, "Unsupported protocol"); - return Protocol.Unknown; + return Protocol.unknown; } version (linux) @@ -1261,68 +1880,98 @@ else version (Windows) // BS_NETWORK_WINDOWS_VERSION >= _WIN32_WINNT_VISTA { __gshared immutable OptInfo[SocketOption.max] s_socketOptions = [ - OptInfo( -1, OptType.Bool, OptType.Bool ), // NonBlocking - OptInfo( SO_KEEPALIVE, OptType.Bool, OptType.Int ), - OptInfo( SO_LINGER, OptType.Duration, OptType.Linger ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), -// OptInfo( SO_RANDOMIZE_PORT, OptType.Bool, OptType.Int ), // TODO: BS_NETWORK_WINDOWS_VERSION >= _WIN32_WINNT_VISTA - OptInfo( SO_SNDBUF, OptType.Int, OptType.Int ), - OptInfo( SO_RCVBUF, OptType.Int, OptType.Int ), - OptInfo( SO_REUSEADDR, OptType.Bool, OptType.Int ), - OptInfo( -1, OptType.Bool, OptType.Unsupported ), // NoSignalPipe - OptInfo( SO_ERROR, OptType.Int, OptType.Int ), - OptInfo( IP_ADD_MEMBERSHIP, OptType.MulticastGroup, OptType.MulticastGroup ), - OptInfo( IP_MULTICAST_LOOP, OptType.Bool, OptType.Int ), - OptInfo( IP_MULTICAST_TTL, OptType.Int, OptType.Int ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), - OptInfo( TCP_NODELAY, OptType.Bool, OptType.Int ), + OptInfo( -1, OptType.bool_, OptType.bool_ ), // NonBlocking + OptInfo( SO_KEEPALIVE, OptType.bool_, OptType.int_ ), + OptInfo( SO_LINGER, OptType.duration, OptType.linger ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), +// OptInfo( SO_RANDOMIZE_PORT, OptType.bool_, OptType.int_ ), // TODO: BS_NETWORK_WINDOWS_VERSION >= _WIN32_WINNT_VISTA + OptInfo( SO_SNDBUF, OptType.int_, OptType.int_ ), + OptInfo( SO_RCVBUF, OptType.int_, OptType.int_ ), + OptInfo( SO_REUSEADDR, OptType.bool_, OptType.int_ ), + OptInfo( -1, OptType.bool_, OptType.unsupported ), // NoSignalPipe + OptInfo( SO_ERROR, OptType.int_, OptType.int_ ), + OptInfo( IP_ADD_MEMBERSHIP, OptType.multicast_group, OptType.multicast_group ), + OptInfo( IP_MULTICAST_LOOP, OptType.bool_, OptType.int_ ), + OptInfo( IP_MULTICAST_TTL, OptType.int_, OptType.int_ ), + OptInfo( IP_PKTINFO, OptType.bool_, OptType.int_ ), + OptInfo( IPV6_RECVPKTINFO, OptType.bool_, OptType.int_ ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), + OptInfo( TCP_NODELAY, OptType.bool_, OptType.int_ ), ]; } else version (linux) // BS_NETWORK_WINDOWS_VERSION >= _WIN32_WINNT_VISTA { __gshared immutable OptInfo[SocketOption.max] s_socketOptions = [ - OptInfo( -1, OptType.Bool, OptType.Bool ), // NonBlocking - OptInfo( SO_KEEPALIVE, OptType.Bool, OptType.Int ), - OptInfo( SO_LINGER, OptType.Duration, OptType.Linger ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), - OptInfo( SO_SNDBUF, OptType.Int, OptType.Int ), - OptInfo( SO_RCVBUF, OptType.Int, OptType.Int ), - OptInfo( SO_REUSEADDR, OptType.Bool, OptType.Int ), - OptInfo( -1, OptType.Bool, OptType.Unsupported ), // NoSignalPipe - OptInfo( SO_ERROR, OptType.Int, OptType.Int ), - OptInfo( IP_ADD_MEMBERSHIP, OptType.MulticastGroup, OptType.MulticastGroup ), - OptInfo( IP_MULTICAST_LOOP, OptType.Bool, OptType.Int ), - OptInfo( IP_MULTICAST_TTL, OptType.Int, OptType.Int ), - OptInfo( TCP_KEEPIDLE, OptType.Duration, OptType.Seconds ), - OptInfo( TCP_KEEPINTVL, OptType.Duration, OptType.Seconds ), - OptInfo( TCP_KEEPCNT, OptType.Int, OptType.Int ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), - OptInfo( TCP_NODELAY, OptType.Bool, OptType.Int ), + OptInfo( -1, OptType.bool_, OptType.bool_ ), // NonBlocking + OptInfo( SO_KEEPALIVE, OptType.bool_, OptType.int_ ), + OptInfo( SO_LINGER, OptType.duration, OptType.linger ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), + OptInfo( SO_SNDBUF, OptType.int_, OptType.int_ ), + OptInfo( SO_RCVBUF, OptType.int_, OptType.int_ ), + OptInfo( SO_REUSEADDR, OptType.bool_, OptType.int_ ), + OptInfo( -1, OptType.bool_, OptType.unsupported ), // NoSignalPipe + OptInfo( SO_ERROR, OptType.int_, OptType.int_ ), + OptInfo( IP_ADD_MEMBERSHIP, OptType.multicast_group, OptType.multicast_group ), + OptInfo( IP_MULTICAST_LOOP, OptType.bool_, OptType.int_ ), + OptInfo( IP_MULTICAST_TTL, OptType.int_, OptType.int_ ), + OptInfo( IP_PKTINFO, OptType.bool_, OptType.int_ ), + OptInfo( IPV6_RECVPKTINFO, OptType.bool_, OptType.int_ ), + OptInfo( TCP_KEEPIDLE, OptType.duration, OptType.seconds ), + OptInfo( TCP_KEEPINTVL, OptType.duration, OptType.seconds ), + OptInfo( TCP_KEEPCNT, OptType.int_, OptType.int_ ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), + OptInfo( TCP_NODELAY, OptType.bool_, OptType.int_ ), ]; } else version (Darwin) { __gshared immutable OptInfo[SocketOption.max] s_socketOptions = [ - OptInfo( -1, OptType.Bool, OptType.Bool ), // NonBlocking - OptInfo( SO_KEEPALIVE, OptType.Bool, OptType.Int ), + OptInfo( -1, OptType.bool_, OptType.bool_ ), // NonBlocking + OptInfo( SO_KEEPALIVE, OptType.bool_, OptType.int_ ), OptInfo( SO_LINGER, OptType.Duration, OptType.Linger ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), - OptInfo( SO_SNDBUF, OptType.Int, OptType.Int ), - OptInfo( SO_RCVBUF, OptType.Int, OptType.Int ), - OptInfo( SO_REUSEADDR, OptType.Bool, OptType.Int ), - OptInfo( SO_NOSIGPIPE, OptType.Bool, OptType.Int ), - OptInfo( SO_ERROR, OptType.Int, OptType.Int ), - OptInfo( IP_ADD_MEMBERSHIP, OptType.MulticastGroup, OptType.MulticastGroup ), - OptInfo( IP_MULTICAST_LOOP, OptType.Bool, OptType.Int ), - OptInfo( IP_MULTICAST_TTL, OptType.Int, OptType.Int ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), - OptInfo( -1, OptType.Unsupported, OptType.Unsupported ), - OptInfo( TCP_KEEPALIVE, OptType.Duration, OptType.Seconds ), - OptInfo( TCP_NODELAY, OptType.Bool, OptType.Int ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), + OptInfo( SO_SNDBUF, OptType.int_, OptType.int_ ), + OptInfo( SO_RCVBUF, OptType.int_, OptType.int_ ), + OptInfo( SO_REUSEADDR, OptType.bool_, OptType.int_ ), + OptInfo( SO_NOSIGPIPE, OptType.bool_, OptType.int_ ), + OptInfo( SO_ERROR, OptType.int_, OptType.int_ ), + OptInfo( IP_ADD_MEMBERSHIP, OptType.multicast_group, OptType.multicast_group ), + OptInfo( IP_MULTICAST_LOOP, OptType.bool_, OptType.int_ ), + OptInfo( IP_MULTICAST_TTL, OptType.int_, OptType.int_ ), + OptInfo( IP_PKTINFO, OptType.bool_, OptType.int_ ), + OptInfo( IPV6_RECVPKTINFO, OptType.bool_, OptType.int_ ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), + OptInfo( TCP_KEEPALIVE, OptType.duration, OptType.seconds ), + OptInfo( TCP_NODELAY, OptType.bool_, OptType.int_ ), + ]; +} +else version (lwIP) +{ + __gshared immutable OptInfo[SocketOption.max] s_socketOptions = [ + OptInfo( -1, OptType.bool_, OptType.bool_ ), // NonBlocking + OptInfo( SO_KEEPALIVE, OptType.bool_, OptType.int_ ), + OptInfo( SO_LINGER, OptType.duration, OptType.linger ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), // RandomizePort + OptInfo( SO_SNDBUF, OptType.int_, OptType.int_ ), + OptInfo( SO_RCVBUF, OptType.int_, OptType.int_ ), + OptInfo( SO_REUSEADDR, OptType.bool_, OptType.int_ ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), // NoSigPipe + OptInfo( SO_ERROR, OptType.int_, OptType.int_ ), + OptInfo( IP_ADD_MEMBERSHIP, OptType.multicast_group, OptType.multicast_group ), + OptInfo( IP_MULTICAST_LOOP, OptType.bool_, OptType.int_ ), + OptInfo( IP_MULTICAST_TTL, OptType.int_, OptType.int_ ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), // IP_PKTINFO + OptInfo( -1, OptType.unsupported, OptType.unsupported ), // IPV6_PKTINFO + OptInfo( TCP_KEEPIDLE, OptType.duration, OptType.seconds ), + OptInfo( TCP_KEEPINTVL, OptType.duration, OptType.seconds ), + OptInfo( TCP_KEEPCNT, OptType.int_, OptType.int_ ), + OptInfo( -1, OptType.unsupported, OptType.unsupported ), // TcpKeepAlive (Apple) + OptInfo( TCP_NODELAY, OptType.bool_, OptType.int_ ), ]; } else @@ -1331,12 +1980,11 @@ else int map_message_flags(MsgFlags flags) { int r = 0; - if (flags & MsgFlags.OOB) r |= MSG_OOB; - if (flags & MsgFlags.Peek) r |= MSG_PEEK; + if (flags & MsgFlags.peek) r |= MSG_PEEK; version (linux) { - if (flags & MsgFlags.Confirm) r |= MSG_CONFIRM; - if (flags & MsgFlags.NoSig) r |= MSG_NOSIGNAL; + if (flags & MsgFlags.confirm) r |= MSG_CONFIRM; + if (flags & MsgFlags.no_sig) r |= MSG_NOSIGNAL; } return r; } @@ -1344,27 +1992,27 @@ int map_message_flags(MsgFlags flags) int map_addrinfo_flags(AddressInfoFlags flags) { int r = 0; - if (flags & AddressInfoFlags.Passive) r |= AI_PASSIVE; - if (flags & AddressInfoFlags.CanonName) r |= AI_CANONNAME; - if (flags & AddressInfoFlags.NumericHost) r |= AI_NUMERICHOST; - if (flags & AddressInfoFlags.NumericServ) r |= AI_NUMERICSERV; - if (flags & AddressInfoFlags.All) r |= AI_ALL; - if (flags & AddressInfoFlags.AddrConfig) r |= AI_ADDRCONFIG; - if (flags & AddressInfoFlags.V4Mapped) r |= AI_V4MAPPED; + if (flags & AddressInfoFlags.passive) r |= AI_PASSIVE; + if (flags & AddressInfoFlags.canon_name) r |= AI_CANONNAME; + if (flags & AddressInfoFlags.numeric_host) r |= AI_NUMERICHOST; + if (flags & AddressInfoFlags.numeric_serv) r |= AI_NUMERICSERV; + if (flags & AddressInfoFlags.all) r |= AI_ALL; + if (flags & AddressInfoFlags.addr_config) r |= AI_ADDRCONFIG; + if (flags & AddressInfoFlags.v4_mapped) r |= AI_V4MAPPED; version (Windows) - if (flags & AddressInfoFlags.FQDN) r |= AI_FQDN; + if (flags & AddressInfoFlags.fqdn) r |= AI_FQDN; return r; } OptLevel get_optlevel(SocketOption opt) { - if (opt < SocketOption.FirstIpOption) return OptLevel.Socket; - else if (opt < SocketOption.FirstIpv6Option) return OptLevel.IP; - else if (opt < SocketOption.FirstIcmpOption) return OptLevel.IPv6; - else if (opt < SocketOption.FirstIcmpv6Option) return OptLevel.ICMP; - else if (opt < SocketOption.FirstTcpOption) return OptLevel.ICMPv6; - else if (opt < SocketOption.FirstUdpOption) return OptLevel.TCP; - else return OptLevel.UDP; + if (opt < SocketOption.first_ip_option) return OptLevel.socket; + else if (opt < SocketOption.first_ipv6_option) return OptLevel.ip; + else if (opt < SocketOption.first_icmp_option) return OptLevel.ipv6; + else if (opt < SocketOption.first_icmpv6_option) return OptLevel.icmp; + else if (opt < SocketOption.first_tcp_option) return OptLevel.icmpv6; + else if (opt < SocketOption.first_udp_option) return OptLevel.tcp; + else return OptLevel.udp; } @@ -1374,8 +2022,35 @@ version (Windows) void crt_bootup() { WSADATA wsaData; - int result = WSAStartup(MAKEWORD(2, 2), &wsaData); + int result = WSAStartup(0x0202, &wsaData); // what if this fails??? + + // this is truly the worst thing I ever wrote!! + enum SIO_GET_EXTENSION_FUNCTION_POINTER = 0xC8000006; + struct GUID { uint Data1; ushort Data2, Data3; ubyte[8] Data4; } + __gshared immutable GUID WSAID_WSASENDMSG = GUID(0xA441E712, 0x754F, 0x43CA, [0x84,0xA7,0x0D,0xEE,0x44,0xCF,0x60,0x6D]); + __gshared immutable GUID WSAID_WSARECVMSG = GUID(0xF689D7C8, 0x6F1F, 0x436B, [0x8A,0x53,0xE5,0x4F,0xE3,0x51,0xC3,0x22]); + + Socket dummy; + uint bytes = 0; + if (!create_socket(AddressFamily.ipv4, SocketType.datagram, Protocol.udp, dummy)) + goto FAIL; + if (WSAIoctl(dummy.handle, SIO_GET_EXTENSION_FUNCTION_POINTER, cast(void*)&WSAID_WSASENDMSG, cast(uint)GUID.sizeof, + &WSASendMsg, cast(uint)WSASendMsgFn.sizeof, &bytes, null, null) != 0) + goto FAIL; + assert(bytes == WSASendMsgFn.sizeof); + if (WSAIoctl(dummy.handle, SIO_GET_EXTENSION_FUNCTION_POINTER, cast(void*)&WSAID_WSARECVMSG, cast(uint)GUID.sizeof, + &WSARecvMsg, cast(uint)WSARecvMsgFn.sizeof, &bytes, null, null) != 0) + goto FAIL; + assert(bytes == WSARecvMsgFn.sizeof); + dummy.close(); + if (!WSASendMsg || !WSARecvMsg) + goto FAIL; + return; + + FAIL: + import urt.log; + writeWarning("Failed to get WSASendMsg/WSARecvMsg function pointers - sendmsg() won't work, recvfrom() won't be able to report the dst address"); } pragma(crt_destructor) @@ -1394,43 +2069,97 @@ version (Windows) { // stuff that's missing from the windows headers... - enum: int { + enum : int + { AI_NUMERICSERV = 0x0008, AI_ALL = 0x0100, AI_V4MAPPED = 0x0800, AI_FQDN = 0x4000, } - struct pollfd + struct ip_mreq { - SOCKET fd; // Socket handle - SHORT events; // Requested events to monitor - SHORT revents; // Returned events indicating status + in_addr imr_multiaddr; + in_addr imr_interface; } - alias WSAPOLLFD = pollfd; - alias PWSAPOLLFD = pollfd*; - alias LPWSAPOLLFD = pollfd*; - - enum: short { - POLLRDNORM = 0x0100, - POLLRDBAND = 0x0200, - POLLIN = (POLLRDNORM | POLLRDBAND), - POLLPRI = 0x0400, - - POLLWRNORM = 0x0010, - POLLOUT = (POLLWRNORM), - POLLWRBAND = 0x0020, - - POLLERR = 0x0001, - POLLHUP = 0x0002, - POLLNVAL = 0x0004 + + struct WSAMSG + { + LPSOCKADDR name; + int namelen; + LPWSABUF lpBuffers; + uint dwBufferCount; + WSABUF Control; + uint dwFlags; } + alias LPWSAMSG = WSAMSG*; - extern(Windows) int WSAPoll(LPWSAPOLLFD fdArray, uint fds, int timeout); + struct WSABUF + { + uint len; + char* buf; + } + alias LPWSABUF = WSABUF*; - struct ip_mreq + alias WSASendMsgFn = extern(Windows) int function(SOCKET s, LPWSAMSG lpMsg, uint dwFlags, uint* lpNumberOfBytesSent, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); + alias WSARecvMsgFn = extern(Windows) int function(SOCKET s, LPWSAMSG lpMsg, uint* lpdwNumberOfBytesRecvd, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); + __gshared WSASendMsgFn WSASendMsg; + __gshared WSARecvMsgFn WSARecvMsg; + + struct IN_PKTINFO { - in_addr imr_multiaddr; - in_addr imr_interface; + in_addr ipi_addr; + uint ipi_ifindex; + } + struct IN6_PKTINFO + { + in6_addr ipi6_addr; + uint ipi6_ifindex; + } + + struct WSACMSGHDR + { + size_t cmsg_len; + int cmsg_level; + int cmsg_type; } + alias LPWSACMSGHDR = WSACMSGHDR*; + + LPWSACMSGHDR WSA_CMSG_FIRSTHDR(LPWSAMSG msg) + => msg.Control.len >= WSACMSGHDR.sizeof ? cast(LPWSACMSGHDR)msg.Control.buf : null; + + LPWSACMSGHDR WSA_CMSG_NXTHDR(LPWSAMSG msg, LPWSACMSGHDR cmsg) + { + if (!cmsg) + return WSA_CMSG_FIRSTHDR(msg); + if (cast(ubyte*)cmsg + WSA_CMSGHDR_ALIGN(cmsg.cmsg_len) + WSACMSGHDR.sizeof > cast(ubyte*)msg.Control.buf + msg.Control.len) + return null; + return cast(LPWSACMSGHDR)(cast(ubyte*)cmsg + WSA_CMSGHDR_ALIGN(cmsg.cmsg_len)); + } + + void* WSA_CMSG_DATA(LPWSACMSGHDR cmsg) + => cast(ubyte*)cmsg + WSA_CMSGDATA_ALIGN(WSACMSGHDR.sizeof); + + size_t WSA_CMSGHDR_ALIGN(size_t length) + => (length + WSACMSGHDR.alignof-1) & ~(WSACMSGHDR.alignof-1); + + size_t WSA_CMSGDATA_ALIGN(size_t length) + => (length + size_t.alignof-1) & ~(size_t.alignof-1); + + struct WSAOVERLAPPED + { + uint Internal; + uint InternalHigh; + uint Offset; + uint OffsetHigh; + HANDLE hEvent; + } + alias LPWSAOVERLAPPED = WSAOVERLAPPED*; + + alias LPWSAOVERLAPPED_COMPLETION_ROUTINE = void function(uint dwError, uint cbTransferred, LPWSAOVERLAPPED lpOverlapped, uint dwFlags); + + extern(Windows) int WSASend(SOCKET s, LPWSABUF lpBuffers, uint dwBufferCount, uint* lpNumberOfBytesSent, uint dwFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); + extern(Windows) int WSASendTo(SOCKET s, LPWSABUF lpBuffers, uint dwBufferCount, uint* lpNumberOfBytesSent, uint dwFlags, const(sockaddr)* lpTo, int iTolen, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine); } + +} // SocketCallbacks diff --git a/src/urt/string/ansi.d b/src/urt/string/ansi.d index eac1150..6828cf1 100644 --- a/src/urt/string/ansi.d +++ b/src/urt/string/ansi.d @@ -68,33 +68,101 @@ enum ANSI_RESET = "\x1b[0m"; nothrow @nogc: -size_t parseANSICode(const(char)[] text) +size_t parse_ansi_code(const(char)[] text) { - import urt.string.ascii : isNumeric; + import urt.string.ascii : is_numeric; if (text.length < 3 || text[0] != '\x1b') return 0; if (text[1] != '[' && text[1] != 'O') return 0; size_t i = 2; - for (; i < text.length && (text[i].isNumeric || text[i] == ';'); ++i) + for (; i < text.length && (text[i].is_numeric || text[i] == ';'); ++i) {} if (i == text.length) return 0; return i + 1; } -char[] stripDecoration(char[] text) pure +size_t visible_width(const(char)[] text) { - return stripDecoration(text, text); + import urt.string.uni : uni_seq_len; + + size_t width = 0; + size_t i = 0; + while (i < text.length) + { + if (text[i] == '\x1b' && i + 1 < text.length && text[i + 1] == '[') + { + i += 2; + while (i < text.length && text[i] != 'm') + ++i; + if (i < text.length) + ++i; + } + else + { + ++width; + i += uni_seq_len(text[i .. $]); + } + } + return width; +} + +const(char)[] visible_slice(const(char)[] text, char[] buf, size_t start = 0, size_t end = size_t.max) +{ + import urt.string.uni : uni_seq_len; + + size_t out_pos = 0; + size_t visible = 0; + + for (size_t i = 0; i < text.length; ) + { + if (text[i] == '\x1b' && i + 1 < text.length && text[i + 1] == '[') + { + size_t seq_start = i; + i += 2; + while (i < text.length && text[i] != 'm') + ++i; + if (i < text.length) + ++i; + size_t seq_len = i - seq_start; + if (out_pos + seq_len <= buf.length) + { + buf[out_pos .. out_pos + seq_len] = text[seq_start .. i]; + out_pos += seq_len; + } + } + else + { + size_t ch_len = uni_seq_len(text[i .. $]); + if (visible >= start && visible < end) + { + if (out_pos + ch_len <= buf.length) + { + buf[out_pos .. out_pos + ch_len] = text[i .. i + ch_len]; + out_pos += ch_len; + } + } + ++visible; + i += ch_len; + } + } + + return buf[0 .. out_pos]; +} + +char[] strip_decoration(char[] text) pure +{ + return strip_decoration(text, text); } -char[] stripDecoration(const(char)[] text, char[] buffer) pure +char[] strip_decoration(const(char)[] text, char[] buffer) pure { size_t len = text.length, outLen = 0; char* dst = buffer.ptr; const(char)* src = text.ptr; - bool writeOutput = text.ptr != buffer.ptr; + bool write_output = text.ptr != buffer.ptr; for (size_t i = 0; i < len;) { char c = src[i]; @@ -106,11 +174,11 @@ char[] stripDecoration(const(char)[] text, char[] buffer) pure if (j < len && src[j] == 'm') { i = j + 1; - writeOutput = true; + write_output = true; continue; } } - if (BranchMoreExpensiveThanStore || writeOutput) + if (BranchMoreExpensiveThanStore || write_output) dst[outLen] = c; // skip stores where unnecessary (probably the common case) ++outLen; } diff --git a/src/urt/string/ascii.d b/src/urt/string/ascii.d index 5541d3a..36f814f 100644 --- a/src/urt/string/ascii.d +++ b/src/urt/string/ascii.d @@ -2,14 +2,14 @@ module urt.string.ascii; -char[] toLower(const(char)[] str) pure nothrow +char[] to_lower(const(char)[] str) pure nothrow { - return toLower(str, new char[str.length]); + return to_lower(str, new char[str.length]); } -char[] toUpper(const(char)[] str) pure nothrow +char[] to_upper(const(char)[] str) pure nothrow { - return toUpper(str, new char[str.length]); + return to_upper(str, new char[str.length]); } @@ -17,7 +17,7 @@ nothrow @nogc: // some character category flags... // 1 = alpha, 2 = numeric, 4 = white, 8 = newline, 10 = control, 20 = ???, 40 = url, 80 = hex -__gshared immutable ubyte[128] charDetails = [ +private __gshared immutable ubyte[128] char_details = [ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x14, 0x18, 0x10, 0x10, 0x18, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x00, @@ -28,20 +28,20 @@ __gshared immutable ubyte[128] charDetails = [ 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x00, 0x00, 0x00, 0x40, 0x10 ]; -__gshared immutable char[16] hexDigits = "0123456789ABCDEF"; +__gshared immutable char[16] hex_digits = "0123456789ABCDEF"; -bool isSpace(char c) pure => c < 128 && (charDetails[c] & 4); -bool isNewline(char c) pure => c < 128 && (charDetails[c] & 8); -bool isWhitespace(char c) pure => c < 128 && (charDetails[c] & 0xC); -bool isAlpha(char c) pure => c < 128 && (charDetails[c] & 1); -bool isNumeric(char c) pure => cast(uint)(c - '0') <= 9; -bool isAlphaNumeric(char c) pure => c < 128 && (charDetails[c] & 3); -bool isHex(char c) pure => c < 128 && (charDetails[c] & 0x80); -bool isControlChar(char c) pure => c < 128 && (charDetails[c] & 0x10); -bool isURL(char c) pure => c < 128 && (charDetails[c] & 0x40); +bool is_space(char c) pure => c < 128 && (char_details[c] & 4); +bool is_newline(char c) pure => c < 128 && (char_details[c] & 8); +bool is_whitespace(char c) pure => c < 128 && (char_details[c] & 0xC); +bool is_alpha(char c) pure => c < 128 && (char_details[c] & 1); +bool is_numeric(char c) pure => cast(uint)(c - '0') <= 9; +bool is_alpha_numeric(char c) pure => c < 128 && (char_details[c] & 3); +bool is_hex(char c) pure => c < 128 && (char_details[c] & 0x80); +bool is_control_char(char c) pure => c < 128 && (char_details[c] & 0x10); +bool is_url(char c) pure => c < 128 && (char_details[c] & 0x40); -char toLower(char c) pure +char to_lower(char c) pure { // this is the typical way; which is faster on a weak arch? // if (c >= 'A' && c <= 'Z') @@ -53,7 +53,7 @@ char toLower(char c) pure return c; } -char toUpper(char c) pure +char to_upper(char c) pure { // this is the typical way; which is faster on a weak arch? // if (c >= 'a' && c <= 'z') @@ -65,26 +65,62 @@ char toUpper(char c) pure return c; } -char[] toLower(const(char)[] str, char[] buffer) pure +char[] to_lower(const(char)[] str, char[] buffer) pure { + if (buffer.length < str.length) + return null; foreach (i; 0 .. str.length) - buffer[i] = toLower(str[i]); - return buffer; + buffer[i] = str[i].to_lower; + return buffer[0..str.length]; } -char[] toUpper(const(char)[] str, char[] buffer) pure +char[] to_upper(const(char)[] str, char[] buffer) pure { + if (buffer.length < str.length) + return null; foreach (i; 0 .. str.length) - buffer[i] = toUpper(str[i]); - return buffer; + buffer[i] = str[i].to_upper; + return buffer[0..str.length]; } -char[] toLower(char[] str) pure +char[] to_lower(char[] str) pure { - return toLower(str, str); + return to_lower(str, str); } -char[] toUpper(char[] str) pure +char[] to_upper(char[] str) pure { - return toUpper(str, str); + return to_upper(str, str); } + +ptrdiff_t cmp(bool case_insensitive = false)(const(char)[] a, const(char)[] b) pure +{ + if (a.length != b.length) + return a.length - b.length; + static if (case_insensitive) + { + for (size_t i = 0; i < a.length; ++i) + { + char ca = a[i].to_lower; + char cb = b[i].to_lower; + if (ca != cb) + return ca - cb; + } + } + else + { + for (size_t i = 0; i < a.length; ++i) + if (a[i] != b[i]) + return a[i] - b[i]; + } + return 0; +} + +ptrdiff_t icmp(const(char)[] a, const(char)[] b) pure + => cmp!true(a, b); + +bool eq(const(char)[] a, const(char)[] b) pure + => cmp(a, b) == 0; + +bool ieq(const(char)[] a, const(char)[] b) pure + => cmp!true(a, b) == 0; diff --git a/src/urt/string/format.d b/src/urt/string/format.d index 6016615..993a41b 100644 --- a/src/urt/string/format.d +++ b/src/urt/string/format.d @@ -1,99 +1,138 @@ module urt.string.format; -import urt.conv : parseIntFast; +import urt.conv : parse_int_fast; import urt.string; import urt.traits; import urt.util; public import urt.mem.temp : tformat, tconcat, tstring; - nothrow @nogc: debug { + // format functions are not re-entrant! static bool InFormatFunction = false; } alias StringifyFunc = ptrdiff_t delegate(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) nothrow @nogc; -alias IntifyFunc = ptrdiff_t delegate() nothrow @nogc; +struct Arg +{ + StringifyFunc fn; +} -ptrdiff_t toString(T)(auto ref T value, char[] buffer) +struct Format { - import urt.string.format : FormatArg; + this(T)(ref const T value, const(char)[] format) pure nothrow @nogc + { + fn = get_to_string_func(value); + fmt = format; + } + + StringifyFunc fn; + const(char)[] fmt; +} +ptrdiff_t toString(T)(ref const T value, char[] buffer, const(char)[] format = null, const(FormatArg)[] formatArgs = null) +{ debug InFormatFunction = true; - FormatArg a = FormatArg(value); - ptrdiff_t r = a.getString(buffer, null, null); + ptrdiff_t r = get_to_string_func(value)(buffer, format, formatArgs); debug InFormatFunction = false; return r; } -char[] concat(Args...)(char[] buffer, auto ref Args args) +alias formatValue = toString; // TODO: remove me? + +auto normalise_args(Args...)(ref const Args args) { - static if (Args.length == 0) + import urt.meta.tuple : Tuple; + alias NormalisedArgs = NormaliseArgs!Args; + Tuple!(NormalisedArgs) result = void; + +// pragma(msg, "[ ", Args, " ] --> [ ", NormalisedArgs, " ]"); + + static foreach (i; 0 .. Args.length) { + static if (is(NormalisedArgs[i] == char) || is(NormalisedArgs[i] == Format)) + result[i] = args[i]; + else static if (is(NormalisedArgs[i] == Arg)) + result[i] = Arg(get_to_string_func(args[i])); + else static if (is(NormalisedArgs[i] == Array!char) || is(NormalisedArgs[i] == String) || is(NormalisedArgs[i] == MutableString!N, size_t N)) + static assert(false, "use container[] at the callsite!"); + else + result[i] = args[i][]; + } + return result; +} + +char[] concat(Args...)(char[] buffer, auto ref const Args args) +{ + pragma(inline, true); + + alias NormalisedArgs = NormaliseArgs!Args; + + static if (Args.length == 0) return buffer.ptr[0 .. 0]; + else + return concat_impl(buffer, normalise_args(forward!args)); +} + +import urt.meta.tuple : Tuple; +char[] concat_impl(Args...)(char[] buffer, Tuple!Args args) +{ + size_t[Args.length] lens = void; + size_t length = 0; + static foreach (i; 0 .. Args.length) + { + static if (is(Args[i] == char)) + length += 1; + else static if (is(Args[i] == Arg)) + { + lens[i] = args[i].fn(null, null, null); + length += lens[i]; + } + else static if (is(Args[i] == Format)) + { + lens[i] = args[i].fn(null, args[i].fmt, null); + length += lens[i]; + } + else + { + lens[i] = args[i].length; + length += lens[i]; + } } - else static if ((Args.length == 1 && allAreStrings!Args) || allConstCorrectStrings!Args) + if (buffer.ptr) { - // this implementation handles pure string concatenations - if (!buffer.ptr) + if (length > buffer.length) + return null; + char* p = buffer.ptr; + static foreach (i; 0 .. Args.length) { - size_t length = 0; - static foreach (i, s; args) + static if (is(Args[i] == char)) + *p++ = args[i]; + else static if (is(Args[i] == Arg)) { - static if (is(typeof(s) : char)) - length += 1; - else - length += s.length; + args[i].fn(p[0..lens[i]], null, null); + p += lens[i]; } - return (cast(char*)null)[0 .. length]; - } - size_t offset = 0; - static foreach (i, s; args) - { - static if (is(typeof(s) : char)) + else static if (is(Args[i] == Format)) { - if (buffer.length < offset + 1) - return null; - buffer.ptr[offset++] = s; + args[i].fn(p[0..lens[i]], args[i].fmt, null); + p += lens[i]; } else { - if (buffer.length < offset + s.length) - return null; - buffer.ptr[offset .. offset + s.length] = s.ptr[0 .. s.length]; - offset += s.length; + p[0..lens[i]] = args[i].ptr[0..lens[i]]; + p += lens[i]; } } - return buffer.ptr[0 .. offset]; - } - static if (allAreStrings!Args) - { - // TODO: why can't inline this?! -// pragma(inline, true); - - // avoid duplicate instantiations with different attributes... - return concat!(constCorrectedStrings!Args)(buffer, args); - } - else - { - // this implementation handles all the other kinds of things! - - debug if (!__ctfe) InFormatFunction = true; - FormatArg[Args.length] argFuncs; - // TODO: no need to collect int-ify functions in the arg set... - static foreach(i, arg; args) - argFuncs[i] = FormatArg(arg); - char[] r = concatImpl(buffer, argFuncs); - debug if (!__ctfe) InFormatFunction = false; - return r; } + return buffer.ptr[0 .. length]; } -char[] format(Args...)(char[] buffer, const(char)[] fmt, ref Args args) +char[] format(Args...)(char[] buffer, const(char)[] fmt, auto ref Args args) { debug InFormatFunction = true; FormatArg[Args.length] argArr = void; @@ -104,553 +143,696 @@ char[] format(Args...)(char[] buffer, const(char)[] fmt, ref Args args) return r; } -ptrdiff_t formatValue(T)(auto ref T value, char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) -{ - static if (is(typeof(&value.toString) : StringifyFunc)) - return value.toString(buffer, format, formatArgs); - else - return value.defFormat.toString(buffer, format, formatArgs); -} - struct FormatArg { - enum IsAggregate(T) = is(T == struct) || is(T == class); - private this(T)(ref T value) pure nothrow @nogc { - static if (IsAggregate!T && is(typeof(&value.toString) : StringifyFunc)) - toString = &value.toString; - else static if (IsAggregate!T && __traits(compiles, value.toString(buffer, "format", cast(FormatArg[])null) == 0)) - { - // wrap in a delegate that adjusts for format + args... - static assert(false); - } - else static if (IsAggregate!T && __traits(compiles, value.toString(buffer, "format") == 0)) - { - // wrap in a delegate that adjusts for format... - static assert(false); - } - else static if (IsAggregate!T && __traits(compiles, value.toString(buffer) == 0)) - { - // wrap in a delegate... - static assert(false); - } - else static if (IsAggregate!T && __traits(compiles, value.toString((const(char)[]){}) == 0)) - { - // version with a sink function... - // wrap in a delegate that adjusts for format + args... - static assert(false); - } - else - toString = &defFormat(value).toString; + getString = get_to_string_func(value); - static if (is(typeof(&defFormat(value).toInt))) - toInt = &defFormat(value).toInt; - else - toInt = null; + static if (can_default_int!T) + _to_int_fun = &DefInt!T.to_int; } - ptrdiff_t getString(char[] buffer, const(char)[] format, const(FormatArg)[] args) const nothrow @nogc - { - return toString(buffer, format, args); - } + StringifyFunc getString; + ptrdiff_t getLength(const(char)[] format, const(FormatArg)[] args) const nothrow @nogc - { - return toString(null, format, args); - } + => getString(null, format, args); - bool canInt() const nothrow @nogc - { - return toInt != null; - } - ptrdiff_t getInt() const nothrow @nogc + bool canInt() const pure nothrow @nogc + => _to_int_fun !is null; + + ptrdiff_t getInt() const pure nothrow @nogc { - return toInt(); + ptrdiff_t delegate() pure nothrow @nogc to_int; + to_int.ptr = getString.ptr; + to_int.funcptr = _to_int_fun; + return to_int(); } private: - // TODO: we could assert that the delegate pointers match, and only store it once... - StringifyFunc toString; - IntifyFunc toInt; + // only store the function pointer, since 'this' will be common + ptrdiff_t function() pure nothrow @nogc _to_int_fun; } private: -pragma(inline, true) -DefFormat!T* defFormat(T)(ref const(T) value) pure nothrow @nogc +import urt.array; +enum is_some_string(T) = is(T == char) || is(T : const char[]) || is(T : const(String)) || is(T : const(MutableString!N), size_t N) || is(T : const(Array!(char, N)), size_t N); + +template num_string_args(Args...) { - return cast(DefFormat!T*)&value; + static if (Args.length == 0) + enum num_string_args = 0; + else static if (is_some_string!(Args[0])) + enum num_string_args = 1 + num_string_args!(Args[1 .. $]); + else + enum num_string_args = 0; } -ptrdiff_t defToString(T)(ref const(T) value, char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) nothrow @nogc +template NormaliseArgs(Args...) { - return (cast(DefFormat!T*)&value).toString(buffer, format, formatArgs); + import urt.meta : AliasSeq; + alias NormaliseArgs = AliasSeq!(); + static foreach (Arg; Args) + NormaliseArgs = AliasSeq!(NormaliseArgs, NormaliseOthers!(NormaliseConst!Arg)); } -struct DefFormat(T) +template NormaliseOthers(T) { - T value; + static if (is(T : const(char)[]) || is(T == char)) + alias NormaliseOthers = T; + else static if (is(T == String) || is(T == Array!char) || is(T == MutableString!N, size_t N)) + alias NormaliseOthers = const(char)[]; + else + alias NormaliseOthers = Arg; +} - ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const nothrow @nogc - { - static if (is(T == typeof(null))) - { - if (!buffer.ptr) - return 4; - if (buffer.length < 4) - return -1; - buffer[0 .. 4] = "null"; - return 4; - } - else static if (is(T == bool)) - { - size_t len = value ? 4 : 5; - if (!buffer.ptr) - return len; - if (buffer.length < len) - return -1; - buffer[0 .. len] = value ? "true" : "false"; - return len; - } - else static if (is(T == char) || is(T == wchar) || is(T == dchar)) - { - if (is(T == char) || value <= 0x7F) - { - if (buffer.ptr) - { - if (buffer.length < 1) - return -1; - buffer[0] = cast(char)value; - } - return 1; - } - else if (value <= 0x7FF) +template NormaliseConst(T) +{ + static if (is(T == const(U), U) || is(T == immutable(U), U)) + alias NormaliseConst = NormaliseConst!U; + else static if (is(T == immutable(U)[], U) || is(T == U[], U)) + alias NormaliseConst = const(U)[]; + else static if (is(T == U[N], U, size_t N)) + alias NormaliseConst = const(U)[]; + else + alias NormaliseConst = T; +} + +alias StringifyFuncReduced = ptrdiff_t delegate(char[] buffer, const(char)[] format) nothrow @nogc; +alias StringifyFuncReduced2 = ptrdiff_t delegate(char[] buffer) nothrow @nogc; + +enum can_default_int(T) = is_some_int!T || is(T == bool); + +template to_string_overload_index(T) +{ + static if (!is(T == Unqual!T)) // minimise redundant instantiations + enum to_string_overload_index = to_string_overload_index!(Unqual!T); + else + enum to_string_overload_index = () { + static if (__traits(hasMember, T, "toString")) { - if (buffer.ptr) + // multiple passes so that we correctly preference the overload with more arguments... + static foreach (i, overload; __traits(getOverloads, T, "toString", true)) { - if (buffer.length < 2) - return -1; - buffer[0] = cast(char)(0xC0 | (value >> 6)); - buffer[1] = cast(char)(0x80 | (value & 0x3F)); + static if (is(typeof(&overload) : typeof(StringifyFunc.funcptr))) + return i; + else static if (is(typeof(&overload!()) : typeof(StringifyFunc.funcptr))) + return i; } - return 2; - } - else if (value <= 0xFFFF) - { - if (buffer.ptr) + static foreach (i, overload; __traits(getOverloads, T, "toString", true)) { - if (buffer.length < 3) - return -1; - buffer[0] = cast(char)(0xE0 | (value >> 12)); - buffer[1] = cast(char)(0x80 | ((value >> 6) & 0x3F)); - buffer[2] = cast(char)(0x80 | (value & 0x3F)); + static if (is(typeof(&overload) : typeof(StringifyFuncReduced.funcptr))) + return i; + else static if (is(typeof(&overload!()) : typeof(StringifyFuncReduced.funcptr))) + return i; } - return 3; - } - else if (value <= 0x10FFFF) - { - if (buffer.ptr) + static foreach (i, overload; __traits(getOverloads, T, "toString", true)) { - if (buffer.length < 4) - return -1; - buffer[0] = cast(char)(0xF0 | (value >> 18)); - buffer[1] = cast(char)(0x80 | ((value >> 12) & 0x3F)); - buffer[2] = cast(char)(0x80 | ((value >> 6) & 0x3F)); - buffer[3] = cast(char)(0x80 | (value & 0x3F)); + static if (is(typeof(&overload) : typeof(StringifyFuncReduced2.funcptr))) + return i; + else static if (is(typeof(&overload!()) : typeof(StringifyFuncReduced2.funcptr))) + return i; } - return 4; } + return -1; + }(); +} + +StringifyFunc get_to_string_func(T)(ref T value) +{ + enum overload_index = to_string_overload_index!T; + + static if (overload_index >= 0) + { + alias overload = __traits(getOverloads, T, "toString", true)[overload_index]; + static if (__traits(isTemplate, overload)) + alias to_string = overload!(); + else + alias to_string = overload; + + // TODO: we can't alias the __traits(child) expression, so we need to repeat it everywhere! + + alias method_type = typeof(&__traits(child, value, to_string)); + + static if (is(method_type : StringifyFunc)) + return &__traits(child, value, to_string); + + else static if (is(method_type : StringifyFuncReduced) || is(method_type : StringifyFuncReduced2)) + { + StringifyFunc d; + static if (is(T == struct)) + d.ptr = &value; else - { - assert(false, "Invalid code point"); - return 0; - } + d.ptr = cast(void*)value; + static if (is(method_type : StringifyFuncReduced)) + d.funcptr = &ToStringShim.shim1!T; + else + d.funcptr = &ToStringShim.shim2!T; + return d; } - else static if (is(T == double) || is(T == float)) - { - import urt.conv : formatFloat, formatInt; - char[16] tmp = void; - if (format.length && format[0] == '*') - { - bool success; - size_t arg = format[1..$].parseIntFast(success); - if (!success || !formatArgs[arg].canInt) - return -2; - size_t width = formatArgs[arg].getInt; - size_t len = width.formatInt(tmp); - format = tmp[0..len]; - } - return formatFloat(value, buffer, format); - } - else static if (is(T == ulong) || is(T == long)) + // TODO: do we want to support toString variants with sink instead of buffer? + + return null; + } + else static if (is_unsigned_int!T && T.sizeof <= size_t.sizeof) + { + StringifyFunc fn; + fn.ptr = cast(void*)size_t(value); + fn.funcptr = &DefFormat!size_t.toString; + return fn; + } + else static if (is_signed_int!T && T.sizeof <= size_t.sizeof) + { + StringifyFunc fn; + fn.ptr = cast(void*)cast(size_t)ptrdiff_t(value); + fn.funcptr = &DefFormat!ptrdiff_t.toString; + return fn; + } + else static if (is_some_char!T) + { + StringifyFunc fn; + fn.ptr = cast(void*)cast(size_t)dchar(value); + fn.funcptr = &DefFormat!dchar.toString; + return fn; + } + else static if (T.sizeof <= size_t.sizeof) + { + StringifyFunc fn; + *cast(Unqual!T*)&fn.ptr = value; + fn.funcptr = &DefFormat!T.toString; + return fn; + } + else + return &(cast(DefFormat!T*)&value).toString; +} + +struct ToStringShim +{ + ptrdiff_t shim1(T)(char[] buffer, const(char)[] format, const(FormatArg)[]) + { + static if (is(T == struct)) + ref T _this = *cast(T*)&this; + else + T _this = cast(T)cast(void*)&this; + return _this.toString(buffer, format); + } + ptrdiff_t shim2(T)(char[] buffer, const(char)[], const(FormatArg)[]) + { + static if (is(T == struct)) + ref T _this = *cast(T*)&this; + else + T _this = cast(T)cast(void*)&this; + return _this.toString(buffer); + } +} + +struct DefInt(T) +{ + static if (T.sizeof > size_t.sizeof) + T value; + + ptrdiff_t to_int() const pure nothrow @nogc + { + static if (T.sizeof <= size_t.sizeof) { - import urt.conv : formatInt, formatUint; + T value = void; + *cast(size_t*)&value = cast(size_t)cast(void*)&this; + } - // TODO: what formats are interesting for ints? + static if (T.max > ptrdiff_t.max) + debug assert(value <= ptrdiff_t.max); + return cast(ptrdiff_t)value; + } +} - bool leadingZeroes = false; - bool toLower = false; - bool varLen = false; - ptrdiff_t padding = 0; - uint base = 10; +ptrdiff_t defToString(T)(ref const(T) value, char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) nothrow @nogc + => get_to_string_func(value)(buffer, format, formatArgs); - static if (is(T == long)) +template DefFormat(T) +{ + static if (is(T == const U, U) || is(T == immutable U, U)) + alias DefFormat = DefFormat!U; + else static if (is(T == const(U)[], U) || is(T == immutable(U)[], U)) + alias DefFormat = DefFormat!(U[]); + else struct DefFormat + { + static assert(!is(T == const), "How did this slip through?"); + + static if (T.sizeof > size_t.sizeof) + const T value; + + ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const nothrow @nogc + { + static if (T.sizeof <= size_t.sizeof) { - bool showSign = false; - if (format.length && format[0] == '+') - { - showSign = true; - format.popFront; - } + T value = void; + *cast(size_t*)&value = cast(size_t)cast(void*)&this; } - if (format.length && format[0] == '0') + + static if (is(T == U[N], U, size_t N)) + return defToString!(U[])(value[], buffer, format, formatArgs); + else static if (is(T : String) || is(T : MutableString!N, size_t N) || is(T : Array!(char, N), size_t N)) + return defToString!(char[])(value[], buffer, format, formatArgs); + else static if (is(T == typeof(null))) { - leadingZeroes = true; - format.popFront; + if (!buffer.ptr) + return 4; + if (buffer.length < 4) + return -1; + buffer[0 .. 4] = "null"; + return 4; } - if (format.length && format[0] == '*') + else static if (is(T == bool)) { - varLen = true; - format.popFront; + size_t len = value ? 4 : 5; + if (!buffer.ptr) + return len; + if (buffer.length < len) + return -1; + buffer[0 .. len] = value ? "true" : "false"; + return len; } - if (format.length && format[0].isNumeric) + else static if (is(T == char) || is(T == wchar) || is(T == dchar)) { - bool success; - padding = format.parseIntFast(success); - if (varLen) + if (is(T == char) || value <= 0x7F) { - if (padding < 0 || !formatArgs[padding].canInt) - return -2; - padding = formatArgs[padding].getInt; + if (buffer.ptr) + { + if (buffer.length < 1) + return -1; + buffer[0] = cast(char)value; + } + return 1; + } + else if (value <= 0x7FF) + { + if (buffer.ptr) + { + if (buffer.length < 2) + return -1; + buffer[0] = cast(char)(0xC0 | (value >> 6)); + buffer[1] = cast(char)(0x80 | (value & 0x3F)); + } + return 2; + } + else if (value <= 0xFFFF) + { + if (buffer.ptr) + { + if (buffer.length < 3) + return -1; + buffer[0] = cast(char)(0xE0 | (value >> 12)); + buffer[1] = cast(char)(0x80 | ((value >> 6) & 0x3F)); + buffer[2] = cast(char)(0x80 | (value & 0x3F)); + } + return 3; + } + else if (value <= 0x10FFFF) + { + if (buffer.ptr) + { + if (buffer.length < 4) + return -1; + buffer[0] = cast(char)(0xF0 | (value >> 18)); + buffer[1] = cast(char)(0x80 | ((value >> 12) & 0x3F)); + buffer[2] = cast(char)(0x80 | ((value >> 6) & 0x3F)); + buffer[3] = cast(char)(0x80 | (value & 0x3F)); + } + return 4; + } + else + { + assert(false, "Invalid code point"); + return 0; } } - if (format.length) + else static if (is(T == double) || is(T == float)) { - char b = format[0] | 0x20; - if (b == 'x') + import urt.conv : format_float, format_int; + + char[16] tmp = void; + if (format.length && format[0] == '*') { - base = 16; - toLower = format[0] == 'x' && buffer.ptr; + bool success; + size_t arg = format[1..$].parse_int_fast(success); + if (!success || !formatArgs[arg].canInt) + return -2; + size_t width = formatArgs[arg].getInt; + size_t len = width.format_int(tmp); + format = tmp[0..len]; } - else if (b == 'b') - base = 2; - else if (b == 'o') - base = 8; - else if (b == 'd') - base = 10; - format.popFront; + return format_float(value, buffer, format); } - - static if (is(T == long)) - size_t len = formatInt(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' ', showSign); - else - size_t len = formatUint(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' '); - - if (toLower && len > 0) + else static if (is(T == ulong) || is(T == long)) { - for (size_t i = 0; i < len; ++i) - if (cast(uint)(buffer.ptr[i] - 'A') < 26) - buffer.ptr[i] |= 0x20; + return format_int(value, is(T == long), buffer, format, formatArgs); } - - return len; - } - else static if (is(T == ubyte) || is(T == ushort) || is(T == uint)) - { - return defToString(ulong(value), buffer, format, formatArgs); - } - else static if (is(T == byte) || is(T == short) || is(T == int)) - { - return defToString(long(value), buffer, format, formatArgs); - } - else static if (is(T == const(char)*) || is(T == const(char*))) - { - const char[] t = value[0 .. value.strlen]; - return t.defToString(buffer, format, formatArgs); - } - else static if (is(T : const(U)*, U)) - { - static assert(size_t.sizeof == 4 || size_t.sizeof == 8); - enum Fmt = "0" ~ (size_t.sizeof == 4 ? "8" : "16") ~ "X"; - size_t p = cast(size_t)value; - return p.defToString(buffer, Fmt, null); - } - else static if (is(T == const char[])) - { - bool leftJustify = false; - bool varLen = false; - ptrdiff_t width = value.length; - if (format.length && format[0] == '-') + else static if (is(T == ubyte) || is(T == ushort) || is(T == uint)) { - leftJustify = true; - format.popFront; + return defToString(ulong(value), buffer, format, formatArgs); } - if (format.length && format[0] == '*') + else static if (is(T == byte) || is(T == short) || is(T == int)) { - varLen = true; - format.popFront; + return defToString(long(value), buffer, format, formatArgs); } - if (varLen && (!format.length || !format[0].isNumeric)) - return -2; - if (format.length && format[0].isNumeric) + else static if (is(T == const(char)*)) + { + const char[] t = value[0 .. value.strlen]; + return t.defToString(buffer, format, formatArgs); + } + else static if (is(T : const(U)*, U)) { - bool success; - width = format.parseIntFast(success); - if (varLen) + static assert(size_t.sizeof == 4 || size_t.sizeof == 8); + enum Fmt = "0" ~ (size_t.sizeof == 4 ? "8" : "16") ~ "X"; + size_t p = cast(size_t)value; + return p.defToString(buffer, Fmt, null); + } + else static if (is(T == char[])) + { + bool leftJustify = false; + bool varLen = false; + ptrdiff_t width = value.length; + if (format.length && format[0] == '-') { - if (width < 0 || !formatArgs[width].canInt) - return -2; - width = formatArgs[width].getInt; + leftJustify = true; + format.popFront; + } + if (format.length && format[0] == '*') + { + varLen = true; + format.popFront; + } + if (varLen && (!format.length || !format[0].is_numeric)) + return -2; + if (format.length && format[0].is_numeric) + { + bool success; + width = format.parse_int_fast(success); + if (varLen) + { + if (width < 0 || !formatArgs[width].canInt) + return -2; + width = formatArgs[width].getInt; + } + if (width < value.length) + width = value.length; } - if (width < value.length) - width = value.length; - } - if (!buffer.ptr) - return width; - if (buffer.length < width) - return -1; + if (!buffer.ptr) + return width; + if (buffer.length < width) + return -1; - // TODO: accept padd string in the formatSpec? + // TODO: accept padd string in the formatSpec? - size_t padding = width - value.length; - size_t pad = 0, len = 0; - if (!leftJustify && padding > 0) - { - pad = buffer.length < padding ? buffer.length : padding; - buffer[0 .. pad] = ' '; - buffer.takeFront(pad); + size_t padding = width - value.length; + size_t pad = 0, len = 0; + if (!leftJustify && padding > 0) + { + pad = buffer.length < padding ? buffer.length : padding; + buffer[0 .. pad] = ' '; + buffer.takeFront(pad); + } + len = buffer.length < value.length ? buffer.length : value.length; + buffer[0 .. len] = value[0 .. len]; + if (padding > 0 && leftJustify) + { + buffer.takeFront(len); + pad = buffer.length < padding ? buffer.length : padding; + buffer[0 .. pad] = ' '; + } + return pad + len; } - len = buffer.length < value.length ? buffer.length : value.length; - buffer[0 .. len] = value[0 .. len]; - if (padding > 0 && leftJustify) +// else static if (is(T == wchar[])) +// { +// } +// else static if (is (T == U[], U : dchar)) +// { +// // TODO: UTF ENCODE... +// } + else static if (is(T == void[])) { - buffer.takeFront(len); - pad = buffer.length < padding ? buffer.length : padding; - buffer[0 .. pad] = ' '; - } - return pad + len; - } - else static if (is(T : const char[])) - { - return defToString!(const char[])(cast(const char[])value[], buffer, format, formatArgs); - } -// else static if (is(T : const(wchar)[])) -// { -// } -// else static if (is (T : const(U)[], U : dchar)) -// { -// // TODO: UTF ENCODE... -// } - else static if (is(T == void[]) || is(T == const(void)[])) - { - if (!value.length) - return 0; + if (!value.length) + return 0; - int grp1 = 1, grp2 = 0; - if (format.length && format[0].isNumeric) - { - bool success; - grp1 = cast(int)format.parseIntFast(success); - if (success && format.length > 0 && format[0] == ':' && - format.length > 1 && format[1].isNumeric) + int grp1 = 1, grp2 = 0; + if (format.length && format[0].is_numeric) { - format.popFront(); - grp2 = cast(int)format.parseIntFast(success); + bool success; + grp1 = cast(int)format.parse_int_fast(success); + if (success && format.length > 0 && format[0] == ':' && + format.length > 1 && format[1].is_numeric) + { + format.popFront(); + grp2 = cast(int)format.parse_int_fast(success); + } + if (!success) + return -2; } - if (!success) - return -2; - } - if (!buffer.ptr) - { - size_t len = value.length*2; - if (grp1) - len += (value.length-1) / grp1; - return len; - } + if (!buffer.ptr) + { + size_t len = value.length*2; + if (grp1) + len += (value.length-1) / grp1; + return len; + } - char[] hex = toHexString(cast(ubyte[])value, buffer, grp1, grp2); - return hex.length; - } - else static if (is(T : const U[], U)) - { - // arrays of other stuff - size_t len = 1; - if (buffer.ptr) - { - if (buffer.length < 1) + char[] hex = toHexString(cast(ubyte[])value, buffer, grp1, grp2); + if (!hex.ptr) return -1; - buffer[0] = '['; + return hex.length; } - - for (size_t i = 0; i < value.length; ++i) + else static if (is(T : const U[], U)) { - if (i > 0) + // arrays of other stuff + size_t len = 1; + if (buffer.ptr) + { + if (buffer.length < 1) + return -1; + buffer[0] = '['; + } + + for (size_t i = 0; i < value.length; ++i) { + if (i > 0) + { + if (buffer.ptr) + { + if (len == buffer.length) + return -1; + buffer[len] = ','; + } + ++len; + } + + FormatArg arg = FormatArg(value[i]); if (buffer.ptr) { - if (len == buffer.length) - return -1; - buffer[len] = ','; + ptrdiff_t argLen = arg.getString(buffer.ptr[len .. buffer.length], format, formatArgs); + if (argLen < 0) + return argLen; + len += argLen; } - ++len; + else + len += arg.getLength(format, formatArgs); } - FormatArg arg = FormatArg(value[i]); if (buffer.ptr) { - ptrdiff_t argLen = arg.getString(buffer.ptr[len .. buffer.length], format, formatArgs); - if (argLen < 0) - return argLen; - len += argLen; + if (len == buffer.length) + return -1; + buffer[len] = ']'; } - else - len += arg.getLength(format, formatArgs); + return ++len; } - - if (buffer.ptr) + else static if (is(T B == enum)) { - if (len == buffer.length) - return -1; - buffer[len] = ']'; - } - return ++len; - } - else static if (is(T B == enum)) - { - // TODO: optimise short enums with a TABLE! + import urt.conv : format_int; + import urt.meta.enuminfo : enum_key_from_value; - // TODO: should probably return FQN ??? - string key = null; - val: switch (value) - { - static foreach (i, KeyName; __traits(allMembers, T)) + const(char)[] key = enum_key_from_value!T(value); + + // TODO: should probably return FQN ??? + if (key) { - static if (!EnumKeyIsDuplicate!(T, i)) + size_t len = T.stringof.length + 1 + key.length; + if (buffer.ptr) { - case __traits(getMember, T, KeyName): - key = KeyName; - break val; + if (buffer.length < len) + return -1; + buffer[0 .. T.stringof.length] = T.stringof; + buffer[T.stringof.length] = '.'; + buffer[T.stringof.length + 1 .. len] = key[]; } + return len; } - default: - if (!buffer.ptr) - return T.stringof.length + 2 + defToString!B(cast(B)value, null, null, null); - if (buffer.length < T.stringof.length + 2) - return -1; - buffer[0 .. T.stringof.length] = T.stringof; - buffer[T.stringof.length] = '('; - ptrdiff_t len = defToString!B(*cast(B*)&value, buffer[T.stringof.length + 1 .. $], null, null); - if (len < 0) - return len; - len = T.stringof.length + 2 + len; - if (buffer.length < len) - return -1; - buffer[len - 1] = ')'; + char[24] val = void; + ptrdiff_t len = format_int(long(value), val[]); + if (len <= 0) return len; - } - - size_t len = T.stringof.length + 1 + key.length; - if (!buffer.ptr) - return len; - - if (buffer.length < len) - return -1; - buffer[0 .. T.stringof.length] = T.stringof; - buffer[T.stringof.length] = '.'; - buffer[T.stringof.length + 1 .. len] = key[]; - return len; - } - else static if (is(T == class)) - { - try - { - const(char)[] t = (cast()value).toString(); + size_t total_len = T.stringof.length + 2 + len; if (!buffer.ptr) - return t.length; - if (buffer.length < t.length) - return -1; - buffer[0 .. t.length] = t[]; - return t.length; - } - catch (Exception) - return -1; - } - else static if (is(T == const)) - { - return defToString!(Unqual!T)(cast()value, buffer, format, formatArgs); - } - else static if (is(T == struct)) - { - // general structs - if (buffer.ptr) - { - if (buffer.length < T.stringof.length + 2) + return total_len; + if (buffer.length < total_len) return -1; buffer[0 .. T.stringof.length] = T.stringof; buffer[T.stringof.length] = '('; + buffer[T.stringof.length + 1 .. total_len - 1] = val[0 .. len]; + buffer[total_len - 1] = ')'; + return total_len; + } + else static if (is(T == class)) + { + return value.toString(buffer, format, formatArgs); } + else static if (is(T == struct)) + { + static assert(!__traits(hasMember, T, "toString"), "Struct with custom toString not properly selected!"); - size_t len = T.stringof.length + 1; - static foreach (i; 0 .. value.tupleof.length) - {{ - static if (i > 0) + // general structs + if (buffer.ptr) { + if (buffer.length < T.stringof.length + 2) + return -1; + buffer[0 .. T.stringof.length] = T.stringof; + buffer[T.stringof.length] = '('; + } + + size_t len = T.stringof.length + 1; + static foreach (i; 0 .. value.tupleof.length) + {{ + static if (i > 0) + { + if (buffer.ptr) + { + if (len + 2 > buffer.length) + return -1; + buffer[len .. len + 2] = ", "; + } + len += 2; + } + + FormatArg arg = FormatArg(value.tupleof[i]); if (buffer.ptr) { - if (len + 2 > buffer.length) - return -1; - buffer[len .. len + 2] = ", "; + ptrdiff_t argLen = arg.getString(buffer.ptr[len .. buffer.length], null, null); + if (argLen < 0) + return argLen; + len += argLen; } - len += 2; - } + else + len += arg.getLength(null, null); + + }} - FormatArg arg = FormatArg(value.tupleof[i]); if (buffer.ptr) { - ptrdiff_t argLen = arg.getString(buffer.ptr[len .. buffer.length], null, null); - if (argLen < 0) - return argLen; - len += argLen; + if (len == buffer.length) + return -1; + buffer[len] = ')'; } - else - len += arg.getLength(null, null); - - }} - - if (buffer.ptr) + return ++len; + } + else static if (is(T == function)) { - if (len == buffer.length) - return -1; - buffer[len] = ')'; + assert(false, "TODO"); + return 0; + } + else static if (is(T == delegate)) + { + assert(false, "TODO"); + return 0; } - return ++len; + else + static assert(false, "Not implemented for type: ", T.stringof); } - else - static assert(false, "Not implemented for type: ", T.stringof); } +} + +ptrdiff_t format_int(ulong value, bool signed, char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) +{ + import urt.conv : format_int, format_uint; + + // TODO: what formats are interesting for ints? - static if (isSomeInt!T || is(T == bool)) + bool show_sign = false; + bool leadingZeroes = false; + bool to_lower = false; + bool varLen = false; + ptrdiff_t padding = 0; + uint base = 10; + + if (signed && format.length && format[0] == '+') + { + show_sign = true; + format.popFront; + } + if (format.length && format[0] == '0') { - ptrdiff_t toInt() const pure nothrow @nogc + leadingZeroes = true; + format.popFront; + } + if (format.length && format[0] == '*') + { + varLen = true; + format.popFront; + } + if (format.length && format[0].is_numeric) + { + bool success; + padding = format.parse_int_fast(success); + if (varLen) + { + if (padding < 0 || !formatArgs[padding].canInt) + return -2; + padding = formatArgs[padding].getInt; + } + } + if (format.length) + { + char b = format[0] | 0x20; + if (b == 'x') { - static if (T.max > ptrdiff_t.max) - debug assert(value <= ptrdiff_t.max); - return cast(ptrdiff_t)value; + base = 16; + to_lower = format[0] == 'x' && buffer.ptr; } + else if (b == 'b') + base = 2; + else if (b == 'o') + base = 8; + else if (b == 'd') + base = 10; + format.popFront; } + + ptrdiff_t len; + if (signed) + len = format_int(cast(ptrdiff_t)value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' ', show_sign); + else + len = format_uint(value, buffer, base, cast(uint)padding, leadingZeroes ? '0' : ' '); + + if (to_lower && len > 0) + { + for (size_t i = 0; i < len; ++i) + if (cast(uint)(buffer.ptr[i] - 'A') < 26) + buffer.ptr[i] |= 0x20; + } + + return len; } -char[] concatImpl(char[] buffer, const(FormatArg)[] args) nothrow @nogc +char[] concat_impl(char[] buffer, const(StringifyFunc)[] args) nothrow @nogc { size_t len = 0; foreach (a; args) { - ptrdiff_t r = a.getString(buffer.ptr ? buffer[len..$] : null, null, null); + ptrdiff_t r = a(buffer.ptr ? buffer[len..$] : null, null, null); if (r < 0) return null; len += r; @@ -744,7 +926,7 @@ ptrdiff_t parseFormat(ref const(char)[] format, ref char[] buffer, const(FormatA // get the arg index bool success; - arg = format.parseIntFast(success); + arg = format.parse_int_fast(success); if (!success) { assert(false, "Invalid format string: Number expected!"); @@ -807,7 +989,7 @@ ptrdiff_t parseFormat(ref const(char)[] format, ref char[] buffer, const(FormatA // } bool success; - ptrdiff_t index = formatSpec.parseIntFast(success); + ptrdiff_t index = formatSpec.parse_int_fast(success); // if (varRef) // { // if (arg < 0 || !args[arg].canInt) @@ -861,6 +1043,8 @@ ptrdiff_t parseFormat(ref const(char)[] format, ref char[] buffer, const(FormatA if (bytes < 0) return -2; char[] t = formatImpl(buffer, indirectFormat.ptr[0 .. bytes], args); + if (!t.ptr) + return -1; len = t.length; } else @@ -916,40 +1100,6 @@ unittest } - - -// a template that tests is all template args are a char array, or a char -template allAreStrings(Args...) -{ - static if (Args.length == 1) - enum allAreStrings = is(Args[0] : const(char[])) || is(isSomeChar!(Args[0])); - else - enum allAreStrings = (is(Args[0] : const(char[])) || is(isSomeChar!(Args[0]))) && allAreStrings!(Args[1 .. $]); -} - -template allConstCorrectStrings(Args...) -{ - static if (Args.length == 1) - enum allConstCorrectStrings = is(Args[0] == const(char[])) || is(Args[0] == const char); - else - enum allConstCorrectStrings = (is(Args[0] == const(char[])) || is(Args[0] == const char)) && allConstCorrectStrings!(Args[1 .. $]); -} - -template constCorrectedStrings(Args...) -{ - import urt.meta : AliasSeq; - alias constCorrectedStrings = AliasSeq!(); - static foreach (Ty; Args) - { - static if (is(Ty : const(char)[])) - constCorrectedStrings = AliasSeq!(constCorrectedStrings, const(char[])); - else static if (isSomeChar!Ty) - constCorrectedStrings = AliasSeq!(constCorrectedStrings, const(char)); - else - static assert(false, "Argument must be a char array or a char: ", T); - } -} - template EnumKeyIsDuplicate(Enum, size_t item) { alias Elements = __traits(allMembers, Enum); diff --git a/src/urt/string/package.d b/src/urt/string/package.d index c4abc6f..77ec36b 100644 --- a/src/urt/string/package.d +++ b/src/urt/string/package.d @@ -1,132 +1,249 @@ module urt.string; +import urt.string.uni; +import urt.traits : is_some_char; + public import urt.string.ascii; public import urt.string.string; public import urt.string.tailstring; // seful string operations defined elsewhere public import urt.array : empty, popFront, popBack, takeFront, takeBack; -public import urt.mem : strlen; - - -enum TempStringBufferLen = 1024; -enum TempStringMaxLen = TempStringBufferLen / 2; +public import urt.mem : strlen, wcslen; +public import urt.mem.temp : tstringz, twstringz; -static char[TempStringBufferLen] s_tempStringBuffer; -static size_t s_tempStringBufferPos = 0; +nothrow @nogc: -char[] allocTempString(size_t len) nothrow @nogc +size_t strlen_s(const(char)[] s) pure { - assert(len <= TempStringMaxLen); + size_t len = 0; + while (len < s.length && s[len] != '\0') + ++len; + return len; +} - if (len <= TempStringBufferLen - s_tempStringBufferPos) +ptrdiff_t cmp(bool case_insensitive = false, T, U)(const(T)[] a, const(U)[] b) pure +{ + static if (case_insensitive) + return uni_compare_i(a, b); + else { - char[] s = s_tempStringBuffer[s_tempStringBufferPos .. s_tempStringBufferPos + len]; - s_tempStringBufferPos += len; - return s; + static if (is(T == U)) + { + if (a.length != b.length) + return a.length - b.length; + } + return uni_compare(a, b); } - s_tempStringBufferPos = len; - return s_tempStringBuffer[0 .. len]; } -char* tstringz(const(char)[] str) nothrow @nogc -{ - char[] buffer = allocTempString(str.length + 1); - buffer[0..str.length] = str[]; - buffer[str.length] = 0; - return buffer.ptr; -} +ptrdiff_t icmp(T, U)(const(T)[] a, const(U)[] b) pure + => cmp!true(a, b); + +bool eq(const(char)[] a, const(char)[] b) pure + => cmp(a, b) == 0; + +bool ieq(const(char)[] a, const(char)[] b) pure + => cmp!true(a, b) == 0; -wchar* twstringz(const(char)[] str) nothrow @nogc +size_t findFirst(bool case_insensitive = false, T, U)(const(T)[] s, const U c) + if (is_some_char!T && is_some_char!U) { - wchar[] buffer = cast(wchar[])allocTempString((str.length + 1) * 2); + static if (is(U == char)) + assert(c <= 0x7F, "Invalid UTF character"); + else static if (is(U == wchar)) + assert(c < 0xD800 || c >= 0xE000, "Invalid UTF character"); - // TODO: actually decode UTF8 into UTF16!! + // TODO: what if `c` is 'ß'? do we find "ss" in case-insensitive mode? + // and if `c` is 's', do we match 'ß'? + + static if (case_insensitive) + const U lc = cast(U)c.uni_case_fold(); + else + alias lc = c; - foreach (i, c; str) - buffer[i] = c; - buffer[str.length] = 0; - return buffer.ptr; + size_t i = 0; + while (i < s.length) + { + static if (U.sizeof <= T.sizeof) + { + enum l = 1; + dchar d = s[i]; + } + else + { + size_t l; + dchar d = next_dchar(s[i..$], l); + } + static if (case_insensitive) + { + static if (is(U == char)) + { + // only fold the ascii characters, since lc is known to be ascii + if (uint(d - 'A') < 26) + d |= 0x20; + } + else + d = d.uni_case_fold(); + } + if (d == lc) + break; + i += l; + } + return i; } -ptrdiff_t cmp(const(char)[] a, const(char)[] b) pure nothrow @nogc +size_t find_first_i(T, U)(const(T)[] s, U c) + if (is_some_char!T && is_some_char!U) + => findFirst!true(s, c); + +size_t findLast(bool case_insensitive = false, T, U)(const(T)[] s, const U c) + if (is_some_char!T && is_some_char!U) { - if (a.length != b.length) - return a.length - b.length; - for (size_t i = 0; i < a.length; ++i) + static assert(case_insensitive == false, "TODO"); + + static if (is(U == char)) + assert(c <= 0x7F, "Invalid unicode character"); + else static if (is(U == wchar)) + assert(c >= 0xD800 && c < 0xE000, "Invalid unicode character"); + + ptrdiff_t last = s.length-1; + while (last >= 0) { - ptrdiff_t diff = a[i] - b[i]; - if (diff) - return diff; + static if (U.sizeof <= T.sizeof) + { + if (s[last] == c) + return cast(size_t)last; + } + else + { + // this is tricky, because we need to seek backwards to the start of surrogate sequences + assert(false, "TODO"); + } } - return 0; + return s.length; } -ptrdiff_t icmp(const(char)[] a, const(char)[] b) pure nothrow @nogc +size_t find_last_i(T, U)(const(T)[] s, U c) + if (is_some_char!T && is_some_char!U) + => findLast!true(s, c); + +size_t findFirst(bool case_insensitive = false, T, U)(const(T)[] s, const(U)[] t) + if (is_some_char!T && is_some_char!U) { - if (a.length != b.length) - return a.length - b.length; - for (size_t i = 0; i < a.length; ++i) + if (t.length == 0) + return 0; + + // fast-path for one-length tokens + size_t l = t.uni_seq_len(); + if (l == t.length) { - ptrdiff_t diff = toLower(a[i]) - toLower(b[i]); - if (diff) - return diff; + dchar c = t.next_dchar(l); + if (c < 0x80) + return findFirst!case_insensitive(s, cast(char)c); + if (c < 0x10000) + return findFirst!case_insensitive(s, cast(wchar)c); + return findFirst!case_insensitive(s, c); } - return 0; + + size_t offset = 0; + while (offset < s.length) + { + + static if (case_insensitive) + int c = uni_compare_i(s[offset .. $], t); + else + int c = uni_compare(s[offset .. $], t); + if (c == int.max || c == 0) + return offset; + if (c == int.min) + return s.length; + offset += s[offset .. $].uni_seq_len(); + } + return s.length; +} + +size_t find_first_i(T, U)(const(T)[] s, const(U)[] t) + if (is_some_char!T && is_some_char!U) + => findFirst!true(s, t); + +size_t findLast(bool case_insensitive = false, T, U)(const(T)[] s, const(U)[] t) + if (is_some_char!T && is_some_char!U) +{ + // this is tricky, because we need to seek backwards to the start of surrogate sequences + assert(false, "TODO"); } -bool ieq(const(char)[] a, const(char)[] b) pure nothrow @nogc - => icmp(a, b) == 0; +size_t find_last_i(T, U)(const(T)[] s, const(U)[] t) + if (is_some_char!T && is_some_char!U) + => findLast!true(s, t); -bool startsWith(const(char)[] s, const(char)[] prefix) pure nothrow @nogc +bool contains(bool case_insensitive = false, T, U)(const(T)[] s, U c, size_t *offset = null) + if (is_some_char!T && is_some_char!U) +{ + size_t i = findFirst!case_insensitive(s, c); + if (i == s.length) + return false; + if (offset) + *offset = i; + return true; +} + +bool contains(bool case_insensitive = false, T, U)(const(T)[] s, const(U)[] t, size_t *offset = null) + if (is_some_char!T && is_some_char!U) +{ + size_t i = findFirst!case_insensitive(s, t); + if (i == s.length) + return false; + if (offset) + *offset = i; + return true; +} + +bool contains_i(T, U)(const(T)[] s, U c, size_t *offset = null) + if (is_some_char!T && is_some_char!U) + => contains!true(s, c, offset); + +bool contains_i(T, U)(const(T)[] s, const(U)[] t, size_t *offset = null) + if (is_some_char!T && is_some_char!U) + => contains!true(s, t, offset); + +bool startsWith(const(char)[] s, const(char)[] prefix) pure { if (s.length < prefix.length) return false; - return s[0 .. prefix.length] == prefix[]; + return cmp(s[0 .. prefix.length], prefix) == 0; } -bool endsWith(const(char)[] s, const(char)[] suffix) pure nothrow @nogc +bool endsWith(const(char)[] s, const(char)[] suffix) pure { if (s.length < suffix.length) return false; - return s[$ - suffix.length .. $] == suffix[]; + return cmp(s[$ - suffix.length .. $], suffix) == 0; } -inout(char)[] trim(bool Front = true, bool Back = true)(inout(char)[] s) pure nothrow @nogc +inout(char)[] trim(alias pred = is_whitespace, bool Front = true, bool Back = true)(inout(char)[] s) pure { size_t first = 0, last = s.length; static if (Front) { - while (first < s.length && isWhitespace(s.ptr[first])) + while (first < s.length && pred(s.ptr[first])) ++first; } static if (Back) { - while (last > first && isWhitespace(s.ptr[last - 1])) + while (last > first && pred(s.ptr[last - 1])) --last; } return s.ptr[first .. last]; } -alias trimFront = trim!(true, false); +alias trimFront(alias pred = is_whitespace) = trim!(pred, true, false); -alias trimBack = trim!(false, true); +alias trimBack(alias pred = is_whitespace) = trim!(pred, false, true); -inout(char)[] trimComment(char Delimiter)(inout(char)[] s) -{ - size_t i = 0; - for (; i < s.length; ++i) - { - if (s[i] == Delimiter) - break; - } - while(i > 0 && (s[i-1] == ' ' || s[i-1] == '\t')) - --i; - return s[0 .. i]; -} - -inout(char)[] takeLine(ref inout(char)[] s) pure nothrow @nogc +inout(char)[] takeLine(ref inout(char)[] s) pure { for (size_t i = 0; i < s.length; ++i) { @@ -148,20 +265,26 @@ inout(char)[] takeLine(ref inout(char)[] s) pure nothrow @nogc return t; } -inout(char)[] split(char Separator, bool HandleQuotes = true)(ref inout(char)[] s) +inout(char)[] split(char[] separators, bool handle_quotes = true, bool do_trim = true)(ref inout(char)[] s, char* separator = null) pure { - static if (HandleQuotes) + static if (handle_quotes) int inQuotes = 0; else enum inQuotes = false; size_t i = 0; - for (; i < s.length; ++i) + loop: for (; i < s.length; ++i) { - if (s[i] == Separator && !inQuotes) - break; - - static if (HandleQuotes) + static foreach (sep; separators) + { + if (s[i] == sep && !inQuotes) + { + if (separator) + *separator = s[i]; + break loop; + } + } + static if (handle_quotes) { if (s[i] == '"' && !(inQuotes & 0x6)) inQuotes = 1 - inQuotes; @@ -171,40 +294,29 @@ inout(char)[] split(char Separator, bool HandleQuotes = true)(ref inout(char)[] inQuotes = 4 - inQuotes; } } - inout(char)[] t = s[0 .. i].trimBack; - s = i < s.length ? s[i+1 .. $].trimFront : null; + static if (do_trim) + { + inout(char)[] t = s[0 .. i].trimBack; + s = i < s.length ? s[i+1 .. $].trimFront : null; + } + else + { + inout(char)[] t = s[0 .. i]; + s = i < s.length ? s[i+1 .. $] : null; + } return t; } -inout(char)[] split(Separator...)(ref inout(char)[] s, out char sep) +alias split(char separator, bool handle_quotes = true, bool do_trim = true) = split!([separator], handle_quotes, do_trim); + +// TODO: deprecate this one... +inout(char)[] split(separators...)(ref inout(char)[] s, out char sep) pure { sep = '\0'; - int inQuotes = 0; - size_t i = 0; - loop: for (; i < s.length; ++i) - { - static foreach (S; Separator) - { - static assert(is(typeof(S) == char), "Only single character separators supported"); - if (s[i] == S && !inQuotes) - { - sep = s[i]; - break loop; - } - } - if (s[i] == '"' && !(inQuotes & 0x6)) - inQuotes = 1 - inQuotes; - else if (s[i] == '\'' && !(inQuotes & 0x5)) - inQuotes = 2 - inQuotes; - else if (s[i] == '`' && !(inQuotes & 0x3)) - inQuotes = 4 - inQuotes; - } - inout(char)[] t = s[0 .. i].trimBack; - s = i < s.length ? s[i+1 .. $].trimFront : null; - return t; + return split!([separators], true, true)(s, &sep); } -char[] unQuote(const(char)[] s, char[] buffer) pure nothrow @nogc +char[] unQuote(const(char)[] s, char[] buffer) pure { // TODO: should this scan and match quotes rather than assuming there are no rogue closing quotes in the middle of the string? if (s.empty) @@ -223,18 +335,18 @@ char[] unQuote(const(char)[] s, char[] buffer) pure nothrow @nogc return buffer; } -char[] unQuote(char[] s) pure nothrow @nogc +char[] unQuote(char[] s) pure { return unQuote(s, s); } -char[] unQuote(const(char)[] s) nothrow @nogc +char[] unQuote(const(char)[] s) { import urt.mem.temp : talloc; return unQuote(s, cast(char[])talloc(s.length)); } -char[] unEscape(inout(char)[] s, char[] buffer) pure nothrow @nogc +char[] unEscape(inout(char)[] s, char[] buffer) pure { if (s.empty) return null; @@ -266,17 +378,17 @@ char[] unEscape(inout(char)[] s, char[] buffer) pure nothrow @nogc return buffer[0..len]; } -char[] unEscape(char[] s) pure nothrow @nogc +char[] unEscape(char[] s) pure { return unEscape(s, s); } -char[] toHexString(const(void[]) data, char[] buffer, uint group = 0, uint secondaryGroup = 0, const(char)[] seps = " -") pure nothrow @nogc +char[] toHexString(const(void[]) data, char[] buffer, uint group = 0, uint secondaryGroup = 0, const(char)[] seps = " -") pure { - import urt.util : isPowerOf2; - assert(group.isPowerOf2); - assert(secondaryGroup.isPowerOf2); + import urt.util : is_power_of_2; + assert(group.is_power_of_2); + assert(secondaryGroup.is_power_of_2); assert((secondaryGroup == 0 && seps.length > 0) || seps.length > 1, "Secondary grouping requires additional separator"); if (data.length == 0) @@ -296,8 +408,8 @@ char[] toHexString(const(void[]) data, char[] buffer, uint group = 0, uint secon size_t offset = 0; for (size_t i = 0; true; ) { - buffer[offset++] = hexDigits[src[i] >> 4]; - buffer[offset++] = hexDigits[src[i] & 0xF]; + buffer[offset++] = hex_digits[src[i] >> 4]; + buffer[offset++] = hex_digits[src[i] & 0xF]; bool sep = (i & mask) == mask; if (++i == data.length) @@ -307,7 +419,7 @@ char[] toHexString(const(void[]) data, char[] buffer, uint group = 0, uint secon } } -char[] toHexString(const(ubyte[]) data, uint group = 0, uint secondaryGroup = 0, const(char)[] seps = " -") nothrow @nogc +char[] toHexString(const(ubyte[]) data, uint group = 0, uint secondaryGroup = 0, const(char)[] seps = " -") { import urt.mem.temp; @@ -319,7 +431,7 @@ char[] toHexString(const(ubyte[]) data, uint group = 0, uint secondaryGroup = 0, unittest { - ubyte[] data = [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF]; + ubyte[8] data = [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF]; assert(data.toHexString(0) == "0123456789ABCDEF"); assert(data.toHexString(1) == "01 23 45 67 89 AB CD EF"); assert(data.toHexString(2) == "0123 4567 89AB CDEF"); @@ -329,17 +441,476 @@ unittest } -bool wildcardMatch(const(char)[] wildcard, const(char)[] value) +// Pattern grammar: +// * - Match zero or more characters +// ? - Match exactly one character +// # - Match exactly one digit (0-9) +// ~T - Token T is optional (matches with or without T) +// \C - Escape character C (literal match) +// Examples: +// "h*o" matches "hello", "ho" +// "h?llo" matches "hello", "hallo" but not "hllo" +// "v#.#" matches "v1.2", "v3.7" but not "va.b" +// "~ab" matches "ab", "b" +// "h\*o" matches "h*o" literally +bool wildcard_match(const(char)[] wildcard, const(char)[] value, bool value_wildcard = false, bool case_insensitive = false) pure { - // TODO: write this function... + const(char)* a = wildcard.ptr, ae = a + wildcard.length, b = value.ptr, be = b + value.length; - // HACK: we just use this for tail wildcards right now... - for (size_t i = 0; i < wildcard.length; ++i) + static inout(char)* skip_wilds(inout(char)* p, const(char)* pe) { - if (wildcard[i] == '*') - return true; - if (wildcard[i] != value[i]) + while (p < pe) + { + if (*p == '*') + ++p; + else if (*p == '~') + { + one_more: + if (++p < pe) + { + if (*p == '~') + goto one_more; + if (*p++ == '\\' && p < pe) + ++p; + } + } + else break; + } + // HACK: skip trailing slash (handle a degenerate case) + if (p == pe - 1 && *p == '\\') + ++p; + return p; + } + + struct BacktrackState + { + const(char)* a, b; + } + BacktrackState[64] backtrack_stack = void; + size_t backtrack_depth = 0; + + char ca_orig = void, cb_orig = void, ca = void, cb = void; + + while (a < ae && b < be) + { + ca_orig = *a, cb_orig = *b; + + // handle wildcards + if (ca_orig == '*') + { + a = skip_wilds(++a, ae); + if (a == ae) // tail star matches everything + return true; + + const(char)[] a_remain = a[0 .. ae - a]; + for (; b <= be; ++b) + { + if (wildcard_match(a_remain, b[0 .. be - b], value_wildcard, case_insensitive)) + return true; + } return false; + } + + // handle optionals + if (ca_orig == '~') + { + while (++a < ae && *a == '~') {} + if (a == ae) + break; + + if (backtrack_depth < backtrack_stack.length) + { + backtrack_stack[backtrack_depth].a = a + (*a == '\\' ? 2 : 1); + backtrack_stack[backtrack_depth].b = b; + ++backtrack_depth; + } + continue; + } + + // handle escape + ca = ca_orig, cb = cb_orig; + if (ca == '\\') + { + if (++a < ae) + ca = *a; + else + break; + } + + // handle value wildcards + if (value_wildcard) + { + if (cb_orig == '*') + { + b = skip_wilds(++b, be); + if (b == be) // tail star matches everything + return true; + + const(char)[] b_remain = b[0 .. be - b]; + for (; a <= ae; ++a) + { + if (wildcard_match(a[0 .. ae - a], b_remain, true, case_insensitive)) + return true; + } + return false; + } + if (cb_orig == '~') + { + while (++b < be && *b == '~') {} + if (b == be) + break; + + if (backtrack_depth < backtrack_stack.length) + { + backtrack_stack[backtrack_depth].a = a; + backtrack_stack[backtrack_depth].b = b + (*b == '\\' ? 2 : 1); + ++backtrack_depth; + } + continue; + } + if (cb == '\\') + { + if (++b < be) + cb = *b; + else + break; + } + if (cb_orig == '#') + { + if (ca.is_numeric || ca_orig == '#') + goto advance; + } + } + + // compare next char + if (ca_orig == '#') + { + if (cb.is_numeric) + goto advance; + } + else if ((case_insensitive ? ca.to_lower == cb.to_lower : ca == cb) || (ca_orig == '?') || (value_wildcard && cb_orig == '?')) + goto advance; + + try_backtrack: + if (backtrack_depth == 0) + return false; + + --backtrack_depth; + a = backtrack_stack[backtrack_depth].a; + b = backtrack_stack[backtrack_depth].b; + continue; + + advance: + ++a, ++b; + } + + // check the strings are equal... + if (a == ae) + { + if (value_wildcard) + b = skip_wilds(b, be); + if (b == be) + return true; } - return wildcard.length == value.length; + else if (b == be && skip_wilds(a, ae) == ae) + return true; + + goto try_backtrack; +} + +bool wildcard_match_i(const(char)[] wildcard, const(char)[] value, bool value_wildcard = false) pure + => wildcard_match(wildcard, value, value_wildcard, true); + + +unittest +{ + // test findFirst + assert("hello".findFirst('e') == 1); + assert("hello".findFirst('a') == 5); + assert("hello".findFirst("e") == 1); + assert("hello".findFirst("ll") == 2); + assert("hello".findFirst("lo") == 3); + assert("hello".findFirst("la") == 5); + assert("hello".findFirst("low") == 5); + assert("héllo".findFirst('é') == 1); + assert("héllo"w.findFirst('é') == 1); + assert("héllo".findFirst("éll") == 1); + assert("héllo".findFirst('a') == 6); + assert("héllo".findFirst("la") == 6); + assert("hello".find_first_i('E') == 1); + assert("HELLO".find_first_i("e") == 1); + assert("hello".find_first_i("LL") == 2); + assert("héllo".find_first_i('É') == 1); + assert("HÉLLO".find_first_i("é") == 1); + assert("HÉLLO".find_first_i("éll") == 1); + + assert("HÉLLO".contains('É')); + assert(!"HÉLLO".contains('A')); + assert("HÉLLO".contains_i("éll")); + + // test wildcard_match + assert(wildcard_match("hello", "hello")); + assert(!wildcard_match("hello", "world")); + assert(wildcard_match("h*o", "hello")); + assert(wildcard_match("h*", "hello")); + assert(wildcard_match("*o", "hello")); + assert(wildcard_match("*", "hello")); + assert(wildcard_match("h?llo", "hello")); + assert(!wildcard_match("h?llo", "hllo")); + assert(wildcard_match("h??lo", "hello")); + assert(!wildcard_match("a*b", "axxxc")); + + // test optional - basic cases + assert(wildcard_match("~ab", "b")); + assert(wildcard_match("~ab", "ab")); + assert(wildcard_match("a~b", "a")); + assert(wildcard_match("a~b", "ab")); + assert(!wildcard_match("~ab", "a")); + assert(!wildcard_match("a~b", "b")); + assert(!wildcard_match("a~b", "ac")); + + // optional - multiple optionals + assert(wildcard_match("~a~b", "")); + assert(wildcard_match("~a~b", "a")); + assert(wildcard_match("~a~b", "b")); + assert(wildcard_match("~a~b", "ab")); + assert(!wildcard_match("~a~b", "ba")); + assert(wildcard_match("~a~b~c", "")); + assert(wildcard_match("~a~b~c", "ac")); + assert(wildcard_match("~a~b~c", "abc")); + + // optional with wildcards + assert(wildcard_match("~a*", "")); + assert(wildcard_match("~a*", "a")); + assert(wildcard_match("~a*", "abc")); + assert(wildcard_match("*~a", "")); + assert(wildcard_match("*~a", "a")); + assert(wildcard_match("*~a", "xya")); + assert(wildcard_match("a~b*c", "ac")); + assert(wildcard_match("a~b*c", "abc")); + assert(wildcard_match("a~b*c", "abxc")); + assert(wildcard_match("a~b*c", "axbc")); + assert(!wildcard_match("a*~bc", "a")); + assert(wildcard_match("a*~bc", "ac")); + assert(wildcard_match("a*~bc", "abc")); + assert(wildcard_match("a*~bc", "axbc")); + + // optional with ? + assert(wildcard_match("~a?", "a")); + assert(wildcard_match("~a?", "x")); + assert(wildcard_match("~a?", "ax")); + assert(wildcard_match("?~a", "x")); + assert(wildcard_match("?~a", "xa")); + assert(wildcard_match("?~a", "a")); + assert(wildcard_match("?~a", "aa")); + + // optional with # + assert(wildcard_match("~a#", "5")); + assert(wildcard_match("~a#", "a5")); + assert(!wildcard_match("~a#", "a")); + assert(!wildcard_match("~a#", "ax")); + assert(wildcard_match("v~#.#", "v.5")); + assert(wildcard_match("v~#.#", "v1.5")); + + // optional with escape + assert(wildcard_match("~\\*", "")); + assert(wildcard_match("~\\*", "*")); + assert(!wildcard_match("~\\*", "x")); + assert(wildcard_match("a~\\*b", "ab")); + assert(wildcard_match("a~\\*b", "a*b")); + assert(!wildcard_match("a~\\*b", "axb")); + + // double optional ~~ + assert(wildcard_match("~~a", "")); + assert(wildcard_match("~~a", "a")); + assert(!wildcard_match("~~a", "aa")); + + // degenerates + assert(wildcard_match("a\\", "a")); + assert(!wildcard_match("a\\", "")); + assert(!wildcard_match("a\\", "x")); + assert(wildcard_match("a~", "a")); + assert(!wildcard_match("a~", "")); + assert(!wildcard_match("a~", "x")); + assert(wildcard_match("a~\\", "a")); + assert(!wildcard_match("a~\\", "")); + assert(!wildcard_match("a~\\", "x")); + + // optional with value_wildcard - basic + assert(wildcard_match("~b", "", true)); + assert(wildcard_match("~b", "b", true)); + assert(wildcard_match("", "~b", true)); + assert(wildcard_match("b", "~b", true)); + assert(wildcard_match("ab", "~ab", true)); + assert(wildcard_match("ab", "~a~b", true)); + assert(wildcard_match("~ab", "ab", true)); + assert(wildcard_match("~ab", "~ab", true)); + assert(wildcard_match("~ab", "~a~b", true)); + assert(wildcard_match("a~b", "~ab", true)); + assert(wildcard_match("a~b", "~a~b", true)); + assert(wildcard_match("~a~b", "~ab", true)); + assert(wildcard_match("~a~b", "~a~b", true)); + + // optional with value_wildcard - with wildcards on rhs + assert(wildcard_match("~a", "*", true)); + assert(wildcard_match("~ab", "*", true)); + assert(wildcard_match("~a~b", "*", true)); + assert(wildcard_match("*", "~a", true)); + assert(wildcard_match("*", "~ab", true)); + assert(wildcard_match("*", "~a~b", true)); + assert(wildcard_match("~a", "?", true)); + assert(wildcard_match("?", "~a", true)); + assert(wildcard_match("~ab", "?", true)); + assert(wildcard_match("~ab", "?b", true)); + assert(wildcard_match("~ab", "a?", true)); + assert(wildcard_match("~ab", "??", true)); + assert(wildcard_match("?", "~ab", true)); + assert(wildcard_match("?b", "~ab", true)); + assert(wildcard_match("a?", "~ab", true)); + assert(wildcard_match("??", "~ab", true)); + assert(wildcard_match("~ab", "?b", true)); + assert(wildcard_match("~abc", "?c", true)); + assert(wildcard_match("~a*", "*", true)); + assert(wildcard_match("*~a", "*", true)); + assert(wildcard_match("*", "~a*", true)); + assert(wildcard_match("*", "*~a", true)); + assert(wildcard_match("ab", "*~c", true)); + assert(wildcard_match("abc", "a?~c", true)); + + // optional on both sides with wildcards + assert(wildcard_match("~a*", "*~b", true)); + assert(wildcard_match("a~b*", "*~cd", true)); + assert(wildcard_match("~ab", "*b", true)); + assert(wildcard_match("~ab", "*ab", true)); + assert(wildcard_match("x~ab", "*ab", true)); + assert(wildcard_match("*b", "~ab", true)); + assert(wildcard_match("*ab", "~ab", true)); + assert(wildcard_match("*ab", "x~ab", true)); + assert(wildcard_match("~a~b", "~c~d", true)); + assert(wildcard_match("~a~b", "~c~da", true)); + assert(wildcard_match("~a~b", "~c~db", true)); + assert(wildcard_match("~a~b", "~c~dab", true)); + assert(wildcard_match("~a~bc", "~c~d", true)); + assert(wildcard_match("~a~bd", "~c~d", true)); + assert(wildcard_match("~a~bcd", "~c~d", true)); + assert(wildcard_match("*a~bc*d", "xxacxxd", true)); + assert(!wildcard_match("*a~bc*de", "xxabcxxd", true)); + assert(!wildcard_match("*a~bc*d", "xxabcxxde", true)); + assert(wildcard_match("*a~bc*d", "~x~x~a~c~x~x~d", true)); + + // case insensitive with optional + assert(wildcard_match_i("~a", "A")); + assert(wildcard_match_i("~aB", "Ab")); + assert(wildcard_match_i("A~b", "aB")); + + // multiple wildcards + assert(wildcard_match("*l*o", "hello")); + assert(wildcard_match("h*l*o", "hello")); + assert(wildcard_match("h*l*", "hello")); + assert(wildcard_match("*e*l*", "hello")); + assert(wildcard_match("*h*e*l*l*o*", "hello")); + + // wildcards with sequences in between + assert(wildcard_match("h*ll*", "hello")); + assert(wildcard_match("*el*", "hello")); + assert(wildcard_match("h*el*o", "hello")); + assert(!wildcard_match("h*el*x", "hello")); + assert(wildcard_match("*lo", "hello")); + assert(!wildcard_match("*lx", "hello")); + + // mixed wildcards and single matches + assert(wildcard_match("h?*o", "hello")); + assert(wildcard_match("h*?o", "hello")); + assert(wildcard_match("?e*o", "hello")); + assert(wildcard_match("h?ll?", "hello")); + assert(!wildcard_match("h?ll?", "hllo")); + + // overlapping wildcards + assert(wildcard_match("**hello", "hello")); + assert(wildcard_match("hello**", "hello")); + assert(wildcard_match("h**o", "hello")); + assert(wildcard_match("*?*", "hello")); + assert(wildcard_match("?*?", "hello")); + assert(!wildcard_match("?*?", "x")); + assert(wildcard_match("?*?", "xx")); + + // escape sequences + assert(wildcard_match("\\*", "*")); + assert(wildcard_match("\\?", "?")); + assert(wildcard_match("\\#", "#")); + assert(wildcard_match("\\\\", "\\")); + assert(!wildcard_match("\\*", "a")); + assert(!wildcard_match("\\?", "a")); + assert(!wildcard_match("\\#", "5")); + assert(wildcard_match("h\\*o", "h*o")); + assert(!wildcard_match("h\\*o", "hello")); + assert(wildcard_match("\\*\\?\\\\", "*?\\")); + assert(wildcard_match("a\\*b*c", "a*bxyzc")); + assert(wildcard_match("*\\**", "hello*world")); + + // edge cases + assert(wildcard_match("", "")); + assert(!wildcard_match("", "a")); + assert(wildcard_match("*", "")); + assert(wildcard_match("**", "")); + assert(!wildcard_match("?", "")); + assert(wildcard_match("a*b*c", "abc")); + assert(wildcard_match("a*b*c", "aXbYc")); + assert(wildcard_match("a*b*c", "aXXbYYc")); + + // value_wildcard tests - bidirectional matching + assert(wildcard_match("hello", "h*o", true)); + assert(wildcard_match("h*o", "hello", true)); + assert(wildcard_match("h?llo", "he?lo", true)); + assert(wildcard_match("h\\*o", "h\\*o", true)); + assert(wildcard_match("test*", "*test", true)); + + // both sides have wildcards + assert(wildcard_match("h*o", "h*o", true)); + assert(wildcard_match("*hello*", "*world*", true)); + assert(wildcard_match("a*b*c", "a*b*c", true)); + assert(wildcard_match("*", "*", true)); + assert(wildcard_match("?", "?", true)); + + // complex interplay - wildcards matching wildcards + assert(wildcard_match("a*c", "a?c", true)); + assert(wildcard_match("a?c", "a*c", true)); + assert(wildcard_match("*abc", "?abc", true)); + assert(wildcard_match("abc*", "abc?", true)); + + // multiple wildcards on both sides + assert(wildcard_match("a*b*c", "a?b?c", true)); + assert(wildcard_match("*a*b*", "?a?b?", true)); + assert(wildcard_match("a**b", "a*b", true)); + assert(wildcard_match("a*b", "a**b", true)); + + // wildcards at different positions + assert(wildcard_match("*test", "test*", true)); + assert(wildcard_match("test*end", "*end", true)); + assert(wildcard_match("*middle*", "start*", true)); + + // edge cases with value_wildcard + assert(wildcard_match("", "", true)); + assert(wildcard_match("*", "", true)); + assert(wildcard_match("", "*", true)); + assert(wildcard_match("**", "*", true)); + assert(wildcard_match("*", "**", true)); + + // digit wildcard tests + assert(wildcard_match("#", "0")); + assert(wildcard_match("#", "5")); + assert(wildcard_match("#", "9")); + assert(!wildcard_match("#", "a")); + assert(!wildcard_match("#", "A")); + assert(!wildcard_match("#", "")); + assert(!wildcard_match("#", "12")); + assert(!wildcard_match("#", "#")); + assert(wildcard_match("#", "#", true)); + assert(!wildcard_match("#", "\\#", true)); + assert(wildcard_match("v#.#", "v1.2")); + assert(!wildcard_match("v#.#", "va.b")); + assert(!wildcard_match("v#.#", "v1.")); + assert(wildcard_match("port-##", "port-42")); + assert(wildcard_match("#*#", "123")); + assert(!wildcard_match("#*#", "abc")); } diff --git a/src/urt/string/regex.d b/src/urt/string/regex.d new file mode 100644 index 0000000..80e81aa --- /dev/null +++ b/src/urt/string/regex.d @@ -0,0 +1,952 @@ +module urt.string.regex; + +import urt.mem.allocator; + +nothrow @nogc: + + +struct RegexMatch +{ + const(char)[] full; + const(char)[][Regex.MaxGroups] captures; + ubyte num_captures; +} + +bool regex_match(const(char)[] text, const(char)[] pattern, ref RegexMatch result) +{ + Regex re = regex_compile(pattern, tempAllocator()); + if (!re.valid) + return false; + return re.exec(text, result); +} + +// Compiled regex program. Single contiguous allocation for all instructions +// and character class data. Compile with regex_compile(), run with exec(). +// +// Supported syntax: +// . any character +// \d \D digit / non-digit +// \s \S whitespace / non-whitespace +// \w \W word char [a-zA-Z0-9_] / non-word +// \. \\ etc literal escapes +// [abc] character class +// [^abc] negated character class +// [a-z] character range in class +// * + ? greedy quantifiers +// *? +? ?? non-greedy (lazy) quantifiers +// (...) capture group (first group returned) +// (a|b) alternation +// ^ $ anchors (start/end of text) +struct Regex +{ +nothrow @nogc: + + enum MaxGroups = 8; + + bool valid() const + => data.ptr !is null; + + // Execute against text. Unanchored unless pattern used ^. + bool exec(const(char)[] text, ref RegexMatch result) const + { + if (!valid) + return false; + + ref const Header hdr = *cast(const(Header)*)data.ptr; + const(Inst)[] code = (cast(const(Inst)*)(data.ptr + Header.sizeof))[0 .. hdr.num_insts]; + const(ClassRange)[] ranges = (cast(const(ClassRange)*)(data.ptr + Header.sizeof + hdr.num_insts * Inst.sizeof))[0 .. hdr.num_ranges]; + const(ClassDef)[] classes = (cast(const(ClassDef)*)(data.ptr + Header.sizeof + hdr.num_insts * Inst.sizeof + hdr.num_ranges * ClassRange.sizeof))[0 .. hdr.num_classes]; + + struct Thread + { + ushort ipc, pos; + ushort[MaxGroups] gs, ge; + } + + Thread[512] stack = void; + ushort sp; + + foreach (start; 0 .. hdr.anchored ? 1 : text.length + 1) + { + sp = 0; + Thread t; + t.ipc = 0; + t.pos = cast(ushort)start; + t.gs[] = ushort.max; + t.ge[] = ushort.max; + + bool matched = false; + + while (true) + { + if (t.ipc >= code.length) + break; + + Inst inst = code[t.ipc]; + + final switch (inst.op) with (Op) + { + case literal: + if (t.pos < text.length && text[t.pos] == inst.operand) + ++t.pos, ++t.ipc; + else + goto backtrack; + break; + case any: + if (t.pos < text.length) + ++t.pos, ++t.ipc; + else + goto backtrack; + break; + case digit: + if (t.pos < text.length && is_digit(text[t.pos])) + ++t.pos, ++t.ipc; + else + goto backtrack; + break; + case not_digit: + if (t.pos < text.length && !is_digit(text[t.pos])) + ++t.pos, ++t.ipc; + else + goto backtrack; + break; + case space: + if (t.pos < text.length && is_space(text[t.pos])) + ++t.pos, ++t.ipc; + else + goto backtrack; + break; + case not_space: + if (t.pos < text.length && !is_space(text[t.pos])) + ++t.pos, ++t.ipc; + else + goto backtrack; + break; + case word: + if (t.pos < text.length && is_word(text[t.pos])) + ++t.pos, ++t.ipc; + else + goto backtrack; + break; + case not_word: + if (t.pos < text.length && !is_word(text[t.pos])) + ++t.pos, ++t.ipc; + else + goto backtrack; + break; + case char_class: + if (t.pos < text.length && match_class(classes, ranges, inst.operand, text[t.pos])) + ++t.pos, ++t.ipc; + else + goto backtrack; + break; + case anchor_start: + if (t.pos == 0) + ++t.ipc; + else + goto backtrack; + break; + case anchor_end: + if (t.pos == text.length) + ++t.ipc; + else + goto backtrack; + break; + case group_open: + if (inst.operand < MaxGroups) + t.gs[inst.operand] = t.pos; + ++t.ipc; + break; + case group_close: + if (inst.operand < MaxGroups) + t.ge[inst.operand] = t.pos; + ++t.ipc; + break; + case split: + if (sp < stack.length) + { + stack[sp] = t; + if (inst.operand == 0) + { + stack[sp].ipc = inst.aux; + ++t.ipc; + } + else + { + stack[sp].ipc = cast(ushort)(t.ipc + 1); + t.ipc = inst.aux; + } + ++sp; + } + else + goto backtrack; + break; + case jump: + t.ipc = inst.aux; + break; + case Op.match: + matched = true; + break; + } + + if (matched) + break; + continue; + + backtrack: + if (sp == 0) + break; + t = stack[--sp]; + } + + if (matched) + { + result.full = text[start .. t.pos]; + result.num_captures = 0; + foreach (g; 0 .. hdr.num_groups) + { + if (g >= MaxGroups) + break; + if (t.gs[g] != ushort.max && t.ge[g] != ushort.max) + { + result.captures[g] = text[t.gs[g] .. t.ge[g]]; + result.num_captures = cast(ubyte)(g + 1); + } + else + result.captures[g] = null; + } + return true; + } + } + + return false; + } + +private: + const(ubyte)[] data; // Header ~ Inst[] ~ ClassRange[] ~ ClassDef[] + + struct Header + { + ushort num_insts; + ubyte num_classes; + ubyte num_ranges; + ubyte num_groups; + bool anchored; + } + + enum Op : ubyte + { + literal, any, digit, not_digit, space, not_space, word, not_word, + char_class, anchor_start, anchor_end, group_open, group_close, + split, jump, match, + } + + struct Inst + { + Op op; + ubyte operand; + ushort aux; + } + + struct ClassRange + { + char lo, hi; + } + + struct ClassDef + { + ubyte start, count; + bool negated; + } + + static bool is_digit(char ch) { return ch >= '0' && ch <= '9'; } + static bool is_space(char ch) { return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'; } + static bool is_word(char ch) { return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || is_digit(ch) || ch == '_'; } + + static bool match_class(const(ClassDef)[] classes, const(ClassRange)[] ranges, ubyte idx, char ch) + { + if (idx >= classes.length) + return false; + ref const ClassDef cd = classes[idx]; + bool found = false; + foreach (i; cd.start .. cd.start + cd.count) + { + if (ch >= ranges[i].lo && ch <= ranges[i].hi) + { + found = true; + break; + } + } + return cd.negated ? !found : found; + } +} + + +Regex regex_compile(const(char)[] pattern, NoGCAllocator allocator = defaultAllocator()) +{ + import urt.mem : memcpy; + + alias Op = Regex.Op; + alias Inst = Regex.Inst; + alias ClassRange = Regex.ClassRange; + alias ClassDef = Regex.ClassDef; + + // TODO: replace these stack buffers with Array!N when it works... + Inst[256] code = void; + ClassRange[128] class_ranges = void; + ClassDef[32] class_defs = void; + ubyte num_classes, total_ranges, num_groups; + ushort pc; + bool anchored; + + bool emit(Op op, ubyte operand = 0, ushort aux = 0) + { + if (pc >= code.length - 1) + return false; + code[pc++] = Inst(op, operand, aux); + return true; + } + + bool shift_code(ushort start, ushort end, ushort n) + { + if (end + n >= code.length) + return false; + for (ushort i = cast(ushort)(end + n - 1); i >= start + n; --i) + code[i] = code[cast(ushort)(i - n)]; + for (ushort i = 0; i < end + n; ++i) + { + if (code[i].op == Op.split || code[i].op == Op.jump) + { + if (code[i].aux >= start && code[i].aux < end) + code[i].aux += n; + } + } + pc += n; + return true; + } + + struct GroupFrame + { + ushort group_pc; + ubyte group_id; + ubyte num_jumps; + ushort[8] jump_pcs; + ushort last_split; + bool has_alt; + } + GroupFrame[16] gframes = void; + ubyte gdepth; + + Regex fail; + + size_t pi = 0; + while (pi < pattern.length) + { + ushort atom_start = pc; + char c = pattern[pi++]; + + switch (c) + { + case '^': + if (!emit(Op.anchor_start)) + return fail; + continue; + case '$': + if (!emit(Op.anchor_end)) + return fail; + continue; + + case '|': + { + // Alternation: a|b|c compiles as cascading splits: + // split(alt1, L1) → alt1 → jump(end) → L1: split(alt2, L2) → alt2 → jump(end) → L2: alt3 → end + // First | inserts a split before alt1. Subsequent | patch the previous split's aux. + if (gdepth == 0) + return fail; + ref GroupFrame gf = gframes[gdepth - 1]; + + // Emit jump to skip over remaining alternatives (patched at ')') + if (!emit(Op.jump)) + return fail; + if (gf.num_jumps < gf.jump_pcs.length) + gf.jump_pcs[gf.num_jumps++] = cast(ushort)(pc - 1); + + // Insert a split before the current alternative. + // For a|b: split(a, b). For a|b|c: split(a, split(b, c)). + // Each | inserts a split before its preceding alternative that + // points forward to the next one. + { + // Find where the current alternative started — it's right after + // the previous split (or after group_open for the first alt). + ushort split_pos; + if (!gf.has_alt) + split_pos = cast(ushort)(gf.group_pc + 1); // after group_open + else + split_pos = cast(ushort)(gf.last_split + 1); // after previous split + + if (!shift_code(split_pos, pc, 1)) + return fail; + code[split_pos] = Inst(Op.split, 0, pc); // aux = start of next alt + gf.last_split = split_pos; + + // Fix jump_pcs that shifted + foreach (ref jp; gf.jump_pcs[0 .. gf.num_jumps]) + { + if (jp >= split_pos) + ++jp; + } + } + gf.has_alt = true; + continue; + } + + case '.': + if (!emit(Op.any)) + return fail; + break; + + case '\\': + if (pi >= pattern.length) + return fail; + switch (pattern[pi++]) + { + case 'd': + if (!emit(Op.digit)) + return fail; + break; + case 'D': + if (!emit(Op.not_digit)) + return fail; + break; + case 's': + if (!emit(Op.space)) + return fail; + break; + case 'S': + if (!emit(Op.not_space)) + return fail; + break; + case 'w': + if (!emit(Op.word)) + return fail; + break; + case 'W': + if (!emit(Op.not_word)) + return fail; + break; + default: + if (!emit(Op.literal, cast(ubyte)pattern[pi-1])) + return fail; + break; + } + break; + + case '[': + { + if (num_classes >= class_defs.length) + return fail; + bool negated = pi < pattern.length && pattern[pi] == '^'; + if (negated) + ++pi; + ubyte rs = total_ranges; + while (pi < pattern.length && pattern[pi] != ']') + { + if (total_ranges >= class_ranges.length) + return fail; + char lo = pattern[pi++]; + if (lo == '\\' && pi < pattern.length) + lo = pattern[pi++]; + if (pi + 1 < pattern.length && pattern[pi] == '-' && pattern[pi+1] != ']') + { + ++pi; + char hi = pattern[pi++]; + if (hi == '\\' && pi < pattern.length) + hi = pattern[pi++]; + class_ranges[total_ranges++] = ClassRange(lo, hi); + } + else + class_ranges[total_ranges++] = ClassRange(lo, lo); + } + if (pi < pattern.length) + ++pi; + class_defs[num_classes] = ClassDef(rs, cast(ubyte)(total_ranges - rs), negated); + if (!emit(Op.char_class, num_classes++)) + return fail; + break; + } + + case '(': + { + if (gdepth >= gframes.length) + return fail; + ubyte gid = num_groups++; + gframes[gdepth] = GroupFrame.init; + gframes[gdepth].group_id = gid; + gframes[gdepth].group_pc = pc; + ++gdepth; + if (!emit(Op.group_open, gid)) + return fail; + continue; + } + + case ')': + { + if (gdepth == 0) + return fail; + --gdepth; + ref GroupFrame gf = gframes[gdepth]; + + // Patch all | jumps to land on the group_close (not past it) + foreach (ref jp; gf.jump_pcs[0 .. gf.num_jumps]) + code[jp].aux = pc; + + if (!emit(Op.group_close, gf.group_id)) + return fail; + + atom_start = gf.group_pc; + break; + } + + default: + if (!emit(Op.literal, cast(ubyte)c)) + return fail; + break; + } + + // Quantifier + if (pi < pattern.length && (pattern[pi] == '*' || pattern[pi] == '+' || pattern[pi] == '?')) + { + char q = pattern[pi++]; + bool is_lazy = pi < pattern.length && pattern[pi] == '?'; + if (is_lazy) + ++pi; + + ushort body_start = atom_start; + ushort body_end = pc; + ubyte lazy_flag = is_lazy ? 1 : 0; + + if (q == '*') + { + if (!shift_code(body_start, body_end, 1)) + return fail; + ushort after = cast(ushort)(pc + 1); + code[body_start] = Inst(Op.split, lazy_flag, after); + if (!emit(Op.jump, 0, body_start)) + return fail; + } + else if (q == '+') + { + if (!emit(Op.split, lazy_flag, cast(ushort)(pc + 1))) + return fail; + code[pc - 1] = Inst(Op.split, lazy_flag, cast(ushort)(pc + 1)); + if (!emit(Op.jump, 0, body_start)) + return fail; + } + else // '?' + { + if (!shift_code(body_start, body_end, 1)) + return fail; + code[body_start] = Inst(Op.split, lazy_flag, pc); + } + } + } + + if (gdepth != 0) + return fail; // unclosed group + + if (!emit(Op.match)) + return fail; + + anchored = pattern.length > 0 && pattern[0] == '^'; + + // Pack into single allocation: Header ~ Inst[pc] ~ ClassRange[total_ranges] ~ ClassDef[num_classes] + size_t size = Regex.Header.sizeof + pc * Inst.sizeof + total_ranges * ClassRange.sizeof + num_classes * ClassDef.sizeof; + ubyte[] buf = cast(ubyte[])allocator.alloc(size); + if (!buf) + return fail; + + Regex.Header* hdr = cast(Regex.Header*)buf.ptr; + hdr.num_insts = pc; + hdr.num_classes = num_classes; + hdr.num_ranges = total_ranges; + hdr.num_groups = num_groups; + hdr.anchored = anchored; + + size_t off = Regex.Header.sizeof; + memcpy(buf.ptr + off, code.ptr, pc * Inst.sizeof); + off += pc * Inst.sizeof; + memcpy(buf.ptr + off, class_ranges.ptr, total_ranges * ClassRange.sizeof); + off += total_ranges * ClassRange.sizeof; + memcpy(buf.ptr + off, class_defs.ptr, num_classes * ClassDef.sizeof); + + Regex result; + result.data = cast(const(ubyte)[])buf; + return result; +} + + +unittest +{ + RegexMatch m; + + // -- Literals -- + + assert(regex_match("hello world", "world", m)); + assert(m.full == "world"); + assert(m.num_captures == 0); + + assert(regex_match("abc", "abc", m)); + assert(m.full == "abc"); + + assert(!regex_match("hello world", "xyz", m)); + + // match at start, middle, end + assert(regex_match("abc", "a", m)); + assert(m.full == "a"); + assert(regex_match("abc", "b", m)); + assert(m.full == "b"); + assert(regex_match("abc", "c", m)); + assert(m.full == "c"); + + // -- Dot (any char) -- + + assert(regex_match("abc", "a.c", m)); + assert(m.full == "abc"); + + assert(!regex_match("ac", "a.c", m)); // dot requires exactly one char + + assert(regex_match("a\tc", "a.c", m)); // dot matches tab + assert(m.full == "a\tc"); + + // -- Character classes -- + + assert(regex_match("test123", "[0-9]+", m)); + assert(m.full == "123"); + + assert(regex_match("abc123", "[^a-z]+", m)); // negated + assert(m.full == "123"); + + assert(regex_match("x", "[xyz]", m)); + assert(m.full == "x"); + + assert(!regex_match("a", "[xyz]", m)); + + assert(regex_match("B", "[A-Za-z]", m)); // multiple ranges + assert(m.full == "B"); + + assert(regex_match("-", "[a\\-z]", m)); // escaped - in class + assert(m.full == "-"); + + // -- Escape sequences -- + + assert(regex_match("foo 42 bar", `\d+`, m)); + assert(m.full == "42"); + + assert(regex_match("hello world", `\w+`, m)); + assert(m.full == "hello"); + + assert(regex_match("key: value", `\s+`, m)); + assert(m.full == " "); + + // negated escapes + assert(regex_match("abc 123", `\D+`, m)); + assert(m.full == "abc "); + + assert(regex_match("abc 123", `\S+`, m)); + assert(m.full == "abc"); + + assert(regex_match(" abc", `\W+`, m)); + assert(m.full == " "); + + // escaped special characters + assert(regex_match("a.b", `a\.b`, m)); + assert(m.full == "a.b"); + + assert(!regex_match("axb", `a\.b`, m)); + + assert(regex_match("(hi)", `\(hi\)`, m)); + assert(m.full == "(hi)"); + + assert(regex_match("a*b", `a\*b`, m)); + assert(m.full == "a*b"); + + assert(regex_match("a|b", `a\|b`, m)); + assert(m.full == "a|b"); + + assert(regex_match("a\\b", `a\\b`, m)); + assert(m.full == "a\\b"); + + assert(regex_match("[x]", `\[x\]`, m)); + assert(m.full == "[x]"); + + // -- Anchors -- + + assert(regex_match("hello", "^hello$", m)); + assert(m.full == "hello"); + + assert(!regex_match("say hello", "^hello", m)); + assert(regex_match("say hello", "hello$", m)); + assert(m.full == "hello"); + + assert(!regex_match("hello!", "^hello$", m)); + + // ^ on empty string + assert(regex_match("", "^$", m)); + assert(m.full == ""); + + // -- Simple groups (no alternation) -- + + assert(regex_match("abc", "(abc)", m)); + assert(m.full == "abc"); + assert(m.num_captures == 1); + assert(m.captures[0] == "abc"); + + assert(regex_match("abc", "a(b)c", m)); + assert(m.full == "abc"); + assert(m.captures[0] == "b"); + + // -- Alternation -- + + // two branches + assert(regex_match("true", "(true|false)", m)); + assert(m.captures[0] == "true"); + + assert(regex_match("false", "(true|false)", m)); + assert(m.captures[0] == "false"); + + assert(!regex_match("maybe", "^(true|false)$", m)); + + // three branches + assert(regex_match("red", "(red|green|blue)", m)); + assert(m.captures[0] == "red"); + + assert(regex_match("green", "(red|green|blue)", m)); + assert(m.captures[0] == "green"); + + assert(regex_match("blue", "(red|green|blue)", m)); + assert(m.captures[0] == "blue"); + + assert(!regex_match("yellow", "^(red|green|blue)$", m)); + + // alternation with shared prefix/suffix + assert(regex_match("foobar", "(foo|foobar)", m)); + // greedy: tries foo first, succeeds — but we're not anchored, so full match is "foo" + assert(m.captures[0] == "foo"); + + // alternation with surrounding literal + assert(regex_match("catdog", "cat(and|or)?dog", m)); // no "and"/"or", ? makes it optional + assert(m.full == "catdog"); + + assert(regex_match("catanddog", "cat(and|or)dog", m)); + assert(m.captures[0] == "and"); + + assert(regex_match("catordog", "cat(and|or)dog", m)); + assert(m.captures[0] == "or"); + + // -- Greedy quantifiers: *, +, ? -- + + // * — zero or more + assert(regex_match("aaa", "a*", m)); + assert(m.full == "aaa"); + + assert(regex_match("bbb", "a*", m)); // zero-length match at start + assert(m.full == ""); + + assert(regex_match("", "a*", m)); // empty string, zero-length match + assert(m.full == ""); + + // + — one or more + assert(regex_match("aaa", "a+", m)); + assert(m.full == "aaa"); + + assert(!regex_match("bbb", "a+", m)); + + assert(regex_match("baaab", "a+", m)); + assert(m.full == "aaa"); + + // ? — zero or one + assert(regex_match("colour", "colou?r", m)); + assert(m.full == "colour"); + + assert(regex_match("color", "colou?r", m)); + assert(m.full == "color"); + + // greedy * consumes as much as possible + assert(regex_match("bold", "<.*>", m)); + assert(m.full == "bold"); + + // -- Lazy quantifiers: *?, +?, ?? -- + + // lazy * — shortest match + assert(regex_match("bold", "<.*?>", m)); + assert(m.full == ""); + + // lazy + — at least one, but shortest + assert(regex_match("aaa", "a+?", m)); + assert(m.full == "a"); + + // lazy ? — prefer zero + assert(regex_match("ab", "a??b", m)); + assert(m.full == "ab"); // a?? tries empty first but needs 'b', backtracks to 'a' + + // -- Quantifier on groups -- + + // group with + + assert(regex_match("ababab", "(ab)+", m)); + assert(m.full == "ababab"); + // last capture of the repeated group + assert(m.captures[0] == "ab"); + + // group with * + assert(regex_match("xyzxyz", "(xyz)*", m)); + assert(m.full == "xyzxyz"); + + // group with ? + assert(regex_match("foobar", "(foo)?bar", m)); + assert(m.full == "foobar"); + assert(m.captures[0] == "foo"); + + assert(regex_match("bar", "(foo)?bar", m)); + assert(m.full == "bar"); + + // alternation group with quantifier + assert(regex_match("abcabc", "(abc|def)+", m)); + assert(m.full == "abcabc"); + + assert(regex_match("abcdef", "(abc|def)+", m)); + assert(m.full == "abcdef"); + + // -- Nested groups -- + + assert(regex_match("abc", "((a)(b)(c))", m)); + assert(m.num_captures == 4); + assert(m.captures[0] == "abc"); // outer group + assert(m.captures[1] == "a"); + assert(m.captures[2] == "b"); + assert(m.captures[3] == "c"); + + // nested with alternation + assert(regex_match("ab", "((a|x)(b|y))", m)); + assert(m.captures[0] == "ab"); + assert(m.captures[1] == "a"); + assert(m.captures[2] == "b"); + + // -- Quantifier + class interactions -- + + assert(regex_match("abc123def", "[a-z]+", m)); + assert(m.full == "abc"); + + assert(regex_match("abc123def", "[a-z]+?", m)); // lazy + assert(m.full == "a"); + + assert(regex_match("123", "[0-9]?", m)); + assert(m.full == "1"); + + assert(regex_match("abc", "[0-9]?", m)); // zero-length match + assert(m.full == ""); + + // -- Quantifier + escape interactions -- + + assert(regex_match(" \t ", `\s+`, m)); + assert(m.full == " \t "); + + assert(regex_match("hello123world", `\w+\d+\w+`, m)); + assert(m.full == "hello123world"); + + // -- Complex scraping patterns -- + + assert(regex_match("voltage: 52.3V", `voltage:\s*(\d+\.\d+)`, m)); + assert(m.captures[0] == "52.3"); + + assert(regex_match("active", `(\w+)`, m)); + assert(m.captures[0] == "active"); + + assert(regex_match("power: 1500W", `power:\s*(\d+)`, m)); + assert(m.captures[0] == "1500"); + + assert(regex_match("temp=25.6C", `temp=(\d+\.?\d*)`, m)); + assert(m.captures[0] == "25.6"); + + // multiple captures in a real pattern + assert(regex_match("error 404: not found", `(\w+)\s+(\d+):\s+(.+)`, m)); + assert(m.num_captures == 3); + assert(m.captures[0] == "error"); + assert(m.captures[1] == "404"); + assert(m.captures[2] == "not found"); + + // key=value extraction + assert(regex_match("host=192.168.1.1 port=502", `(\w+)=(\S+)`, m)); + assert(m.captures[0] == "host"); + assert(m.captures[1] == "192.168.1.1"); + + // CSV-like: extract fields + assert(regex_match("42,hello,true", `^(\d+),(\w+),(true|false)`, m)); + assert(m.captures[0] == "42"); + assert(m.captures[1] == "hello"); + assert(m.captures[2] == "true"); + + // -- Compile once, match many -- + + Regex re = regex_compile(`(\d+)\s*([A-Z]+)`); + assert(re.valid); + + assert(re.exec("reading: 42 V", m)); + assert(m.num_captures == 2); + assert(m.captures[0] == "42"); + assert(m.captures[1] == "V"); + + assert(re.exec("other: 100 MW", m)); + assert(m.captures[0] == "100"); + assert(m.captures[1] == "MW"); + + assert(!re.exec("no numbers here", m)); + + // -- Edge cases -- + + // empty text, empty pattern + assert(regex_match("", "", m)); + assert(m.full == ""); + + // empty pattern matches empty at start of any text + assert(regex_match("abc", "", m)); + assert(m.full == ""); + + // no captures + assert(regex_match("abc", `\w+`, m)); + assert(m.num_captures == 0); + + // unmatched parens should fail to compile + assert(!regex_compile("abc)").valid); + assert(!regex_compile("(abc").valid); + + // pattern with only anchors + assert(regex_match("", "^$", m)); + assert(!regex_match("x", "^$", m)); + + // .* at start — heavy backtracking but should work + assert(regex_match("the end", ".*end", m)); + assert(m.full == "the end"); + + // .* with capture + assert(regex_match("key: value here", `(\w+):\s*(.*)$`, m)); + assert(m.captures[0] == "key"); + assert(m.captures[1] == "value here"); + + // single char edge + assert(regex_match("x", "x", m)); + assert(m.full == "x"); + + assert(!regex_match("", "x", m)); + + // quantifier on dot + assert(regex_match("abc", ".+", m)); + assert(m.full == "abc"); + + assert(regex_match("a", ".+", m)); + assert(m.full == "a"); + + assert(!regex_match("", ".+", m)); + + assert(regex_match("", ".*", m)); + assert(m.full == ""); +} diff --git a/src/urt/string/string.d b/src/urt/string/string.d index b1281cd..1f8bf8f 100644 --- a/src/urt/string/string.d +++ b/src/urt/string/string.d @@ -3,7 +3,7 @@ module urt.string.string; import urt.lifetime : forward, move; import urt.mem; import urt.mem.string : CacheString; -import urt.hash : fnv1aHash, fnv1aHash64; +import urt.hash : fnv1a, fnv1a64; import urt.string.tailstring : TailString; public import urt.array : Alloc_T, Alloc, Reserve_T, Reserve, Concat_T, Concat; @@ -23,16 +23,70 @@ enum StringAlloc : ubyte TempString, // allocates in the temp ring buffer; could be overwritten at any time! // these must be last... (because comparison logic) - StringCache, // writes to the immutable string cache - StringCacheDedup, // writes to the immutable string cache with de-duplication + StringCache, // writes to the immutable string cache with de-duplication } struct StringAllocator { - char* delegate(ushort bytes, void* userData) nothrow @nogc alloc; - void delegate(char* s) nothrow @nogc free; + char* delegate(ushort bytes, void* userData) pure nothrow @nogc alloc; + void delegate(char* s) pure nothrow @nogc free; } +struct StringCacheBuilder +{ +nothrow @nogc: + this(char[] buffer) pure + { + assert(buffer.length >= 2 && buffer.length <= ushort.max, "Invalid buffer length"); + buffer[0..2] = 0; + this._buffer = buffer; + this._offset = 2; + } + + ushort add_string(const(char)[] s) pure + { + if (s.length == 0) + return 0; + + assert(s.length <= MaxStringLen, "String too long"); + assert(_offset + s.length + 2 + (s.length & 1) <= _buffer.length, "Not enough space in buffer"); + if (__ctfe) + { + version (LittleEndian) + { + _buffer[_offset + 0] = cast(char)(s.length & 0xFF); + _buffer[_offset + 1] = cast(char)(s.length >> 8); + } + else + { + _buffer[_offset + 0] = cast(char)(s.length >> 8); + _buffer[_offset + 1] = cast(char)(s.length & 0xFF); + } + } + else + *cast(ushort*)(_buffer.ptr + _offset) = cast(ushort)s.length; + + ushort result = cast(ushort)(_offset + 2); + _buffer[result .. result + s.length] = s[]; + _offset = cast(ushort)(result + s.length); + if (_offset & 1) + _buffer[_offset++] = '\0'; + return result; + } + + size_t used() const pure + => _offset; + + size_t remaining() const pure + => _buffer.length - _offset; + + bool full() const pure + => _offset == _buffer.length; + +private: + char[] _buffer; + ushort _offset; +} //enum String StringLit(string s) = s.makeString; template StringLit(const(char)[] lit, bool zeroTerminate = true) @@ -58,16 +112,16 @@ template StringLit(const(char)[] lit, bool zeroTerminate = true) return buffer; }(); pragma(aligned, 2) - private __gshared literal = LiteralData; + private __gshared immutable literal = LiteralData; - enum String StringLit = String(literal.ptr + 2, false); + enum StringLit = immutable(String)(literal.ptr + 2, false); } - String makeString(const(char)[] s) nothrow { if (s.length == 0) return String(null); + assert(__ctfe, "only for compile-time use"); return makeString(s, new char[2 + s.length]); } @@ -87,11 +141,11 @@ String makeString(const(char)[] s, StringAlloc allocator, void* userData = null) { return String(writeString(cast(char*)tempAllocator().alloc(2 + s.length, 2).ptr + 2, s), false); } - else if (allocator >= StringAlloc.StringCache) + else if (allocator == StringAlloc.StringCache) { import urt.mem.string : CacheString, addString; - CacheString cs = s.addString(allocator == StringAlloc.StringCacheDedup); + CacheString cs = s.addString(); return String(cs.ptr, false); } assert(false, "Invalid string allocator"); @@ -118,12 +172,51 @@ String makeString(const(char)[] s, char[] buffer) nothrow @nogc return String(writeString(buffer.ptr + 2, s), false); } +char* writeString(char* buffer, const(char)[] str) pure nothrow @nogc +{ + // TODO: assume the calling code has confirmed the length is within spec + if (__ctfe) + { + version (LittleEndian) + { + buffer[-2] = cast(char)(str.length & 0xFF); + buffer[-1] = cast(char)(str.length >> 8); + } + else + { + buffer[-2] = cast(char)(str.length >> 8); + buffer[-1] = cast(char)(str.length & 0xFF); + } + } + else + (cast(ushort*)buffer)[-1] = cast(ushort)str.length; + buffer[0 .. str.length] = str[]; + return buffer; +} + +String as_string(const(char)* s) pure nothrow @nogc + => String(s, false); + +inout(char)[] as_dstring(inout(char)* s) pure nothrow @nogc +{ + debug assert(s !is null); + + if (__ctfe) + { + version (LittleEndian) + ushort len = cast(ushort)(s[-2] | (s[-1] << 8)); + else + ushort len = cast(ushort)(s[-1] | (s[-2] << 8)); + return s[0 .. len]; + } + else + return s[0 .. (cast(ushort*)s)[-1]]; +} struct String { nothrow @nogc: - - alias toString this; + alias This = typeof(this); const(char)* ptr; @@ -132,21 +225,19 @@ nothrow @nogc: this.ptr = null; } - this(ref inout typeof(this) rhs) inout pure + this(ref inout This rhs) inout pure { ptr = rhs.ptr; - if (ptr) + if (!ptr) + return; + if (ushort* rc = ((cast(ushort*)ptr)[-1] >> 15) ? cast(ushort*)ptr - 2 : null) { - ushort* rc = ((cast(ushort*)ptr)[-1] >> 15) ? cast(ushort*)ptr - 2 : null; - if (rc) - { - assert((*rc & 0x3FFF) < 0x3FFF, "Reference count overflow"); - ++*rc; - } + assert((*rc & 0x3FFF) < 0x3FFF, "Reference count overflow"); + ++*rc; } } - this(size_t Embed)(MutableString!Embed str) inout //pure TODO: PUT THIS BACK!! + this(size_t Embed)(MutableString!Embed str) inout pure { if (!str.ptr) return; @@ -156,7 +247,7 @@ nothrow @nogc: if (Embed > 0 && str.ptr == str.embed.ptr + 2) { // clone the string - this(writeString(stringAllocators[0].alloc(cast(ushort)str.length, null), str[]), true); + this(writeString(get_string_allocator(0).alloc(cast(ushort)str.length, null), str[]), true); return; } } @@ -177,7 +268,7 @@ nothrow @nogc: ptr = cs.ptr; } - ~this() + ~this() pure { if (ptr) decRef(); @@ -188,7 +279,20 @@ nothrow @nogc: // TODO: I made this return ushort, but normally length() returns size_t ushort length() const pure - => ptr ? ((cast(ushort*)ptr)[-1] & 0x7FFF) : 0; + { + if (__ctfe) + { + version (LittleEndian) + return ptr ? cast(ushort)(ptr[-2] | (ptr[-1] << 8)) & 0x7FFF : 0; + else + return ptr ? cast(ushort)((ptr[-1] | (ptr[-2] << 8)) & 0x7FFF) : 0; + } + else + return ptr ? ((cast(ushort*)ptr)[-1] & 0x7FFF) : 0; + } + + bool empty() const pure + => length() == 0; bool opCast(T : bool)() const pure => ptr != null && ((cast(ushort*)ptr)[-1] & 0x7FFF) != 0; @@ -218,7 +322,6 @@ nothrow @nogc: ptr = cs.ptr; } - bool opEquals(const(char)[] rhs) const pure { if (!ptr) @@ -227,6 +330,15 @@ nothrow @nogc: return len == rhs.length && (ptr == rhs.ptr || ptr[0 .. len] == rhs[]); } + int opEquals(ref const String rhs) const pure + => opEquals(rhs[]); + + int opEquals(size_t N)(ref const MutableString!N rhs) const pure + => opEquals(rhs[]); + + int opEquals(size_t N)(ref const Array!N rhs) const pure + => opEquals(rhs[]); + int opCmp(const(char)[] rhs) const pure { import urt.algorithm : compare; @@ -235,14 +347,24 @@ nothrow @nogc: return compare(ptr[0 .. length()], rhs); } + int opCmp(ref const String rhs) const pure + => opCmp(rhs[]); + + int opCmp(size_t N)(ref const MutableString!N rhs) const pure + => opCmp(rhs[]); + + int opCmp(size_t N)(ref const Array!N rhs) const pure + => opCmp(rhs[]); + size_t toHash() const pure { if (!ptr) return 0; + ushort len = (cast(ushort*)ptr)[-1] & 0x7FFF; static if (size_t.sizeof == 4) - return fnv1aHash(cast(ubyte[])ptr[0 .. length]); + return fnv1a(cast(ubyte[])ptr[0 .. len]); else - return fnv1aHash64(cast(ubyte[])ptr[0 .. length]); + return fnv1a64(cast(ubyte[])ptr[0 .. len]); } const(char)[] opIndex() const pure @@ -263,11 +385,10 @@ nothrow @nogc: size_t opDollar() const pure => length(); -private: - auto __debugOverview() const pure { debug return ptr[0 .. length].debugExcapeString(); else return ptr[0 .. length]; } - auto __debugExpanded() const pure => ptr[0 .. length]; - auto __debugStringView() const pure => ptr[0 .. length]; + bool has_rc() const pure + => ptr && ((cast(ushort*)ptr)[-1] >> 15) != 0; +private: ushort* refCounter() const pure => ((cast(ushort*)ptr)[-1] >> 15) ? cast(ushort*)ptr - 2 : null; @@ -280,12 +401,12 @@ private: } } - void decRef() + void decRef() pure { if (ushort* rc = refCounter()) { if ((*rc & 0x3FFF) == 0) - stringAllocators[*rc >> 14].free(cast(char*)ptr); + get_string_allocator(*rc >> 14).free(cast(char*)ptr); else --*rc; } @@ -297,6 +418,13 @@ private: if (refCounted) *cast(ushort*)(ptr - 2) |= 0x8000; } + + version (Windows) + { + auto __debugOverview() const pure => ptr ? ptr[0 .. length].debugExcapeString() : null; + auto __debugExpanded() const pure => ptr ? ptr[0 .. length] : null; + auto __debugStringView() const pure => ptr ? ptr[0 .. length] : null; + } } unittest @@ -315,7 +443,9 @@ unittest assert(!emptyLit); // opCast!bool // Test makeString (default allocator) - String s1 = makeString("World"); + // TODO: reinstate the GC for debug allocations... +// String s1 = makeString("World"); + String s1 = StringLit!"World"; assert(s1.length == 5); assert(s1 == "World"); @@ -338,6 +468,7 @@ unittest assert(nullStr == ""); assert(!nullStr); + // TODO: reinstate once GC makeString is available // Test assignment and reference counting (basic check) String s3 = s1; // s3 references the same data as s1 assert(s3.ptr == s1.ptr); @@ -347,7 +478,7 @@ unittest assert(s3.length == 5); // Test equality - String s4 = makeString("World"); + String s4 = StringLit!"World"; assert(s3 == s4); // Different allocations, same content assert(s3 != "world"); // Case sensitive assert(s3 != "Worl"); @@ -414,8 +545,6 @@ nothrow @nogc: static assert(Embed == 0, "Not without move semantics!"); - alias toString this; - char* ptr; // TODO: DELETE POSTBLIT! @@ -470,12 +599,12 @@ nothrow @nogc: append(forward!things); } - this(Args...)(Format_T, auto ref Args args) + this(Args...)(Format_T, const(char)[] format, auto ref Args args) { - format(forward!args); + this.format(format, forward!args); } - ~this() + ~this() pure { freeStringBuffer(ptr); } @@ -487,9 +616,57 @@ nothrow @nogc: ushort length() const pure => ptr ? ((cast(ushort*)ptr)[-1] & 0x7FFF) : 0; + bool empty() const pure + => length() == 0; + bool opCast(T : bool)() const pure => ptr != null && ((cast(ushort*)ptr)[-1] & 0x7FFF) != 0; + bool opEquals(const(char)[] rhs) const pure + { + if (!ptr) + return rhs.length == 0; + ushort len = (cast(ushort*)ptr)[-1] & 0x7FFF; + return len == rhs.length && (ptr == rhs.ptr || ptr[0 .. len] == rhs[]); + } + + int opEquals(ref const String rhs) const pure + => opEquals(rhs[]); + + int opEquals(size_t N)(ref const MutableString!N rhs) const pure + => opEquals(rhs[]); + + int opEquals(size_t N)(ref const Array!N rhs) const pure + => opEquals(rhs[]); + + int opCmp(const(char)[] rhs) const pure + { + import urt.algorithm : compare; + if (!ptr) + return rhs.length == 0 ? 0 : -1; + return compare(ptr[0 .. length], rhs); + } + + int opCmp(ref const String rhs) const pure + => opCmp(rhs[]); + + int opCmp(size_t N)(ref const MutableString!N rhs) const pure + => opCmp(rhs[]); + + int opCmp(size_t N)(ref const Array!N rhs) const pure + => opCmp(rhs[]); + + size_t toHash() const pure + { + if (!ptr) + return 0; + ushort len = (cast(ushort*)ptr)[-1] & 0x7FFF; + static if (size_t.sizeof == 4) + return fnv1a(cast(ubyte[])ptr[0 .. len]); + else + return fnv1a64(cast(ubyte[])ptr[0 .. len]); + } + void opAssign(ref const typeof(this) rh) { opAssign(rh[]); @@ -499,11 +676,6 @@ nothrow @nogc: opAssign(rh[]); } - void opAssign(typeof(null)) - { - clear(); - } - void opAssign(char c) { reserve(1); @@ -597,78 +769,55 @@ nothrow @nogc: ref MutableString!Embed append(Things...)(auto ref Things things) { - insert(length(), forward!things); - return this; + import urt.string.format : normalise_args; + return insert_impl(length(), normalise_args(things)); } - ref MutableString!Embed appendFormat(Things...)(auto ref Things things) + ref MutableString!Embed append_format(Things...)(const(char)[] format, auto ref Things args) { - insertFormat(length(), forward!things); - return this; + return insert_format(length(), format, forward!args); } ref MutableString!Embed concat(Things...)(auto ref Things things) { + import urt.string.format : normalise_args; if (ptr) writeLength(0); - insert(0, forward!things); - return this; + return insert_impl(0, normalise_args(things)); } - ref MutableString!Embed format(Args...)(auto ref Args args) + ref MutableString!Embed format(Args...)(const(char)[] format, auto ref Args args) { if (ptr) writeLength(0); - insertFormat(0, forward!args); - return this; + return insert_format(0, format, forward!args); } ref MutableString!Embed insert(Things...)(size_t offset, auto ref Things things) { - import urt.string.format : _concat = concat; - import urt.util : max, nextPowerOf2; - - char* oldPtr = ptr; - size_t oldLen = length(); - - size_t insertLen = _concat(null, things).length; - size_t newLen = oldLen + insertLen; - if (newLen == oldLen) - return this; - debug assert(newLen <= MaxStringLen, "String too long"); - - size_t oldAlloc = allocated(); - ptr = newLen <= oldAlloc ? oldPtr : allocStringBuffer(max(16, cast(ushort)newLen + 4).nextPowerOf2 - 4); - memmove(ptr + offset + insertLen, oldPtr + offset, oldLen - offset); - _concat(ptr[offset .. offset + insertLen], forward!things); - writeLength(newLen); - - if (oldPtr && ptr != oldPtr) - { - ptr[0 .. offset] = oldPtr[0 .. offset]; - freeStringBuffer(oldPtr); - } - return this; + import urt.string.format : normalise_args; + auto args = normalise_args(things); + return insert_impl(offset, args); } - ref MutableString!Embed insertFormat(Things...)(size_t offset, auto ref Things things) + ref MutableString!Embed insert_format(Things...)(size_t offset, const(char)[] format, auto ref Things args) { import urt.string.format : _format = format; - import urt.util : max, nextPowerOf2; + import urt.util : max, next_power_of_2; char* oldPtr = ptr; size_t oldLen = length(); - size_t insertLen = _format(null, things).length; + size_t insertLen = _format(null, format, args).length; size_t newLen = oldLen + insertLen; if (newLen == oldLen) return this; debug assert(newLen <= MaxStringLen, "String too long"); size_t oldAlloc = allocated(); - ptr = newLen <= oldAlloc ? oldPtr : allocStringBuffer(max(16, cast(ushort)newLen + 4).nextPowerOf2 - 4); + ptr = newLen <= oldAlloc ? oldPtr : allocStringBuffer(max(16, cast(ushort)newLen + 4).next_power_of_2 - 4); memmove(ptr + offset + insertLen, oldPtr + offset, oldLen - offset); - _format(ptr[offset .. offset + insertLen], forward!things); + _format(ptr[offset .. offset + insertLen], format, forward!args); writeLength(newLen); if (oldPtr && ptr != oldPtr) @@ -762,7 +911,7 @@ private: return buffer + 4; } - void freeStringBuffer(char* buffer) + void freeStringBuffer(char* buffer) pure { if (!buffer) return; @@ -773,9 +922,41 @@ private: defaultAllocator().free(buffer[0 .. 4 + *cast(ushort*)buffer]); } - auto __debugOverview() const pure { debug return ptr[0 .. length].debugExcapeString(); else return ptr[0 .. length]; } - auto __debugExpanded() const pure => ptr[0 .. length]; - auto __debugStringView() const pure => ptr[0 .. length]; + import urt.meta.tuple : Tuple; + ref MutableString!Embed insert_impl(Things...)(size_t offset, Tuple!Things args) + { + import urt.string.format : concat_impl; + import urt.util : max, next_power_of_2; + + char* oldPtr = ptr; + size_t oldLen = length(); + + size_t insertLen = concat_impl(null, args).length; + size_t newLen = oldLen + insertLen; + if (newLen == oldLen) + return this; + debug assert(newLen <= MaxStringLen, "String too long"); + + size_t oldAlloc = allocated(); + ptr = newLen <= oldAlloc ? oldPtr : allocStringBuffer(max(16, cast(ushort)newLen + 4).next_power_of_2 - 4); + memmove(ptr + offset + insertLen, oldPtr + offset, oldLen - offset); + concat_impl(ptr[offset .. offset + insertLen], args); + writeLength(newLen); + + if (oldPtr && ptr != oldPtr) + { + ptr[0 .. offset] = oldPtr[0 .. offset]; + freeStringBuffer(oldPtr); + } + return this; + } + + version (Windows) + { + auto __debugOverview() const pure => ptr ? ptr[0 .. length].debugExcapeString() : null; + auto __debugExpanded() const pure => ptr ? ptr[0 .. length] : null; + auto __debugStringView() const pure => ptr ? ptr[0 .. length] : null; + } } unittest @@ -792,6 +973,9 @@ unittest assert(s == "Hello, world!\n"); assert(s.length == 14); + s = null; + assert(s.length == 0); + MutableString!0 s_long; s_long.reserve(4567); s_long = "Start"; @@ -824,11 +1008,11 @@ unittest m.append(" Text"); assert(m == "X! More Text"); - // appendFormat + // append_format m.clear(); - m.appendFormat("Value: {0}", 123); + m.append_format("Value: {0}", 123); assert(m == "Value: 123"); - m.appendFormat(", String: {0}", "abc"); + m.append_format(", String: {0}", "abc"); assert(m == "Value: 123, String: abc"); // concat @@ -849,13 +1033,13 @@ unittest m.insert(m.length, "!"); // End (same as append) assert(m == "My Super String!"); - // insertFormat + // insert_format m = "Data"; - m.insertFormat(0, "[{0}] ", 1); + m.insert_format(0, "[{0}] ", 1); assert(m == "[1] Data"); - m.insertFormat(4, "\\{{0}\\}", "fmt"); + m.insert_format(4, "\\{{0}\\}", "fmt"); assert(m == "[1] {fmt}Data"); - m.insertFormat(m.length, " End"); + m.insert_format(m.length, " End"); assert(m == "[1] {fmt}Data End"); // erase @@ -933,48 +1117,50 @@ private: __gshared StringAllocator[4] stringAllocators; static assert(stringAllocators.length <= 4, "Only 2 bits reserved to store allocator index"); -char* writeString(char* buffer, const(char)[] str) pure nothrow @nogc +ref StringAllocator get_string_allocator(uint i) pure nothrow @nogc { - // TODO: assume the calling code has confirmed the length is within spec - (cast(ushort*)buffer)[-1] = cast(ushort)str.length; - buffer[0 .. str.length] = str[]; - return buffer; + alias PureHack = ref StringAllocator function(uint i) pure nothrow @nogc; + static ref StringAllocator get_allocator(uint i) nothrow @nogc { return stringAllocators[i]; }; + return (cast(PureHack)&get_allocator)(i); } -package(urt) void initStringAllocators() +package(urt) void initStringAllocators() nothrow @nogc { - stringAllocators[StringAlloc.Default].alloc = (ushort bytes, void* userData) { - char* buffer = cast(char*)defaultAllocator().alloc(bytes + 4, ushort.alignof).ptr; + alias PureAlloc = void[] delegate(size_t, size_t) pure nothrow @nogc; + alias PureFree = void delegate(void[]) pure nothrow @nogc; + + stringAllocators[StringAlloc.Default].alloc = (ushort bytes, void* userData) pure { + char* buffer = cast(char*)(cast(PureAlloc)&defaultAllocator().alloc)(bytes + 4, ushort.alignof).ptr; *cast(ushort*)buffer = StringAlloc.Default << 14; // allocator = default, rc = 0 return buffer + 4; }; - stringAllocators[StringAlloc.Default].free = (char* str) { + stringAllocators[StringAlloc.Default].free = (char* str) pure { ushort len = (cast(ushort*)str)[-1] & 0x7FFF; str -= 4; - defaultAllocator().free(str[0 .. 4 + len]); + (cast(PureFree)&defaultAllocator().free)(str[0 .. 4 + len]); }; - stringAllocators[StringAlloc.Explicit].alloc = (ushort bytes, void* userData) { + stringAllocators[StringAlloc.Explicit].alloc = (ushort bytes, void* userData) pure { NoGCAllocator a = cast(NoGCAllocator)userData; - char* buffer = cast(char*)a.alloc(size_t.sizeof*2 + bytes, size_t.alignof).ptr; + char* buffer = cast(char*)(cast(PureAlloc)&a.alloc)(size_t.sizeof*2 + bytes, size_t.alignof).ptr; *cast(NoGCAllocator*)buffer = a; buffer += size_t.sizeof*2; (cast(ushort*)buffer)[-2] = StringAlloc.Explicit << 14; // allocator = explicit, rc = 0 return buffer; }; - stringAllocators[StringAlloc.Explicit].free = (char* str) { + stringAllocators[StringAlloc.Explicit].free = (char* str) pure { NoGCAllocator a = *cast(NoGCAllocator*)(str - size_t.sizeof*2); ushort len = (cast(ushort*)str)[-1] & 0x7FFF; str -= size_t.sizeof*2; - a.free(str[0 .. size_t.sizeof*2 + len]); + (cast(PureFree)&a.free)(str[0 .. size_t.sizeof*2 + len]); }; } -debug +version (Windows) { - char[] debugExcapeString(const char[] s) pure nothrow + char[] debugExcapeString(const char[] s) pure nothrow @nogc { - char[] t = new char[s.length*2]; + char[] t = debug_alloc!char(s.length*2); int d; foreach (i; 0 .. s.length) { diff --git a/src/urt/string/tailstring.d b/src/urt/string/tailstring.d index 3664342..383ac30 100644 --- a/src/urt/string/tailstring.d +++ b/src/urt/string/tailstring.d @@ -23,7 +23,7 @@ struct TailString(T) else { const(char)* thisptr = cast(const(char)*)&this; - const(char)* sptr = s.ptr - (s.ptr[-1] < 128 ? 1 : 2); + const(char)* sptr = s.ptr - 2; assert(sptr > thisptr && sptr - thisptr <= offset.max, "!!"); offset = cast(ubyte)(sptr - thisptr); } @@ -39,7 +39,7 @@ struct TailString(T) if (offset == 0) return null; const(char)* ptr = (cast(const(char)*)&this) + offset; - return ptr[0] < 128 ? ptr + 1 : ptr + 2; + return ptr + 2; } size_t length() const nothrow @nogc @@ -64,7 +64,7 @@ struct TailString(T) else { const(char)* thisptr = cast(const(char)*)&this; - const(char)* sptr = s.ptr - (s.ptr[-1] < 128 ? 1 : 2); + const(char)* sptr = s.ptr - 2; assert(sptr > thisptr && sptr - thisptr <= offset.max, "!!"); offset = cast(ubyte)(sptr - thisptr); } @@ -81,9 +81,9 @@ struct TailString(T) import urt.hash; static if (size_t.sizeof == 4) - return fnv1aHash(cast(ubyte[])toString()); + return fnv1a(cast(ubyte[])toString()); else - return fnv1aHash64(cast(ubyte[])toString()); + return fnv1a64(cast(ubyte[])toString()); } private: @@ -94,9 +94,12 @@ private: this.offset = offset; } - auto __debugOverview() => toString; - auto __debugExpanded() => toString; - auto __debugStringView() => toString; + version (Windows) + { + auto __debugOverview() => toString; + auto __debugExpanded() => toString; + auto __debugStringView() => toString; + } } diff --git a/src/urt/string/uni.d b/src/urt/string/uni.d index ee9b551..56297fc 100644 --- a/src/urt/string/uni.d +++ b/src/urt/string/uni.d @@ -1,18 +1,155 @@ module urt.string.uni; -nothrow @nogc: +import urt.string.ascii : to_lower, to_upper; +import urt.traits : is_some_char; +pure nothrow @nogc: -size_t uniConvert(const(char)[] s, wchar[] buffer) + +size_t uni_seq_len(const(char)[] str) +{ + debug assert(str.length > 0); + + const(char)* s = str.ptr; + if (s[0] < 0x80) // 1-byte sequence: 0xxxxxxx + return 1; + else if ((s[0] & 0xE0) == 0xC0) // 2-byte sequence: 110xxxxx 10xxxxxx + return (str.length >= 2 && (s[1] & 0xC0) == 0x80) ? 2 : 1; + else if ((s[0] & 0xF0) == 0xE0) // 3-byte sequence: 1110xxxx 10xxxxxx 10xxxxxx + return (str.length >= 3 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80) ? 3 : + (str.length >= 2 && (s[1] & 0xC0) == 0x80) ? 2 : 1; + else if ((s[0] & 0xF8) == 0xF0) // 4-byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + return (str.length >= 4 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80 && (s[3] & 0xC0) == 0x80) ? 4 : + (str.length >= 3 && (s[1] & 0xC0) == 0x80 && (s[2] & 0xC0) == 0x80) ? 3 : + (str.length >= 2 && (s[1] & 0xC0) == 0x80) ? 2 : 1; + return 1; // Invalid UTF-8 sequence +} + +size_t uni_seq_len(const(wchar)[] str) +{ + debug assert(str.length > 0); + + const(wchar)* s = str.ptr; + if (s[0] >= 0xD800 && s[0] < 0xDC00 && str.length >= 2 && s[1] >= 0xDC00 && s[1] < 0xE000) + return 2; // Surrogate pair: 110110xxxxxxxxxx 110111xxxxxxxxxx + return 1; +} + +pragma(inline, true) +size_t uni_seq_len(const(dchar)[] s) + => 1; + +size_t uni_strlen(C)(const(C)[] s) + if (is_some_char!C) +{ + static if (is(C == dchar)) + { + pragma(inline, true); + return s.length; + } + else + { + size_t count = 0; + while (s.length) + { + size_t l = s.uni_seq_len; + s = s[l .. $]; + ++count; + } + return count; + } +} + +dchar next_dchar(const(char)[] s, out size_t seq_len) +{ + assert(s.length > 0); + + const(char)* p = s.ptr; + if ((*p & 0x80) == 0) // 1-byte sequence: 0xxxxxxx + { + seq_len = 1; + return *p; + } + else if ((*p & 0xE0) == 0xC0) // 2-byte sequence: 110xxxxx 10xxxxxx + { + if (s.length >= 2 && (p[1] & 0xC0) == 0x80) + { + seq_len = 2; + return ((p[0] & 0x1F) << 6) | (p[1] & 0x3F); + } + } + else if ((*p & 0xF0) == 0xE0) // 3-byte sequence: 1110xxxx 10xxxxxx 10xxxxxx + { + if (s.length >= 3 && (p[1] & 0xC0) == 0x80 && (p[2] & 0xC0) == 0x80) + { + seq_len = 3; + return ((p[0] & 0x0F) << 12) | ((p[1] & 0x3F) << 6) | (p[2] & 0x3F); + } + // check for seq_len == 2 error cases + if (s.length >= 2 && (p[1] & 0xC0) == 0x80) + { + seq_len = 2; + return 0xFFFD; + } + } + else if ((*p & 0xF8) == 0xF0) // 4-byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + { + if (s.length >= 4 && (p[1] & 0xC0) == 0x80 && (p[2] & 0xC0) == 0x80 && (p[3] & 0xC0) == 0x80) + { + seq_len = 4; + return ((p[0] & 0x07) << 18) | ((p[1] & 0x3F) << 12) | ((p[2] & 0x3F) << 6) | (p[3] & 0x3F); + } + // check for seq_len == 2..3 error cases + if (s.length >= 2 && (p[1] & 0xC0) == 0x80) + { + if (s.length == 2 || (p[2] & 0xC0) != 0x80) + seq_len = 2; + else + seq_len = 3; + return 0xFFFD; + } + } + seq_len = 1; + return 0xFFFD; // Invalid UTF-8 sequence +} + +dchar next_dchar(const(wchar)[] s, out size_t seq_len) +{ + assert(s.length > 0); + + const(wchar)* p = s.ptr; + if (p[0] < 0xD800 || p[0] >= 0xE000) + { + seq_len = 1; + return p[0]; + } + if (p[0] < 0xDC00 && s.length >= 2 && p[1] >= 0xDC00 && p[1] < 0xE000) // Surrogate pair: 110110xxxxxxxxxx 110111xxxxxxxxxx + { + seq_len = 2; + return 0x10000 + ((p[0] - 0xD800) << 10) + (p[1] - 0xDC00); + } + seq_len = 1; + return 0xFFFD; // Invalid UTF-16 sequence +} + +pragma(inline, true) +dchar next_dchar(const(dchar)[] s, out size_t seq_len) +{ + assert(s.length > 0); + seq_len = 1; + return s[0]; +} + +size_t uni_convert(const(char)[] s, wchar[] buffer) { const(char)* p = s.ptr; const(char)* pend = p + s.length; wchar* b = buffer.ptr; - wchar* bEnd = buffer.ptr + buffer.length; + wchar* bend = buffer.ptr + buffer.length; while (p < pend) { - if (b >= bEnd) + if (b >= bend) return 0; // End of output buffer if ((*p & 0x80) == 0) // 1-byte sequence: 0xxxxxxx *b++ = *p++; @@ -32,7 +169,7 @@ size_t uniConvert(const(char)[] s, wchar[] buffer) } else if ((*p & 0xF8) == 0xF0) // 4-byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx { - if (p + 3 >= pend || b + 1 >= bEnd) + if (p + 3 >= pend || b + 1 >= bend) return 0; // Unexpected end of input/output dchar codepoint = ((p[0] & 0x07) << 18) | ((p[1] & 0x3F) << 12) | ((p[2] & 0x3F) << 6) | (p[3] & 0x3F); codepoint -= 0x10000; @@ -47,16 +184,16 @@ size_t uniConvert(const(char)[] s, wchar[] buffer) return b - buffer.ptr; } -size_t uniConvert(const(char)[] s, dchar[] buffer) +size_t uni_convert(const(char)[] s, dchar[] buffer) { const(char)* p = s.ptr; const(char)* pend = p + s.length; dchar* b = buffer.ptr; - dchar* bEnd = buffer.ptr + buffer.length; + dchar* bend = buffer.ptr + buffer.length; while (p < pend) { - if (b >= bEnd) + if (b >= bend) return 0; if ((*p & 0x80) == 0) // 1-byte sequence: 0xxxxxxx *b++ = *p++; @@ -84,16 +221,16 @@ size_t uniConvert(const(char)[] s, dchar[] buffer) return b - buffer.ptr; } -size_t uniConvert(const(wchar)[] s, char[] buffer) +size_t uni_convert(const(wchar)[] s, char[] buffer) { const(wchar)* p = s.ptr; const(wchar)* pend = p + s.length; char* b = buffer.ptr; - char* bEnd = buffer.ptr + buffer.length; + char* bend = buffer.ptr + buffer.length; while (p < pend) { - if (b >= bEnd) + if (b >= bend) return 0; // End of output buffer if (p[0] >= 0xD800) { @@ -103,7 +240,7 @@ size_t uniConvert(const(wchar)[] s, char[] buffer) return 0; // Unexpected end of input if (p[0] < 0xDC00) // Surrogate pair: 110110xxxxxxxxxx 110111xxxxxxxxxx { - if (b + 3 >= bEnd) + if (b + 3 >= bend) return 0; // End of output buffer dchar codepoint = 0x10000 + ((p[0] - 0xD800) << 10) + (p[1] - 0xDC00); b[0] = 0xF0 | (codepoint >> 18); @@ -120,7 +257,7 @@ size_t uniConvert(const(wchar)[] s, char[] buffer) *b++ = cast(char)*p++; else if (*p < 0x800) // 2-byte sequence: 110xxxxx 10xxxxxx { - if (b + 1 >= bEnd) + if (b + 1 >= bend) return 0; // End of output buffer b[0] = 0xC0 | cast(char)(*p >> 6); b[1] = 0x80 | (*p++ & 0x3F); @@ -129,7 +266,7 @@ size_t uniConvert(const(wchar)[] s, char[] buffer) else // 3-byte sequence: 1110xxxx 10xxxxxx 10xxxxxx { three_byte_seq: - if (b + 2 >= bEnd) + if (b + 2 >= bend) return 0; // End of output buffer b[0] = 0xE0 | (*p >> 12); b[1] = 0x80 | ((*p >> 6) & 0x3F); @@ -140,24 +277,24 @@ size_t uniConvert(const(wchar)[] s, char[] buffer) return b - buffer.ptr; } -size_t uniConvert(const(wchar)[] s, dchar[] buffer) +size_t uni_convert(const(wchar)[] s, dchar[] buffer) { const(wchar)* p = s.ptr; const(wchar)* pend = p + s.length; dchar* b = buffer.ptr; - dchar* bEnd = buffer.ptr + buffer.length; + dchar* bend = buffer.ptr + buffer.length; while (p < pend) { - if (b >= bEnd) + if (b >= bend) return 0; // End of output buffer - if (p[0] >= 0xD800 && p[0] < 0xE000) + if ((p[0] >> 11) == 0x1B) { if (p + 1 >= pend) return 0; // Unexpected end of input - if (p[0] < 0xDC00) // Surrogate pair: 110110xxxxxxxxxx 110111xxxxxxxxxx + if (p[0] < 0xDC00 && (p[1] >> 10) == 0x37) // Surrogate pair: 110110xxxxxxxxxx 110111xxxxxxxxxx { - *b++ = 0x10000 + ((p[0] - 0xD800) << 10) + (p[1] - 0xDC00); + *b++ = 0x10000 + ((p[0] & 0x3FF) << 10 | (p[1] & 0x3FF)); p += 2; continue; } @@ -168,22 +305,22 @@ size_t uniConvert(const(wchar)[] s, dchar[] buffer) return b - buffer.ptr; } -size_t uniConvert(const(dchar)[] s, char[] buffer) +size_t uni_convert(const(dchar)[] s, char[] buffer) { const(dchar)* p = s.ptr; const(dchar)* pend = p + s.length; char* b = buffer.ptr; - char* bEnd = buffer.ptr + buffer.length; + char* bend = buffer.ptr + buffer.length; while (p < pend) { - if (b >= bEnd) + if (b >= bend) return 0; // End of output buffer if (*p < 0x80) // 1-byte sequence: 0xxxxxxx *b++ = cast(char)*p++; else if (*p < 0x800) // 2-byte sequence: 110xxxxx 10xxxxxx { - if (b + 1 >= bEnd) + if (b + 1 >= bend) return 0; // End of output buffer b[0] = 0xC0 | cast(char)(*p >> 6); b[1] = 0x80 | (*p++ & 0x3F); @@ -191,7 +328,7 @@ size_t uniConvert(const(dchar)[] s, char[] buffer) } else if (*p < 0x10000) // 3-byte sequence: 1110xxxx 10xxxxxx 10xxxxxx { - if (b + 2 >= bEnd) + if (b + 2 >= bend) return 0; // End of output buffer b[0] = 0xE0 | cast(char)(*p >> 12); b[1] = 0x80 | ((*p >> 6) & 0x3F); @@ -200,7 +337,7 @@ size_t uniConvert(const(dchar)[] s, char[] buffer) } else if (*p < 0x110000) // 4-byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx { - if (b + 3 >= bEnd) + if (b + 3 >= bend) return 0; // End of output buffer b[0] = 0xF0 | (*p >> 18); b[1] = 0x80 | ((*p >> 12) & 0x3F); @@ -214,22 +351,22 @@ size_t uniConvert(const(dchar)[] s, char[] buffer) return b - buffer.ptr; } -size_t uniConvert(const(dchar)[] s, wchar[] buffer) +size_t uni_convert(const(dchar)[] s, wchar[] buffer) { const(dchar)* p = s.ptr; const(dchar)* pend = p + s.length; wchar* b = buffer.ptr; - wchar* bEnd = buffer.ptr + buffer.length; + wchar* bend = buffer.ptr + buffer.length; while (p < pend) { - if (b >= bEnd) + if (b >= bend) return 0; // End of output buffer if (*p < 0x10000) *b++ = cast(wchar)*p++; else if (*p < 0x110000) { - if (b + 1 >= bEnd) + if (b + 1 >= bend) return 0; // End of output buffer dchar codepoint = *p++ - 0x10000; b[0] = 0xD800 | (codepoint >> 10); @@ -242,10 +379,507 @@ size_t uniConvert(const(dchar)[] s, wchar[] buffer) return b - buffer.ptr; } +char uni_to_lower(char c) + => c.to_lower(); + +dchar uni_to_lower(dchar c) +{ + if (uint(c - 'A') < 26) + return c | 0x20; + + // TODO: this is a deep rabbit-hole! (and the approach might not be perfect) + + if (c < 0xFF) + { + if (c >= 0xC0) // Latin-1 Supplement + return g_to_lower_latin_1.ptr[c - 0xC0]; + } + else if (c <= 0x556) + { + if (c >= 0x370) + { + if (c < 0x400) // Greek and Coptic + return 0x300 | g_to_lower_greek.ptr[c - 0x370]; + else if (c < 0x460) // Cyrillic + { + if (c < 0x410) + return c + 0x50; + else if (c < 0x430) + return c + 0x20; + } + else if (c < 0x530) // Cyrillic Supplement + { + if (c >= 0x482 && c < 0x48A) // exceptions + return c; + return c | 1; + } + else if (c >= 0x531) // Armenian + return c + 0x30; + } + else if (c < 0x180) // Latin Extended-A + return (0x100 | g_to_lower_latin_extended_a.ptr[c - 0xFF]) - 1; + } + else if (c <= 0x1CB0) // Georgian + { + if (c >= 0x1C90) + return c - 0x0BC0; // Mtavruli -> Mkhedruli + else if (c >= 0x10A0 && c <= 0x10C5) + return c + 0x1C60; // Asomtavruli -> Nuskhuri + } + else if (c >= 0x1E00) + { + if (c < 0x1F00) // Latin Extended Additional + { + if (c >= 0x1E96 && c < 0x1EA0) // exceptions + { + if (c == 0x1E9E) // 'ẞ' -> 'ß' + return 0xDF; + return c; + } + return c | 1; + } + else if (c <= 0x1FFC) // Greek Extended + { + if (c < 0x1F70) + return c & ~0x8; + return 0x1F00 | g_to_lower_greek_extended.ptr[c - 0x1F70]; + } + else if (c < 0x2CE4) + { + if (c >= 0x2C80) // Coptic + return c | 1; + } + } + return c; +} + +char uni_to_upper(char c) + => c.to_upper(); + +dchar uni_to_upper(dchar c) +{ + if (uint(c - 'a') < 26) + return c ^ 0x20; + + // TODO: this is a deep rabbit-hole! (and the approach might not be perfect) + + if (c < 0xFF) + { + if (c == 0xDF) // 'ß' -> 'ẞ' + return 0x1E9E; + if (c >= 0xC0) // Latin-1 Supplement + return g_to_upper_latin_1.ptr[c - 0xC0]; + } + else if (c <= 0x586) + { + if (c >= 0x370) + { + if (c < 0x400) // Greek and Coptic + return 0x300 | g_to_upper_greek.ptr[c - 0x370]; + else if (c < 0x460) // Cyrillic + { + if (c >= 0x450) + return c - 0x50; + else if (c >= 0x430) + return c - 0x20; + } + else if (c < 0x530) // Cyrillic Supplement + { + if (c >= 0x482 && c < 0x48A) // exceptions + return c; + return c & ~1; + } + else if (c >= 0x561) // Armenian + return c - 0x30; + } + else if (c < 0x180) // Latin Extended-A + return 0x100 | g_to_upper_latin_extended_a.ptr[c - 0xFF]; + } + else if (c <= 0x10F0) // Georgian + { + if (c >= 0x10D0) + return c + 0x0BC0; // Mkhedruli -> Mtavruli + } + else if (c >= 0x1E00) + { + if (c < 0x1F00) // Latin Extended Additional + { + if (c >= 0x1E96 && c < 0x1EA0) // exceptions + return c; + return c & ~1; + } + else if (c <= 0x1FFC) // Greek Extended + { + if (c < 0x1F70) + return c | 0x8; + return 0x1F00 | g_to_upper_greek_extended.ptr[c - 0x1F70]; + } + else if (c < 0x2CE4) + { + if (c >= 0x2C80) // Coptic + return c & ~1; + } + else if (c <= 0x2D25) // Georgian + { + if(c >= 0x2D00) + return c - 0x1C60; // Nuskhuri -> Asomtavruli + } + } + return c; +} + +char uni_case_fold(char c) + => c.to_lower(); + +dchar uni_case_fold(dchar c) +{ + // case-folding is stronger than to_lower, there may be many misc cases... + if (c >= 0x3C2) // Greek has bonus case-folding... + { + if (c < 0x3FA) + return 0x300 | g_case_fold_greek.ptr[c - 0x3C2]; + } + else if (c == 'ſ') // TODO: pointless? it's in the spec... + return 's'; + return uni_to_lower(c); +} + +int uni_compare(T, U)(const(T)[] s1, const(U)[] s2) + if (is_some_char!T && is_some_char!U) +{ + const(T)* p1 = s1.ptr; + const(T)* p1end = p1 + s1.length; + const(U)* p2 = s2.ptr; + const(U)* p2end = p2 + s2.length; + + // TODO: this is crude and insufficient; doesn't handle compound diacritics, etc (needs a NFKC normalisation step) + + while (true) + { + // return int.min/max in the case that the strings are a sub-string of the other so the caller can detect this case + if (p1 >= p1end) + return p2 < p2end ? int.min : 0; + if (p2 >= p2end) + return int.max; + + dchar a = *p1, b = *p2; + + if (a < 0x80) + { + if (a != b) + return int(a) - int(b); + ++p1; + p2 += b < 0x80 ? 1 : p2[0 .. p2end - p2].uni_seq_len; + } + else if (b < 0x80) + { + if (a != b) + return int(a) - int(b); + p1 += p1[0 .. p1end - p1].uni_seq_len; + ++p2; + } + else + { + size_t al, bl; + a = next_dchar(p1[0 .. p1end - p1], al); + b = next_dchar(p2[0 .. p2end - p2], bl); + if (a != b) + return cast(int)a - cast(int)b; + p1 += al; + p2 += bl; + } + } +} + +int uni_compare_i(T, U)(const(T)[] s1, const(U)[] s2) + if (is_some_char!T && is_some_char!U) +{ + const(T)* p1 = s1.ptr; + const(T)* p1end = p1 + s1.length; + const(U)* p2 = s2.ptr; + const(U)* p2end = p2 + s2.length; + + // TODO: this is crude and insufficient; doesn't handle compound diacritics, etc (needs a NFKC normalisation step) + // that said, it's also overkill for embedded use! + + size_t al, bl; + while (p1 < p1end && p2 < p2end) + { + dchar a = *p1; + dchar b = void; + if (a < 0x80) + { + // ascii fast-path + a = (cast(char)a).to_lower; + b = *p2; + if (uint(b - 'A') < 26) + b |= 0x20; + if (a != b) + { + if (b >= 0x80) + { + // `b` is not ascii; break-out to the slow path... + al = 1; + goto uni_compare_load_b; + } + return cast(int)a - cast(int)b; + } + ++p1; + ++p2; + } + else + { + a = next_dchar(p1[0 .. p1end - p1], al).uni_case_fold; + uni_compare_load_b: + b = next_dchar(p2[0 .. p2end - p2], bl).uni_case_fold; + uni_compare_a_b: + if (a != b) + { + // it is _SO UNFORTUNATE_ that the ONLY special-case letter in all of unicode is german 'ß' (0xDF)!! + if (b == 0xDF) + { + if (a != 's') + return cast(int)a - cast(int)'s'; + if (++p1 == p1end) + return -1; // only one 's', so the a-side is a shorter string + a = next_dchar(p1[0 .. p1end - p1], al).uni_case_fold; + b = 's'; + p2 += bl - 1; + bl = 1; + goto uni_compare_a_b; + } + else if (a == 0xDF) + { + if (b != 's') + return cast(int)'s' - cast(int)b; + if (++p2 == p2end) + return 1; // only one 's', so the b-side is a shorter string + a = 's'; + p1 += al - 1; + al = 1; + goto uni_compare_load_b; + } + return cast(int)a - cast(int)b; + } + p1 += al; + p2 += bl; + } + } + + // return int.min/max in the case that the strings are a sub-string of the other so the caller can detect this case + return (p1 < p1end) ? int.max : (p2 < p2end) ? int.min : 0; +} + + +private: + +// this is a helper to crush character maps into single byte arrays... +ubyte[N] map_chars(size_t N)(ubyte function(wchar) pure nothrow @nogc translate, wchar[N] map) +{ + if (__ctfe) + { + ubyte[N] result; + foreach (i; 0 .. N) + result[i] = translate(map[i]); + return result; + } + else + assert(false, "Not for runtime!"); +} + +// lookup tables for case conversion +__gshared immutable g_to_lower_latin_1 = map_chars(c => cast(ubyte)c, to_lower_latin[0 .. 0x3F]); +__gshared immutable g_to_upper_latin_1 = map_chars(c => cast(ubyte)c, to_upper_latin[0 .. 0x3F]); +__gshared immutable g_to_lower_latin_extended_a = map_chars(c => cast(ubyte)(c + 1), to_lower_latin[0x3F .. 0xC0]); // calculate `(0x100 | table[n]) - 1` at runtime +__gshared immutable g_to_upper_latin_extended_a = map_chars(c => cast(ubyte)c, to_upper_latin[0x3F .. 0xC0]); // calculate `0x100 | table[n]` at runtime +__gshared immutable g_to_lower_greek = map_chars(c => cast(ubyte)c, to_lower_greek); // calculate `0x300 | table[n]` at runtime +__gshared immutable g_to_upper_greek = map_chars(c => cast(ubyte)c, to_upper_greek); // calculate `0x300 | table[n]` at runtime +__gshared immutable g_case_fold_greek = map_chars(c => cast(ubyte)c, case_fold_greek); // calculate `0x300 | table[n]` at runtime +__gshared immutable g_to_lower_greek_extended = map_chars(c => cast(ubyte)c, to_lower_greek_extended); // calculate `0x1F00 | table[n]` at runtime +__gshared immutable g_to_upper_greek_extended = map_chars(c => cast(ubyte)c, to_upper_greek_extended); // calculate `0x1F00 | table[n]` at runtime + +// Latin-1 Supplement and Latin Extended-A +enum wchar[0x180 - 0xC0] to_lower_latin = [ + 'à', 'á', 'â', 'ã', 'ä', 'å', 'æ', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', + 'ð', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', 0xD7,'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'þ', 'ß', + 'à', 'á', 'â', 'ã', 'ä', 'å', 'æ', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', + 'ð', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', 0xF7,'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'þ', 'ÿ', + 'ā', 'ā', 'ă', 'ă', 'ą', 'ą', 'ć', 'ć', 'ĉ', 'ĉ', 'ċ', 'ċ', 'č', 'č', 'ď', 'ď', + 'đ', 'đ', 'ē', 'ē', 'ĕ', 'ĕ', 'ė', 'ė', 'ę', 'ę', 'ě', 'ě', 'ĝ', 'ĝ', 'ğ', 'ğ', + 'ġ', 'ġ', 'ģ', 'ģ', 'ĥ', 'ĥ', 'ħ', 'ħ', 'ĩ', 'ĩ', 'ī', 'ī', 'ĭ', 'ĭ', 'į', 'į', + 0x130,0x131,'ij', 'ij', 'ĵ', 'ĵ', 'ķ', 'ķ',0x138,'ĺ', 'ĺ', 'ļ', 'ļ', 'ľ', 'ľ', 'ŀ', + 'ŀ', 'ł', 'ł', 'ń', 'ń', 'ņ', 'ņ', 'ň', 'ň',0x149,'ŋ', 'ŋ', 'ō', 'ō', 'ŏ', 'ŏ', + 'ő', 'ő', 'œ', 'œ', 'ŕ', 'ŕ', 'ŗ', 'ŗ', 'ř', 'ř', 'ś', 'ś', 'ŝ', 'ŝ', 'ş', 'ş', + 'š', 'š', 'ţ', 'ţ', 'ť', 'ť', 'ŧ', 'ŧ', 'ũ', 'ũ', 'ū', 'ū', 'ŭ', 'ŭ', 'ů', 'ů', + 'ű', 'ű', 'ų', 'ų', 'ŵ', 'ŵ', 'ŷ', 'ŷ', 'ÿ', 'ź', 'ź', 'ż', 'ż', 'ž', 'ž', 'ſ' +]; + +enum wchar[0x180 - 0xC0] to_upper_latin = [ + 'À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Æ', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï', + 'Ð', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', 0xD7,'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', 'Þ', 'ẞ', + 'À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Æ', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï', + 'Ð', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', 0xF7,'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', 'Þ', 'Ÿ', + 'Ā', 'Ā', 'Ă', 'Ă', 'Ą', 'Ą', 'Ć', 'Ć', 'Ĉ', 'Ĉ', 'Ċ', 'Ċ', 'Č', 'Č', 'Ď', 'Ď', + 'Đ', 'Đ', 'Ē', 'Ē', 'Ĕ', 'Ĕ', 'Ė', 'Ė', 'Ę', 'Ę', 'Ě', 'Ě', 'Ĝ', 'Ĝ', 'Ğ', 'Ğ', + 'Ġ', 'Ġ', 'Ģ', 'Ģ', 'Ĥ', 'Ĥ', 'Ħ', 'Ħ', 'Ĩ', 'Ĩ', 'Ī', 'Ī', 'Ĭ', 'Ĭ', 'Į', 'Į', + 0x130,0x131,'IJ', 'IJ', 'Ĵ', 'Ĵ', 'Ķ', 'Ķ',0x138,'Ĺ', 'Ĺ', 'Ļ', 'Ļ', 'Ľ', 'Ľ', 'Ŀ', + 'Ŀ', 'Ł', 'Ł', 'Ń', 'Ń', 'Ņ', 'Ņ', 'Ň', 'Ň',0x149,'Ŋ', 'Ŋ', 'Ō', 'Ō', 'Ŏ', 'Ŏ', + 'Ő', 'Ő', 'Œ', 'Œ', 'Ŕ', 'Ŕ', 'Ŗ', 'Ŗ', 'Ř', 'Ř', 'Ś', 'Ś', 'Ŝ', 'Ŝ', 'Ş', 'Ş', + 'Š', 'Š', 'Ţ', 'Ţ', 'Ť', 'Ť', 'Ŧ', 'Ŧ', 'Ũ', 'Ũ', 'Ū', 'Ū', 'Ŭ', 'Ŭ', 'Ů', 'Ů', + 'Ű', 'Ű', 'Ų', 'Ų', 'Ŵ', 'Ŵ', 'Ŷ', 'Ŷ', 'Ÿ', 'Ź', 'Ź', 'Ż', 'Ż', 'Ž', 'Ž', 'S' +]; + +// Greek and Coptic +enum wchar[0x400 - 0x370] to_lower_greek = [ + 'ͱ', 'ͱ', 'ͳ', 'ͳ',0x374,0x375,'ͷ','ͷ',0x378,0x379,0x37A,'ͻ','ͼ','ͽ',0x37E,'ϳ', +0x380,0x381,0x382,0x383,0x384,0x385,'ά',0x387,'έ','ή','ί',0x38B,'ό',0x38D,'ύ', 'ώ', + 0x390,'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ', 'ι', 'κ', 'λ', 'μ', 'ν', 'ξ', 'ο', + 'π', 'ρ',0x3A2,'σ', 'τ', 'υ', 'φ', 'χ', 'ψ', 'ω', 'ϊ', 'ϋ', 'ά', 'έ', 'ή', 'ί', + 0x3B0,'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ', 'ι', 'κ', 'λ', 'μ', 'ν', 'ξ', 'ο', + 'π', 'ρ', 'ς', 'σ', 'τ', 'υ', 'φ', 'χ', 'ψ', 'ω', 'ϊ', 'ϋ', 'ό', 'ύ', 'ώ', 'ϗ', + 'ϐ', 'ϑ', 'υ', 'ύ', 'ϋ', 'ϕ', 'ϖ', 'ϗ', 'ϙ', 'ϙ', 'ϛ', 'ϛ', 'ϝ', 'ϝ', 'ϟ', 'ϟ', + 'ϡ', 'ϡ', 'ϣ', 'ϣ', 'ϥ', 'ϥ', 'ϧ', 'ϧ', 'ϩ', 'ϩ', 'ϫ', 'ϫ', 'ϭ', 'ϭ', 'ϯ', 'ϯ', + 'ϰ', 'ϱ', 'ϲ', 'ϳ', 'θ', 'ϵ',0x3F6,'ϸ', 'ϸ', 'ϲ', 'ϻ', 'ϻ',0x3FC,'ͻ', 'ͼ', 'ͽ' +]; + +enum wchar[0x400 - 0x370] to_upper_greek = [ + 'Ͱ', 'Ͱ', 'Ͳ', 'Ͳ',0x374,0x375,'Ͷ','Ͷ',0x378,0x379,0x37A,'Ͻ','Ͼ','Ͽ',0x37E,'Ϳ', +0x380,0x381,0x382,0x383,0x384,0x385,'Ά',0x387,'Έ','Ή','Ί',0x38B,'Ό',0x38D,'Ύ', 'Ώ', + 0x390,'Α', 'Β', 'Γ', 'Δ', 'Ε', 'Ζ', 'Η', 'Θ', 'Ι', 'Κ', 'Λ', 'Μ', 'Ν', 'Ξ', 'Ο', + 'Π', 'Ρ',0x3A2,'Σ', 'Τ', 'Υ', 'Φ', 'Χ', 'Ψ', 'Ω', 'Ϊ', 'Ϋ', 'Ά', 'Έ', 'Ή', 'Ί', + 0x3B0,'Α', 'Β', 'Γ', 'Δ', 'Ε', 'Ζ', 'Η', 'Θ', 'Ι', 'Κ', 'Λ', 'Μ', 'Ν', 'Ξ', 'Ο', + 'Π', 'Ρ', 'Σ', 'Σ', 'Τ', 'Υ', 'Φ', 'Χ', 'Ψ', 'Ω', 'Ϊ', 'Ϋ', 'Ό', 'Ύ', 'Ώ', 'Ϗ', + 'Β','Θ',0x3D2,0x3D3,0x3D4,'Φ','Π', 'Ϗ', 'Ϙ', 'Ϙ', 'Ϛ', 'Ϛ', 'Ϝ', 'Ϝ', 'Ϟ', 'Ϟ', + 'Ϡ', 'Ϡ', 'Ϣ', 'Ϣ', 'Ϥ', 'Ϥ', 'Ϧ', 'Ϧ', 'Ϩ', 'Ϩ', 'Ϫ', 'Ϫ', 'Ϭ', 'Ϭ', 'Ϯ', 'Ϯ', + 'Κ', 'Ρ', 'Ϲ', 'Ϳ', 'ϴ', 'Ε',0x3F6,'Ϸ', 'Ϸ', 'Ϲ', 'Ϻ', 'Ϻ',0x3FC,'Ͻ', 'Ͼ', 'Ͽ' +]; + +enum wchar[0x3FA - 0x3C2] case_fold_greek = [ + 'σ', 'σ', 'τ', 'υ', 'φ', 'χ', 'ψ', 'ω', 'ϊ', 'ϋ', 'ό', 'ύ', 'ώ', 'ϗ', + 'β', 'θ', 'υ', 'ύ', 'ϋ', 'φ', 'π', 'ϗ', 'ϙ', 'ϙ', 'ϛ', 'ϛ', 'ϝ', 'ϝ', 'ϟ', 'ϟ', + 'ϡ', 'ϡ', 'ϣ', 'ϣ', 'ϥ', 'ϥ', 'ϧ', 'ϧ', 'ϩ', 'ϩ', 'ϫ', 'ϫ', 'ϭ', 'ϭ', 'ϯ', 'ϯ', + 'κ', 'ρ', 'σ', 'ϳ', 'θ', 'ε',0x3F6,'ϸ', 'ϸ', 'σ' +]; + +enum wchar[0x1FFD - 0x1F70] to_lower_greek_extended = [ + 'ὰ', 'ά', 'ὲ', 'έ', 'ὴ', 'ή', 'ὶ', 'ί', 'ὸ', 'ό', 'ὺ', 'ύ', 'ὼ', 'ώ', 0x1F7E,0x1F7F, + 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', + 'ᾐ', 'ᾑ', 'ᾒ', 'ᾓ', 'ᾔ', 'ᾕ', 'ᾖ', 'ᾗ', 'ᾐ', 'ᾑ', 'ᾒ', 'ᾓ', 'ᾔ', 'ᾕ', 'ᾖ', 'ᾗ', + 'ᾠ', 'ᾡ', 'ᾢ', 'ᾣ', 'ᾤ', 'ᾥ', 'ᾦ', 'ᾧ', 'ᾠ', 'ᾡ', 'ᾢ', 'ᾣ', 'ᾤ', 'ᾥ', 'ᾦ', 'ᾧ', + 'ᾰ', 'ᾱ', 'ᾲ', 'ᾳ', 'ᾴ', 0x1FB5, 'ᾶ', 'ᾷ', 'ᾰ', 'ᾱ', 'ὰ', 'ά', 'ᾳ', 0x1FBD,0x1FBE,0x1FBF, +0x1FC0,0x1FC1,'ῂ', 'ῃ', 'ῄ', 0x1FC5, 'ῆ', 'ῇ', 'ὲ', 'έ', 'ὴ', 'ή', 'ῃ', 0x1FCD,0x1FCE,0x1FCF, + 'ῐ', 'ῑ', 'ῒ', 'ΐ', 0x1FD4,0x1FD5, 'ῖ', 'ῗ', 'ῐ', 'ῑ', 'ὶ', 'ί',0x1FDC,0x1FDD,0x1FDE,0x1FDF, + 'ῠ', 'ῡ', 'ῢ', 'ΰ', 'ῤ', 'ῥ', 'ῦ', 'ῧ', 'ῠ', 'ῡ', 'ὺ', 'ύ', 'ῥ', 0x1FED,0x1FEE,0x1FEF, +0x1FF0,0x1FF1,'ῲ', 'ῳ', 'ῴ', 0x1FF5, 'ῶ', 'ῷ', 'ὸ', 'ό', 'ὼ', 'ώ', 'ῳ' +]; + +enum wchar[0x1FFD - 0x1F70] to_upper_greek_extended = [ + 'Ὰ', 'Ά', 'Ὲ', 'Έ', 'Ὴ', 'Ή', 'Ὶ', 'Ί', 'Ὸ', 'Ό', 'Ὺ', 'Ύ', 'Ὼ', 'Ώ', 0x1F7E,0x1F7F, + 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', + 'ᾘ', 'ᾙ', 'ᾚ', 'ᾛ', 'ᾜ', 'ᾝ', 'ᾞ', 'ᾟ', 'ᾘ', 'ᾙ', 'ᾚ', 'ᾛ', 'ᾜ', 'ᾝ', 'ᾞ', 'ᾟ', + 'ᾨ', 'ᾩ', 'ᾪ', 'ᾫ', 'ᾬ', 'ᾭ', 'ᾮ', 'ᾯ', 'ᾨ', 'ᾩ', 'ᾪ', 'ᾫ', 'ᾬ', 'ᾭ', 'ᾮ', 'ᾯ', + 'Ᾰ', 'Ᾱ', 0x1FB2, 'ᾼ', 0x1FB4,0x1FB5,0x1FB6,0x1FB7, 'Ᾰ', 'Ᾱ', 'Ὰ', 'Ά', 'ᾼ', 0x1FBD,0x1FBE,0x1FBF, +0x1FC0,0x1FC1,0x1FC2, 'ῌ', 0x1FC4,0x1FC5,0x1FC6,0x1FC7, 'Ὲ', 'Έ', 'Ὴ', 'Ή', 'ῌ', 0x1FCD,0x1FCE,0x1FCF, + 'Ῐ', 'Ῑ', 0x1FD2,0x1FD3,0x1FD4,0x1FD5,0x1FD6,0x1FD7, 'Ῐ', 'Ῑ', 'Ὶ', 'Ί',0x1FDC,0x1FDD,0x1FDE,0x1FDF, + 'Ῠ', 'Ῡ', 0x1FE2,0x1FE3,0x1FE4, 'Ῥ', 0x1FE6,0x1FE7, 'Ῠ', 'Ῡ', 'Ὺ', 'Ύ', 'Ῥ', 0x1FED,0x1FEE,0x1FEF, +0x1FF0,0x1FF1,0x1FF2, 'ῼ', 0x1FF4,0x1FF5,0x1FF6,0x1FF7, 'Ὸ', 'Ό', 'Ὼ', 'Ώ', 'ῼ' +]; + +// NOTE: Cyrillic is runtime calculable, no tables required! + + unittest { - immutable dstring unicodeTest = + immutable ushort[5] surrogates = [ 0xD800, 0xD800, 0xDC00, 0xD800, 0x0020 ]; + + // test uni_seq_len functions + assert(uni_seq_len("Hello, World!") == 1); + assert(uni_seq_len("ñowai!") == 2); + assert(uni_seq_len("你好") == 3); + assert(uni_seq_len("😊wow!") == 4); + assert(uni_seq_len("\xFFHello") == 1); + assert(uni_seq_len("\xC2") == 1); + assert(uni_seq_len("\xC2Hello") == 1); + assert(uni_seq_len("\xE2") == 1); + assert(uni_seq_len("\xE2Hello") == 1); + assert(uni_seq_len("\xE2\x82") == 2); + assert(uni_seq_len("\xE2\x82Hello") == 2); + assert(uni_seq_len("\xF0") == 1); + assert(uni_seq_len("\xF0Hello") == 1); + assert(uni_seq_len("\xF0\x9F") == 2); + assert(uni_seq_len("\xF0\x9FHello") == 2); + assert(uni_seq_len("\xF0\x9F\x98") == 3); + assert(uni_seq_len("\xF0\x9F\x98Hello") == 3); + assert(uni_seq_len("Hello, World!"w) == 1); + assert(uni_seq_len("ñowai!"w) == 1); + assert(uni_seq_len("你好"w) == 1); + assert(uni_seq_len("😊wow!"w) == 2); + assert(uni_seq_len(cast(wchar[])surrogates[0..1]) == 1); + assert(uni_seq_len(cast(wchar[])surrogates[0..2]) == 1); + assert(uni_seq_len(cast(wchar[])surrogates[2..3]) == 1); + assert(uni_seq_len(cast(wchar[])surrogates[3..5]) == 1); + assert(uni_seq_len("😊wow!"d) == 1); + + // test uni_strlen + assert(uni_strlen("Hello, World!") == 13); + assert(uni_strlen("ñowai!") == 6); + assert(uni_strlen("你好") == 2); + assert(uni_strlen("😊wow!") == 5); + assert(uni_strlen("\xFFHello") == 6); + assert(uni_strlen("\xC2") == 1); + assert(uni_strlen("\xC2Hello") == 6); + assert(uni_strlen("\xE2") == 1); + assert(uni_strlen("\xE2Hello") == 6); + assert(uni_strlen("\xE2\x82") == 1); + assert(uni_strlen("\xE2\x82Hello") == 6); + assert(uni_strlen("\xF0") == 1); + assert(uni_strlen("\xF0Hello") == 6); + assert(uni_strlen("\xF0\x9F") == 1); + assert(uni_strlen("\xF0\x9FHello") == 6); + assert(uni_strlen("\xF0\x9F\x98") == 1); + assert(uni_strlen("\xF0\x9F\x98Hello") == 6); + assert(uni_strlen("Hello, World!"w) == 13); + assert(uni_strlen("ñowai!"w) == 6); + assert(uni_strlen("你好"w) == 2); + assert(uni_strlen("😊wow!"w) == 5); + assert(uni_strlen(cast(wchar[])surrogates[0..1]) == 1); + assert(uni_strlen(cast(wchar[])surrogates[0..2]) == 2); + assert(uni_strlen(cast(wchar[])surrogates[2..3]) == 1); + assert(uni_strlen(cast(wchar[])surrogates[3..5]) == 2); + assert(uni_strlen("😊wow!"d) == 5); + + // test next_dchar functions + size_t sl; + assert(next_dchar("Hello, World!", sl) == 'H' && sl == 1); + assert(next_dchar("ñowai!", sl) == 'ñ' && sl == 2); + assert(next_dchar("你好", sl) == '你' && sl == 3); + assert(next_dchar("😊wow!", sl) == '😊' && sl == 4); + assert(next_dchar("\xFFHello", sl) == '�' && sl == 1); + assert(next_dchar("\xC2", sl) == '�' && sl == 1); + assert(next_dchar("\xC2Hello", sl) == '�' && sl == 1); + assert(next_dchar("\xE2", sl) == '�' && sl == 1); + assert(next_dchar("\xE2Hello", sl) == '�' && sl == 1); + assert(next_dchar("\xE2\x82", sl) == '�' && sl == 2); + assert(next_dchar("\xE2\x82Hello", sl) == '�' && sl == 2); + assert(next_dchar("\xF0", sl) == '�' && sl == 1); + assert(next_dchar("\xF0Hello", sl) == '�' && sl == 1); + assert(next_dchar("\xF0\x9F", sl) == '�' && sl == 2); + assert(next_dchar("\xF0\x9FHello", sl) == '�' && sl == 2); + assert(next_dchar("\xF0\x9F\x98", sl) == '�' && sl == 3); + assert(next_dchar("\xF0\x9F\x98Hello", sl) == '�' && sl == 3); + assert(next_dchar("Hello, World!"w, sl) == 'H' && sl == 1); + assert(next_dchar("ñowai!"w, sl) == 'ñ' && sl == 1); + assert(next_dchar("你好"w, sl) == '你' && sl == 1); + assert(next_dchar("😊wow!"w, sl) == '😊' && sl == 2); + assert(next_dchar(cast(wchar[])surrogates[0..1], sl) == '�' && sl == 1); + assert(next_dchar(cast(wchar[])surrogates[0..2], sl) == '�' && sl == 1); + assert(next_dchar(cast(wchar[])surrogates[2..3], sl) == '�' && sl == 1); + assert(next_dchar(cast(wchar[])surrogates[3..5], sl) == '�' && sl == 1); + assert(next_dchar("😊wow!"d, sl) == '😊' && sl == 1); + + immutable dstring unicode_test = "Basic ASCII: Hello, World!\n" ~ + "Extended Latin: Café, résumé, naïve, jalapeño\n" ~ "BMP Examples: 你好, مرحبا, שלום, 😊, ☂️\n" ~ "Supplementary Planes: 𐍈, 𝒜, 🀄, 🚀\n" ~ "Surrogate Pair Test: 😀👨‍👩‍👧‍👦\n" ~ @@ -257,20 +891,142 @@ unittest "U+E000–U+FFFF Range:  (U+E000), 豈 (U+F900)" ~ "Control Characters: \u0008 \u001B \u0000\n"; - char[1024] utf8Buffer; - wchar[512] utf16Buffer; - dchar[512] utf32Buffer; + char[1024] utf8_buffer; + wchar[512] utf16_buffer; + dchar[512] utf32_buffer; // test all conversions with characters in every significant value range - size_t utf8Len = uniConvert(unicodeTest, utf8Buffer); // D-C - size_t utf16Len = uniConvert(utf8Buffer[0..utf8Len], utf16Buffer); // C-W - size_t utf32Len = uniConvert(utf16Buffer[0..utf16Len], utf32Buffer); // W-D - utf16Len = uniConvert(utf32Buffer[0..utf32Len], utf16Buffer); // D-W - utf8Len = uniConvert(utf16Buffer[0..utf16Len], utf8Buffer); // W-C - utf32Len = uniConvert(utf8Buffer[0..utf8Len], utf32Buffer); // C-D - assert(unicodeTest[] == utf32Buffer[0..utf32Len]); + size_t utf8_len = uni_convert(unicode_test, utf8_buffer); // D-C + size_t utf16_len = uni_convert(utf8_buffer[0..utf8_len], utf16_buffer); // C-W + size_t utf32_len = uni_convert(utf16_buffer[0..utf16_len], utf32_buffer); // W-D + utf16_len = uni_convert(utf32_buffer[0..utf32_len], utf16_buffer); // D-W + utf8_len = uni_convert(utf16_buffer[0..utf16_len], utf8_buffer); // W-C + utf32_len = uni_convert(utf8_buffer[0..utf8_len], utf32_buffer); // C-D + assert(unicode_test[] == utf32_buffer[0..utf32_len]); // TODO: test all the error cases; invalid characters, buffer overflows, truncated inputs, etc... //... -} + // test uni_to_lower and uni_to_upper + assert(uni_to_lower('A') == 'a'); + assert(uni_to_lower('Z') == 'z'); + assert(uni_to_lower('a') == 'a'); + assert(uni_to_lower('z') == 'z'); + assert(uni_to_lower('À') == 'à'); + assert(uni_to_lower('Ý') == 'ý'); + assert(uni_to_lower('Ÿ') == 'ÿ'); + assert(uni_to_lower('ÿ') == 'ÿ'); + assert(uni_to_lower('ß') == 'ß'); + assert(uni_to_lower('ẞ') == 'ß'); + assert(uni_to_lower('Α') == 'α'); + assert(uni_to_lower('Ω') == 'ω'); + assert(uni_to_lower('α') == 'α'); + assert(uni_to_lower('ω') == 'ω'); + assert(uni_to_lower('Ḁ') == 'ḁ'); + assert(uni_to_lower('ḁ') == 'ḁ'); + assert(uni_to_lower('ẝ') == 'ẝ'); + assert(uni_to_lower('😊') == '😊'); + assert(uni_to_upper('a') == 'A'); + assert(uni_to_upper('z') == 'Z'); + assert(uni_to_upper('A') == 'A'); + assert(uni_to_upper('Z') == 'Z'); + assert(uni_to_upper('à') == 'À'); + assert(uni_to_upper('ý') == 'Ý'); + assert(uni_to_upper('ÿ') == 'Ÿ'); + assert(uni_to_upper('Ÿ') == 'Ÿ'); + assert(uni_to_upper('ß') == 'ẞ'); + assert(uni_to_upper('ẞ') == 'ẞ'); + assert(uni_to_upper('α') == 'Α'); + assert(uni_to_upper('ω') == 'Ω'); + assert(uni_to_upper('Α') == 'Α'); + assert(uni_to_upper('Ω') == 'Ω'); + assert(uni_to_upper('ḁ') == 'Ḁ'); + assert(uni_to_upper('Ḁ') == 'Ḁ'); + assert(uni_to_upper('😊') == '😊'); + assert(uni_to_upper('џ') == 'Џ'); + assert(uni_to_upper('Џ') == 'Џ'); + assert(uni_to_upper('д') == 'Д'); + assert(uni_to_upper('Д') == 'Д'); + assert(uni_to_upper('ѻ') == 'Ѻ'); + assert(uni_to_upper('Ѻ') == 'Ѻ'); + assert(uni_to_upper('ԫ') == 'Ԫ'); + assert(uni_to_upper('Ԫ') == 'Ԫ'); + assert(uni_to_upper('ա') == 'Ա'); + assert(uni_to_upper('Ա') == 'Ա'); + + // test uni_compare + assert(uni_compare("Hello", "Hello") == 0); + assert(uni_compare("Hello", "hello") < 0); + assert(uni_compare("hello", "Hello") > 0); + assert(uni_compare("Hello", "Hello, World!") < 0); + assert(uni_compare("Café", "Café") == 0); + assert(uni_compare("Café", "CafÉ") > 0); + assert(uni_compare("CafÉ", "Café") < 0); + assert(uni_compare("Hello, 世界", "Hello, 世界") == 0); + assert(uni_compare("Hello, 世界", "Hello, 世") > 0); + assert(uni_compare("Hello, 世", "Hello, 世界") < 0); + assert(uni_compare("Hello, 😊", "Hello, 😊") == 0); + assert(uni_compare("Hello, 😊", "Hello, 😢") < 0); + assert(uni_compare("😊A", "😊a") < 0); + + // test uni_compare_i + assert(uni_compare_i("Hello", "Hello") == 0); + assert(uni_compare_i("Hello", "hello") == 0); + assert(uni_compare_i("hello", "Hello") == 0); + assert(uni_compare_i("Hello", "Hello, World!") < 0); + assert(uni_compare_i("hello", "HORLD") < 0); + assert(uni_compare_i("Hello, 世界", "hello, 世界") == 0); + assert(uni_compare_i("Hello, 世界", "hello, 世") > 0); + assert(uni_compare_i("Hello, 世", "hello, 世界") < 0); + assert(uni_compare_i("Hello, 😊", "hello, 😊") == 0); + assert(uni_compare_i("Hello, 😊", "hello, 😢") < 0); + assert(uni_compare_i("😊a", "😊B") < 0); + assert(uni_compare_i("AZÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ", "azàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþ") == 0); // basic latin + latin-1 supplement + assert(uni_compare_i("ŸĀĦĬIJĹŁŇŊŒŠŦŶŹŽ", "ÿāħĭijĺłňŋœšŧŷźž") == 0); // more latin extended-a characters + assert(uni_compare_i("ḀṤẔẠỸỺỼỾ", "ḁṥẕạỹỻỽỿ") == 0); // just the extended latin + + // test various language pangrams! + assert(uni_compare_i("The quick brown fox jumps over the lazy dog", "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG") == 0); // english + assert(uni_compare_i("Sævör grét áðan því úlpan var ónýt", "SÆVÖR GRÉT ÁÐAN ÞVÍ ÚLPAN VAR ÓNÝT") == 0); // icelandic + assert(uni_compare_i("Příliš žluťoučký kůň úpěl ďábelské ódy.", "PŘÍLIŠ ŽLUŤOUČKÝ KŮŇ ÚPĚL ĎÁBELSKÉ ÓDY.") == 0); // czech + assert(uni_compare_i("Zażółć gęślą jaźń.", "ZAŻÓŁĆ GĘŚLĄ JAŹŃ.") == 0); // polish + assert(uni_compare_i("Φλεγματικά χρώματα που εξοβελίζουν ψευδαισθήσεις.", "ΦΛΕΓΜΑΤΙΚΆ ΧΡΏΜΑΤΑ ΠΟΥ ΕΞΟΒΕΛΊΖΟΥΝ ΨΕΥΔΑΙΣΘΉΣΕΙΣ.") == 0); // greek + assert(uni_compare_i("Любя, съешь щипцы, — вздохнёт мэр, — Кайф жгуч!", "ЛЮБЯ, СЪЕШЬ ЩИПЦЫ, — ВЗДОХНЁТ МЭР, — КАЙФ ЖГУЧ!") == 0); // russian + assert(uni_compare_i("Բել դղյակի ձախ ժամն օֆ ազգությանը ցպահանջ չճշտած վնաս էր եւ փառք։", "ԲԵԼ ԴՂՅԱԿԻ ՁԱԽ ԺԱՄՆ ՕՖ ԱԶԳՈՒԹՅԱՆԸ ՑՊԱՀԱՆՋ ՉՃՇՏԱԾ ՎՆԱՍ ԷՐ ԵՒ ՓԱՌՔ։") == 0); // armenian + assert(uni_compare_i("აბგად ევზეთ იკალ მანო, პაჟა რასტა უფქა ღაყაშ, ჩაცა ძაწა ჭახა ჯაჰო", "ᲐᲑᲒᲐᲓ ᲔᲕᲖᲔᲗ ᲘᲙᲐᲚ ᲛᲐᲜᲝ, ᲞᲐᲟᲐ ᲠᲐᲡᲢᲐ ᲣᲤᲥᲐ ᲦᲐᲧᲐᲨ, ᲩᲐᲪᲐ ᲫᲐᲬᲐ ᲭᲐᲮᲐ ᲯᲐᲰᲝ") == 0); // georgian modern + assert(uni_compare_i("ⴘⴄⴅ ⴟⴓ ⴠⴡⴢ ⴣⴤⴥ ⴇⴍⴚ ⴞⴐⴈ ⴝⴋⴊⴈ.", "ႸႤႥ ႿႳ ჀჁჂ ჃჄჅ ႧႭႺ ႾႰႨ ႽႫႪႨ.") == 0); // georgian ecclesiastical + assert(uni_compare_i("Ⲁⲛⲟⲕ ⲡⲉ ϣⲏⲙ ⲛ̄ⲕⲏⲙⲉ.", "ⲀⲚⲞⲔ ⲠⲈ ϢⲎⲘ Ⲛ̄ⲔⲎⲘⲈ.") == 0); // coptic + + // test the special-cases around german 'ß' (0xDF) and 'ẞ' (0x1E9E) + // check sort order + assert(uni_compare_i("ß", "sr") > 0); + assert(uni_compare_i("ß", "ss") == 0); + assert(uni_compare_i("ß", "st") < 0); + assert(uni_compare_i("sr", "ß") < 0); + assert(uni_compare_i("ss", "ß") == 0); + assert(uni_compare_i("st", "ß") > 0); + // check truncated comparisons + assert(uni_compare_i("ß", "s") > 0); + assert(uni_compare_i("ß", "r") > 0); + assert(uni_compare_i("ß", "t") < 0); + assert(uni_compare_i("s", "ß") < 0); + assert(uni_compare_i("r", "ß") < 0); + assert(uni_compare_i("t", "ß") > 0); + assert(uni_compare_i("ä", "ß") > 0); + assert(uni_compare_i("sß", "ss") > 0); + assert(uni_compare_i("sß", "ß") > 0); + assert(uni_compare_i("sß", "ßß") < 0); + assert(uni_compare_i("ss", "sß") < 0); + assert(uni_compare_i("ß", "sß") < 0); + assert(uni_compare_i("ßß", "sß") > 0); + // check uneven/recursive comparisons + assert(uni_compare_i("ßẞ", "ẞß") == 0); + assert(uni_compare_i("sß", "ẞs") == 0); + assert(uni_compare_i("ẞs", "sß") == 0); + assert(uni_compare_i("ẞß", "sßs") == 0); + assert(uni_compare_i("sẞs", "ẞß") == 0); + assert(uni_compare_i("sẞsß", "ßsßs") == 0); + assert(uni_compare_i("ẞsßs", "sßsß") == 0); + assert(uni_compare_i("ßßßs", "sßßß") == 0); + assert(uni_compare_i("sßßß", "ßßßs") == 0); +} diff --git a/src/urt/system.d b/src/urt/system.d index 84f97a1..3619523 100644 --- a/src/urt/system.d +++ b/src/urt/system.d @@ -4,13 +4,24 @@ import urt.platform; import urt.processor; import urt.time; +version (Espressif) +{ + enum uint MALLOC_CAP_DEFAULT = 1 << 12; + extern(C) nothrow @nogc + { + size_t heap_caps_get_total_size(uint caps); + size_t heap_caps_get_free_size(uint caps); + size_t heap_caps_get_minimum_free_size(uint caps); + } +} + nothrow @nogc: enum IdleParams : ubyte { - SystemRequired = 1, // stop the system from going to sleep - DisplayRequired = 2, // keep the display turned on + system_required = 1, // stop the system from going to sleep + display_required = 2, // keep the display turned on } extern(C) noreturn abort(); @@ -26,39 +37,71 @@ void sleep(Duration duration) version (Windows) { - import core.sys.windows.winbase : Sleep; + import urt.internal.sys.windows.winbase : Sleep; Sleep(cast(uint)duration.as!"msecs"); } - else + else version (Embedded) { - // TODO: use nanosleep; usleep is deprecated! + import urt.driver.timer; + import urt.driver.irq; + static if (has_mtime) + { + ulong deadline = mtime_read() + duration.as!"usecs"; + static if (has_wfi_sleep) + { + mtimecmp_write_oneshot(deadline); + auto was_enabled = enable_irq(IrqClass.timer); + while (mtime_read() < deadline) + wait_for_interrupt(); + if (!was_enabled) + disable_irq(IrqClass.timer); + } + else + { + while (mtime_read() < deadline) {} + } + } + } + else + { usleep(cast(uint)duration.as!"usecs"); } } struct SystemInfo { - string osName; + string os_name; string processor; - ulong totalMemory; - ulong availableMemory; + ulong total_memory; // total physical RAM or heap region + ulong used_memory; // actively allocated + ulong reserved_memory; // claimed from OS/sbrk (>= used, includes freed blocks) + ulong avail_memory; // system-wide available for new allocations + ulong peak_memory; // high-water mark (0 if unavailable) Duration uptime; } -SystemInfo getSysInfo() +SystemInfo get_sysinfo() { SystemInfo r; - r.osName = Platform; - r.processor = ProcessorFamily; + r.os_name = Platform; + r.processor = ProcessorName; version (Windows) { MEMORYSTATUSEX mem; mem.dwLength = MEMORYSTATUSEX.sizeof; if (GlobalMemoryStatusEx(&mem)) { - r.totalMemory = mem.ullTotalPhys; - r.availableMemory = mem.ullAvailPhys; + r.total_memory = mem.ullTotalPhys; + r.avail_memory = mem.ullAvailPhys; + } + PROCESS_MEMORY_COUNTERS pmc; + pmc.cb = PROCESS_MEMORY_COUNTERS.sizeof; + if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, pmc.sizeof)) + { + r.reserved_memory = pmc.WorkingSetSize; // resident pages + r.used_memory = pmc.PagefileUsage; // committed private bytes + r.peak_memory = pmc.PeakWorkingSetSize; } r.uptime = msecs(GetTickCount64()); } @@ -70,41 +113,72 @@ SystemInfo getSysInfo() if (sysinfo(&info) < 0) assert(false, "sysinfo() failed!"); - r.totalMemory = cast(ulong)info.totalram * info.mem_unit; - r.availableMemory = cast(ulong)info.freeram * info.mem_unit; + auto unit = cast(ulong)info.mem_unit; + r.total_memory = info.totalram * unit; + r.avail_memory = info.freeram * unit; + + // Process-level stats from /proc/self/status + r.reserved_memory = read_proc_self_field("VmRSS:"); // resident set + r.used_memory = read_proc_self_field("VmSize:"); // virtual size (committed) + r.peak_memory = read_proc_self_field("VmPeak:"); r.uptime = seconds(info.uptime); } else version (Posix) { - import core.sys.posix.unistd; + import urt.internal.sys.posix; int pages = sysconf(_SC_PHYS_PAGES); + int avail = sysconf(_SC_AVPHYS_PAGES); int page_size = sysconf(_SC_PAGE_SIZE); assert(pages >= 0 && page_size >= 0, "sysconf() failed!"); - r.totalMemory = cast(ulong)pages * page_size; - static assert(false, "TODO: need `availableMemory`"); + r.total_memory = cast(ulong)pages * page_size; + r.avail_memory = (avail >= 0) ? cast(ulong)avail * page_size : 0; + r.used_memory = r.total_memory - r.avail_memory; + r.reserved_memory = r.used_memory; + } + else version (Espressif) + { + r.total_memory = heap_caps_get_total_size(MALLOC_CAP_DEFAULT); + r.avail_memory = heap_caps_get_free_size(MALLOC_CAP_DEFAULT); + r.used_memory = r.total_memory - r.avail_memory; + r.peak_memory = r.total_memory - heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT); + r.uptime = getAppTime(); + } + else version (Bouffalo) + { + auto mi = mallinfo(); + r.total_memory = heap_len(); + r.used_memory = mi.uordblks; + r.reserved_memory = mi.arena; + r.avail_memory = r.total_memory - mi.arena + mi.fordblks; + r.peak_memory = mi.max_total_mem; + r.uptime = getAppTime(); } return r; } -void setSystemIdleParams(IdleParams params) +void set_system_idle_params(IdleParams params) { version (Windows) { - import core.sys.windows.winbase; + import urt.internal.sys.windows.winbase; enum EXECUTION_STATE ES_SYSTEM_REQUIRED = 0x00000001; enum EXECUTION_STATE ES_DISPLAY_REQUIRED = 0x00000002; enum EXECUTION_STATE ES_CONTINUOUS = 0x80000000; - SetThreadExecutionState(ES_CONTINUOUS | (params.SystemRequired ? ES_SYSTEM_REQUIRED : 0) | (params.DisplayRequired ? ES_DISPLAY_REQUIRED : 0)); + SetThreadExecutionState(ES_CONTINUOUS | ((params & IdleParams.system_required) ? ES_SYSTEM_REQUIRED : 0) | ((params & IdleParams.display_required) ? ES_DISPLAY_REQUIRED : 0)); } else version (Posix) { // TODO: ...we're not likely to run on a POSIX desktop system any time soon... } + else version (FreeStanding) + { + // Bare-metal: no idle state management needed + } else static assert(0, "Not implemented"); } @@ -112,19 +186,126 @@ void setSystemIdleParams(IdleParams params) unittest { - SystemInfo info = getSysInfo(); + SystemInfo info = get_sysinfo(); assert(info.uptime > Duration.zero); import urt.io; - writelnf("System info: {0} - {1}, mem: {2}kb ({3}kb)", info.osName, info.processor, info.totalMemory / (1024), info.availableMemory / (1024)); + writelnf("System: {0} - {1}", info.os_name, info.processor); + writelnf(" total: {0}kb used: {1}kb reserved: {2}kb free: {3}kb peak: {4}kb", + info.total_memory / 1024, info.used_memory / 1024, + info.reserved_memory / 1024, info.avail_memory / 1024, + info.peak_memory / 1024); + + version (Embedded) + { + import urt.driver.irq; + static if (has_irq_diagnostics) + { + writelnf(" IRQ total: {0}", irq_count); + foreach (i; 0 .. irq_histogram.length) + { + if (irq_histogram[i] > 0) + writelnf(" IRQ {0}: {1}", i, irq_histogram[i]); + } + } + } } package: +version (Bouffalo) +{ + extern(C) extern __gshared { + void* __heap_start; + void* __heap_end; + } + + struct Mallinfo + { + size_t arena; // total space from sbrk + size_t ordblks; // number of free chunks + size_t smblks; // unused + size_t hblks; // unused + size_t hblkhd; // unused + size_t uordblks; // total allocated space + size_t fordblks; // total free space + size_t keepcost; // releasable space + size_t aordblks; // number of allocated chunks + size_t max_total_mem; // max total allocated space + } + extern(C) Mallinfo mallinfo() @nogc nothrow; + + extern(C) void* _sbrk(int incr) @nogc nothrow; + + size_t heap_len() + => cast(size_t)&__heap_end - cast(size_t)&__heap_start; +} + +version (linux) +{ + // Read a field from /proc/self/status, returns value in bytes (field is in kB) + ulong read_proc_self_field(string field) nothrow @nogc + { + import urt.file : File, open, read, close, FileOpenMode; + + File f; + if (!f.open("/proc/self/status", FileOpenMode.ReadExisting)) + return 0; + + char[4096] buf = void; + size_t n; + auto r = f.read(buf, n); + f.close(); + if (!r || n == 0) + return 0; + + auto content = buf[0 .. n]; + // Find field name in content + for (size_t i = 0; i + field.length < content.length; ++i) + { + if (content[i .. i + field.length] == field) + { + // Skip whitespace after field name + size_t j = i + field.length; + while (j < content.length && (content[j] == ' ' || content[j] == '\t')) + ++j; + // Parse number + ulong val = 0; + while (j < content.length && content[j] >= '0' && content[j] <= '9') + { + val = val * 10 + (content[j] - '0'); + ++j; + } + // /proc/self/status reports in kB + return val * 1024; + } + } + return 0; + } +} + version (Windows) { - import core.sys.windows.winbase : GlobalMemoryStatusEx, MEMORYSTATUSEX; + import urt.internal.sys.windows.winbase : GlobalMemoryStatusEx, GetCurrentProcess, MEMORYSTATUSEX; + + struct PROCESS_MEMORY_COUNTERS + { + uint cb; + uint PageFaultCount; + size_t PeakWorkingSetSize; + size_t WorkingSetSize; + size_t QuotaPeakPagedPoolUsage; + size_t QuotaPagedPoolUsage; + size_t QuotaPeakNonPagedPoolUsage; + size_t QuotaNonPagedPoolUsage; + size_t PagefileUsage; + size_t PeakPagefileUsage; + } + + extern(Windows) int GetProcessMemoryInfo(void* Process, PROCESS_MEMORY_COUNTERS* ppsmemCounters, uint cb) nothrow @nogc; + + pragma(lib, "psapi"); extern(Windows) ulong GetTickCount64(); diff --git a/src/urt/thread.d b/src/urt/thread.d new file mode 100644 index 0000000..2be17bc --- /dev/null +++ b/src/urt/thread.d @@ -0,0 +1,71 @@ +module urt.thread; + +import urt.atomic; + +nothrow @nogc: + + +// thread-safe FIFO for passing data between threads. +// uses a spinlock to protect enqueue/dequeue. +struct ThreadSafeQueue(uint capacity = 64, T = void*) +{ +nothrow @nogc: + + // returns false if queue is full (item not enqueued). + bool enqueue(T item) + { + while (!cas(&_lock, false, true)) {} + uint count = _tail >= _head ? _tail - _head : capacity - _head + _tail; + if (count >= capacity) + { + _lock = false; + return false; + } + _queue[_tail] = item; + _tail = (_tail + 1) % capacity; + _lock = false; + return true; + } + + static if (is(T == U*, U) || is(T == void*)) + { + // dequeue a pointer, or null if empty. + T dequeue() + { + while (!cas(&_lock, false, true)) {} + if (_head == _tail) + { + _lock = false; + return null; + } + auto result = _queue[_head]; + _head = (_head + 1) % capacity; + _lock = false; + return result; + } + } + else + { + // dequeue a value type via output parameter. + // returns false if empty. + bool dequeue(T* out_) + { + while (!cas(&_lock, false, true)) {} + if (_head == _tail) + { + _lock = false; + return false; + } + *out_ = _queue[_head]; + _head = (_head + 1) % capacity; + _lock = false; + return true; + } + } + +private: + T[capacity] _queue; + shared uint _head; + shared uint _tail; + shared bool _lock; +} diff --git a/src/urt/time.d b/src/urt/time.d index 646af39..e68322c 100644 --- a/src/urt/time.d +++ b/src/urt/time.d @@ -1,16 +1,20 @@ module urt.time; -import urt.traits : isSomeFloat; +import urt.traits : is_some_float; version (Windows) { - import core.sys.windows.windows; + import urt.internal.sys.windows; - extern (Windows) void GetSystemTimePreciseAsFileTime(FILETIME* lpSystemTimeAsFileTime) nothrow @nogc; + extern(Windows) void GetSystemTimePreciseAsFileTime(FILETIME* lpSystemTimeAsFileTime) nothrow @nogc; } else version (Posix) { - import core.sys.posix.time; + import urt.internal.sys.posix; +} +else version (Embedded) +{ + import urt.driver.timer; } nothrow @nogc: @@ -28,7 +32,8 @@ enum Day : ubyte enum Month : ubyte { - January = 1, + Unspecified = 0, + January, February, March, April, @@ -63,10 +68,15 @@ pure nothrow @nogc: T opCast(T)() const if (is(T == Time!c, Clock c) && c != clock) { - static if (clock == Clock.Monotonic && c == Clock.SystemTime) - return SysTime(ticks + ticksSinceBoot); + static if (is(T == Time!c, Clock c) && c != clock) + { + static if (clock == Clock.Monotonic && c == Clock.SystemTime) + return SysTime(ticks + sys_time_offset); + else + return MonoTime(ticks - sys_time_offset); + } else - return MonoTime(ticks - ticksSinceBoot); + static assert(false, "constraint out of sync"); } bool opEquals(Time!clock b) const @@ -82,9 +92,9 @@ pure nothrow @nogc: static if (clock != c) { static if (clock == Clock.Monotonic) - t1 += ticksSinceBoot; + t1 += sys_time_offset; else - t2 += ticksSinceBoot; + t2 += sys_time_offset; } return Duration(t1 - t2); } @@ -100,27 +110,47 @@ pure nothrow @nogc: import urt.string.format : FormatArg; ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const { - long ms = (ticks != 0 ? appTime(this) : Duration()).as!"msecs"; - if (!buffer.ptr) - return 2 + timeToString(ms, null); - if (buffer.length < 2) - return -1; - buffer[0..2] = "T+"; - ptrdiff_t len = timeToString(ms, buffer[2..$]); - return len < 0 ? len : 2 + len; + static if (clock == Clock.SystemTime) + { + DateTime dt = getDateTime(this); + return dt.toString(buffer, format, formatArgs); + } + else + { + long ns = (ticks != 0 ? appTime(this) : Duration()).as!"nsecs"; + if (!buffer.ptr) + return 2 + timeToString(ns, null); + if (buffer.length < 2) + return -1; + buffer[0..2] = "T+"; + ptrdiff_t len = timeToString(ns, buffer[2..$]); + return len < 0 ? len : 2 + len; + } } - auto __debugOverview() const + ptrdiff_t fromString(const(char)[] s) { - debug + static if (clock == Clock.SystemTime) { - import urt.mem.temp; - char[] b = cast(char[])talloc(64); - ptrdiff_t len = toString(b, null, null); - return b[0..len]; + DateTime dt; + ptrdiff_t len = dt.fromString(s); + if (len >= 0) + this = getSysTime(dt); + return len; } else - return appTime(this).as!"msecs"; + { + assert(false, "TODO: ???"); // what is the format we parse? + } + } + + version (Windows) + auto __debugOverview() const + { + import urt.mem; + char[] b = debug_alloc!char(64); + ptrdiff_t len = toString(b, null, null); + return b[0..len]; } } @@ -137,8 +167,8 @@ pure nothrow @nogc: bool opCast(T : bool)() const => ticks != 0; - T opCast(T)() const if (isSomeFloat!T) - => cast(T)ticks / cast(T)ticksPerSecond; + T opCast(T)() const if (is_some_float!T) + => cast(T)ticks / cast(T)ticks_per_second; bool opEquals(Duration b) const => ticks == b.ticks; @@ -161,21 +191,21 @@ pure nothrow @nogc: long as(string base)() const { static if (base == "nsecs") - return ticks*nsecMultiplier; + return ticks*nsec_multiplier; else static if (base == "usecs") - return ticks*nsecMultiplier / 1_000; + return ticks*nsec_multiplier / 1_000; else static if (base == "msecs") - return ticks*nsecMultiplier / 1_000_000; + return ticks*nsec_multiplier / 1_000_000; else static if (base == "seconds") - return ticks*nsecMultiplier / 1_000_000_000; + return ticks*nsec_multiplier / 1_000_000_000; else static if (base == "minutes") - return ticks*nsecMultiplier / 60_000_000_000; + return ticks*nsec_multiplier / 60_000_000_000; else static if (base == "hours") - return ticks*nsecMultiplier / 3_600_000_000_000; + return ticks*nsec_multiplier / 3_600_000_000_000; else static if (base == "days") - return ticks*nsecMultiplier / 86_400_000_000_000; + return ticks*nsec_multiplier / 86_400_000_000_000; else static if (base == "weeks") - return ticks*nsecMultiplier / 604_800_000_000_000; + return ticks*nsec_multiplier / 604_800_000_000_000; else static assert(false, "Invalid base"); } @@ -183,11 +213,105 @@ pure nothrow @nogc: import urt.string.format : FormatArg; ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const { - return timeToString(as!"msecs", buffer); + return timeToString(as!"nsecs", buffer); } - auto __debugOverview() const - => cast(double)this; + ptrdiff_t fromString(const(char)[] s) + { + import urt.conv : parse_int; + import urt.string.ascii : is_alpha, ieq; + + if (s.length == 0) + return -1; + + size_t offset = 0; + long total_nsecs = 0; + ubyte last_unit = 8; + + while (offset < s.length) + { + // Parse number + size_t len; + long value = s[offset..$].parse_int(&len); + if (len == 0) + return last_unit != 8 ? offset : -1; + offset += len; + + if (offset >= s.length) + return -1; + + // Parse unit + size_t unit_start = offset; + while (offset < s.length && s[offset].is_alpha) + offset++; + + if (offset == unit_start) + return -1; + + const(char)[] unit = s[unit_start..offset]; + + // Convert unit to nanoseconds and check for duplicates + ubyte this_unit; + if (unit.ieq("w")) + { + value *= 604_800_000_000_000; + this_unit = 7; + } + else if (unit.ieq("d")) + { + value *= 86_400_000_000_000; + this_unit = 6; + } + else if (unit.ieq("h")) + { + value *= 3_600_000_000_000; + this_unit = 5; + } + else if (unit.ieq("m")) + { + value *= 60_000_000_000; + this_unit = 4; + } + else if (unit.ieq("s")) + { + value *= 1_000_000_000; + this_unit = 3; + } + else if (unit.ieq("ms")) + { + value *= 1_000_000; + this_unit = 2; + } + else if (unit.ieq("us")) + { + value *= 1_000; + this_unit = 1; + } + else if (unit.ieq("ns")) + this_unit = 0; + else + return -1; + + // Check for ordering, duplicates, and only one of ms/us/ns may be present + if (this_unit >= last_unit || (this_unit < 2 && last_unit < 3)) + return -1; + last_unit = this_unit; + + total_nsecs += value; + } + + if (last_unit == 8) + return -1; + + ticks = total_nsecs / nsec_multiplier; + return offset; + } + + version (Windows) + { + auto __debugOverview() const + => cast(double)this; + } } alias Timer = FixedTimer!(); @@ -276,76 +400,303 @@ pure nothrow @nogc: this = mixin("this " ~ op ~ " rhs;"); } + int opCmp(DateTime dt) const + { + int r = year - dt.year; + if (r != 0) return r; + r = month - dt.month; + if (r != 0) return r; + r = day - dt.day; + if (r != 0) return r; + r = hour - dt.hour; + if (r != 0) return r; + r = minute - dt.minute; + if (r != 0) return r; + r = second - dt.second; + if (r != 0) return r; + return ns - dt.ns; + } + import urt.string.format : FormatArg; ptrdiff_t toString(char[] buffer, const(char)[] format, const(FormatArg)[] formatArgs) const { - import urt.conv : formatInt; + import urt.conv : format_int, format_uint; - size_t offset = 0; - uint y = year; - if (year <= 0) + ptrdiff_t len; + if (!buffer.ptr) { - if (buffer.length < 3) - return -1; - y = -year + 1; - buffer[0 .. 3] = "BC "; - offset += 3; + len = 15; // all the fixed chars + len += year.format_int(null); + if (ns) + { + ++len; // the dot + uint nsecs = ns; + uint m = 0; + while (nsecs) + { + ++len; + uint digit = nsecs / digit_multipliers[m]; + nsecs -= digit * digit_multipliers[m++]; + } + } + return len; } - ptrdiff_t len = year.formatInt(buffer[offset..$]); - if (len < 0 || len == buffer.length) + + len = year.format_int(buffer[]); + if (len < 0 || len + 15 > buffer.length) return -1; - offset += len; + size_t offset = len; buffer[offset++] = '-'; - len = month.formatInt(buffer[offset..$]); - if (len < 0 || len == buffer.length) - return -1; - offset += len; + buffer[offset++] = '0' + (month / 10); + buffer[offset++] = '0' + (month % 10); buffer[offset++] = '-'; - len = day.formatInt(buffer[offset..$]); - if (len < 0 || len == buffer.length) + buffer[offset++] = '0' + (day / 10); + buffer[offset++] = '0' + (day % 10); + buffer[offset++] = 'T'; + buffer[offset++] = '0' + (hour / 10); + buffer[offset++] = '0' + (hour % 10); + buffer[offset++] = ':'; + buffer[offset++] = '0' + (minute / 10); + buffer[offset++] = '0' + (minute % 10); + buffer[offset++] = ':'; + buffer[offset++] = '0' + (second / 10); + buffer[offset++] = '0' + (second % 10); + if (ns) + { + if (offset == buffer.length) + return -1; + buffer[offset++] = '.'; + uint nsecs = ns; + uint m = 0; + while (nsecs) + { + if (offset == buffer.length) + return -1; + int digit = nsecs / digit_multipliers[m]; + buffer[offset++] = cast(char)('0' + digit); + nsecs -= digit * digit_multipliers[m++]; + } + } + // TODO: timezone suffix? + return offset; + } + + ptrdiff_t fromString(const(char)[] s) + { + import urt.conv : parse_int, parse_uint; + import urt.string.ascii : ieq, is_numeric, is_whitespace, to_lower; + + month = Month.Unspecified; + day = 0; + hour = 0; + minute = 0; + second = 0; + ns = 0; + + size_t offset = 0; + + // parse year + if (s.length >= 2 && s[0..2].ieq("bc")) + { + offset = 2; + if (s.length >= 3 && s[2] == ' ') + ++offset; + } + size_t len; + long value = s[offset..$].parse_int(&len); + if (len == 0) return -1; + if (offset > 0) + { + if (value <= 0) + return -1; // no year 0, or negative years BC! + value = -value + 1; + } + if (value < short.min || value > short.max) + return -1; + year = cast(short)value; offset += len; - buffer[offset++] = ' '; - len = hour.formatInt(buffer[offset..$], 10, 2, '0'); - if (len < 0 || len == buffer.length) + + if (offset == s.length || (s[offset] != '-' && s[offset] != '/')) + return offset; + + // parse month + value = s[++offset..$].parse_int(&len); + if (len == 0) + { + if (s.length < 3) + return -1; + foreach (i; 0..12) + { + if (s[offset..offset+3].ieq(g_month_names[i])) + { + value = i + 1; + len = 3; + goto got_month; + } + } + return -1; + got_month: + } + else if (value < 1 || value > 12) return -1; + month = cast(Month)value; offset += len; - buffer[offset++] = ':'; - len = minute.formatInt(buffer[offset..$], 10, 2, '0'); - if (len < 0 || len == buffer.length) + + if (offset == s.length || (s[offset] != '-' && s[offset] != '/')) + return offset; + + // parse day + value = s[++offset..$].parse_int(&len); + if (len == 0 || value < 1 || value > 31) return -1; + day = cast(ubyte)value; offset += len; - buffer[offset++] = ':'; - len = second.formatInt(buffer[offset..$], 10, 2, '0'); - if (len < 0 || len == buffer.length) + + if (offset == s.length || (s[offset] != 'T' && s[offset] != ' ')) + return offset; + + // parse hour + value = s[++offset..$].parse_int(&len); + if (len != 2 || value < 0 || value > 23) return -1; + hour = cast(ubyte)value; offset += len; - buffer[offset++] = '.'; - len = (ns / 1_000_000).formatInt(buffer[offset..$], 10, 3, '0'); - if (len < 0) - return len; - return offset + len; + + if (offset == s.length) + return offset; + + if (s[offset] == ':') + { + // parse minute + value = s[++offset..$].parse_int(&len); + if (len != 2 || value < 0 || value > 59) + return -1; + minute = cast(ubyte)value; + offset += len; + + if (offset == s.length) + return offset; + + if (s[offset] == ':') + { + // parse second + value = s[++offset..$].parse_int(&len); + if (len != 2 || value < 0 || value > 59) + return -1; + second = cast(ubyte)value; + offset += len; + + if (offset < s.length && s[offset] == '.') + { + // parse fractions + ++offset; + uint multiplier = 100_000_000; + while (offset < s.length && multiplier > 0 && s[offset].is_numeric) + { + ns += (s[offset++] - '0') * multiplier; + multiplier /= 10; + } + if (multiplier == 100_000_000) + return offset-1; // no number after the dot + } + } + else if (s[offset] == '.') + { + // fraction of minute + assert(false, "TODO"); + } + } + else if (s[offset] == '.') + { + // fraction of hour + assert(false, "TODO"); + } + + if (offset == s.length) + return offset; + + if (s[offset].to_lower == 'z') + { + // TODO: UTC timezone designator... +// assert(false, "TODO: we need to know our local timezone..."); + + return offset + 1; + } + + size_t tz_offset = offset; + if (s[offset] == ' ') + ++tz_offset; + if (s[tz_offset] != '-' && s[tz_offset] != '+') + return offset; + bool tz_neg = s[tz_offset] == '-'; + tz_offset += 1; + + // parse timezone (00:00) + int tz_hr, tz_min; + + value = s[tz_offset..$].parse_uint(&len); + if (len == 0) + return offset; + + if (len == 4) + { + if (value > 2359) + return -1; + tz_min = cast(int)(value % 100); + if (tz_min > 59) + return -1; + tz_hr = cast(int)(value / 100); + tz_offset += 4; + } + else + { + if (len != 2 || value > 59) + return -1; + + tz_hr = cast(int)value; + tz_offset += 2; + + if (tz_offset < s.length && s[tz_offset] == ':') + { + value = s[tz_offset+1..$].parse_uint(&len); + if (len != 0) + { + if (len != 2 || value > 59) + return -1; + tz_min = cast(int)value; + tz_offset += 3; + } + } + } + + if (tz_neg) + tz_hr = -tz_hr; + +// assert(false, "TODO: we need to know our local timezone..."); + + return tz_offset; } } Duration dur(string base)(long value) pure { static if (base == "nsecs") - return Duration(value / nsecMultiplier); + return Duration(value / nsec_multiplier); else static if (base == "usecs") - return Duration(value*1_000 / nsecMultiplier); + return Duration(value*1_000 / nsec_multiplier); else static if (base == "msecs") - return Duration(value*1_000_000 / nsecMultiplier); + return Duration(value*1_000_000 / nsec_multiplier); else static if (base == "seconds") - return Duration(value*1_000_000_000 / nsecMultiplier); + return Duration(value*1_000_000_000 / nsec_multiplier); else static if (base == "minutes") - return Duration(value*60_000_000_000 / nsecMultiplier); + return Duration(value*60_000_000_000 / nsec_multiplier); else static if (base == "hours") - return Duration(value*3_600_000_000_000 / nsecMultiplier); + return Duration(value*3_600_000_000_000 / nsec_multiplier); else static if (base == "days") - return Duration(value*86_400_000_000_000 / nsecMultiplier); + return Duration(value*86_400_000_000_000 / nsec_multiplier); else static if (base == "weeks") - return Duration(value*604_800_000_000_000 / nsecMultiplier); + return Duration(value*604_800_000_000_000 / nsec_multiplier); else static assert(false, "Invalid base"); } @@ -369,6 +720,13 @@ MonoTime getTime() clock_gettime(CLOCK_MONOTONIC, &ts); return MonoTime(ts.tv_sec * 1_000_000_000 + ts.tv_nsec); } + else version (Embedded) + { + static if (has_mtime) + return MonoTime(mtime_read()); + else + static assert(false, "getTime: no monotonic timer for this platform"); + } else { static assert(false, "TODO"); @@ -389,40 +747,32 @@ SysTime getSysTime() clock_gettime(CLOCK_REALTIME, &ts); return SysTime(ts.tv_sec * 1_000_000_000 + ts.tv_nsec); } + else version (Embedded) + { + static if (has_mtime) + return SysTime(mtime_read() + sys_time_offset); + else + static assert(false, "getSysTime: no monotonic timer for this platform"); + } else { static assert(false, "TODO"); } } +SysTime getSysTime(DateTime time) pure +{ + return from_unix_time_ns(datetime_to_unix_ns(time)); +} DateTime getDateTime() { - version (Windows) - return fileTimeToDateTime(getSysTime()); - else version (Posix) - { - timespec ts; - clock_gettime(CLOCK_REALTIME, &ts); - return realtimeToDateTime(ts); - } - else - static assert(false, "TODO"); + return getDateTime(getSysTime()); } -DateTime getDateTime(SysTime time) +DateTime getDateTime(SysTime time) pure { - version (Windows) - return fileTimeToDateTime(time); - else version (Posix) - { - timespec ts; - ts.tv_sec = cast(time_t)(time.ticks / 1_000_000_000); - ts.tv_nsec = cast(uint)(time.ticks % 1_000_000_000); - return realtimeToDateTime(ts); - } - else - static assert(false, "TODO"); + return unix_ns_to_datetime(unixTimeNs(time)); } Duration getAppTime() @@ -436,9 +786,23 @@ Duration appTime(SysTime t) pure ulong unixTimeNs(SysTime t) pure { version (Windows) - return (t.ticks - 116444736000000000UL) * 100UL; + return (t.ticks - unix_epoch_as_filetime) * 100UL; else version (Posix) return t.ticks; + else version (Embedded) + return t.ticks * nsec_multiplier; + else + static assert(false, "TODO"); +} + +SysTime from_unix_time_ns(ulong ns) pure +{ + version (Windows) + return SysTime(ns / 100UL + unix_epoch_as_filetime); + else version (Posix) + return SysTime(ns); + else version (Embedded) + return SysTime(ns / nsec_multiplier); else static assert(false, "TODO"); } @@ -446,49 +810,102 @@ ulong unixTimeNs(SysTime t) pure Duration abs(Duration d) pure => Duration(d.ticks < 0 ? -d.ticks : d.ticks); +bool wall_time_set() +{ + return has_wall_time; +} + +void set_utc_time(ulong unix_ns) +{ + cast()sys_time_offset = unix_ns / nsec_multiplier - getTime().ticks; + has_wall_time = true; + + version (Windows) + { + import urt.internal.sys.windows; + + ulong ft = unix_ns / 100 + unix_epoch_as_filetime; + SYSTEMTIME st; + FileTimeToSystemTime(cast(FILETIME*)&ft, &st); + SetSystemTime(&st); + } + else version (Posix) + { + timespec ts; + ts.tv_sec = cast(time_t)(unix_ns / 1_000_000_000); + ts.tv_nsec = cast(uint)(unix_ns % 1_000_000_000); + clock_settime(CLOCK_REALTIME, &ts); + } + else version (Embedded) + { + static if (has_rtc) + { + auto p = hbn_persist(); + ulong mtime_ticks = unix_ns / nsec_multiplier; + ulong sec = mtime_ticks / mtime_freq_hz; + ulong frac = mtime_ticks % mtime_freq_hz; + ulong hbn_ticks = sec * rtc_freq_hz + frac * rtc_freq_hz / mtime_freq_hz; + p.utc_offset = cast(long)hbn_ticks - cast(long)rtc_read(); + p.magic = HbnPersist.HBN_MAGIC; + } + } +} + private: -immutable MonoTime startTime; +__gshared immutable MonoTime startTime; + +__gshared immutable string[12] g_month_names = [ "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec" ]; +__gshared immutable uint[9] digit_multipliers = [ 100_000_000, 10_000_000, 1_000_000, 100_000, 10_000, 1_000, 100, 10, 1 ]; version (Windows) { - immutable uint ticksPerSecond; - immutable uint nsecMultiplier; - immutable ulong ticksSinceBoot; + enum ulong unix_epoch_as_filetime = 116_444_736_000_000_000UL; + + immutable uint ticks_per_second; + immutable uint nsec_multiplier; } else version (Posix) { - enum uint ticksPerSecond = 1_000_000_000; - enum uint nsecMultiplier = 1; - immutable ulong ticksSinceBoot; + enum uint ticks_per_second = 1_000_000_000; + enum uint nsec_multiplier = 1; } +else version (Embedded) +{ + enum uint ticks_per_second = mtime_freq_hz; + enum uint nsec_multiplier = 1_000_000_000 / mtime_freq_hz; +} + +__gshared immutable ulong sys_time_offset; +__gshared bool has_wall_time; -package(urt) void initClock() +package(urt) void init_clock() { cast()startTime = getTime(); version (Windows) { - import core.sys.windows.windows; + import urt.internal.sys.windows; import urt.util : min; LARGE_INTEGER freq; QueryPerformanceFrequency(&freq); - cast()ticksPerSecond = cast(uint)freq.QuadPart; - cast()nsecMultiplier = 1_000_000_000 / ticksPerSecond; + cast()ticks_per_second = cast(uint)freq.QuadPart; + cast()nsec_multiplier = 1_000_000_000 / ticks_per_second; // we want the ftime for QPC 0; which should be the boot time // we'll repeat this 100 times and take the minimum, and we should be within probably nanoseconds of the correct value LARGE_INTEGER qpc; - ulong ftime, bootTime = ulong.max; + ulong ftime, boot_time = ulong.max; foreach (i; 0 .. 100) { QueryPerformanceCounter(&qpc); GetSystemTimePreciseAsFileTime(cast(FILETIME*)&ftime); - bootTime = min(bootTime, ftime - qpc.QuadPart); + boot_time = min(boot_time, ftime - qpc.QuadPart); } - cast()ticksSinceBoot = bootTime; + cast()sys_time_offset = boot_time; + has_wall_time = true; } else version (Posix) { @@ -496,35 +913,67 @@ package(urt) void initClock() // this doesn't really give time since boot, since MONOTIME is not guaranteed to be zero at system startup... timespec mt, rt; - ulong bootTime = ulong.max; + ulong boot_time = ulong.max; foreach (i; 0 .. 100) { clock_gettime(CLOCK_MONOTONIC, &mt); clock_gettime(CLOCK_REALTIME, &rt); - bootTime = min(bootTime, rt.tv_sec*1_000_000_000 + rt.tv_nsec - mt.tv_sec*1_000_000_000 - mt.tv_nsec); + boot_time = min(boot_time, rt.tv_sec*1_000_000_000 + rt.tv_nsec - mt.tv_sec*1_000_000_000 - mt.tv_nsec); + } + cast()sys_time_offset = boot_time; + has_wall_time = true; + } + else version (Embedded) + { + timer_init(); + + static if (has_rtc) + { + rtc_enable(); + recalc_sys_time_offset(); + } + else + { + cast()sys_time_offset = 0; } - cast()ticksSinceBoot = bootTime; } else static assert(false, "TODO"); } -ptrdiff_t timeToString(long ms, char[] buffer) pure +ptrdiff_t timeToString(long ns, char[] buffer) pure { - import urt.conv : formatInt; + import urt.conv : format_int; - long hr = ms / 3_600_000; + int hr = cast(int)(ns / 3_600_000_000_000); + ns = ns < 0 ? -ns % 3_600_000_000_000 : ns % 3_600_000_000_000; + uint remainder = cast(uint)(ns % 1_000_000_000); if (!buffer.ptr) - return hr.formatInt(null, 10, 2, '0') + 10; + { + size_t tail = 6; + if (remainder) + { + ++tail; + uint m = 0; + do + { + ++tail; + uint digit = cast(uint)(remainder / digit_multipliers[m]); + remainder -= digit * digit_multipliers[m++]; + } + while (remainder); + } + return hr.format_int(null, 10, 2, '0') + tail; + } - ptrdiff_t len = hr.formatInt(buffer, 10, 2, '0'); - if (len < 0 || buffer.length < len + 10) + ptrdiff_t len = hr.format_int(buffer, 10, 2, '0'); + if (len < 0 || buffer.length < len + 6) return -1; - ubyte min = cast(ubyte)(ms / 60_000 % 60); - ubyte sec = cast(ubyte)(ms / 1000 % 60); - ms %= 1000; + uint min_sec = cast(uint)(ns / 1_000_000_000); + uint min = min_sec / 60; + uint sec = min_sec % 60; buffer.ptr[len++] = ':'; buffer.ptr[len++] = cast(char)('0' + (min / 10)); @@ -532,10 +981,22 @@ ptrdiff_t timeToString(long ms, char[] buffer) pure buffer.ptr[len++] = ':'; buffer.ptr[len++] = cast(char)('0' + (sec / 10)); buffer.ptr[len++] = cast(char)('0' + (sec % 10)); - buffer.ptr[len++] = '.'; - buffer.ptr[len++] = cast(char)('0' + (ms / 100)); - buffer.ptr[len++] = cast(char)('0' + ((ms/10) % 10)); - buffer.ptr[len++] = cast(char)('0' + (ms % 10)); + if (remainder) + { + if (buffer.length < len + 2) + return -1; + buffer.ptr[len++] = '.'; + uint i = 0; + while (remainder) + { + if (buffer.length <= len) + return -1; + uint m = digit_multipliers[i++]; + uint digit = cast(uint)(remainder / m); + buffer.ptr[len++] = cast(char)('0' + digit); + remainder -= digit * m; + } + } return len; } @@ -544,55 +1005,218 @@ unittest import urt.mem.temp; assert(tconcat(msecs(3_600_000*3 + 60_000*47 + 1000*34 + 123))[] == "03:47:34.123"); - assert(tconcat(msecs(3_600_000*-123))[] == "-123:00:00.000"); - - assert(getTime().toString(null, null, null) == 14); + assert(tconcat(msecs(3_600_000*-123))[] == "-123:00:00"); + assert(tconcat(usecs(3_600_000_000*-123 + 1))[] == "-122:59:59.999999"); + assert(MonoTime().toString(null, null, null) == 10); assert(tconcat(getTime())[0..2] == "T+"); + + // Test Duration.fromString with compound formats + Duration d; + + // Simple single units + assert(d.fromString("5s") == 2 && d.as!"seconds" == 5); + assert(d.fromString("10m") == 3 && d.as!"minutes" == 10); + + // Compound durations + assert(d.fromString("1h30m") == 5 && d.as!"minutes" == 90); + assert(d.fromString("5m30s") == 5 && d.as!"seconds" == 330); + + // Duplicate units should fail + assert(d.fromString("30m30m") == -1); + assert(d.fromString("1h2h") == -1); + assert(d.fromString("5s10s") == -1); + + // Out-of-order units should fail + assert(d.fromString("30s5m") == -1); // s before m + assert(d.fromString("1m2h") == -1); // m before h + assert(d.fromString("5s10ms5m") == -1); // m after s + assert(d.fromString("5s10ms10ns") == -1); // ms and ns (only one sub-second unit allowed) + + // Improper units should fail + assert(d.fromString("30z") == -1); // not a time denomination + + // Correctly ordered units should work + assert(d.fromString("1d2h30m15s") == 10 && d.as!"seconds" == 86_400 + 7_200 + 1_800 + 15); + + // Test DateTime.fromString + DateTime dt; + assert(dt.fromString("2024-06-15T12:34:56") == 19); + assert(dt.fromString("-100/06/15 12:34:56") == 19); + assert(dt.fromString("BC100-AUG-15 12:34:56.789") == 25); + assert(dt.fromString("BC 10000-AUG-15 12:34:56.789123456") == 34); + assert(dt.fromString("1-1-1 01:01:01") == 14); + assert(dt.fromString("1-1-1 01:01:01.") == 14); + assert(dt.fromString("2025") == 4); + assert(dt.fromString("2025-10") == 7); + assert(dt.fromString("2025-01-01") == 10); + assert(dt.fromString("2025-01-01 00") == 13); + assert(dt.fromString("2025-01-01 00:10") == 16); + assert(dt.fromString("2025-01-01 00:10z") == 17); + assert(dt.fromString("2025-01-01 00+00:00") == 19); + assert(dt.fromString("2025-01-01 00-1030") == 18); + assert(dt.fromString("2025-01-01 00+08") == 16); + + assert(dt.fromString("BC -10") == -1); + assert(dt.fromString("2024-0-15 12:34:56") == -1); + assert(dt.fromString("2024-13-15 12:34:56") == -1); + assert(dt.fromString("2024-1-0 12:34:56") == -1); + assert(dt.fromString("2024-1-32 12:34:56") == -1); + assert(dt.fromString("2024-1-1 24:34:56") == -1); + assert(dt.fromString("2024-1-1 01:60:56") == -1); + assert(dt.fromString("2024-1-1 01:01:60") == -1); + assert(dt.fromString("10000-1-1 1:01:01") == -1); + + // ---- unix_ns_to_datetime / datetime_to_unix_ns ---- + + // Unix epoch: 1970-01-01 00:00:00 UTC, Thursday + dt = unix_ns_to_datetime(0); + assert(dt.year == 1970 && dt.month == Month.January && dt.day == 1); + assert(dt.hour == 0 && dt.minute == 0 && dt.second == 0 && dt.ns == 0); + assert(dt.wday == Day.Thursday); + + // Round-trip at epoch + assert(datetime_to_unix_ns(dt) == 0); + + // 2000-01-01 00:00:00 UTC = 946684800 seconds, Saturday + dt = unix_ns_to_datetime(946_684_800UL * 1_000_000_000); + assert(dt.year == 2000 && dt.month == Month.January && dt.day == 1); + assert(dt.wday == Day.Saturday); + assert(datetime_to_unix_ns(dt) == 946_684_800UL * 1_000_000_000); + + // 2024-02-29 (leap year) 12:00:00 = 1709208000 seconds, Thursday + dt = unix_ns_to_datetime(1_709_208_000UL * 1_000_000_000); + assert(dt.year == 2024 && dt.month == Month.February && dt.day == 29); + assert(dt.hour == 12 && dt.minute == 0 && dt.second == 0); + assert(dt.wday == Day.Thursday); + assert(datetime_to_unix_ns(dt) == 1_709_208_000UL * 1_000_000_000); + + // 1900-03-01 - 1900 is NOT a leap year (century rule) + // 1900-03-01 00:00:00 = -2203891200 seconds... negative, skip + // Instead test 2100-03-01 (also not a leap year) + // 2100-03-01 00:00:00 = 4107542400 seconds, Monday + dt = unix_ns_to_datetime(4_107_542_400UL * 1_000_000_000); + assert(dt.year == 2100 && dt.month == Month.March && dt.day == 1); + assert(dt.wday == Day.Monday); + + // 2000 IS a leap year (400-year rule), Feb 29 exists + // 2000-02-29 00:00:00 = 951782400 seconds, Tuesday + dt = unix_ns_to_datetime(951_782_400UL * 1_000_000_000); + assert(dt.year == 2000 && dt.month == Month.February && dt.day == 29); + assert(dt.wday == Day.Tuesday); + + // Sub-second precision: 2025-06-15 08:30:45.123456789 + dt.year = 2025; dt.month = Month.June; dt.day = 15; + dt.hour = 8; dt.minute = 30; dt.second = 45; dt.ns = 123_456_789; + ulong ns = datetime_to_unix_ns(dt); + auto dt2 = unix_ns_to_datetime(ns); + assert(dt2.year == 2025 && dt2.month == Month.June && dt2.day == 15); + assert(dt2.hour == 8 && dt2.minute == 30 && dt2.second == 45); + assert(dt2.ns == 123_456_789); + + // Dec 31 ? Jan 1 boundary + dt = unix_ns_to_datetime(1_735_689_599UL * 1_000_000_000); // 2024-12-31 23:59:59 + assert(dt.year == 2024 && dt.month == Month.December && dt.day == 31); + assert(dt.hour == 23 && dt.minute == 59 && dt.second == 59); + dt = unix_ns_to_datetime(1_735_689_600UL * 1_000_000_000); // 2025-01-01 00:00:00 + assert(dt.year == 2025 && dt.month == Month.January && dt.day == 1); + assert(dt.wday == Day.Wednesday); } -version (Windows) +DateTime unix_ns_to_datetime(ulong ns) pure { - DateTime fileTimeToDateTime(SysTime ftime) - { - version (BigEndian) - static assert(false, "Only works in little endian!"); + ulong total_sec = ns / 1_000_000_000; + uint remainder_ns = cast(uint)(ns % 1_000_000_000); + + uint sod = cast(uint)(total_sec % 86_400); + long days = cast(long)(total_sec / 86_400); + + DateTime dt; + dt.hour = cast(ubyte)(sod / 3600); + dt.minute = cast(ubyte)(sod % 3600 / 60); + dt.second = cast(ubyte)(sod % 60); + dt.ns = remainder_ns; + + dt.wday = cast(Day)((days + 4) % 7); + + days += 719_468; + long era = (days >= 0 ? days : days - 146_096) / 146_097; + uint doe = cast(uint)(days - era * 146_097); + uint yoe = (doe - doe / 1460 + doe / 36524 - doe / 146_096) / 365; + long y = yoe + era * 400; + uint doy = doe - (365 * yoe + yoe / 4 - yoe / 100); + uint mp = (5 * doy + 2) / 153; + uint d = doy - (153 * mp + 2) / 5 + 1; + uint m = mp < 10 ? mp + 3 : mp - 9; + if (m <= 2) + ++y; + + dt.year = cast(short)y; + dt.month = cast(Month)m; + dt.day = cast(ubyte)d; + + return dt; +} - SYSTEMTIME stime; - FileTimeToSystemTime(cast(FILETIME*)&ftime.ticks, &stime); +ulong datetime_to_unix_ns(DateTime dt) pure +{ + long y = dt.year; + uint m = dt.month; + uint d = dt.day; - DateTime dt; - dt.year = stime.wYear; - dt.month = cast(Month)stime.wMonth; - dt.wday = cast(Day)stime.wDayOfWeek; - dt.day = cast(ubyte)stime.wDay; - dt.hour = cast(ubyte)stime.wHour; - dt.minute = cast(ubyte)stime.wMinute; - dt.second = cast(ubyte)stime.wSecond; - dt.ns = (ftime.ticks % 10_000_000) * 100; + if (m <= 2) + --y; + long era = (y >= 0 ? y : y - 399) / 400; + uint yoe = cast(uint)(y - era * 400); + uint doy = (153 * (m > 2 ? m - 3 : m + 9) + 2) / 5 + d - 1; + uint doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; + long days = era * 146_097 + doe - 719_468; - debug assert(stime.wMilliseconds == dt.msec); + ulong total_sec = cast(ulong)(days * 86_400 + dt.hour*3600 + dt.minute*60 + dt.second); - return dt; - } + return total_sec * 1_000_000_000 + dt.ns; } -else version (Posix) + +version (Embedded) static if (has_rtc) { - DateTime realtimeToDateTime(timespec ts) - { - tm t; - gmtime_r(&ts.tv_sec, &t); - - DateTime dt; - dt.year = cast(short)(t.tm_year + 1900); - dt.month = cast(Month)(t.tm_mon + 1); - dt.wday = cast(Day)t.tm_wday; - dt.day = cast(ubyte)t.tm_mday; - dt.hour = cast(ubyte)t.tm_hour; - dt.minute = cast(ubyte)t.tm_min; - dt.second = cast(ubyte)t.tm_sec; - dt.ns = cast(uint)ts.tv_nsec; - - return dt; + __gshared ulong last_hbn; + + void correct_drift() + { + auto p = hbn_persist(); + if (p.magic != HbnPersist.HBN_MAGIC) + return; + + ulong now_hbn = rtc_read(); + + // detect 40-bit wrap: counter went backwards since last check + if (now_hbn < last_hbn) + p.utc_offset += ulong(1) << 40; + last_hbn = now_hbn; + + ulong sys_mtime = mtime_read() + sys_time_offset; + + // What does HBN + offset think it is (converted to mtime ticks)? + ulong hbn_total = now_hbn + p.utc_offset; + ulong sys_hbn = hbn_total / rtc_freq_hz * mtime_freq_hz + + hbn_total % rtc_freq_hz * mtime_freq_hz / rtc_freq_hz; + + // difference is accumulated drift; fold into utc_offset + long drift_mtime = sys_mtime - sys_hbn; + p.utc_offset += drift_mtime * rtc_freq_hz / mtime_freq_hz; + } + + void recalc_sys_time_offset() + { + auto p = hbn_persist(); + if (p.magic == HbnPersist.HBN_MAGIC) + { + last_hbn = rtc_read(); + long hbn_total = cast(long)(last_hbn + p.utc_offset); + long hbn_unix_mtime = hbn_total / rtc_freq_hz * mtime_freq_hz + + hbn_total % rtc_freq_hz * mtime_freq_hz / rtc_freq_hz; + cast()sys_time_offset = cast(ulong)(hbn_unix_mtime - cast(long)mtime_read()); + has_wall_time = true; + } } } diff --git a/src/urt/traits.d b/src/urt/traits.d index 30ef675..6f8a761 100644 --- a/src/urt/traits.d +++ b/src/urt/traits.d @@ -3,67 +3,67 @@ module urt.traits; import urt.meta; -enum bool isType(alias X) = is(X); +enum bool is_type(alias X) = is(X); -enum bool isBoolean(T) = __traits(isUnsigned, T) && is(T : bool); +enum bool is_boolean(T) = __traits(isUnsigned, T) && is(T : bool); -enum bool isUnsignedInt(T) = is(Unqual!T == ubyte) || is(Unqual!T == ushort) || is(Unqual!T == uint) || is(Unqual!T == ulong); -enum bool isSignedInt(T) = is(Unqual!T == byte) || is(Unqual!T == short) || is(Unqual!T == int) || is(Unqual!T == long); -enum bool isSomeInt(T) = isUnsignedInt!T || isSignedInt!T; -enum bool isUnsignedIntegral(T) = is(Unqual!T == bool) || isUnsignedInt!T || isSomeChar!T; -enum bool isSignedIntegral(T) = isSignedInt!T; -enum bool isIntegral(T) = isUnsignedIntegral!T || isSignedIntegral!T; -enum bool isSomeFloat(T) = is(Unqual!T == float) || is(Unqual!T == double) || is(Unqual!T == real); +enum bool is_unsigned_int(T) = is(Unqual!T == ubyte) || is(Unqual!T == ushort) || is(Unqual!T == uint) || is(Unqual!T == ulong); +enum bool is_signed_int(T) = is(Unqual!T == byte) || is(Unqual!T == short) || is(Unqual!T == int) || is(Unqual!T == long); +enum bool is_some_int(T) = is_unsigned_int!T || is_signed_int!T; +enum bool is_unsigned_integral(T) = is_boolean!T || is_unsigned_int!T || is_some_char!T; +enum bool is_signed_integral(T) = is_signed_int!T; +enum bool is_integral(T) = is_unsigned_integral!T || is_signed_integral!T; +enum bool is_some_float(T) = is(Unqual!T == float) || is(Unqual!T == double) || is(Unqual!T == real); -enum bool isEnum(T) = is(T == enum); -template enumType(T) - if (isEnum!T) +enum bool is_enum(T) = is(T == enum); +template EnumType(T) + if (is_enum!T) { static if (is(T E == enum)) - alias enumType = E; + alias EnumType = E; else static assert(false, "How this?"); } -template isUnsigned(T) +template is_unsigned(T) { static if (!__traits(isUnsigned, T)) - enum isUnsigned = false; + enum is_unsigned = false; else static if (is(T U == enum)) - enum isUnsigned = isUnsigned!U; + enum is_unsigned = is_unsigned!U; else - enum isUnsigned = __traits(isZeroInit, T) // Not char, wchar, or dchar. + enum is_unsigned = __traits(isZeroInit, T) // Not char, wchar, or dchar. && !is(immutable T == immutable bool) && !is(T == __vector); } -enum bool isSigned(T) = __traits(isArithmetic, T) && !__traits(isUnsigned, T) && is(T : real); +enum bool is_signed(T) = __traits(isArithmetic, T) && !__traits(isUnsigned, T) && is(T : real); -template isSomeChar(T) +template is_some_char(T) { static if (!__traits(isUnsigned, T)) - enum isSomeChar = false; + enum is_some_char = false; else static if (is(T U == enum)) - enum isSomeChar = isSomeChar!U; + enum is_some_char = is_some_char!U; else - enum isSomeChar = !__traits(isZeroInit, T); + enum is_some_char = !__traits(isZeroInit, T); } -enum bool isSomeFunction(alias T) = is(T == return) || is(typeof(T) == return) || is(typeof(&T) == return); -enum bool isFunctionPointer(alias T) = is(typeof(*T) == function); -enum bool isDelegate(alias T) = is(typeof(T) == delegate) || is(T == delegate); +enum bool is_some_function(alias T) = is(T == return) || is(typeof(T) == return) || is(typeof(&T) == return); +enum bool is_function_pointer(alias T) = is(typeof(*T) == function); +enum bool is_delegate(alias T) = is(typeof(T) == delegate) || is(T == delegate); -template isCallable(alias callable) +template is_callable(alias callable) { static if (is(typeof(&callable.opCall) == delegate)) - enum bool isCallable = true; + enum bool is_callable = true; else static if (is(typeof(&callable.opCall) V : V*) && is(V == function)) - enum bool isCallable = true; + enum bool is_callable = true; else static if (is(typeof(&callable.opCall!()) TemplateInstanceType)) - enum bool isCallable = isCallable!TemplateInstanceType; + enum bool is_callable = is_callable!TemplateInstanceType; else static if (is(typeof(&callable!()) TemplateInstanceType)) - enum bool isCallable = isCallable!TemplateInstanceType; + enum bool is_callable = is_callable!TemplateInstanceType; else - enum bool isCallable = isSomeFunction!callable; + enum bool is_callable = is_some_function!callable; } @@ -79,7 +79,7 @@ template Unqual(T : const U, U) template Unsigned(T) { - static if (isUnsigned!T) + static if (is_unsigned!T) alias Unsigned = T; else static if (is(T == long)) alias Unsigned = ulong; @@ -113,7 +113,7 @@ template Unsigned(T) template Signed(T) { - static if (isSigned!T) + static if (is_signed!T) alias Unsigned = T; else static if (is(T == ulong)) alias Signed = long; @@ -144,7 +144,7 @@ template Signed(T) } template ReturnType(alias func) - if (isCallable!func) + if (is_callable!func) { static if (is(FunctionTypeOf!func R == return)) alias ReturnType = R; @@ -153,7 +153,7 @@ template ReturnType(alias func) } template Parameters(alias func) - if (isCallable!func) + if (is_callable!func) { static if (is(FunctionTypeOf!func P == function)) alias Parameters = P; @@ -161,26 +161,26 @@ template Parameters(alias func) static assert(0, "argument has no parameters"); } -template ParameterIdentifierTuple(alias func) - if (isCallable!func) +template parameter_identifier_tuple(alias func) + if (is_callable!func) { static if (is(FunctionTypeOf!func PT == __parameters)) { - alias ParameterIdentifierTuple = AliasSeq!(); + alias parameter_identifier_tuple = AliasSeq!(); static foreach (i; 0 .. PT.length) { - static if (!isFunctionPointer!func && !isDelegate!func + static if (!is_function_pointer!func && !is_delegate!func // Unnamed parameters yield CT error. && is(typeof(__traits(identifier, PT[i .. i+1]))) // Filter out unnamed args, which look like (Type) instead of (Type name). && PT[i].stringof != PT[i .. i+1].stringof[1..$-1]) { - ParameterIdentifierTuple = AliasSeq!(ParameterIdentifierTuple, + parameter_identifier_tuple = AliasSeq!(parameter_identifier_tuple, __traits(identifier, PT[i .. i+1])); } else { - ParameterIdentifierTuple = AliasSeq!(ParameterIdentifierTuple, ""); + parameter_identifier_tuple = AliasSeq!(parameter_identifier_tuple, ""); } } } @@ -188,12 +188,12 @@ template ParameterIdentifierTuple(alias func) { static assert(0, func.stringof ~ " is not a function"); // avoid pointless errors - alias ParameterIdentifierTuple = AliasSeq!(); + alias parameter_identifier_tuple = AliasSeq!(); } } template FunctionTypeOf(alias func) - if (isCallable!func) + if (is_callable!func) { static if ((is(typeof(& func) Fsym : Fsym*) && is(Fsym == function)) || is(typeof(& func) Fsym == delegate)) alias FunctionTypeOf = Fsym; // HIT: (nested) function symbol @@ -218,27 +218,29 @@ template FunctionTypeOf(alias func) } // is T a primitive/builtin type? -enum isPrimitive(T) = isIntegral!T || isSomeFloat!T || (isEnum!T && isPrimitive!(enumType!T) || - is(T == P*, P) || is(T == S[], S) || (is(T == A[N], A, size_t N) && isPrimitive!A) || +enum is_primitive(T) = is_integral!T || is_some_float!T || (is_enum!T && is_primitive!(EnumType!T) || + is(T == P*, P) || is(T == S[], S) || (is(T == A[N], A, size_t N) && is_primitive!A) || is(T == R function(Args), R, Args...) || is(T == R delegate(Args), R, Args...)); -enum isDefaultConstructible(T) = isPrimitive!T || (is(T == struct) && __traits(compiles, { T t; })); +enum is_trivial(T) = __traits(isPOD, T); -enum isConstructible(T, Args...) = (isPrimitive!T && (Args.length == 0 || (Args.length == 1 && is(Args[0] : T)))) || +enum is_default_constructible(T) = is_primitive!T || (is(T == struct) && __traits(compiles, { T t; })); + +enum is_constructible(T, Args...) = (is_primitive!T && (Args.length == 0 || (Args.length == 1 && is(Args[0] : T)))) || (is(T == struct) && __traits(compiles, (Args args) { T x = T(args); })); // this probably fails if the struct can't be assigned to x... TODO: use placement new? // TODO: we need to know it's not calling an elaborate constructor... -//enum isTriviallyConstructible(T, Args...) = (isPrimitive!T && (Args.length == 0 || (Args.length == 1 && is(Args[0] : T)))) || -// (is(T == struct) && __traits(compiles, (Args args) { auto x = T(args); })); // this probably fails if the struct can't be assigned to x... TODO: use placement new? +//enum is_trivially_constructible(T, Args...) = (is_primitive!T && (Args.length == 0 || (Args.length == 1 && is(Args[0] : T)))) || +// (is(T == struct) && __traits(compiles, (Args args) { auto x = T(args); })); // this probably fails if the struct can't be assigned to x... TODO: use placement new? -//enum isCopyConstructible(T) = isPrimitive!T || (is(T == struct) && __traits(compiles, { T u = lvalueOf!T; })); -//enum isMoveConstructible(T) = isPrimitive!T || (is(T == struct) && __traits(compiles, { T u = rvalueOf!T; })); +//enum is_copy_constructible(T) = is_primitive!T || (is(T == struct) && __traits(compiles, { T u = lvalue_of!T; })); +//enum is_move_constructible(T) = is_primitive!T || (is(T == struct) && __traits(compiles, { T u = rvalue_of!T; })); -enum isTriviallyDefaultConstructible(T) = isDefaultConstructible!T; // dlang doesn't have elaborate default constructors (YET...) -//enum isTriviallyCopyConstructible(T) = isPrimitive!T; // TODO: somehow find out if there is no copy constructor -//enum isTriviallyMoveConstructible(T) = isPrimitive!T || is(T == struct); // TODO: somehow find out if there is no move constructor +enum is_trivially_default_constructible(T) = is_default_constructible!T; // dlang doesn't have elaborate default constructors (YET...) +//enum is_trivially_copy_constructible(T) = is_primitive!T; // TODO: somehow find out if there is no copy constructor +//enum is_trivially_move_constructible(T) = is_primitive!T || is(T == struct); // TODO: somehow find out if there is no move constructor // helpers to test certain expressions private struct __InoutWorkaroundStruct{} -@property T rvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init) pure nothrow @nogc; -@property ref T lvalueOf(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init) pure nothrow @nogc; +@property T rvalue_of(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init) pure nothrow @nogc; +@property ref T lvalue_of(T)(inout __InoutWorkaroundStruct = __InoutWorkaroundStruct.init) pure nothrow @nogc; diff --git a/src/urt/util.d b/src/urt/util.d index 044f47f..4971ddd 100644 --- a/src/urt/util.d +++ b/src/urt/util.d @@ -30,33 +30,33 @@ pure: auto min(T, U)(auto ref inout T a, auto ref inout U b) { - return a < b ? a : b; + return b < a ? b : a; } auto max(T, U)(auto ref inout T a, auto ref inout U b) { - return a > b ? a : b; + return b > a ? b : a; } template Align(size_t value, size_t alignment = size_t.sizeof) { - static assert(isPowerOf2(alignment), "Alignment must be a power of two: ", alignment); + static assert(is_power_of_2(alignment), "Alignment must be a power of two: ", alignment); enum Align = alignTo(value, alignment); } -enum IsAligned(size_t value) = isAligned(value); -enum IsPowerOf2(size_t value) = isPowerOf2(value); -enum NextPowerOf2(size_t value) = nextPowerOf2(value); +enum IsAligned(size_t value) = is_aligned(value); +enum IsPowerOf2(size_t value) = is_power_of_2(value); +enum NextPowerOf2(size_t value) = next_power_of_2(value); -bool isPowerOf2(T)(T x) - if (isSomeInt!T) +bool is_power_of_2(T)(T x) + if (is_some_int!T) { return (x & (x - 1)) == 0; } -T nextPowerOf2(T)(T x) - if (isSomeInt!T) +T next_power_of_2(T)(T x) + if (is_some_int!T) { x -= 1; x |= x >> 1; @@ -71,43 +71,81 @@ T nextPowerOf2(T)(T x) return cast(T)(x + 1); } -T alignDown(size_t alignment, T)(T value) - if (isSomeInt!T || is(T == U*, U)) +T align_down(size_t alignment, T)(T value) + if (is_some_int!T || is(T == U*, U)) { return cast(T)(cast(size_t)value & ~(alignment - 1)); } -T alignDown(T)(T value, size_t alignment) - if (isSomeInt!T || is(T == U*, U)) +T align_down(T)(T value, size_t alignment) + if (is_some_int!T || is(T == U*, U)) { return cast(T)(cast(size_t)value & ~(alignment - 1)); } -T alignUp(size_t alignment, T)(T value) - if (isSomeInt!T || is(T == U*, U)) +T align_up(size_t alignment, T)(T value) + if (is_some_int!T || is(T == U*, U)) { return cast(T)((cast(size_t)value + (alignment - 1)) & ~(alignment - 1)); } -T alignUp(T)(T value, size_t alignment) - if (isSomeInt!T || is(T == U*, U)) +T align_up(T)(T value, size_t alignment) + if (is_some_int!T || is(T == U*, U)) { return cast(T)((cast(size_t)value + (alignment - 1)) & ~(alignment - 1)); } -bool isAligned(size_t alignment, T)(T value) - if (isSomeInt!T || is(T == U*, U)) +bool is_aligned(size_t alignment, T)(T value) + if (is_some_int!T || is(T == U*, U)) { - static assert(IsPowerOf2!alignment, "Alignment must be a power of two!"); + static assert(IsPowerOf2!alignment, "Alignment must be a power of two"); + static assert(T.sizeof <= size_t.sizeof, "TODO"); return (cast(size_t)value & (alignment - 1)) == 0; } -bool isAligned(T)(T value, size_t alignment) - if (isSomeInt!T || is(T == U*, U)) +bool is_aligned(T)(T value, size_t alignment) + if (is_some_int!T || is(T == U*, U)) { + static assert(T.sizeof <= size_t.sizeof, "TODO"); return (cast(size_t)value & (alignment - 1)) == 0; } +T rol(T)(const T value, const uint count) pure + if (__traits(isIntegral, T) && __traits(isUnsigned, T)) +{ + assert(count < 8 * T.sizeof); + if (count == 0) + return cast(T)value; + return cast(T)((value << count) | (value >> (T.sizeof * 8 - count))); +} + +T ror(T)(const T value, const uint count) pure + if (__traits(isIntegral, T) && __traits(isUnsigned, T)) +{ + assert(count < 8 * T.sizeof); + if (count == 0) + return cast(T)value; + return cast(T)((value >> count) | (value << (T.sizeof * 8 - count))); +} + +T rol(uint count, T)(const T value) pure + if (__traits(isIntegral, T) && __traits(isUnsigned, T)) +{ + static assert(count < 8 * T.sizeof); + static if (count == 0) + return cast(T)value; + return cast(T)((value << count) | (value >> (T.sizeof * 8 - count))); +} + +T ror(uint count, T)(const T value) pure + if (__traits(isIntegral, T) && __traits(isUnsigned, T)) +{ + static assert(count < 8 * T.sizeof); + static if (count == 0) + return cast(T)value; + return cast(T)((value >> count) | (value << (T.sizeof * 8 - count))); +} + /+ ubyte log2(ubyte val) { @@ -140,7 +178,7 @@ ubyte log2(ubyte val) } ubyte log2(T)(T val) - if (isSomeInt!T && T.sizeof > 1) + if (is_some_int!T && T.sizeof > 1) { if (T.sizeof > 4 && val >> 32) { @@ -172,7 +210,7 @@ ubyte log2(T)(T val) +/ ubyte log2(T)(T x) - if (isIntegral!T) + if (is_integral!T) { ubyte result = 0; static if (T.sizeof > 4) @@ -201,7 +239,7 @@ ubyte log2(T)(T x) } ubyte clz(bool nonZero = false, T)(T x) - if (isIntegral!T) + if (is_integral!T) { static if (nonZero) debug assert(x != 0); @@ -270,7 +308,7 @@ ubyte clz(T : bool)(T x) => x ? 7 : 8; ubyte ctz(bool nonZero = false, T)(T x) - if (isIntegral!T) + if (is_integral!T) { static if (nonZero) debug assert(x != 0); @@ -378,7 +416,7 @@ ubyte ctz(T : bool)(T x) => x ? 0 : 8; ubyte popcnt(T)(T x) - if (isIntegral!T) + if (is_integral!T) { if (__ctfe || !IS_LDC_OR_GDC) { @@ -410,9 +448,9 @@ ubyte popcnt(T)(T x) ubyte popcnt(T : bool)(T x) => x ? 1 : 0; -ubyte byteReverse(ubyte v) +ubyte byte_reverse(ubyte v) => v; -ushort byteReverse(ushort v) +ushort byte_reverse(ushort v) { if (__ctfe || !IS_LDC_OR_GDC) return cast(ushort)((v << 8) | (v >> 8)); @@ -421,7 +459,7 @@ ushort byteReverse(ushort v) else assert(false, "Unreachable"); } -uint byteReverse(uint v) +uint byte_reverse(uint v) { if (__ctfe || !IS_LDC_OR_GDC) return cast(uint)((v << 24) | ((v & 0xFF00) << 8) | ((v >> 8) & 0xFF00) | (v >> 24)); @@ -430,7 +468,7 @@ uint byteReverse(uint v) else assert(false, "Unreachable"); } -ulong byteReverse(ulong v) +ulong byte_reverse(ulong v) { if (__ctfe || !IS_LDC_OR_GDC) return cast(ulong)((v << 56) | ((v & 0xFF00) << 40) | ((v & 0xFF0000) << 24) | ((v & 0xFF000000) << 8) | ((v >> 8) & 0xFF000000) | ((v >> 24) & 0xFF0000) | ((v >> 40) & 0xFF00) | (v >> 56)); @@ -439,17 +477,17 @@ ulong byteReverse(ulong v) else assert(false, "Unreachable"); } -pragma(inline, true) T byteReverse(T)(T val) - if (!isIntegral!T) +pragma(inline, true) T byte_reverse(T)(T val) + if (!is_integral!T) { - import urt.meta : intForWidth; - alias U = intForWidth!(T.sizeof*8); - U r = byteReverse(*cast(U*)&val); + import urt.meta : IntForWidth; + alias U = IntForWidth!(T.sizeof*8); + U r = byte_reverse(*cast(U*)&val); return *cast(T*)&r; } -T bitReverse(T)(T x) - if (isSomeInt!T) +T bit_reverse(T)(T x) + if (is_some_int!T) { if (__ctfe || !IS_LDC) { @@ -458,7 +496,7 @@ T bitReverse(T)(T x) // TODO: these may be inferior on platforms where mul is slow... static if (size_t.sizeof == 8) { - // return cast(ubyte)((b*0x0202020202ULL & 0x010884422010ULL) % 1023; // only 3 ops, but uses div! +// return cast(ubyte)((b*0x0202020202ULL & 0x010884422010ULL) % 1023; // only 3 ops, but uses div! return cast(ubyte)(cast(ulong)(x*0x80200802UL & 0x0884422110)*0x0101010101 >> 32); } else @@ -476,7 +514,84 @@ T bitReverse(T)(T x) static if (T.sizeof == 1) return x; else - return byteReverse(x); + return byte_reverse(x); + } + } + else static if (false) // TODO: DMD, x86, 4 or 8 bytes... + { + static if (T.sizeof == 4) + { + asm pure nothrow @nogc { naked; } + + version (D_InlineAsm_X86_64) + { + version (Win64) + asm pure nothrow @nogc { mov EAX, ECX; } + else + asm pure nothrow @nogc { mov EAX, EDI; } + } + + asm pure nothrow @nogc + { + mov EDX, EAX; + shr EAX, 1; + and EDX, 0x5555_5555; + and EAX, 0x5555_5555; + shl EDX, 1; + or EAX, EDX; + mov EDX, EAX; + shr EAX, 2; + and EDX, 0x3333_3333; + and EAX, 0x3333_3333; + shl EDX, 2; + or EAX, EDX; + mov EDX, EAX; + shr EAX, 4; + and EDX, 0x0f0f_0f0f; + and EAX, 0x0f0f_0f0f; + shl EDX, 4; + or EAX, EDX; + bswap EAX; + ret; + } + } + else + { + asm pure nothrow @nogc { naked; } + + version (Win64) + asm pure nothrow @nogc { mov RAX, RCX; } + else + asm pure nothrow @nogc { mov RAX, RDI; } + + asm pure nothrow @nogc + { + mov RDX, RAX; + shr RAX, 1; + mov RCX, 0x5555_5555_5555_5555L; + and RDX, RCX; + and RAX, RCX; + shl RDX, 1; + or RAX, RDX; + + mov RDX, RAX; + shr RAX, 2; + mov RCX, 0x3333_3333_3333_3333L; + and RDX, RCX; + and RAX, RCX; + shl RDX, 2; + or RAX, RDX; + + mov RDX, RAX; + shr RAX, 4; + mov RCX, 0x0f0f_0f0f_0f0f_0f0fL; + and RDX, RCX; + and RAX, RCX; + shl RDX, 4; + or RAX, RDX; + bswap RAX; + ret; + } } } else @@ -531,39 +646,39 @@ unittest assert(x.swap(y) == 10); assert(x.swap(30) == 20); - static assert(isPowerOf2(0) == true); - static assert(isPowerOf2(1) == true); - static assert(isPowerOf2(2) == true); - static assert(isPowerOf2(3) == false); - static assert(isPowerOf2(4) == true); - static assert(isPowerOf2(5) == false); - static assert(isPowerOf2(ulong(uint.max) + 1) == true); - static assert(isPowerOf2(ulong.max) == false); - assert(isPowerOf2(0) == true); - assert(isPowerOf2(1) == true); - assert(isPowerOf2(2) == true); - assert(isPowerOf2(3) == false); - assert(isPowerOf2(4) == true); - assert(isPowerOf2(5) == false); - assert(isPowerOf2(ulong(uint.max) + 1) == true); - assert(isPowerOf2(ulong.max) == false); - - static assert(nextPowerOf2(0) == 0); - static assert(nextPowerOf2(1) == 1); - static assert(nextPowerOf2(2) == 2); - static assert(nextPowerOf2(3) == 4); - static assert(nextPowerOf2(4) == 4); - static assert(nextPowerOf2(5) == 8); - static assert(nextPowerOf2(uint.max) == 0); - static assert(nextPowerOf2(ulong(uint.max)) == ulong(uint.max) + 1); - assert(nextPowerOf2(0) == 0); - assert(nextPowerOf2(1) == 1); - assert(nextPowerOf2(2) == 2); - assert(nextPowerOf2(3) == 4); - assert(nextPowerOf2(4) == 4); - assert(nextPowerOf2(5) == 8); - assert(nextPowerOf2(uint.max) == 0); - assert(nextPowerOf2(ulong(uint.max)) == ulong(uint.max) + 1); + static assert(is_power_of_2(0) == true); + static assert(is_power_of_2(1) == true); + static assert(is_power_of_2(2) == true); + static assert(is_power_of_2(3) == false); + static assert(is_power_of_2(4) == true); + static assert(is_power_of_2(5) == false); + static assert(is_power_of_2(ulong(uint.max) + 1) == true); + static assert(is_power_of_2(ulong.max) == false); + assert(is_power_of_2(0) == true); + assert(is_power_of_2(1) == true); + assert(is_power_of_2(2) == true); + assert(is_power_of_2(3) == false); + assert(is_power_of_2(4) == true); + assert(is_power_of_2(5) == false); + assert(is_power_of_2(ulong(uint.max) + 1) == true); + assert(is_power_of_2(ulong.max) == false); + + static assert(next_power_of_2(0) == 0); + static assert(next_power_of_2(1) == 1); + static assert(next_power_of_2(2) == 2); + static assert(next_power_of_2(3) == 4); + static assert(next_power_of_2(4) == 4); + static assert(next_power_of_2(5) == 8); + static assert(next_power_of_2(uint.max) == 0); + static assert(next_power_of_2(ulong(uint.max)) == ulong(uint.max) + 1); + assert(next_power_of_2(0) == 0); + assert(next_power_of_2(1) == 1); + assert(next_power_of_2(2) == 2); + assert(next_power_of_2(3) == 4); + assert(next_power_of_2(4) == 4); + assert(next_power_of_2(5) == 8); + assert(next_power_of_2(uint.max) == 0); + assert(next_power_of_2(ulong(uint.max)) == ulong(uint.max) + 1); static assert(log2(ubyte(0)) == 0); static assert(log2(ubyte(1)) == 0); @@ -677,50 +792,50 @@ unittest assert(popcnt(true) == 1); assert(popcnt('D') == 2); // 0x44 - static assert(bitReverse(ubyte(0)) == 0); - static assert(bitReverse(ubyte(1)) == 128); - static assert(bitReverse(ubyte(2)) == 64); - static assert(bitReverse(ubyte(3)) == 192); - static assert(bitReverse(ubyte(4)) == 32); - static assert(bitReverse(ubyte(5)) == 160); - static assert(bitReverse(ubyte(6)) == 96); - static assert(bitReverse(ubyte(7)) == 224); - static assert(bitReverse(ubyte(8)) == 16); - static assert(bitReverse(ubyte(255)) == 255); - static assert(bitReverse(ushort(0b1101100010000000)) == 0b0000000100011011); - static assert(bitReverse(uint(0x73810000)) == 0x000081CE); - static assert(bitReverse(ulong(0x7381000000000000)) == 0x00000000000081CE); - assert(bitReverse(ubyte(0)) == 0); - assert(bitReverse(ubyte(1)) == 128); - assert(bitReverse(ubyte(2)) == 64); - assert(bitReverse(ubyte(3)) == 192); - assert(bitReverse(ubyte(4)) == 32); - assert(bitReverse(ubyte(5)) == 160); - assert(bitReverse(ubyte(6)) == 96); - assert(bitReverse(ubyte(7)) == 224); - assert(bitReverse(ubyte(8)) == 16); - assert(bitReverse(ubyte(255)) == 255); - assert(bitReverse(ushort(0b1101100010000000)) == 0b0000000100011011); - assert(bitReverse(uint(0x73810000)) == 0x000081CE); - assert(bitReverse(ulong(0x7381000000000000)) == 0x00000000000081CE); - - static assert(byteReverse(0x12) == 0x12); - static assert(byteReverse(0x1234) == 0x3412); - static assert(byteReverse(0x12345678) == 0x78563412); - static assert(byteReverse(0x123456789ABCDEF0) == 0xF0DEBC9A78563412); - static assert(byteReverse(true) == true); - static assert(byteReverse(char(0x12)) == char(0x12)); - static assert(byteReverse(wchar(0x1234)) == wchar(0x3412)); - static assert(byteReverse(cast(dchar)0x12345678) == cast(dchar)0x78563412); - assert(byteReverse(0x12) == 0x12); - assert(byteReverse(0x1234) == 0x3412); - assert(byteReverse(0x12345678) == 0x78563412); - assert(byteReverse(0x123456789ABCDEF0) == 0xF0DEBC9A78563412); - assert(byteReverse(true) == true); - assert(byteReverse(char(0x12)) == char(0x12)); - assert(byteReverse(wchar(0x1234)) == wchar(0x3412)); - assert(byteReverse(cast(dchar)0x12345678) == cast(dchar)0x78563412); + static assert(bit_reverse(ubyte(0)) == 0); + static assert(bit_reverse(ubyte(1)) == 128); + static assert(bit_reverse(ubyte(2)) == 64); + static assert(bit_reverse(ubyte(3)) == 192); + static assert(bit_reverse(ubyte(4)) == 32); + static assert(bit_reverse(ubyte(5)) == 160); + static assert(bit_reverse(ubyte(6)) == 96); + static assert(bit_reverse(ubyte(7)) == 224); + static assert(bit_reverse(ubyte(8)) == 16); + static assert(bit_reverse(ubyte(255)) == 255); + static assert(bit_reverse(ushort(0b1101100010000000)) == 0b0000000100011011); + static assert(bit_reverse(uint(0x73810000)) == 0x000081CE); + static assert(bit_reverse(ulong(0x7381000000000000)) == 0x00000000000081CE); + assert(bit_reverse(ubyte(0)) == 0); + assert(bit_reverse(ubyte(1)) == 128); + assert(bit_reverse(ubyte(2)) == 64); + assert(bit_reverse(ubyte(3)) == 192); + assert(bit_reverse(ubyte(4)) == 32); + assert(bit_reverse(ubyte(5)) == 160); + assert(bit_reverse(ubyte(6)) == 96); + assert(bit_reverse(ubyte(7)) == 224); + assert(bit_reverse(ubyte(8)) == 16); + assert(bit_reverse(ubyte(255)) == 255); + assert(bit_reverse(ushort(0b1101100010000000)) == 0b0000000100011011); + assert(bit_reverse(uint(0x73810000)) == 0x000081CE); + assert(bit_reverse(ulong(0x7381000000000000)) == 0x00000000000081CE); + + static assert(byte_reverse(0x12) == 0x12); + static assert(byte_reverse(0x1234) == 0x3412); + static assert(byte_reverse(0x12345678) == 0x78563412); + static assert(byte_reverse(0x123456789ABCDEF0) == 0xF0DEBC9A78563412); + static assert(byte_reverse(true) == true); + static assert(byte_reverse(char(0x12)) == char(0x12)); + static assert(byte_reverse(wchar(0x1234)) == wchar(0x3412)); + static assert(byte_reverse(cast(dchar)0x12345678) == cast(dchar)0x78563412); + assert(byte_reverse(0x12) == 0x12); + assert(byte_reverse(0x1234) == 0x3412); + assert(byte_reverse(0x12345678) == 0x78563412); + assert(byte_reverse(0x123456789ABCDEF0) == 0xF0DEBC9A78563412); + assert(byte_reverse(true) == true); + assert(byte_reverse(char(0x12)) == char(0x12)); + assert(byte_reverse(wchar(0x1234)) == wchar(0x3412)); + assert(byte_reverse(cast(dchar)0x12345678) == cast(dchar)0x78563412); float frev; *cast(uint*)&frev = 0x0000803F; - assert(byteReverse(1.0f) is frev); + assert(byte_reverse(1.0f) is frev); } diff --git a/src/urt/uuid.d b/src/urt/uuid.d new file mode 100644 index 0000000..ce30b97 --- /dev/null +++ b/src/urt/uuid.d @@ -0,0 +1,90 @@ +module urt.uuid; + +import urt.conv : format_uint, parse_uint; +import urt.string.format : FormatArg; + +nothrow @nogc: + +enum GUID UUID(string s) = () { UUID g; ptrdiff_t n = g.fromString(s); assert(n == s.length, "Not a valid GUID/UUID"); return g; }(); + +struct GUID +{ +nothrow @nogc: +align(1): + uint data1; + ushort data2; + ushort data3; + ubyte[8] data4; + + bool opEquals(ref const GUID rh) const pure + => data1 == rh.data1 && data2 == rh.data2 && data3 == rh.data3 && data4 == rh.data4; + + bool opCast(T : bool)() const pure + => data1 != 0 || data2 != 0 || data3 != 0 || data4 != typeof(data4).init; + + // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + ptrdiff_t toString(char[] buf, const(char)[], const(FormatArg)[]) const pure + { + import urt.string.ascii : hex_digits; + if (!buf.ptr) + return 36; + if (buf.length < 36) + return -1; + format_uint(data1, buf[0 .. 8], 16, 8, '0'); + buf[8] = '-'; + format_uint(data2, buf[9 .. 13], 16, 4, '0'); + buf[13] = '-'; + format_uint(data3, buf[14 .. 18], 16, 4, '0'); + buf[18] = '-'; + buf[19] = hex_digits[data4[0] >> 4]; + buf[20] = hex_digits[data4[0] & 0xf]; + buf[21] = hex_digits[data4[1] >> 4]; + buf[22] = hex_digits[data4[1] & 0xf]; + buf[23] = '-'; + buf[24] = hex_digits[data4[2] >> 4]; + buf[25] = hex_digits[data4[2] & 0xf]; + buf[26] = hex_digits[data4[3] >> 4]; + buf[27] = hex_digits[data4[3] & 0xf]; + buf[28] = hex_digits[data4[4] >> 4]; + buf[29] = hex_digits[data4[4] & 0xf]; + buf[30] = hex_digits[data4[5] >> 4]; + buf[31] = hex_digits[data4[5] & 0xf]; + buf[32] = hex_digits[data4[6] >> 4]; + buf[33] = hex_digits[data4[6] & 0xf]; + buf[34] = hex_digits[data4[7] >> 4]; + buf[35] = hex_digits[data4[7] & 0xf]; + return 36; + } + + ptrdiff_t fromString(const(char)[] s) pure + { + if (s.length < 36 || s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-') + return -1; + size_t n; + ulong d1 = s[0 .. 8].parse_uint(&n, 16); if (n != 8) return -1; + ulong d2 = s[9 .. 13].parse_uint(&n, 16); if (n != 4) return -1; + ulong d3 = s[14 .. 18].parse_uint(&n, 16); if (n != 4) return -1; + data1 = cast(uint)d1; + data2 = cast(ushort)d2; + data3 = cast(ushort)d3; + foreach (i; 0 .. 8) + { + size_t off = i < 2 ? 19 + i*2 : 20 + i*2; + ulong b = s[off .. off + 2].parse_uint(&n, 16); + if (n != 2) return -1; + data4[i] = cast(ubyte)b; + } + return 36; + } + + debug auto __debugOverview() + { + import urt.mem; + char[] buf = debug_alloc!char(36); + toString(buf, null, null); + return buf[0 .. 36]; + } +} + +static assert(GUID.sizeof == 16); +static assert(GUID.alignof == 1); diff --git a/src/urt/variant.d b/src/urt/variant.d index 9f5fe2c..95ce4af 100644 --- a/src/urt/variant.d +++ b/src/urt/variant.d @@ -1,22 +1,33 @@ module urt.variant; +import urt.algorithm : compare; import urt.array; +import urt.conv; import urt.kvp; import urt.lifetime; import urt.map; +import urt.mem.allocator; +import urt.meta.enuminfo : enum_info, VoidEnumInfo; import urt.si.quantity; -import urt.si.unit : ScaledUnit; +import urt.si.unit : ScaledUnit, Second, Nanosecond; +import urt.string; +import urt.time; import urt.traits; +import urt.util : swap; nothrow @nogc: enum ValidUserType(T) = (is(T == struct) || is(T == class)) && + is(Unqual!T == T) && !is(T == Variant) && !is(T == VariantKVP) && !is(T == Array!U, U) && !is(T : const(char)[]) && - !is(T == Quantity!(T, U), T, alias U); + !is(T == Quantity!(T, U), T, alias U) && + !is(T == Duration) && + !is(T == String) && + !is(T == MutableString!N, size_t N); alias VariantKVP = KVP!(const(char)[], Variant); @@ -31,14 +42,59 @@ nothrow @nogc: if (rh.type == Type.Map || rh.type == Type.Array) { Array!Variant arr = rh.nodeArray; - takeNodeArray(arr); + take_node_array(arr); + flags = rh.flags; + } + else if ((rh.flags & Flags.NeedDestruction) == 0) + { + __pack = rh.__pack; + } + else if (rh.isString) + { + *cast(String*)&value.s = *cast(String*)&rh.value.s; + count = rh.count; + flags = rh.flags; + } + else if (rh.isBuffer) + { + ptr = defaultAllocator.alloc(rh.count).ptr; + ptr[0 .. rh.count] = rh.ptr[0 .. rh.count]; + count = rh.count; flags = rh.flags; } else + assert(false, "What kind of thing is this?"); + } + + this(ref const Variant rh) inout + { + if (rh.type == Type.Map || rh.type == Type.Array) + { + auto arr = Array!Variant(Reserve, rh.nodeArray.length); + foreach (ref i; rh.nodeArray) + arr.pushBack(i); + flags = rh.flags; + } + else if ((rh.flags & Flags.NeedDestruction) == 0) { - assert((rh.flags & Flags.NeedDestruction) == 0); __pack = rh.__pack; } + else if (rh.isString) + { + *cast(String*)&value.s = *cast(String*)&rh.value.s; + count = rh.count; + flags = rh.flags; + } + else if (rh.isBuffer) + { + void* mem = defaultAllocator.alloc(rh.count).ptr; + mem[0 .. rh.count] = rh.ptr[0 .. rh.count]; + ptr = cast(inout(void)*)mem; + count = rh.count; + flags = rh.flags; + } + else + assert(false, "What kind of thing is this?"); } version (EnableMoveSemantics) { @@ -65,85 +121,150 @@ nothrow @nogc: } this(I)(I i) - if (is(I == byte) || is(I == short)) - { - flags = Flags.NumberInt; - value.l = i; - if (i >= 0) - flags |= Flags.UintFlag | Flags.Uint64Flag; - } - this(I)(I i) - if (is(I == ubyte) || is(I == ushort)) + if (is_some_int!I) { - flags = cast(Flags)(Flags.NumberUint | Flags.IntFlag); - value.ul = i; + static if (is_signed_int!I) + value.l = i; + else + value.ul = i; + + static if (is(I == ubyte) || is(I == ushort)) + flags = cast(Flags)(Flags.NumberUint | Flags.IntFlag | Flags.Int64Flag); + else static if (is(I == byte) || is(I == short) || is(I == int)) + { + flags = Flags.NumberInt; + if (i >= 0) + flags |= Flags.UintFlag | Flags.Uint64Flag; + } + else static if (is(I == uint)) + { + flags = Flags.NumberUint; + if (i <= int.max) + flags |= Flags.IntFlag; + } + else static if (is(I == long)) + { + flags = Flags.NumberInt64; + if (i >= 0) + { + flags |= Flags.Uint64Flag; + if (i <= int.max) + flags |= Flags.IntFlag | Flags.UintFlag; + else if (i <= uint.max) + flags |= Flags.UintFlag; + } + else if (i >= int.min) + flags |= Flags.IntFlag; + } + else static if (is(I == ulong)) + { + flags = Flags.NumberUint64; + if (i <= int.max) + flags |= Flags.IntFlag | Flags.UintFlag | Flags.Int64Flag; + else if (i <= uint.max) + flags |= Flags.UintFlag | Flags.Int64Flag; + else if (i <= long.max) + flags |= Flags.Int64Flag; + } } - this(int i) + this(F)(F f) + if (is_some_float!F) { - flags = Flags.NumberInt; - value.l = i; - if (i >= 0) - flags |= Flags.UintFlag | Flags.Uint64Flag; + import urt.math : float_is_integer; + + if (int sign = float_is_integer(f, value.ul)) + { + if (sign < 0) + { + flags = Flags.NumberInt64; + if (value.l >= int.min) + flags |= Flags.IntFlag; + } + else + { + flags = Flags.NumberUint64; + if (value.ul <= int.max) + flags |= Flags.IntFlag | Flags.UintFlag | Flags.Int64Flag; + else if (value.ul <= uint.max) + flags |= Flags.UintFlag | Flags.Int64Flag; + else if (value.ul <= long.max) + flags |= Flags.Int64Flag; + } + } + else + { + static if (is(F == float)) + flags = Flags.NumberFloat; + else + flags = Flags.NumberDouble; + value.d = f; + } } - this(uint i) + this(E)(const E e) + if (is(E == enum)) { - flags = Flags.NumberUint; - value.ul = i; - if (i <= int.max) - flags |= Flags.IntFlag; + static if (is(E T == enum)) + this(T(e), enum_info!E.make_void()); } - this(long i) + this(T)(T value, const(VoidEnumInfo)* e) { - flags = Flags.NumberInt64; - value.l = i; - if (i >= 0) + static assert(!is(T == enum), "T should be a numeric type"); + + this(value); + static if (size_t.sizeof == 8) { - flags |= Flags.Uint64Flag; - if (i <= int.max) - flags |= Flags.IntFlag | Flags.UintFlag; - else if (i <= uint.max) - flags |= Flags.UintFlag; + size_t ptr = cast(size_t)e; + assert((ptr >> 48) == 0, "Uh on! High ptr bits set... we must be in the distant future!"); + count = cast(uint)ptr; + alloc = cast(ushort)(ptr >> 32); } - else if (i >= int.min) - flags |= Flags.IntFlag; + else + count = cast(size_t)e; + flags |= Flags.Enum; } - this(ulong i) + this(U, ScaledUnit _U)(Quantity!(U, _U) q) { - flags = Flags.NumberUint64; - value.ul = i; - if (i <= int.max) - flags |= Flags.IntFlag | Flags.UintFlag | Flags.Int64Flag; - else if (i <= uint.max) - flags |= Flags.UintFlag | Flags.Int64Flag; - else if (i <= long.max) - flags |= Flags.Int64Flag; + this(q.value); + count = q.unit.pack; } - this(float f) + this(Duration dur) { - flags = Flags.NumberFloat; - value.d = f; + this(dur.as!"nsecs"); + count = Nanosecond.pack; } - this(double d) + + this(String s) { - flags = Flags.NumberDouble; - value.d = d; + if (s.empty) + { + flags = Flags.Null; + return; + } + flags = Flags.String; + value.s = s.ptr; + count = s.length; + if (s.has_rc()) + flags |= Flags.NeedDestruction; + s.ptr = null; } - this(U, ScaledUnit _U)(Quantity!(U, _U) q) + this(size_t N)(MutableString!N s) { - this(q.value); - flags |= Flags.IsQuantity; - count = q.unit.pack; + this(String(s.move)); } - this(const(char)[] s) // TODO: (S)(S s) -// if (is(S : const(char)[])) + this(const(char)[] s) { + if (s.length == 0) + { + flags = Flags.Null; + return; + } if (s.length < embed.length) { flags = Flags.ShortString; @@ -152,17 +273,43 @@ nothrow @nogc: return; } flags = Flags.String; - value.s = s.ptr; + flags |= Flags.NeedDestruction; + String t = s.makeString(defaultAllocator); + value.s = t.ptr.swap(null); count = cast(uint)s.length; } + this(T)(T[] buffer) + if (is(Unqual!T == void)) + { + if (buffer.length == 0) + { + flags = Flags.Null; + return; + } + if (buffer.length < embed.length) + { + flags = Flags.ShortBuffer; + embed[0 .. buffer.length] = cast(char[])buffer[]; + embed[$-1] = cast(ubyte)buffer.length; + return; + } + flags = Flags.Buffer; + flags |= Flags.NeedDestruction; + void[] mem = defaultAllocator.alloc(buffer.length); + mem[] = buffer[]; + ptr = mem.ptr; + count = cast(uint)buffer.length; + } + this(Variant[] a) { flags = Flags.Array; nodeArray = a[]; } + this(T)(T[] a) - if (!is(T == Variant) && !is(T == VariantKVP) && !is(T : dchar)) + if (!is(T == Variant) && !is(T == VariantKVP) && !is(T : dchar) && !is(Unqual!T == void)) { flags = Flags.Array; nodeArray.reserve(a.length); @@ -172,7 +319,7 @@ nothrow @nogc: this(Array!Variant a) { - takeNodeArray(a); + take_node_array(a); flags = Flags.Array; } @@ -203,25 +350,40 @@ nothrow @nogc: this(T)(auto ref T thing) if (ValidUserType!T) { - alias dummy = MakeTypeDetails!T; - - flags = Flags.User; - static if (is(T == class)) - { - count = UserTypeId!T; - value.p = cast(void*)&thing; - } - else static if (EmbedUserType!T) - { - flags |= Flags.Embedded; - alloc = UserTypeShortId!T; - emplace(cast(T*)embed.ptr, forward!thing); - } + static if (is(Unqual!T == MonoTime)) + this(cast(SysTime)thing); else { -// flags |= Flags.NeedDestruction; // if T has a destructor... - count = UserTypeId!T; - assert(false, "TODO: alloc for the object..."); + alias dummy = MakeTypeDetails!T; + + flags = Flags.User; + static if (is(T == class)) + { + count = UserTypeId!T; + alloc = type_detail_index!T(); + ptr = cast(void*)thing; + } + else static if (EmbedUserType!T) + { + alloc = UserTypeId!T; + flags |= Flags.Embedded; + + if (TypeDetailsFor!T.destroy) // TODO: we should check the same condition that determined if there is a destruct function... + flags |= Flags.NeedDestruction; + + emplace(cast(T*)embed.ptr, forward!thing); + } + else + { + count = UserTypeId!T; + alloc = type_detail_index!T(); + + if (TypeDetailsFor!T.destroy) // TODO: we should check the same condition that determined if there is a destruct function... + flags |= Flags.NeedDestruction; + + ptr = defaultAllocator().alloc(T.sizeof, T.alignof).ptr; + emplace(cast(T*)ptr, forward!thing); + } } } @@ -230,12 +392,35 @@ nothrow @nogc: destroy!false(); } + void opAssign(ref Variant value) + { + if (&this is &value) + return; // TODO: should this be an assert instead of a graceful handler? + destroy!false(); + new(this) Variant(value); + } + + void opAssign(ref const Variant value) + { + if (&this is &value) + return; // TODO: should this be an assert instead of a graceful handler? + destroy!false(); + new(this) Variant(value); + } + + version (EnableMoveSemantics) { + void opAssign(Variant value) + { + destroy!false(); + new(this) Variant(__rvalue(value)); // TODO: value.move + } + } + // TODO: since this is a catch-all, the error messages will be a bit shit // maybe we can find a way to constrain it to valid inputs? void opAssign(T)(auto ref T value) { - destroy!false(); - emplace(&this, forward!value); + this = Variant(value); } // TODO: do we want Variant to support +=, ~=, etc...? @@ -266,6 +451,228 @@ nothrow @nogc: return nodeArray.pushBack(); } + bool opEquals(ref const Variant rhs) const pure + { + return opCmp(rhs) == 0; + } + bool opEquals(T)(auto ref const T rhs) const + if (!is(T == Variant)) + { + // TODO: handle short-cut array/map comparisons? + static if (is(T == typeof(null))) + return type == Type.Null || ((type == Type.String || type == Type.Array || type == Type.Map) && empty()); + else static if (is(T == bool)) + { + if (!isBool) + return false; // do non-zero numbers evaluate true? what about non-zero strings? etc... + return asBool == rhs; + } + else static if (is_some_int!T || is_some_float!T) + { + if (!isNumber || isQuantity) + return false; + static if (is_some_int!T) if (!canFitInt!T) + return false; + return as!T == rhs; + } + else static if (is(T == Quantity!(U, _U), U, ScaledUnit _U)) + { + if (!isNumber) + return false; + return asQuantity!double() == rhs; + } + else static if (is(T E == enum)) + { + // TODO: should we also do string key comparisons? + return opEquals(cast(E)rhs); + } + else static if (is(T : const(char)[]) || is(T : const String) || is(T : const MutableString!N, size_t N)) + return isString && asString() == rhs[]; + else static if (ValidUserType!T) + return isUser!T && asUser!T == rhs; + else + static assert(false, "TODO: variant comparison with '", T.stringof, "' not supported"); + } + + int opCmp(ref const Variant rhs) const pure + { + const(Variant)* a, b; + bool invert = false; + if (this.type <= rhs.type) + a = &this, b = &rhs; + else + a = &rhs, b = &this, invert = true; + + int r = 0; + final switch (a.type) + { + case Type.Null: + if (b.type == Type.Null) + return 0; + else if ((b.type == Type.Buffer || b.type == Type.Array || b.type == Type.Map) && b.empty()) + return 0; + r = -1; // sort null before other things... + break; + + case Type.True: + case Type.False: + // if both sides are bool + if (b.type <= Type.False) + { + r = a.asBool - b.asBool; + break; + } + // TODO: maybe we don't want to accept bool/number comparison? + goto case; // we will compare bools with numbers... + + case Type.Number: + if (b.type <= Type.Number) + { + static double asDoubleWithBool(ref const Variant v) + => v.isBool() ? double(v.asBool()) : v.asDouble(); + + uint aunit = a.count; + uint bunit = b.count; + if (aunit || bunit) + { + // we can't compare different units + if ((aunit & 0xFFFFFF) != (bunit & 0xFFFFFF)) + { + r = (aunit & 0xFFFFFF) - (bunit & 0xFFFFFF); + break; + } + + // matching units, but we'll only do quantity comparison if there is some scaling + if ((aunit >> 24) != (bunit >> 24)) + { + VarQuantity aq = a.isNumber ? a.asQuantity!double() : VarQuantity(double(a.asBool())); + VarQuantity bq = b.isNumber ? b.asQuantity!double() : VarQuantity(double(b.asBool())); + r = aq.opCmp(bq); + break; + } + } + + if (a.flags & Flags.DoubleFlag || b.flags & Flags.DoubleFlag) + { + // float comparison + // TODO: determine if float/bool comparison seems right? is: -1 < false < 0.9 < true < 1.1? + double af = asDoubleWithBool(*a); + double bf = asDoubleWithBool(*b); + r = af < bf ? -1 : af > bf ? 1 : 0; + break; + } + + // TODO: this could be further optimised by comparing the value range flags... + if ((a.flags & (Flags.Int64Flag | Flags.IsBool)) == 0) + { + ulong aul = a.asUlong(); + if ((b.flags & (Flags.Int64Flag | Flags.IsBool)) == 0) + { + ulong bul = b.asUlong(); + r = aul < bul ? -1 : aul > bul ? 1 : 0; + } + else + r = 1; // a is in ulong range, rhs is not; a is larger... + break; + } + if ((b.flags & (Flags.Int64Flag | Flags.IsBool)) == 0) + { + r = -1; // b is in ulong range, lhs is not; b is larger... + break; + } + + long al = a.isBool() ? a.asBool() : a.asLong(); + long bl = b.isBool() ? b.asBool() : b.asLong(); + r = al < bl ? -1 : al > bl ? 1 : 0; + } + else + r = -1; // sort numbers before other things... + break; + + case Type.Buffer: + if (b.type != Type.Buffer) + { + r = -1; + break; + } + r = compare(cast(const(char)[])a.asBuffer(), cast(const(char)[])b.asBuffer()); + break; + + case Type.Array: + if (b.type != Type.Array) + { + r = -1; + break; + } + r = compare(a.asArray()[], b.asArray()[]); + break; + + case Type.Map: + if (b.type != Type.Map) + { + r = -1; + break; + } + assert(false, "TODO"); + break; + + case Type.User: + uint at = a.userType; + uint bt = b.userType; + if (at != bt) + { + r = at < bt ? -1 : at > bt ? 1 : 0; + break; + } + alias PureHack = ref TypeDetails function(uint index) pure nothrow @nogc; + if (flags & Flags.Embedded) + { + ref const TypeDetails td = (cast(PureHack)&find_type_details)(alloc); + r = td.cmp(a.embed.ptr, b.embed.ptr, 0); + } + else + { + ref const TypeDetails td = (cast(PureHack)&get_type_details)(alloc); + r = td.cmp(a.ptr, b.ptr, 0); + } + break; + } + return invert ? -r : r; + } + int opCmp(T)(auto ref const T rhs) const + if (!is(T == Variant)) + { + // TODO: handle short-cut string, array, map comparisons + static if (is(T == typeof(null))) + return type == Type.Null || ((type == Type.String || type == Type.Array || type == Type.Map) && empty()) ? 0 : 1; + else static if (is(T : const(char)[])) + return isString() ? compare(asString(), rhs) : (type < Type.String ? -1 : 1); + static if (ValidUserType!T) + return compare(asUser!T, rhs); + else + return opCmp(Variant(rhs)); + } + + bool opBinary(string op)(ref const Variant rhs) const pure + if (op == "is") + { + // compare that Variant's are identical, not just equivalent! + assert(false, "TODO"); + } + bool opBinary(string op, T)(auto ref const T rhs) const + if (op == "is") + { + // TODO: handle short-cut array/map comparisons? + static if (is(T == typeof(null))) + return type == Type.Null || ((type == Type.String || type == Type.Array || type == Type.Map) && empty()); + else static if (is(T : const(char)[])) + return isString && asString().ptr is rhs.ptr && length() == rhs.length; + else static if (ValidUserType!T) + return asUser!T is rhs; + else + return opBinary!"is"(Variant(rhs)); + } + bool isNull() const pure => flags == Flags.Null; bool isFalse() const pure @@ -289,55 +696,113 @@ nothrow @nogc: bool isDouble() const pure => (flags & Flags.DoubleFlag) != 0; bool isQuantity() const pure - => (flags & Flags.IsQuantity) != 0; + => isNumber && count != 0 && !is_enum; + bool isDuration() const pure + => isNumber && (count & 0xFFFFFF) == Second.pack; + bool is_enum() const pure + => (flags & Flags.Enum) != 0; + bool isBuffer() const pure + => type == Type.Buffer; bool isString() const pure => (flags & Flags.IsString) != 0; bool isArray() const pure => flags == Flags.Array; bool isObject() const pure => flags == Flags.Map; + bool isUserType() const pure + => (flags & Flags.TypeMask) == Type.User; bool isUser(T)() const pure - if (ValidUserType!T) + if (ValidUserType!(Unqual!T)) { + alias U = Unqual!T; if ((flags & Flags.TypeMask) != Type.User) return false; - static if (EmbedUserType!T) - return alloc == UserTypeShortId!T; + static if (EmbedUserType!U) + return alloc == UserTypeId!U; else - return count == UserTypeId!T; + { + if (count == UserTypeId!U) + return true; + static if (is(T == class)) + { + immutable(TypeDetails)* td = &get_type_details(alloc); + while (td.super_type_id) + { + if (td.super_type_id == UserTypeId!U) + return true; + td = &find_type_details(td.super_type_id); + } + } + return false; + } } + bool canFitInt(I)() const pure + if (is_some_int!I) + { + if (!isNumber || isFloat) + return false; + static if (is(I == ulong)) + return isUlong; + else static if (is(I == long)) + return isLong; + else static if (is(I == uint)) + return isUint; + else static if (is(I == int)) + return isInt; + else static if (is_signed_int!I) + { + if (!isInt) + return false; + int i = asInt(); + return i >= I.min && i <= I.max; + } + else + return isUlong && asUlong <= I.max; + } bool asBool() const pure @property { + if (isNull) + return false; assert(isBool()); return flags == Flags.True; } int asInt() const pure @property { - assert(isInt()); + if (isNull) + return 0; + assert(isInt(), "Value out of range for int"); return cast(int)value.l; } uint asUint() const pure @property { - assert(isUint()); + if (isNull) + return 0; + assert(isUint(), "Value out of range for uint"); return cast(uint)value.ul; } long asLong() const pure @property { - assert(isLong()); + if (isNull) + return 0; + assert(isLong(), "Value out of range for long"); return value.l; } ulong asUlong() const pure @property { - assert(isUlong()); + if (isNull) + return 0; + assert(isUlong(), "Value out of range for ulong"); return value.ul; } double asDouble() const pure @property { - assert(isNumber()); + if (isNull) + return 0; + assert(isNumber); if ((flags & Flags.DoubleFlag) != 0) return value.d; if ((flags & Flags.UintFlag) != 0) @@ -349,33 +814,63 @@ nothrow @nogc: return cast(double)cast(long)value.ul; } + float asFloat() const pure @property + { + if (isNull) + return 0; + assert(isNumber); + if ((flags & Flags.DoubleFlag) != 0) + return value.d; + if ((flags & Flags.UintFlag) != 0) + return cast(float)cast(uint)value.ul; + if ((flags & Flags.IntFlag) != 0) + return cast(float)cast(int)cast(long)value.ul; + if ((flags & Flags.Uint64Flag) != 0) + return cast(float)value.ul; + return cast(float)cast(long)value.ul; + } + Quantity!T asQuantity(T = double)() const pure @property + if (is_some_float!T || is_some_int!T) { - assert(isNumber()); + if (isNull) + return Quantity!T(0); + assert(isNumber); + Quantity!T r; + r.value = as!T; + r.unit.pack = count; + return r; + } - Quantity!double r; - static if (is(T == double)) - r.value = asDouble(); -// else static if (is(T == float)) -// r.value = asFloat(); - else static if (is(T == int)) - r.value = asInt(); - else static if (is(T == uint)) - r.value = asUint(); - else static if (is(T == long)) - r.value = asLong(); - else static if (is(T == ulong)) - r.value = asUlong(); + Duration asDuration() const pure @property + { + assert(isDuration); + alias Nanoseconds = Quantity!(long, Nanosecond); + Nanoseconds ns; + if (size_t.sizeof < 8 && isFloat) // TODO: better way to detect if double is NOT supported in hardware? + ns = cast(Nanoseconds)asQuantity!float(); + else if (isDouble) + ns = cast(Nanoseconds)asQuantity!double(); else - assert(false, "Unsupported quantity type!"); - if (isQuantity()) - r.unit.pack = count; - return r; + ns = asQuantity!long(); + return ns.value.dur!"nsecs"; + } + + const(void)[] asBuffer() const pure + { + if (isNull) + return null; + assert(isBuffer); + if (flags & Flags.Embedded) + return embed[0 .. embed[$-1]]; + return ptr[0 .. count]; } const(char)[] asString() const pure { - assert(isString()); + if (isNull) + return null; + assert(isString); if (flags & Flags.Embedded) return embed[0 .. embed[$-1]]; return value.s[0 .. count]; @@ -397,44 +892,194 @@ nothrow @nogc: } ref inout(T) asUser(T)() inout pure - if (ValidUserType!T && UserTypeReturnByRef!T) + if (ValidUserType!(Unqual!T) && UserTypeReturnByRef!T) { - if (!isUser!T) - assert(false, "Variant is not a " ~ T.stringof); - static assert(!is(T == class), "Should be impossible?"); - static if (EmbedUserType!T) + alias U = Unqual!T; + if (!isUser!U) + assert(false, "Variant is not a " ~ U.stringof); + static assert(!is(U == class), "Should be impossible?"); + static if (EmbedUserType!U) return *cast(inout(T)*)embed.ptr; else return *cast(inout(T)*)ptr; } - inout(T) asUser(T)() inout pure - if (ValidUserType!T && !UserTypeReturnByRef!T) + inout(T) asUser(T)() inout + if (ValidUserType!(Unqual!T) && !UserTypeReturnByRef!T) { - if (!isUser!T) - assert(false, "Variant is not a " ~ T.stringof); - static if (is(T == class)) - return cast(inout(T))ptr; - else static if (EmbedUserType!T) - static assert(false, "TODO: memcpy to a stack local and return that..."); + alias U = Unqual!T; + + // some hacks for builtin time types... + static if (is(U == MonoTime)) + return cast(MonoTime)asUser!SysTime; + else static if (is(T == SysTime)) + { + static assert (EmbedUserType!SysTime && EmbedUserType!DateTime); + if (isUser!SysTime) + return *cast(inout(SysTime)*)embed.ptr; + if (isUser!DateTime) + return getSysTime(*cast(DateTime*)embed.ptr); + assert(false, "Variant is not a timestamp"); + } + else static if (is(T == DateTime)) + { + static assert (EmbedUserType!SysTime && EmbedUserType!DateTime); + if (isUser!DateTime) + return *cast(inout(DateTime)*)embed.ptr; + if (isUser!SysTime) + return getDateTime(*cast(SysTime*)embed.ptr); + assert(false, "Variant is not a timestamp"); + } else - static assert(false, "Should be impossible?"); + { + if (!isUser!U) + assert(false, "Variant is not a " ~ U.stringof); + static if (is(U == class)) + return cast(inout(T))ptr; + else static if (EmbedUserType!U) + { + // TODO: it would be nice to support trivial types and just cast/copy rather than copy_emplace(...) + + // make a copy on the stack and return by value + U r = void; + TypeDetailsFor!U.copy_emplace(cast(U*)embed.ptr, &r, false); + return r; + } + else + static assert(false, "Should be impossible?"); + } + } + + template as(T) + { + static if (!is(T == Unqual!T)) + alias as = as!(Unqual!T); + else static if (is_boolean!T) + alias as = asBool; + else static if (is(T == long)) + alias as = asLong; + else static if (is(T == int)) + alias as = asInt; + else static if (is(T == ulong)) + alias as = asUlong; + else static if (is(T == uint)) + alias as = asUint; + else static if (is_some_int!T) + { + T as() const pure + { + static if (is_signed_int!T) + { + int i = asInt(); + assert(i >= T.min && i <= T.max, "Value out of range for " ~ T.stringof); + } + else + { + uint i = asUint(); + assert(i <= T.max, "Value out of range for " ~ T.stringof); + } + return cast(T)i; + } + } + else static if (is(T == float)) + alias as = asFloat; + else static if (is(T == double)) + alias as = asDouble; + else static if (is(T == real)) + real as() const pure => asDouble; + else static if (is(T == Quantity!(U, _U), U, ScaledUnit _U)) + alias as = asQuantity!U; + else static if (is(T == Duration)) + alias as = asDuration; + else static if (is(const(char)[] : T)) + alias as = asString; + else static if (is(T == String)) + { + String as() const pure + { + if (isNull) + return String(); + assert(isString); + if (flags & Flags.Embedded) + return embed[0 .. embed[$-1]].makeString(defaultAllocator); + return *cast(String*)&value.s; + } + } + else static if (ValidUserType!(Unqual!T)) + alias as = asUser!T; + else + static assert(false, "TODO!"); + } + + Array!Variant take_array() + { + if (flags == Flags.Null) + return Array!Variant(); + assert(isArray()); + Array!Variant r; + swap(r, nodeArray); + flags = Flags.Null; + return r; + } + + String take_string() + { + if (flags == Flags.Null) + return String(); + assert(isString); + if (flags & Flags.Embedded) + return embed[0 .. embed[$-1]].makeString(defaultAllocator); + String s; + s.ptr = value.s; + value.s = null; + flags = Flags.Null; + return s; + } + + ScaledUnit get_unit() const pure + { + assert(isQuantity()); + return *cast(ScaledUnit*)&count; + } + + const(VoidEnumInfo)* get_enum_info() const pure + { + assert(is_enum); + static if (size_t.sizeof == 8) + return cast(VoidEnumInfo*)(count | (size_t(alloc) << 32)); + else + return cast(VoidEnumInfo*)count; } size_t length() const pure { if (flags == Flags.Null) return 0; - else if (isString()) + else if (isBuffer()) return (flags & Flags.Embedded) ? embed[$-1] : count; else if (isArray()) return count; else - assert(false); + assert(false, "Variant does not have `length`"); } bool empty() const pure => isObject() ? count == 0 : length() == 0; + Variant* insert(const(char)[] key, Variant value) + { + if (flags == Flags.Null) + flags = Flags.Map; + else + { + assert(isObject()); + if (getMember(key)) + return null; + } + nodeArray.emplaceBack(key); + move(value, nodeArray.pushBack()); + return &nodeArray.back(); + } + inout(Variant)* getMember(const(char)[] member) inout pure { assert(isObject()); @@ -446,6 +1091,12 @@ nothrow @nogc: return null; } + void set_unit(ScaledUnit unit) + { + assert(isNumber()); + count = unit.pack; + } + // TODO: this seems to interfere with UFCS a lot... // ref inout(Variant) opDispatch(string member)() inout pure // { @@ -479,73 +1130,160 @@ nothrow @nogc: { final switch (type) { - case Variant.Type.Null: - if (!buffer.ptr) - return 4; - if (buffer.length < 4) - return -1; - buffer[0 .. 4] = "null"; - return 4; - - case Variant.Type.False: - if (!buffer.ptr) - return 5; - if (buffer.length < 5) - return -1; - buffer[0 .. 5] = "false"; - return 5; - - case Variant.Type.True: + case Variant.Type.Null: // assume type == 0 + case Variant.Type.True: // assume type == 1 + case Variant.Type.False: // assume type == 2 + __gshared immutable char** values = [ "null", "true", "false" ]; + size_t len = 4 + (type >> 1); if (!buffer.ptr) - return 4; - if (buffer.length < 4) + return len; + if (buffer.length < len) return -1; - buffer[0 .. 4] = "true"; - return 4; + buffer[0 .. len] = values[type][0 .. len]; + return len; case Variant.Type.Number: - import urt.conv; - if (isQuantity()) - assert(false, "TODO: implement quantity formatting for JSON"); + return asQuantity().toString(buffer, format, formatArgs); if (isDouble()) - return asDouble().formatFloat(buffer); + return asDouble().format_float(buffer); // TODO: parse args? - //format + assert(!format, "TODO"); if (flags & Flags.Uint64Flag) - return asUlong().formatUint(buffer); - return asLong().formatInt(buffer); + return asUlong().format_uint(buffer); + return asLong().format_int(buffer); - case Variant.Type.String: - const char[] s = asString(); - if (buffer.ptr) + case Variant.Type.Buffer: + if (isString) + { + const char[] s = asString(); + if (buffer.ptr) + { + // TODO: should we write out quotes? + // a string of a number won't be distinguishable from a number without quotes... + if (buffer.length < s.length) + return -1; + buffer[0 .. s.length] = s[]; + } + return s.length; + } + else { - // TODO: should we write out quotes? - // a string of a number won't be distinguishable from a number without quotes... - if (buffer.length < s.length) - return -1; - buffer[0 .. s.length] = s[]; + import urt.string.format : formatValue; + return formatValue(asBuffer(), buffer, format, formatArgs); } - return s.length; case Variant.Type.Map: case Variant.Type.Array: import urt.format.json; // should we just format this like JSON or something? - return writeJson(this, buffer); + return write_json(this, buffer); case Variant.Type.User: if (flags & Flags.Embedded) - return findTypeDetails(alloc).stringify(embed.ptr, buffer); + return find_type_details(alloc).stringify(cast(void*)embed.ptr, buffer, true, format, formatArgs); + else + return g_type_details[alloc].stringify(cast(void*)ptr, buffer, true, format, formatArgs); + } + } + + ptrdiff_t fromString(const(char)[] s) + { + import urt.string.ascii : is_numeric; + + if (s.empty || s == "null") + { + this = null; + return s.length; + } + if (s == "true") + { + this = true; + return 4; + } + if (s == "false") + { + this = false; + return 5; + } + + if (s[0] == '"') + { + for (size_t i = 1; i < s.length; ++i) + { + if (s[i] == '"') + { + assert(i == s.length - 1, "String must end with a quote"); + this = s[1 .. i]; + return i + 1; + } + } + assert(false, "String has no closing quote"); + } + + if (s[0].is_numeric || ((s[0] == '+' || s[0] == '-') && s.length > 1 && s[1].is_numeric)) + { + size_t taken; + ScaledUnit unit; + int e; + long i = s.parse_int_with_exponent(e, &taken, 10); + if (taken < s.length) + { + size_t t2 = unit.fromString(s[taken .. $]); + if (t2 > 0) + taken += t2; + } + if (taken == s.length) + { + if (e != 0) + this = i * 10.0^^e; + else + this = i; + count = unit.pack; + return taken; + } + } + + align(64) void[256] buffer = void; + this = null; // clear the object since we'll probably use the embed buffer... + foreach (ushort i; 0 .. g_num_type_details) + { + ref immutable TypeDetails td = get_type_details(i); + debug assert(td.alignment <= 64 && td.size <= buffer.sizeof, "Buffer is too small for user type!"); + ptrdiff_t taken = td.stringify(td.embedded ? embed.ptr : buffer.ptr, cast(char[])s, false, null, null); + if (taken > 0) + { + flags = Flags.User; + if (td.destroy) + flags |= Flags.NeedDestruction; + if (td.embedded) + { + flags |= Flags.Embedded; + alloc = cast(ushort)td.type_id; + } else - return findTypeDetails(count).stringify(ptr, buffer); + { + void* object = defaultAllocator().alloc(td.size, td.alignment).ptr; + td.copy_emplace(buffer.ptr, object, true); + if (td.destroy) + td.destroy(buffer.ptr); + ptr = object; + count = td.type_id; + alloc = i; + } + return taken; + } } + + // what is this? + assert(false, "Can't parse variant from string"); } + package: union Value { @@ -586,9 +1324,22 @@ package: Type type() const pure => cast(Type)(flags & Flags.TypeMask); + uint userType() const pure + { + if (flags & Flags.Embedded) + return alloc; // short id + return count; // long id + } + inout(void)* user_ptr() inout pure + { + if (flags & Flags.Embedded) + return embed.ptr; + return ptr; + } + ref inout(Array!Variant) nodeArray() @property inout pure => *cast(inout(Array!Variant)*)&value.n; - void takeNodeArray(ref Array!Variant arr) + void take_node_array(ref Array!Variant arr) { value.n = arr[].ptr; count = cast(uint)arr.length; @@ -598,33 +1349,41 @@ package: void destroy(bool reset = true)() { if (flags & Flags.NeedDestruction) - doDestroy(); + do_destroy(); static if (reset) __pack[] = 0; } - private void doDestroy() + private void do_destroy() { Type t = type(); - if ((t == Type.Map || t == Type.Array) && value.n) + if (isBuffer) + { + if (isString) + *cast(String*)&value.s = null; + else + defaultAllocator().free(ptr[0..count]); + } + else if ((t == Type.Map || t == Type.Array) && value.n) nodeArray.destroy!false(); else if (t == Type.User) { - if (flags & Flags.Embedded) - findTypeDetails(alloc).destroy(embed.ptr); - else - findTypeDetails(count).destroy(ptr); + ref const TypeDetails td = (flags & Flags.Embedded) ? find_type_details(alloc) : g_type_details[alloc]; + if (td.destroy) + td.destroy(user_ptr); + if (!(flags & Flags.Embedded)) + defaultAllocator().free(ptr[0..td.size]); } } enum Type : ushort { Null = 0, - False = 1, - True = 2, + True = 1, + False = 2, Number = 3, - String = 4, + Buffer = 4, Array = 5, Map = 6, User = 7 @@ -641,8 +1400,10 @@ package: NumberUint64 = cast(Flags)Type.Number | Flags.IsNumber | Flags.Uint64Flag, NumberFloat = cast(Flags)Type.Number | Flags.IsNumber | Flags.FloatFlag | Flags.DoubleFlag, NumberDouble = cast(Flags)Type.Number | Flags.IsNumber | Flags.DoubleFlag, - String = cast(Flags)Type.String | Flags.IsString, - ShortString = cast(Flags)Type.String | Flags.IsString | Flags.Embedded, + Buffer = cast(Flags)Type.Buffer, + ShortBuffer = cast(Flags)Type.Buffer | Flags.Embedded, + String = cast(Flags)Type.Buffer | Flags.IsString, + ShortString = cast(Flags)Type.Buffer | Flags.IsString | Flags.Embedded, Array = cast(Flags)Type.Array | Flags.NeedDestruction, Map = cast(Flags)Type.Map | Flags.NeedDestruction, User = cast(Flags)Type.User, @@ -656,7 +1417,7 @@ package: Uint64Flag = 1 << (TypeBits + 6), FloatFlag = 1 << (TypeBits + 7), DoubleFlag = 1 << (TypeBits + 8), - IsQuantity = 1 << (TypeBits + 9), + Enum = 1 << (TypeBits + 9), Embedded = 1 << (TypeBits + 10), NeedDestruction = 1 << (TypeBits + 11), // CopyFlag = 1 << (TypeBits + 12), // maybe we want to know if a thing is a copy, or a reference to an external one? @@ -691,15 +1452,22 @@ unittest private: -import urt.hash : fnv1aHash; +import urt.hash : fnv1a; +import urt.string.format : formatValue, FormatArg; static assert(Variant.sizeof == 16); static assert(Variant.Type.max <= Variant.Flags.TypeMask); -enum uint UserTypeId(T) = fnv1aHash(cast(const(ubyte)[])T.stringof); // maybe this isn't a good enough hash? -enum uint UserTypeShortId(T) = cast(ushort)UserTypeId!T ^ (UserTypeId!T >> 16); +template UserTypeId(T) +{ + enum uint Hash = fnv1a(cast(const(ubyte)[])T.stringof); // maybe this isn't a good enough hash? + static if (!EmbedUserType!T) + enum uint UserTypeId = Hash; + else + enum ushort UserTypeId = cast(ushort)Hash ^ (Hash >> 16); +} enum bool EmbedUserType(T) = is(T == struct) && T.sizeof <= Variant.embed.sizeof - 2 && T.alignof <= Variant.alignof; -enum bool UserTypeReturnByRef(T) = is(T == struct); +enum bool UserTypeReturnByRef(T) = is(T == struct) && !EmbedUserType!T; ptrdiff_t newline(char[] buffer, ref ptrdiff_t offset, int level) { @@ -713,46 +1481,175 @@ ptrdiff_t newline(char[] buffer, ref ptrdiff_t offset, int level) template MakeTypeDetails(T) { + static assert(is(Unqual!T == T), "Only instantiate for mutable types"); + +// pragma(msg, "Add user type: ", T.stringof, EmbedUserType!T ? " - embedded" : ""); + // this is a hack which populates an array of user type details when the program starts // TODO: we can probably NOT do this for class types, and just use RTTI instead... shared static this() { - assert(numTypeDetails < typeDetails.length, "Too many user types!"); - - TypeDetails* ty = &typeDetails[numTypeDetails++]; - static if (EmbedUserType!T) - ty.typeId = UserTypeShortId!T; - else - ty.typeId = UserTypeId!T; - // TODO: I'd like to not generate a destroy function if the data is POD - ty.destroy = (void* val) { - static if (!is(T == class)) - destroy!false(*cast(T*)val); - }; - ty.stringify = (const void* val, char[] buffer) { - import urt.string.format : toString; - return toString(*cast(T*)val, buffer); - }; + assert(g_num_type_details < g_type_details.length, "Too many user types!"); + g_type_details[g_num_type_details++] = TypeDetailsFor!T; } alias MakeTypeDetails = void; } +ushort type_detail_index(T)() pure + if (ValidUserType!T) +{ + ushort count = (cast(ushort function() pure nothrow @nogc)&num_type_details)(); + foreach (ushort i; 0 .. count) + if (get_type_details(i).type_id == UserTypeId!T) + return i; + assert(false, "Why wasn't the type registered?"); +} + struct TypeDetails { - uint typeId; + uint type_id; + uint super_type_id; + ushort size; + ubyte alignment; + bool embedded; + void function(void* src, void* dst, bool move) nothrow @nogc copy_emplace; void function(void* val) nothrow @nogc destroy; - ptrdiff_t function(const void* val, char[] buffer) nothrow @nogc stringify; + ptrdiff_t function(void* val, char[] buffer, bool do_format, const(char)[] format_spec, const(FormatArg)[] format_args) nothrow @nogc stringify; + int function(const void* a, const void* b, int type) pure nothrow @nogc cmp; } -TypeDetails[8] typeDetails; -size_t numTypeDetails = 0; +__gshared TypeDetails[16] g_type_details; +__gshared ushort g_num_type_details = 0; -ref TypeDetails findTypeDetails(uint typeId) +typeof(g_type_details)* type_details() => &g_type_details; +ushort num_type_details() => g_num_type_details; + +ref immutable(TypeDetails) find_type_details(uint type_id) pure { - foreach (i, ref td; typeDetails[0 .. numTypeDetails]) + auto tds = (cast(immutable(typeof(g_type_details)*) function() pure nothrow @nogc)&type_details)(); + ushort count = (cast(ushort function() pure nothrow @nogc)&num_type_details)(); + foreach (i, ref td; (*tds)[0 .. count]) { - if (td.typeId == typeId) + if (td.type_id == type_id) return td; } assert(false, "TypeDetails not found!"); } +ref immutable(TypeDetails) get_type_details(uint index) pure +{ + auto tds = (cast(immutable(typeof(g_type_details)*) function() pure nothrow @nogc)&type_details)(); + debug assert(index < g_num_type_details); + return (*tds)[index]; +} + +public template TypeDetailsFor(T) + if (is(Unqual!T == T) && (is(T == struct) || is(T == class))) +{ + static if (is(T == class) && is(T S == super)) + { + alias Super = Unqual!S; + static if (!is(Super == Object)) + { + alias dummy = MakeTypeDetails!Super; + enum SuperTypeId = UserTypeId!Super; + } + else + enum ushort SuperTypeId = 0; + } + else + enum ushort SuperTypeId = 0; + + static if (!is(T == class)) + { + static void move_emplace_impl(void* src, void* dst, bool move) nothrow @nogc + { + if (move) + moveEmplace(*cast(T*)src, *cast(T*)dst); + else + { + static if (__traits(compiles, { *cast(T*)dst = *cast(const T*)src; })) + *cast(T*)dst = *cast(const T*)src; + else + assert(false, "Can't copy " ~ T.stringof); + } + } + enum move_emplace = &move_emplace_impl; + } + else + enum move_emplace = null; + + static if (!is_trivial!T && is(typeof(destroy!(false, T)))) + { + static void destroy_impl(void* val) nothrow @nogc + { + destroy!false(*cast(T*)val); + } + enum destroy_fun = &destroy_impl; + } + else + enum destroy_fun = null; + + static ptrdiff_t stringify(void* val, char[] buffer, bool do_format, const(char)[] format_spec, const(FormatArg)[] format_args) nothrow @nogc + { + if (do_format) + { + static if (__traits(compiles, { formatValue(*cast(const T*)val, buffer, format_spec, format_args); })) + return formatValue(*cast(const T*)val, buffer, format_spec, format_args); + else + return -1; + } + else + { + static if (is(typeof(parse!T))) + return buffer.parse!T(*cast(T*)val); + else + return -1; + } + } + + int compare(const void* pa, const void* pb, int type) pure nothrow @nogc + { + ref const T a = *cast(const T*)pa; + ref const T b = *cast(const T*)pb; + switch (type) + { + case 0: + static if (is(T == class) || is(T == U*, U) || is(T == V[], V)) + { + if (pa is pb) + return 0; + } + static if (__traits(compiles, { a.opCmp(b); })) + return a.opCmp(b); + else static if (__traits(compiles, { b.opCmp(a); })) + return -b.opCmp(a); + else static if (is(T == class)) + { + ptrdiff_t r = cast(ptrdiff_t)pa - cast(ptrdiff_t)pb; + return r < 0 ? -1 : r > 0 ? 1 : 0; + } + else + assert(false, "No comparison!"); // TODO: hash or stringify the values and order that way? + case 1: + static if (is(T == class) || is(T == U*, U) || is(T == V[], V)) + { + if (pa is pb) + return 1; + } + static if (__traits(compiles, { a.opEquals(b); })) + return a.opEquals(b); + else static if (__traits(compiles, { b.opEquals(a); })) + return b.opEquals(a); + else static if (!is(T == class) && !is(T == U*, U) && !is(T == V[], V)) + return a == b ? 1 : 0; + else + return 0; + case 2: + return pa is pb ? 1 : 0; + default: + assert(false); + } + } + + enum TypeDetailsFor = TypeDetails(UserTypeId!T, SuperTypeId, T.sizeof, T.alignof, EmbedUserType!T, move_emplace, destroy_fun, &stringify, &compare); +} diff --git a/src/urt/zip.d b/src/urt/zip.d index 4bf2521..040a056 100644 --- a/src/urt/zip.d +++ b/src/urt/zip.d @@ -4,164 +4,158 @@ import urt.crc; import urt.endian; import urt.hash; import urt.mem.allocator; +import urt.result; -alias zlib_crc = calculateCRC!(Algorithm.CRC32_ISO_HDLC); +alias zlib_crc = calculate_crc!(Algorithm.crc32_iso_hdlc); nothrow @nogc: // this is a port of tinflate (tiny inflate) -enum error_code : int +enum GzipFlag : ubyte { - OK = 0, /**< Success */ - DATA_ERROR = -3, /**< Input error */ - BUF_ERROR = -5 /**< Not enough room for output */ + ftext = 1, + fhcrc = 2, + fextra = 4, + fname = 8, + fcomment = 16 } -enum gzip_flag : ubyte -{ - FTEXT = 1, - FHCRC = 2, - FEXTRA = 4, - FNAME = 8, - FCOMMENT = 16 -} - -error_code zlib_uncompress(const(void)[] source, void[] dest, out size_t destLen) +Result zlib_uncompress(const(void)[] source, void[] dest, out size_t destLen) { const ubyte* src = cast(const ubyte*)source.ptr; uint sourceLen = cast(uint)source.length; if (sourceLen < 6) - return error_code.DATA_ERROR; + return InternalResult.data_error; ubyte cmf = src[0]; ubyte flg = src[1]; // check checksum if ((256 * cmf + flg) % 31) - return error_code.DATA_ERROR; + return InternalResult.data_error; // check method is deflate if ((cmf & 0x0F) != 8) - return error_code.DATA_ERROR; + return InternalResult.data_error; // check window size is valid if ((cmf >> 4) > 7) - return error_code.DATA_ERROR; + return InternalResult.data_error; // check there is no preset dictionary if (flg & 0x20) - return error_code.DATA_ERROR; + return InternalResult.data_error; - if (uncompress((src + 2)[0 .. sourceLen - 6], dest, destLen) != error_code.OK) - return error_code.DATA_ERROR; + if (!uncompress((src + 2)[0 .. sourceLen - 6], dest, destLen)) + return InternalResult.data_error; if (adler32(dest[0 .. destLen]) != loadBigEndian!uint(cast(uint*)&src[sourceLen - 4])) - return error_code.DATA_ERROR; + return InternalResult.data_error; - return error_code.OK; + return InternalResult.success; } -error_code gzip_uncompressed_length(const(void)[] source, out size_t destLen) +Result gzip_uncompressed_length(const(void)[] source, out size_t destLen) { const ubyte* src = cast(const ubyte*)source.ptr; uint sourceLen = cast(uint)source.length; if (sourceLen < 18) - return error_code.DATA_ERROR; + return InternalResult.data_error; // check id bytes if (src[0] != 0x1F || src[1] != 0x8B) - return error_code.DATA_ERROR; + return InternalResult.data_error; // check method is deflate if (src[2] != 8) - return error_code.DATA_ERROR; + return InternalResult.data_error; ubyte flg = src[3]; // check that reserved bits are zero if (flg & 0xE0) - return error_code.DATA_ERROR; + return InternalResult.data_error; // get decompressed length destLen = loadLittleEndian!uint(cast(uint*)(src + sourceLen - 4)); - return error_code.OK; + return InternalResult.success; } -error_code gzip_uncompress(const(void)[] source, void[] dest, out size_t destLen) +Result gzip_uncompress(const(void)[] source, void[] dest, out size_t destLen) { const ubyte* src = cast(const ubyte*)source.ptr; uint sourceLen = cast(uint)source.length; if (sourceLen < 18) - return error_code.DATA_ERROR; + return InternalResult.data_error; // check id bytes if (src[0] != 0x1F || src[1] != 0x8B) - return error_code.DATA_ERROR; + return InternalResult.data_error; // check method is deflate if (src[2] != 8) - return error_code.DATA_ERROR; + return InternalResult.data_error; ubyte flg = src[3]; // check that reserved bits are zero if (flg & 0xE0) - return error_code.DATA_ERROR; + return InternalResult.data_error; // skip base header of 10 bytes const(ubyte)* start = src + 10; // skip extra data if present - if (flg & gzip_flag.FEXTRA) + if (flg & GzipFlag.fextra) { uint xlen = loadLittleEndian!ushort(cast(ushort*)start); if (xlen > sourceLen - 12) - return error_code.DATA_ERROR; + return InternalResult.data_error; start += xlen + 2; } // skip file name if present - if (flg & gzip_flag.FNAME) + if (flg & GzipFlag.fname) { do { if (start - src >= sourceLen) - return error_code.DATA_ERROR; + return InternalResult.data_error; } while (*start++); } // skip file comment if present - if (flg & gzip_flag.FCOMMENT) + if (flg & GzipFlag.fcomment) { do { if (start - src >= sourceLen) - return error_code.DATA_ERROR; + return InternalResult.data_error; } while (*start++); } // check header crc if present - if (flg & gzip_flag.FHCRC) + if (flg & GzipFlag.fhcrc) { uint hcrc; if (start - src > sourceLen - 2) - return error_code.DATA_ERROR; + return InternalResult.data_error; hcrc = loadLittleEndian!ushort(cast(ushort*)start); if (hcrc != (zlib_crc(src[0 .. start - src]) & 0x0000FFFF)) - return error_code.DATA_ERROR; + return InternalResult.data_error; start += 2; } @@ -169,25 +163,25 @@ error_code gzip_uncompress(const(void)[] source, void[] dest, out size_t destLen // get decompressed length uint dlen = loadLittleEndian!uint(cast(uint*)(src + sourceLen - 4)); if (dlen > dest.length) - return error_code.BUF_ERROR; + return InternalResult.buffer_too_small; if ((src + sourceLen) - start < 8) - return error_code.DATA_ERROR; + return InternalResult.data_error; - if (uncompress(start[0 .. (src + sourceLen) - start - 8], dest, destLen) != error_code.OK) - return error_code.DATA_ERROR; + if (!uncompress(start[0 .. (src + sourceLen) - start - 8], dest, destLen)) + return InternalResult.data_error; if (destLen != dlen) - return error_code.DATA_ERROR; + return InternalResult.data_error; // check CRC32 checksum if (zlib_crc(dest[0..dlen]) != loadLittleEndian!uint(cast(uint*)(src + sourceLen - 8))) - return error_code.DATA_ERROR; + return InternalResult.data_error; - return error_code.OK; + return InternalResult.success; } -error_code uncompress(const(void)[] source, void[] dest, out size_t destLen) +Result uncompress(const(void)[] source, void[] dest, out size_t destLen) { data d; @@ -211,7 +205,7 @@ error_code uncompress(const(void)[] source, void[] dest, out size_t destLen) uint btype = getbits(&d, 2); // Decompress block - error_code res; + Result res; switch (btype) { case 0: @@ -227,20 +221,20 @@ error_code uncompress(const(void)[] source, void[] dest, out size_t destLen) res = inflate_dynamic_block(&d); break; default: - res = error_code.DATA_ERROR; + res = InternalResult.data_error; break; } - if (res != error_code.OK) + if (!res) return res; } while (!bfinal); if (d.overflow) - return error_code.DATA_ERROR; + return InternalResult.data_error; destLen = d.dest - d.dest_start; - return error_code.OK; + return InternalResult.success; } @@ -606,7 +600,7 @@ void build_fixed_trees(tree *lt, tree *dt) } /* Given an array of code lengths, build a tree */ -error_code build_tree(tree *t, const(ubyte)* lengths, ushort num) +Result build_tree(tree *t, const(ubyte)* lengths, ushort num) { ushort[16] offs = void; uint available; @@ -638,7 +632,7 @@ error_code build_tree(tree *t, const(ubyte)* lengths, ushort num) /* Check length contains no more codes than available */ if (used > available) - return error_code.DATA_ERROR; + return InternalResult.data_error; available = 2 * (available - used); offs[i] = num_codes; @@ -650,7 +644,7 @@ error_code build_tree(tree *t, const(ubyte)* lengths, ushort num) * code that it has length 1 */ if ((num_codes > 1 && available > 0) || (num_codes == 1 && t.counts[1] != 1)) - return error_code.DATA_ERROR; + return InternalResult.data_error; /* Fill in symbols sorted by code */ for (i = 0; i < num; ++i) @@ -669,7 +663,7 @@ error_code build_tree(tree *t, const(ubyte)* lengths, ushort num) t.symbols[1] = cast(ushort)(t.max_sym + 1); } - return error_code.OK; + return InternalResult.success; } /* -- Decode functions -- */ @@ -749,7 +743,7 @@ int decode_symbol(data *d, const tree *t) return t.symbols[base + offs]; } -error_code decode_trees(data *d, tree *lt, tree *dt) +Result decode_trees(data *d, tree *lt, tree *dt) { /* Special ordering of code length codes */ __gshared immutable ubyte[19] clcidx = [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ]; @@ -776,7 +770,7 @@ error_code decode_trees(data *d, tree *lt, tree *dt) * See also: https://github.com/madler/zlib/issues/82 */ if (hlit > 286 || hdist > 30) - return error_code.DATA_ERROR; + return InternalResult.data_error; for (i = 0; i < 19; ++i) lengths[i] = 0; @@ -791,13 +785,13 @@ error_code decode_trees(data *d, tree *lt, tree *dt) } /* Build code length tree (in literal/length tree to save space) */ - error_code res = build_tree(lt, lengths.ptr, 19); - if (res != error_code.OK) + Result res = build_tree(lt, lengths.ptr, 19); + if (res != InternalResult.success) return res; /* Check code length tree is not empty */ if (lt.max_sym == -1) - return error_code.DATA_ERROR; + return InternalResult.data_error; /* Decode code lengths for the dynamic trees */ for (num = 0; num < hlit + hdist; ) @@ -805,14 +799,14 @@ error_code decode_trees(data *d, tree *lt, tree *dt) int sym = decode_symbol(d, lt); if (sym > lt.max_sym) - return error_code.DATA_ERROR; + return InternalResult.data_error; switch (sym) { case 16: /* Copy previous code length 3-6 times (read 2 bits) */ if (num == 0) { - return error_code.DATA_ERROR; + return InternalResult.data_error; } sym = lengths[num - 1]; length = getbits_base(d, 2, 3); @@ -834,7 +828,7 @@ error_code decode_trees(data *d, tree *lt, tree *dt) } if (length > hlit + hdist - num) - return error_code.DATA_ERROR; + return InternalResult.data_error; while (length--) lengths[num++] = cast(ubyte)sym; @@ -842,26 +836,26 @@ error_code decode_trees(data *d, tree *lt, tree *dt) /* Check EOB symbol is present */ if (lengths[256] == 0) - return error_code.DATA_ERROR; + return InternalResult.data_error; /* Build dynamic trees */ res = build_tree(lt, lengths.ptr, hlit); - if (res != error_code.OK) + if (res != InternalResult.success) return res; res = build_tree(dt, lengths.ptr + hlit, hdist); - if (res != error_code.OK) + if (res != InternalResult.success) return res; - return error_code.OK; + return InternalResult.success; } /* -- Block inflate functions -- */ /* Given a stream and two trees, inflate a block of data */ -error_code inflate_block_data(data *d, tree *lt, tree *dt) +Result inflate_block_data(data *d, tree *lt, tree *dt) { /* Extra bits and base tables for length codes */ __gshared immutable ubyte[30] length_bits = [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 127 ]; @@ -877,12 +871,12 @@ error_code inflate_block_data(data *d, tree *lt, tree *dt) /* Check for overflow in bit reader */ if (d.overflow) - return error_code.DATA_ERROR; + return InternalResult.data_error; if (sym < 256) { if (d.dest == d.dest_end) - return error_code.BUF_ERROR; + return InternalResult.buffer_too_small; *d.dest++ = cast(ubyte)sym; } else @@ -892,11 +886,11 @@ error_code inflate_block_data(data *d, tree *lt, tree *dt) /* Check for end of block */ if (sym == 256) - return error_code.OK; + return InternalResult.success; /* Check sym is within range and distance tree is not empty */ if (sym > lt.max_sym || sym - 257 > 28 || dt.max_sym == -1) - return error_code.DATA_ERROR; + return InternalResult.data_error; sym -= 257; @@ -907,16 +901,16 @@ error_code inflate_block_data(data *d, tree *lt, tree *dt) /* Check dist is within range */ if (dist > dt.max_sym || dist > 29) - return error_code.DATA_ERROR; + return InternalResult.data_error; /* Possibly get more bits from distance code */ offs = getbits_base(d, dist_bits[dist], dist_base[dist]); if (offs > d.dest - d.dest_start) - return error_code.DATA_ERROR; + return InternalResult.data_error; if (d.dest_end - d.dest < length) - return error_code.BUF_ERROR; + return InternalResult.buffer_too_small; /* Copy match */ for (i = 0; i < length; ++i) @@ -928,10 +922,10 @@ error_code inflate_block_data(data *d, tree *lt, tree *dt) } /* Inflate an uncompressed block of data */ -error_code inflate_uncompressed_block(data *d) +Result inflate_uncompressed_block(data *d) { if (d.source_end - d.source < 4) - return error_code.DATA_ERROR; + return InternalResult.data_error; /* Get length */ uint length = loadLittleEndian!ushort(cast(ushort*)d.source); @@ -941,15 +935,15 @@ error_code inflate_uncompressed_block(data *d) /* Check length */ if (length != (~invlength & 0x0000FFFF)) - return error_code.DATA_ERROR; + return InternalResult.data_error; d.source += 4; if (d.source_end - d.source < length) - return error_code.DATA_ERROR; + return InternalResult.data_error; if (d.dest_end - d.dest < length) - return error_code.BUF_ERROR; + return InternalResult.buffer_too_small; /* Copy block */ while (length--) @@ -959,19 +953,19 @@ error_code inflate_uncompressed_block(data *d) d.tag = 0; d.bitcount = 0; - return error_code.OK; + return InternalResult.success; } -error_code inflate_fixed_block(data *d) +Result inflate_fixed_block(data *d) { build_fixed_trees(&d.ltree, &d.dtree); return inflate_block_data(d, &d.ltree, &d.dtree); } -error_code inflate_dynamic_block(data *d) +Result inflate_dynamic_block(data *d) { - error_code res = decode_trees(d, &d.ltree, &d.dtree); - if (res != error_code.OK) + Result res = decode_trees(d, &d.ltree, &d.dtree); + if (res != InternalResult.success) return res; return inflate_block_data(d, &d.ltree, &d.dtree); }