diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/editor.xml b/.idea/editor.xml new file mode 100644 index 0000000..963c96f --- /dev/null +++ b/.idea/editor.xml @@ -0,0 +1,344 @@ + + + + + \ No newline at end of file diff --git a/.idea/libblepp.iml b/.idea/libblepp.iml new file mode 100644 index 0000000..f08604b --- /dev/null +++ b/.idea/libblepp.iml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..0b76fe5 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..273fa9d --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index b371a29..2cd3fd2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,9 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) endif() option(WITH_EXAMPLES "Build examples" OFF) +option(WITH_BLUEZ_SUPPORT "Build with BlueZ transport support (HCI/L2CAP)" ON) +option(WITH_NIMBLE_SUPPORT "Build with Nimble transport support (/dev/atbm_ioctl)" OFF) +option(WITH_SERVER_SUPPORT "Build with BLE GATT server support" OFF) include(GNUInstallDirs) @@ -33,7 +36,9 @@ set(HEADERS blepp/xtoa.h blepp/att.h blepp/blestatemachine.h - blepp/att_pdu.h) + blepp/att_pdu.h + blepp/blepp_config.h + blepp/bleclienttransport.h) set(SRC src/att_pdu.cc @@ -45,42 +50,202 @@ set(SRC src/pretty_printers.cc src/att.cc src/lescan.cc + src/bleclienttransport.cc ${HEADERS}) +# BlueZ transport support (client + optional server) +if(WITH_BLUEZ_SUPPORT) + list(APPEND HEADERS + blepp/bluez_client_transport.h) + + list(APPEND SRC + src/bluez_client_transport.cc) +endif() + +# Nimble transport support (client + optional server) +if(WITH_NIMBLE_SUPPORT) + list(APPEND HEADERS + blepp/nimble_client_transport.h) + + list(APPEND SRC + src/nimble_client_transport.cc) +endif() + +# Server support headers and sources +if(WITH_SERVER_SUPPORT) + list(APPEND HEADERS + blepp/bletransport.h + blepp/bleattributedb.h + blepp/gatt_services.h + blepp/blegattserver.h) + + list(APPEND SRC + src/bleattributedb.cc + src/blegattserver.cc) + + # BlueZ server transport (only if BlueZ support enabled) + if(WITH_BLUEZ_SUPPORT) + list(APPEND HEADERS + blepp/bluez_transport.h) + + list(APPEND SRC + src/bluez_transport.cc) + endif() + + # Nimble server transport (only if Nimble support enabled) + if(WITH_NIMBLE_SUPPORT) + list(APPEND HEADERS + blepp/nimble_transport.h) + + list(APPEND SRC + src/nimble_transport.cc) + endif() +endif() + LIST(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules) -find_package(Bluez REQUIRED) +# Validate: require at least one transport +if(NOT WITH_BLUEZ_SUPPORT AND NOT WITH_NIMBLE_SUPPORT) + message(FATAL_ERROR "At least one of WITH_BLUEZ_SUPPORT or WITH_NIMBLE_SUPPORT must be enabled") +endif() + +# Find BlueZ if enabled +if(WITH_BLUEZ_SUPPORT) + find_package(Bluez REQUIRED) + include_directories(${BLUEZ_INCLUDE_DIRS}) + message(STATUS "BlueZ transport: ENABLED") +else() + message(STATUS "BlueZ transport: DISABLED") +endif() + +include_directories(${PROJECT_SOURCE_DIR}) + +# Add preprocessor definitions based on options +if(WITH_BLUEZ_SUPPORT) + add_definitions(-DBLEPP_BLUEZ_SUPPORT) +endif() + +if(WITH_NIMBLE_SUPPORT) + add_definitions(-DBLEPP_NIMBLE_SUPPORT) + + # Find Nimble BLE stack (required for Nimble transport) + set(NIMBLE_ROOT_DEFAULT "${CMAKE_SOURCE_DIR}/../atbm-wifi/ble_host/nimble_v42") + + if(DEFINED ENV{NIMBLE_ROOT}) + set(NIMBLE_ROOT $ENV{NIMBLE_ROOT}) + elseif(DEFINED NIMBLE_ROOT) + # Use the NIMBLE_ROOT passed via -DNIMBLE_ROOT=... + else() + set(NIMBLE_ROOT ${NIMBLE_ROOT_DEFAULT}) + endif() + + if(NOT EXISTS "${NIMBLE_ROOT}") + message(FATAL_ERROR "NIMBLE_ROOT not found at ${NIMBLE_ROOT}. " + "Please set NIMBLE_ROOT environment variable or pass -DNIMBLE_ROOT=/path/to/nimble_v42") + endif() + + message(STATUS "Nimble transport: ENABLED (using Nimble from ${NIMBLE_ROOT})") + + # Add Nimble include directories + include_directories( + ${NIMBLE_ROOT}/include + ${NIMBLE_ROOT}/nimble/include + ${NIMBLE_ROOT}/nimble/host/include + ${NIMBLE_ROOT}/nimble/host/services/gap/include + ${NIMBLE_ROOT}/nimble/host/services/gatt/include + ${NIMBLE_ROOT}/nimble/host/util/include + ${NIMBLE_ROOT}/porting/nimble/include + ${NIMBLE_ROOT}/ext/tinycrypt/include + ) + + # Find Nimble libraries (dynamic linking) + # Look for libnimble.so or libnimble.a in NIMBLE_ROOT/lib + find_library(NIMBLE_LIBRARY + NAMES nimble + HINTS ${NIMBLE_ROOT}/lib ${NIMBLE_ROOT}/build + NO_DEFAULT_PATH + ) + + if(NOT NIMBLE_LIBRARY) + message(FATAL_ERROR "Nimble library not found. Please build Nimble as a shared library first.\n" + "Expected location: ${NIMBLE_ROOT}/lib/libnimble.so or ${NIMBLE_ROOT}/build/libnimble.so\n" + "See README for instructions on building Nimble.") + endif() + + message(STATUS "Found Nimble library: ${NIMBLE_LIBRARY}") + set(NIMBLE_LIBRARIES ${NIMBLE_LIBRARY}) +else() + message(STATUS "Nimble transport: DISABLED") +endif() + +if(WITH_SERVER_SUPPORT) + add_definitions(-DBLEPP_SERVER_SUPPORT) + message(STATUS "Server support: ENABLED") +else() + message(STATUS "Server support: DISABLED") +endif() -include_directories(${PROJECT_SOURCE_DIR} ${BLUEZ_INCLUDE_DIRS}) add_library(${PROJECT_NAME} SHARED ${SRC}) -target_link_libraries(${PROJECT_NAME} ${BLUEZ_LIBRARIES}) -set_target_properties(${PROJECT_NAME} PROPERTIES +# Link BlueZ libraries if enabled +if(WITH_BLUEZ_SUPPORT) + target_link_libraries(${PROJECT_NAME} ${BLUEZ_LIBRARIES}) +endif() + +# Link Nimble libraries if enabled +if(WITH_NIMBLE_SUPPORT) + target_link_libraries(${PROJECT_NAME} ${NIMBLE_LIBRARIES}) +endif() + +# Add pthread for server support (needed for std::thread and semaphores) +if(WITH_SERVER_SUPPORT) + find_package(Threads REQUIRED) + target_link_libraries(${PROJECT_NAME} Threads::Threads) +endif() + +set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 11 CMAKE_CXX_STANDARD_REQUIRED YES SOVERSION 5) #----------------------- EXAMPLES -------------------------------- if(WITH_EXAMPLES) - set(EXAMPLES - examples/lescan.cc - examples/blelogger.cc - examples/bluetooth.cc - examples/lescan_simple.cc - examples/temperature.cc) - - foreach (example_src ${EXAMPLES}) - get_filename_component(example_name ${example_src} NAME_WE) + # Transport-agnostic examples (work with any transport) + set(CORE_EXAMPLES + examples/lescan_transport.cc) + foreach (example_src ${CORE_EXAMPLES}) + get_filename_component(example_name ${example_src} NAME_WE) add_executable(${example_name} ${example_src}) - target_link_libraries(${example_name} ${BLUEZ_LIBRARIES} ${PROJECT_NAME}) - - set_target_properties(${example_name} PROPERTIES RUNTIME_OUTPUT_DIRECTORY examples) + target_link_libraries(${example_name} ${PROJECT_NAME}) set_target_properties(${example_name} PROPERTIES CXX_STANDARD 11 CMAKE_CXX_STANDARD_REQUIRED YES RUNTIME_OUTPUT_DIRECTORY examples) endforeach() + + # BlueZ-specific examples (only build when BlueZ support is enabled) + # These use BLEGATTStateMachine::connect_blocking or HCIScanner + if(WITH_BLUEZ_SUPPORT) + set(BLUEZ_EXAMPLES + examples/lescan.cc + examples/blelogger.cc + examples/bluetooth.cc + examples/lescan_simple.cc + examples/temperature.cc + examples/read_device_name.cc + examples/write.cc) + + foreach (example_src ${BLUEZ_EXAMPLES}) + get_filename_component(example_name ${example_src} NAME_WE) + add_executable(${example_name} ${example_src}) + target_link_libraries(${example_name} ${BLUEZ_LIBRARIES} ${PROJECT_NAME}) + set_target_properties(${example_name} PROPERTIES + CXX_STANDARD 11 + CMAKE_CXX_STANDARD_REQUIRED YES + RUNTIME_OUTPUT_DIRECTORY examples) + endforeach() + endif() endif() #----------------------- PKG CONFIGURATION -------------------------------- diff --git a/Makefile.in b/Makefile.in index 7ad89ab..365e7e0 100644 --- a/Makefile.in +++ b/Makefile.in @@ -9,6 +9,13 @@ srcdir = @srcdir@ libdir=@libdir@ LOADLIBES = @LIBS@ +# Transport support flags from configure +BLEPP_BLUEZ_SUPPORT = @BLEPP_BLUEZ_SUPPORT@ +BLEPP_NIMBLE_SUPPORT = @BLEPP_NIMBLE_SUPPORT@ +BLEPP_SERVER_SUPPORT = @BLEPP_SERVER_SUPPORT@ +NIMBLE_ROOT = @NIMBLE_ROOT@ +NIMBLE_LIBDIR = @NIMBLE_LIBDIR@ + vpath %.cc $(srcdir) ifneq "$(DESTDIR)" "" @@ -16,7 +23,7 @@ DESTDIR+=/ endif CXX=@CXX@ -LD=@CXX@ +LD=@LD@ CXXFLAGS=@CXXFLAGS@ -I$(srcdir) LDFLAGS=@LDFLAGS@ @@ -29,9 +36,71 @@ soname1=libble++.so.0 soname2=libble++.so.0.5 set_soname=-Wl,-soname,libble++.so.0 -LIBOBJS=src/att.o src/uuid.o src/bledevice.o src/att_pdu.o src/pretty_printers.o src/blestatemachine.o src/float.o src/logging.o src/lescan.o +# Core library objects (always compiled) +# lescan.o contains parse_advertisement_packet() which is transport-agnostic +LIBOBJS=src/att.o src/uuid.o src/bledevice.o src/att_pdu.o src/pretty_printers.o src/blestatemachine.o src/float.o src/logging.o src/lescan.o src/bleclienttransport.o + +# Validate: require at least one transport (configure already checks this, but keep for manual builds) +ifeq ($(strip $(BLEPP_BLUEZ_SUPPORT)),) +ifeq ($(strip $(BLEPP_NIMBLE_SUPPORT)),) +$(error At least one of BLEPP_BLUEZ_SUPPORT or BLEPP_NIMBLE_SUPPORT must be defined) +endif +endif + +# BlueZ transport support (client + optional server) +ifneq ($(strip $(BLEPP_BLUEZ_SUPPORT)),) +LIBOBJS+=src/bluez_client_transport.o +CXXFLAGS+=-DBLEPP_BLUEZ_SUPPORT +endif + +# Nimble transport support (client + optional server) +ifneq ($(strip $(BLEPP_NIMBLE_SUPPORT)),) +LIBOBJS+=src/nimble_client_transport.o +CXXFLAGS+=-DBLEPP_NIMBLE_SUPPORT +CXXFLAGS+=-DCONFIG_LINUX_BLE_STACK_APP=1 + +# Add Nimble include directories (NIMBLE_ROOT set by configure) +ifneq ($(strip $(NIMBLE_ROOT)),) +CXXFLAGS+=-I$(NIMBLE_ROOT)/nimble/include \ + -I$(NIMBLE_ROOT)/nimble/host/include \ + -I$(NIMBLE_ROOT)/nimble/host/services/gap/include \ + -I$(NIMBLE_ROOT)/nimble/host/services/gatt/include \ + -I$(NIMBLE_ROOT)/nimble/host/util/include \ + -I$(NIMBLE_ROOT)/porting/nimble/include \ + -I$(NIMBLE_ROOT)/ext/tinycrypt/include +endif + +# Link Nimble library (NIMBLE_LIBDIR set by configure) +ifneq ($(strip $(NIMBLE_LIBDIR)),) +LDFLAGS+=-L$(NIMBLE_LIBDIR) -Wl,-rpath,$(NIMBLE_LIBDIR) +LOADLIBES+=-lnimble +endif +endif + +# Server support objects +ifneq ($(strip $(BLEPP_SERVER_SUPPORT)),) +LIBOBJS+=src/bleattributedb.o src/blegattserver.o +CXXFLAGS+=-DBLEPP_SERVER_SUPPORT + +# BlueZ server transport (only if BlueZ support enabled) +ifneq ($(strip $(BLEPP_BLUEZ_SUPPORT)),) +LIBOBJS+=src/bluez_transport.o +endif + +# Nimble server transport (only if Nimble support enabled) +ifneq ($(strip $(BLEPP_NIMBLE_SUPPORT)),) +LIBOBJS+=src/nimble_transport.o +endif -PROGS=examples/lescan examples/blelogger examples/bluetooth examples/lescan_simple examples/temperature examples/read_device_name examples/write +endif + +# Core examples that work with any transport +PROGS=examples/lescan_transport + +# BlueZ-specific examples (use BLEGATTStateMachine::connect_blocking) +ifneq ($(strip $(BLEPP_BLUEZ_SUPPORT)),) +PROGS+=examples/lescan_simple examples/blelogger examples/bluetooth examples/temperature examples/read_device_name examples/write +endif .PHONY: all clean testclean install lib progs test doc install-so install-a install-hdr install-pkgconfig @@ -97,8 +166,17 @@ docs: #Every .cc file in the tests directory is a test -TESTS=$(notdir $(basename $(wildcard $(srcdir)/tests/*.cc))) +# Transport-agnostic tests (work with any transport) +CORE_TESTS=test_transport test_scan +# BlueZ-specific tests (use HCIScanner hardware interface) +BLUEZ_TESTS= + +# Combine tests based on what's enabled +TESTS=$(CORE_TESTS) +ifneq ($(strip $(BLEPP_BLUEZ_SUPPORT)),) +TESTS+=$(BLUEZ_TESTS) +endif #Get the intermediate file names from the list of tests. TEST_RESULT=$(TESTS:%=tests/%.result) @@ -110,11 +188,11 @@ TEST_RESULT=$(TESTS:%=tests/%.result) test:tests/results -#We don't want this file hanging around on failure since we +#We don't want this file hanging around on failure since we #want the build depend on it. If we leave it behing then typing make #twice in a row will suceed, since make will find the file and not try #to rebuild it. -.DELETE_ON_ERROR: tests/results +.DELETE_ON_ERROR: tests/results tests/results:$(TEST_RESULT) cat $(TEST_RESULT) > tests/results diff --git a/NIMBLE_BUILD.md b/NIMBLE_BUILD.md new file mode 100644 index 0000000..a2a2f86 --- /dev/null +++ b/NIMBLE_BUILD.md @@ -0,0 +1,184 @@ +# Building Nimble BLE Stack for libblepp + +This document explains how to build the Apache Nimble BLE stack as a shared library for use with libblepp's Nimble transport. + +## Overview + +libblepp's Nimble transport requires the Apache Nimble BLE stack to be built as a shared library. This avoids bloating libblepp with statically linked Nimble code (which can add ~5MB to the library size). + +## Prerequisites + +- GCC or Clang compiler +- Make +- The Nimble source code (from ATBM project or Apache Nimble repository) + +## Build Instructions + +### Option 1: Build from ATBM Project + +If you're using the ATBM Nimble sources: + +```bash +cd /path/to/atbm-wifi/ble_host/nimble_v42 + +# Create build directory +mkdir -p build +cd build + +# Create a simple Makefile for building libnimble.so +cat > Makefile << 'EOF' +CC = gcc +AR = ar +CFLAGS = -fPIC -O2 -g -Wall + +# Source directories +NIMBLE_ROOT = .. +HOST_SRC = $(NIMBLE_ROOT)/nimble/host/src +SERVICES_GAP = $(NIMBLE_ROOT)/nimble/host/services/gap/src +SERVICES_GATT = $(NIMBLE_ROOT)/nimble/host/services/gatt/src +UTIL_SRC = $(NIMBLE_ROOT)/nimble/host/util/src +PORT_SRC = $(NIMBLE_ROOT)/porting/nimble/src +CORE_SRC = $(NIMBLE_ROOT)/nimble/src +CRYPTO_SRC = $(NIMBLE_ROOT)/ext/tinycrypt/src + +# Include directories +INCLUDES = -I$(NIMBLE_ROOT)/nimble/include \ + -I$(NIMBLE_ROOT)/nimble/host/include \ + -I$(NIMBLE_ROOT)/nimble/host/services/gap/include \ + -I$(NIMBLE_ROOT)/nimble/host/services/gatt/include \ + -I$(NIMBLE_ROOT)/nimble/host/util/include \ + -I$(NIMBLE_ROOT)/porting/nimble/include \ + -I$(NIMBLE_ROOT)/ext/tinycrypt/include + +# Collect all source files +SRCS = $(wildcard $(HOST_SRC)/*.c) \ + $(wildcard $(SERVICES_GAP)/*.c) \ + $(wildcard $(SERVICES_GATT)/*.c) \ + $(wildcard $(UTIL_SRC)/*.c) \ + $(wildcard $(CORE_SRC)/*.c) \ + $(wildcard $(CRYPTO_SRC)/*.c) \ + $(PORT_SRC)/endian.c \ + $(PORT_SRC)/mem.c \ + $(PORT_SRC)/nimble_port.c \ + $(PORT_SRC)/os_mbuf.c \ + $(PORT_SRC)/os_mempool.c \ + $(PORT_SRC)/os_msys_init.c + +OBJS = $(SRCS:.c=.o) + +.PHONY: all clean + +all: libnimble.so + +%.o: %.c + $(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@ + +libnimble.so: $(OBJS) + $(CC) -shared -o $@ $^ + @echo "Built libnimble.so successfully" + +clean: + rm -f $(OBJS) libnimble.so +EOF + +# Build the library +make + +# Verify it was built +ls -lh libnimble.so +``` + +### Option 2: Build from Apache Nimble Repository + +If you're using the official Apache Nimble repository: + +```bash +git clone https://github.com/apache/mynewt-nimble.git +cd mynewt-nimble + +# Follow Apache Nimble's build instructions for your platform +# Typically involves using newt or cmake with specific configuration +``` + +## Installation + +After building, you have two options: + +### Option A: Use from build directory + +Set the `NIMBLE_ROOT` environment variable or CMake/Make variable to point to your Nimble directory: + +```bash +# For CMake +cmake -DWITH_NIMBLE_SUPPORT=ON -DNIMBLE_ROOT=/path/to/atbm-wifi/ble_host/nimble_v42 .. + +# For Make +make BLEPP_NIMBLE_SUPPORT=1 NIMBLE_ROOT=/path/to/atbm-wifi/ble_host/nimble_v42 +``` + +### Option B: Install system-wide + +```bash +# Copy library +sudo cp build/libnimble.so /usr/local/lib/ + +# Copy headers +sudo mkdir -p /usr/local/include/nimble +sudo cp -r nimble/include/* /usr/local/include/nimble/ +sudo cp -r nimble/host/include/* /usr/local/include/nimble/ +# ... copy other necessary headers + +# Update library cache +sudo ldconfig +``` + +## Verification + +To verify the Nimble library is properly built and linkable: + +```bash +# Check library dependencies +ldd build/libnimble.so + +# Check exported symbols +nm -D build/libnimble.so | grep ble_gap + +# Check size (should be much smaller than 5MB) +ls -lh build/libnimble.so +``` + +Expected size: ~500KB - 1MB (depending on optimization and debug symbols) + +## Troubleshooting + +### Library not found error + +If you get an error like: +``` +Nimble library not found. Please build Nimble as a shared library first. +``` + +Make sure: +1. The library was built successfully: `ls $NIMBLE_ROOT/build/libnimble.so` +2. The `NIMBLE_ROOT` path is correct +3. The library is in either `lib/` or `build/` subdirectory of `NIMBLE_ROOT` + +### Runtime linking errors + +If you get runtime errors about missing libraries: + +```bash +# Check where the library is being found +ldd /path/to/libble++.so | grep nimble + +# Add to library path if needed +export LD_LIBRARY_PATH=/path/to/nimble/build:$LD_LIBRARY_PATH +``` + +Or use the rpath option (already included in libblepp's Makefile). + +## Notes + +- The Nimble library must be built with `-fPIC` (Position Independent Code) for shared library support +- Thread support may be required depending on your Nimble port +- Some Nimble ports may require additional dependencies (e.g., FreeRTOS, Zephyr OS layers) diff --git a/README.md b/README.md index 612a5b8..a4cb582 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,332 @@ -# libble++ +# libble++ - Modern C++ Bluetooth Low Energy Library -Implementation of Bluetooth Low Energy functions in modern C++, without -the BlueZ DBUS API. +A modern C++ implementation of Bluetooth Low Energy (BLE) functionality with support for both client (central) and server (peripheral) modes, without requiring the BlueZ D-Bus API. -### Includes -* Scanning for bluetooth packets -* Implementation of the GATT profile and ATT protocol -* Lots of comments, complete with references to the specific part of - the Bluetooth 4.0 standard. -* Example programs +## Features -### Design -Clean, modern C++ with callbacks. Feed it with lambdas (or whatever you like) -to perform an event happens. Access provided to the raw socket FD, so -you can use select(), poll() or blocking IO. +### Core Functionality +- **BLE Central/Client Mode** + - Scan for BLE devices + - Connect to peripherals + - Service discovery (GATT) + - Read/write characteristics + - Subscribe to notifications/indications + - Full ATT protocol implementation -### The example programs -* lescan_simple: Simplest possible program for scanning for devices. Only 2 non boilerplate lines. -* lescan: A "proper" scanning program that cleans up properly. It's got the same 2 lines of BLE related code and a bit of pretty standard unix for dealing with non blocking I/O and signals. -* temperature: A program for logging temperature values from a device providing a standard temperature characteristic. Very short to indicate the usave, but not much error checking. +- **BLE Peripheral/Server Mode** *(optional)* + - Create custom GATT services + - Advertise services + - Accept incoming connections + - Handle read/write requests + - Send notifications/indications + - Attribute database management +### Transport Layer Abstraction +libblepp supports multiple transport layers for maximum hardware compatibility: -### Building the library -There are currently autoconf (./configure) and CMake options. It's not -a complex library to build, so either option should work fine. -Autoconf: +- **BlueZ Transport** (Linux standard) + - Uses HCI sockets for scanning + - Uses L2CAP sockets for connections + - Works with any BlueZ-compatible adapter + +- **ATBM/NimBLE Transport** (hardware-specific) + - Direct ioctl interface (`/dev/atbm_ioctl`) + - Optimized for Altobeam WiFi+BLE combo chips + - Signal-based asynchronous event handling + - Full HCI packet wrapping/unwrapping + +### Design Philosophy +- Clean, modern C++11/14 with callbacks +- Extensively commented with references to Bluetooth 4.0+ specifications +- Direct socket access for `select()`, `poll()`, or blocking I/O +- No dependency on BlueZ D-Bus API +- Thread-safe transport implementations + +## Quick Start + +### Installation + +#### Using CMake (Recommended) +```bash +mkdir build && cd build +cmake .. +make +sudo make install ``` -./configure + +#### Using Autoconf +```bash +./configure make -``` -CMake: +sudo make install ``` -mkdir build && cd build + +### Basic Scanning Example +```cpp +#include +#include + +int main() { + BLEPP::log_level = BLEPP::LogLevels::Info; + + // Create transport (auto-selects available transport) + BLEPP::BLEClientTransport* transport = BLEPP::create_client_transport(); + if (!transport) { + return 1; + } + + // Create scanner + BLEPP::BLEScanner scanner(transport); + scanner.start(); + + while (1) { + std::vector ads = scanner.get_advertisements(); + // Process advertisements... + } + + delete transport; +} +``` + +### Basic Server Example +```cpp +#include + +int main() { + BLEPP::BLEGATTServer server; + + // Add a service with a characteristic + auto service = server.add_service(0x180F); // Battery Service + auto characteristic = service->add_characteristic( + 0x2A19, // Battery Level + BLEPP::ReadOnly + ); + + characteristic->set_read_callback([](auto conn) { + return std::vector{85}; // 85% battery + }); + + server.start_advertising("MyDevice"); + server.run(); // Event loop +} +``` + +## Build Options + +### CMake Build Options + +| Option | Default | Description | +|--------|---------|-------------| +| `WITH_SERVER_SUPPORT` | `OFF` | Enable BLE peripheral/server mode | +| `WITH_BLUEZ_SUPPORT` | `ON` | Enable BlueZ HCI/L2CAP transport | +| `WITH_NIMBLE_SUPPORT` | `OFF` | Enable ATBM/NimBLE ioctl transport | +| `WITH_EXAMPLES` | `OFF` | Build example programs | + +### Build Configuration Examples + +**Client-only (BlueZ):** +```bash cmake .. -make install +make ``` -CMake with examples: -Examples will be in ```build/examples``` + +**Client + Server (BlueZ):** +```bash +cmake -DWITH_SERVER_SUPPORT=ON .. +make +``` + +**Client + Server + ATBM:** +```bash +cmake -DWITH_SERVER_SUPPORT=ON -DWITH_NIMBLE_SUPPORT=ON .. +make ``` + +**Everything with examples:** +```bash +cmake -DWITH_SERVER_SUPPORT=ON -DWITH_NIMBLE_SUPPORT=ON -DWITH_EXAMPLES=ON .. +make +``` + +### Makefile Build Options + +```bash +# Client-only +make + +# Client + Server +make BLEPP_SERVER_SUPPORT=1 + +# Client + Server + ATBM +make BLEPP_SERVER_SUPPORT=1 BLEPP_NIMBLE_SUPPORT=1 BLEPP_BLUEZ_SUPPORT=1 +``` + +See [BUILD_OPTIONS.md](docs/BUILD_OPTIONS.md) for complete build configuration reference. + +## Example Programs + +Located in the `examples/` directory: + +**Client/Central Examples:** +- **lescan_simple** - Minimal BLE scanning example using transport abstraction +- **lescan** - Full-featured scanner with BlueZ HCI (signal handling, duplicate filtering) +- **lescan_transport** - Scanner using transport abstraction layer +- **temperature** - Read and log temperature characteristic with notifications +- **read_device_name** - Simple example reading device name characteristic +- **write** - Write to a characteristic (demonstrates write operations) +- **bluetooth** - Comprehensive example with notifications, plotting, and non-blocking I/O +- **blelogger** - Data logging from custom BLE device + +**Server/Peripheral Examples:** +- **gatt_server** - Complete GATT server with Battery Service, Device Info, and custom services *(requires server support)* + +Build examples with CMake: +```bash mkdir build && cd build cmake -DWITH_EXAMPLES=ON .. -make install -``` \ No newline at end of file +make +``` + +Build with server support: +```bash +cmake -DWITH_SERVER_SUPPORT=ON -DWITH_EXAMPLES=ON .. +make +``` + +Examples will be in `build/examples/`. + +Run examples (most require root for BLE access): +```bash +# Scan for devices +sudo ./examples/lescan + +# Connect and read device name +sudo ./examples/read_device_name AA:BB:CC:DD:EE:FF + +# Run GATT server +sudo ./examples/gatt_server "My BLE Device" +``` + +## Documentation + +- [BUILD_OPTIONS.md](docs/BUILD_OPTIONS.md) - Complete build configuration reference +- [CMAKE_BUILD_GUIDE.md](docs/CMAKE_BUILD_GUIDE.md) - CMake build system guide +- [CLIENT_TRANSPORT_ABSTRACTION.md](docs/CLIENT_TRANSPORT_ABSTRACTION.md) - Transport layer architecture +- [ATBM_IOCTL_API.md](docs/ATBM_IOCTL_API.md) - ATBM transport API reference + +## Requirements + +### Common Requirements +- C++11 or later compiler +- Linux kernel 3.4+ (for BLE support) + +### BlueZ Transport (default) +- BlueZ 5.0 or later +- libbluetooth-dev (development headers) +- Root privileges or `CAP_NET_ADMIN` + `CAP_NET_RAW` capabilities + +Install on Debian/Ubuntu: +```bash +sudo apt-get install libbluetooth-dev +``` + +### ATBM Transport (optional) +- Altobeam WiFi+BLE driver loaded +- `/dev/atbm_ioctl` device accessible +- Appropriate device permissions +- Apache NimBLE 4.2+ (bundled with driver) + +## Architecture + +### Transport Abstraction +``` +┌─────────────────────────────────────┐ +│ Application Code │ +└──────────────┬──────────────────────┘ + │ + ┌──────────┴──────────┐ + │ │ +┌───▼─────┐ ┌────────▼────────┐ +│ Scanner │ │ GATT Client │ +└───┬─────┘ └────────┬────────┘ + │ │ + └──────────┬──────────┘ + │ + ┌──────────▼─────────────┐ + │ BLEClientTransport │ ◄─ Abstract Interface + │ (Pure Virtual) │ + └──────────┬─────────────┘ + │ + ┌────────┴────────┐ + │ │ +┌─────▼──────┐ ┌─────▼──────┐ +│ BlueZ │ │ ATBM │ +│ Transport │ │ Transport │ +└─────┬──────┘ └─────┬──────┘ + │ │ +┌─────▼──────┐ ┌─────▼──────┐ +│ HCI/L2CAP │ │ /dev/atbm │ +│ Sockets │ │ ioctl │ +└────────────┘ └────────────┘ +``` + +## Using libblepp in Your Project + +### CMake +```cmake +find_library(BLEPP_LIB ble++ REQUIRED) +find_path(BLEPP_INCLUDE blepp REQUIRED) + +add_executable(my_app main.cpp) +target_include_directories(my_app PRIVATE ${BLEPP_INCLUDE}) +target_link_libraries(my_app ${BLEPP_LIB} bluetooth pthread) +``` + +### pkg-config +```bash +g++ main.cpp $(pkg-config --cflags --libs libblepp) -o my_app +``` + +### Direct Linking +```bash +g++ main.cpp -lble++ -lbluetooth -lpthread -o my_app +``` + +## License + +[License information - please verify in source] + +## Contributing + +Contributions are welcome! Please ensure: +- Code follows existing style conventions +- Changes include relevant documentation updates +- Build succeeds with all configuration options +- Examples compile and run correctly + +## References + +- Bluetooth Core Specification 4.0+ +- BlueZ 5.x documentation +- Apache NimBLE documentation +- Linux kernel Bluetooth subsystem + +## Version History + +See git history for detailed changelog. Recent major updates include: +- ATBM/NimBLE transport support with ioctl interface +- Transport abstraction layer for multiple hardware backends +- Complete GATT server implementation +- CMake build system alongside autoconf +- Comprehensive documentation suite + +## Support & Issues + +For bugs, feature requests, or questions: +1. Check existing documentation in `docs/` +2. Search closed issues +3. Open a new issue with details about your environment + +## Credits + +Originally designed for BlueZ-based systems, now extended to support multiple transport layers for broader hardware compatibility. diff --git a/blepp/advertising.h b/blepp/advertising.h new file mode 100644 index 0000000..9d95761 --- /dev/null +++ b/blepp/advertising.h @@ -0,0 +1,41 @@ +/* + * + * blepp - Implementation of the Generic ATTribute Protocol + * + * Copyright (C) 2024 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __INC_BLEPP_ADVERTISING_H +#define __INC_BLEPP_ADVERTISING_H + +#include +#include +#include + +namespace BLEPP +{ + +/// Parse a BLE HCI advertisement packet into structured AdvertisingResponse +/// This is transport-agnostic and can be used by both BlueZ and ATBM implementations +/// @param packet Raw HCI event packet data +/// @return Vector of parsed advertising responses (may contain multiple advertisements) +std::vector parse_advertisement_packet(const std::vector& packet); + +} // namespace BLEPP + +#endif // __INC_BLEPP_ADVERTISING_H diff --git a/blepp/att.h b/blepp/att.h index e86d4ac..6374141 100644 --- a/blepp/att.h +++ b/blepp/att.h @@ -44,7 +44,55 @@ * */ +#ifndef __INC_BLEPP_ATT_H +#define __INC_BLEPP_ATT_H + #include +#include +#include + +// Byte order macros for Bluetooth (little-endian) +// Only define if not already provided by bluetooth.h +#ifndef htobs +#ifdef __APPLE__ +#include +#define htobs(x) OSSwapHostToLittleInt16(x) +#define btohs(x) OSSwapLittleToHostInt16(x) +#define htobl(x) OSSwapHostToLittleInt32(x) +#define btohl(x) OSSwapLittleToHostInt32(x) +#else +#include +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define htobs(x) (x) +#define btohs(x) (x) +#define htobl(x) (x) +#define btohl(x) (x) +#else +#include +#define htobs(x) bswap_16(x) +#define btohs(x) bswap_16(x) +#define htobl(x) bswap_32(x) +#define btohl(x) bswap_32(x) +#endif +#endif +#endif // htobs + +// Unaligned access macros (replace BlueZ bt_get_unaligned/bt_put_unaligned) +#define bt_get_unaligned(ptr) \ +__extension__ ({ \ + struct __attribute__((packed)) { \ + __typeof__(*(ptr)) __v; \ + } *__p = (__typeof__(__p)) (ptr); \ + __p->__v; \ +}) + +#define bt_put_unaligned(val, ptr) \ +do { \ + struct __attribute__((packed)) { \ + __typeof__(*(ptr)) __v; \ + } *__p = (__typeof__(__p)) (ptr); \ + __p->__v = (val); \ +} while(0) namespace BLEPP { @@ -321,3 +369,5 @@ namespace BLEPP uint16_t enc_exec_write_req(uint8_t flags, uint8_t *pdu, size_t len); uint16_t dec_exec_write_resp(const uint8_t *pdu, size_t len); } + +#endif // __INC_BLEPP_ATT_H diff --git a/blepp/bleattributedb.h b/blepp/bleattributedb.h new file mode 100644 index 0000000..382c1e4 --- /dev/null +++ b/blepp/bleattributedb.h @@ -0,0 +1,287 @@ +/* + * + * blepp - Implementation of the Generic ATTribute Protocol + * + * Copyright (C) 2024 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __INC_BLEPP_ATTRIBUTEDB_H +#define __INC_BLEPP_ATTRIBUTEDB_H + +#include + +#ifdef BLEPP_SERVER_SUPPORT + +#include +#include +#include +#include +#include +#include +#include + +namespace BLEPP +{ + // Forward declarations + struct GATTServiceDef; + struct GATTCharacteristicDef; + struct GATTDescriptorDef; + + /// ATT Access operations + enum class ATTAccessOp : uint8_t + { + READ_CHR = 0, ///< Read characteristic value + WRITE_CHR = 1, ///< Write characteristic value + READ_DSC = 2, ///< Read descriptor value + WRITE_DSC = 3 ///< Write descriptor value + }; + + /// ATT Attribute permissions + enum ATTPermissions : uint8_t + { + ATT_PERM_NONE = 0x00, + ATT_PERM_READ = 0x01, + ATT_PERM_WRITE = 0x02, + ATT_PERM_READ_ENCRYPT = 0x04, + ATT_PERM_WRITE_ENCRYPT = 0x08, + ATT_PERM_READ_AUTHEN = 0x10, + ATT_PERM_WRITE_AUTHEN = 0x20, + ATT_PERM_READ_AUTHOR = 0x40, + ATT_PERM_WRITE_AUTHOR = 0x80 + }; + + /// GATT Characteristic properties (from Bluetooth spec) + enum GATTCharProperties : uint8_t + { + GATT_CHR_PROP_BROADCAST = 0x01, + GATT_CHR_PROP_READ = 0x02, + GATT_CHR_PROP_WRITE_NO_RSP = 0x04, + GATT_CHR_PROP_WRITE = 0x08, + GATT_CHR_PROP_NOTIFY = 0x10, + GATT_CHR_PROP_INDICATE = 0x20, + GATT_CHR_PROP_AUTH_WRITE = 0x40, + GATT_CHR_PROP_EXTENDED = 0x80 + }; + + /// GATT Characteristic flags (extended properties) + enum GATTCharFlags : uint16_t + { + GATT_CHR_F_BROADCAST = 0x0001, + GATT_CHR_F_READ = 0x0002, + GATT_CHR_F_WRITE_NO_RSP = 0x0004, + GATT_CHR_F_WRITE = 0x0008, + GATT_CHR_F_NOTIFY = 0x0010, + GATT_CHR_F_INDICATE = 0x0020, + GATT_CHR_F_AUTH_SIGN_WRITE = 0x0040, + GATT_CHR_F_RELIABLE_WRITE = 0x0080, + GATT_CHR_F_AUX_WRITE = 0x0100, + GATT_CHR_F_READ_ENC = 0x0200, + GATT_CHR_F_READ_AUTHEN = 0x0400, + GATT_CHR_F_READ_AUTHOR = 0x0800, + GATT_CHR_F_WRITE_ENC = 0x1000, + GATT_CHR_F_WRITE_AUTHEN = 0x2000, + GATT_CHR_F_WRITE_AUTHOR = 0x4000 + }; + + /// Attribute types + enum class AttributeType : uint8_t + { + PRIMARY_SERVICE, ///< Primary service declaration + SECONDARY_SERVICE, ///< Secondary service declaration + INCLUDE, ///< Include declaration + CHARACTERISTIC, ///< Characteristic declaration + CHARACTERISTIC_VALUE, ///< Characteristic value + DESCRIPTOR ///< Descriptor (including CCCD) + }; + + /// A single attribute in the database + struct Attribute + { + uint16_t handle; + AttributeType type; + UUID uuid; + uint8_t permissions; + std::vector value; + + // For characteristic declarations + uint8_t properties; // GATT characteristic properties + uint16_t value_handle; // Points to the characteristic value handle + + // For service declarations + uint16_t end_group_handle; // Last handle in service group + + // Callbacks for dynamic values + std::function& out_data)> read_cb; + std::function& data)> write_cb; + + Attribute() + : handle(0) + , type(AttributeType::DESCRIPTOR) + , permissions(0) + , properties(0) + , value_handle(0) + , end_group_handle(0xFFFF) + {} + }; + + /// GATT Attribute Database + /// Manages all services, characteristics, and descriptors + class BLEAttributeDatabase + { + public: + BLEAttributeDatabase(); + ~BLEAttributeDatabase(); + + /// Register services from definition array + /// @param services Array of service definitions (NimBLE-compatible) + /// @return 0 on success, negative error code on failure + int register_services(const std::vector& services); + + /// Add a primary service + /// @param uuid Service UUID + /// @return Service handle, or 0 on error + uint16_t add_primary_service(const UUID& uuid); + + /// Add a secondary service + /// @param uuid Service UUID + /// @return Service handle, or 0 on error + uint16_t add_secondary_service(const UUID& uuid); + + /// Add an include declaration + /// @param service_handle Handle of the service containing the include + /// @param included_service_handle Handle of the included service + /// @return Include handle, or 0 on error + uint16_t add_include(uint16_t service_handle, uint16_t included_service_handle); + + /// Add a characteristic + /// @param service_handle Service handle + /// @param uuid Characteristic UUID + /// @param properties Characteristic properties (read, write, notify, etc.) + /// @param permissions Attribute permissions + /// @return Characteristic declaration handle, or 0 on error + uint16_t add_characteristic(uint16_t service_handle, + const UUID& uuid, + uint8_t properties, + uint8_t permissions); + + /// Add a descriptor + /// @param char_handle Characteristic handle + /// @param uuid Descriptor UUID + /// @param permissions Attribute permissions + /// @return Descriptor handle, or 0 on error + uint16_t add_descriptor(uint16_t char_handle, + const UUID& uuid, + uint8_t permissions); + + /// Get attribute by handle + /// @param handle Attribute handle + /// @return Pointer to attribute, or nullptr if not found + Attribute* get_attribute(uint16_t handle); + const Attribute* get_attribute(uint16_t handle) const; + + /// Find attributes by type + /// @param start_handle Start of search range + /// @param end_handle End of search range + /// @param type Attribute type to search for + /// @return Vector of matching attributes + std::vector find_by_type(uint16_t start_handle, + uint16_t end_handle, + const UUID& type) const; + + /// Find attributes by type and value + /// @param start_handle Start of search range + /// @param end_handle End of search range + /// @param type Attribute type + /// @param value Value to match + /// @return Vector of matching attributes + std::vector find_by_type_value(uint16_t start_handle, + uint16_t end_handle, + const UUID& type, + const std::vector& value) const; + + /// Find attributes in range + /// @param start_handle Start handle (inclusive) + /// @param end_handle End handle (inclusive) + /// @return Vector of attributes in range + std::vector get_range(uint16_t start_handle, + uint16_t end_handle) const; + + /// Get next handle value + uint16_t get_next_handle() const { return next_handle_; } + + /// Get total number of attributes + size_t size() const { return attributes_.size(); } + + /// Clear all attributes + void clear(); + + /// Set characteristic value + /// @param char_value_handle Characteristic value handle + /// @param value New value + /// @return 0 on success, negative on error + int set_characteristic_value(uint16_t char_value_handle, + const std::vector& value); + + /// Get characteristic value + /// @param char_value_handle Characteristic value handle + /// @return Value, or empty vector if not found + std::vector get_characteristic_value(uint16_t char_value_handle) const; + + /// Set read callback for a characteristic value + int set_read_callback(uint16_t char_value_handle, + std::function& out_data)> cb); + + /// Set write callback for a characteristic value + int set_write_callback(uint16_t char_value_handle, + std::function& data)> cb); + + private: + std::map attributes_; + uint16_t next_handle_; + + // Service handle tracking for end_group_handle updates + struct ServiceInfo { + uint16_t start_handle; + uint16_t end_handle; + }; + std::vector services_; + + /// Allocate a new handle + uint16_t allocate_handle(); + + /// Update service end group handle + void update_service_end_handle(uint16_t service_handle, uint16_t last_handle); + + /// Get properties byte from flags + static uint8_t flags_to_properties(uint16_t flags); + + /// Convert flags to permissions + static uint8_t flags_to_permissions(uint16_t flags); + + /// Add CCCD (Client Characteristic Configuration Descriptor) if needed + /// Called automatically when characteristic has notify or indicate + uint16_t add_cccd(uint16_t char_value_handle); + }; + +} // namespace BLEPP + +#endif // BLEPP_SERVER_SUPPORT +#endif // __INC_BLEPP_ATTRIBUTEDB_H diff --git a/blepp/bleclienttransport.h b/blepp/bleclienttransport.h new file mode 100644 index 0000000..95ac625 --- /dev/null +++ b/blepp/bleclienttransport.h @@ -0,0 +1,191 @@ +/* + * + * blepp - Implementation of the Generic ATTribute Protocol + * + * Copyright (C) 2024 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __INC_BLEPP_BLECLIENTTRANSPORT_H +#define __INC_BLEPP_BLECLIENTTRANSPORT_H + +#include +#include +#include +#include + +namespace BLEPP +{ + /// Scan parameters for BLE device discovery + struct ScanParams + { + enum class ScanType : uint8_t { + Passive = 0x00, // Passive scanning (no scan requests) + Active = 0x01 // Active scanning (send scan requests) + }; + + enum class FilterPolicy : uint8_t { + All = 0x00, // Accept all advertising packets + WhitelistOnly = 0x01 // Accept only whitelisted devices + }; + + enum class FilterDuplicates : uint8_t { + Off = 0x00, // No filtering - get all advertisement events + Software = 0x01, // Filter duplicates in software only + Hardware = 0x02 // Filter duplicates in hardware only + }; + + ScanType scan_type = ScanType::Active; + uint16_t interval_ms = 1280; // Scan interval in ms (default: 1280ms for WiFi coexistence) + uint16_t window_ms = 26; // Scan window in ms (default: 2% duty cycle) + FilterPolicy filter_policy = FilterPolicy::All; + FilterDuplicates filter_duplicates = FilterDuplicates::Software; // Duplicate filtering mode + }; + + /// Advertisement data received during scanning + struct AdvertisementData + { + std::string address; + uint8_t address_type; // 0=public, 1=random + int8_t rssi; + uint8_t event_type; // ADV_IND, SCAN_RSP, etc. + std::vector data; + }; + + /// Connection parameters for BLE client connections + struct ClientConnectionParams + { + std::string peer_address; + uint8_t peer_address_type = 0; // 0=public, 1=random + uint16_t min_interval = 24; // Connection interval min (units of 1.25ms) + uint16_t max_interval = 40; // Connection interval max (units of 1.25ms) + uint16_t latency = 0; // Slave latency + uint16_t timeout = 400; // Supervision timeout (units of 10ms) + }; + + /// Abstract interface for BLE client transport layer + /// Supports both BlueZ (HCI/L2CAP) and Nimble (ioctl) backends + class BLEClientTransport + { + public: + virtual ~BLEClientTransport() = default; + + // ===== Scanning Operations ===== + + /// Start scanning for BLE devices + /// @param params Scan parameters + /// @return 0 on success, negative on error + virtual int start_scan(const ScanParams& params) = 0; + + /// Stop scanning + /// @return 0 on success, negative on error + virtual int stop_scan() = 0; + + /// Get received advertisements (blocking or non-blocking based on implementation) + /// @param ads Output vector to receive advertisements + /// @param timeout_ms Timeout in milliseconds (0 = non-blocking, -1 = blocking) + /// @return Number of advertisements received, negative on error + virtual int get_advertisements(std::vector& ads, int timeout_ms = 0) = 0; + + // ===== Connection Operations ===== + + /// Connect to a BLE device + /// @param params Connection parameters including peer address + /// @return File descriptor/handle on success, negative on error + virtual int connect(const ClientConnectionParams& params) = 0; + + /// Disconnect from device + /// @param fd File descriptor/handle from connect() + /// @return 0 on success, negative on error + virtual int disconnect(int fd) = 0; + + /// Get the file descriptor for select/poll operations + /// @param fd Connection file descriptor/handle + /// @return File descriptor suitable for select/poll, or -1 if not applicable + virtual int get_fd(int fd) const = 0; + + // ===== Data Transfer Operations ===== + + /// Send ATT PDU to connected device + /// @param fd Connection file descriptor/handle + /// @param data PDU data to send + /// @param len Length of data + /// @return Number of bytes sent, negative on error + virtual int send(int fd, const uint8_t* data, size_t len) = 0; + + /// Receive ATT PDU from connected device + /// @param fd Connection file descriptor/handle + /// @param data Buffer to receive data + /// @param max_len Maximum buffer size + /// @return Number of bytes received, negative on error + virtual int receive(int fd, uint8_t* data, size_t max_len) = 0; + + // ===== MTU Operations ===== + + /// Get current MTU for connection + /// @param fd Connection file descriptor/handle + /// @return MTU value, or 23 (default) on error + virtual uint16_t get_mtu(int fd) const = 0; + + /// Set MTU for connection + /// @param fd Connection file descriptor/handle + /// @param mtu New MTU value + /// @return 0 on success, negative on error + virtual int set_mtu(int fd, uint16_t mtu) = 0; + + // ===== Transport Information ===== + + /// Get transport type name (for debugging) + /// @return Transport name ("BlueZ", "Nimble", etc.) + virtual const char* get_transport_name() const = 0; + + /// Check if transport is available on this system + /// @return true if transport can be used + virtual bool is_available() const = 0; + + /// Get the Bluetooth MAC address used by this transport + /// @return MAC address as string in format "XX:XX:XX:XX:XX:XX", or empty string on error + virtual std::string get_mac_address() const = 0; + + // ===== Callbacks (optional, for async operation) ===== + + std::function on_advertisement; + std::function on_connected; + std::function on_disconnected; + std::function on_data_received; + }; + + /// Factory function to create appropriate transport based on build configuration + /// Tries BlueZ first (if available), then Nimble + /// @return Pointer to transport implementation (caller owns), nullptr if none available + BLEClientTransport* create_client_transport(); + +#ifdef BLEPP_BLUEZ_SUPPORT + /// Create BlueZ client transport explicitly + /// @return Pointer to BlueZ transport implementation (caller owns) + BLEClientTransport* create_bluez_client_transport(); +#endif + +#ifdef BLEPP_NIMBLE_SUPPORT + /// Create Nimble client transport explicitly + /// @return Pointer to Nimble transport implementation (caller owns) + BLEClientTransport* create_nimble_client_transport(); +#endif + +} // namespace BLEPP + +#endif // __INC_BLEPP_BLECLIENTTRANSPORT_H diff --git a/blepp/blegattserver.h b/blepp/blegattserver.h new file mode 100644 index 0000000..160bcf5 --- /dev/null +++ b/blepp/blegattserver.h @@ -0,0 +1,252 @@ +/* + * + * blepp - Implementation of the Generic ATTribute Protocol + * + * Copyright (C) 2024 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __INC_BLEPP_GATT_SERVER_H +#define __INC_BLEPP_GATT_SERVER_H + +#include + +#ifdef BLEPP_SERVER_SUPPORT + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace BLEPP +{ + /// Per-connection state for GATT server + struct ConnectionState + { + uint16_t conn_handle; + uint16_t mtu; ///< Negotiated MTU (default 23) + std::map cccd_values; ///< CCCD values per characteristic + bool connected; + std::chrono::steady_clock::time_point connection_time; ///< When connection was established + }; + + /// BLE GATT Server + /// Implements a complete GATT server using an attribute database and transport layer + class BLEGATTServer + { + public: + /// Constructor + /// @param transport Transport implementation (BlueZ or ATBM) + explicit BLEGATTServer(std::unique_ptr transport); + + /// Destructor + ~BLEGATTServer(); + + /// Get the attribute database + /// @return Reference to the attribute database + BLEAttributeDatabase& db() { return db_; } + const BLEAttributeDatabase& db() const { return db_; } + + /// Register services from definitions + /// @param services Vector of service definitions + /// @return 0 on success, negative on error + int register_services(const std::vector& services); + + /// Start advertising + /// @param params Advertising parameters + /// @return 0 on success, negative on error + int start_advertising(const AdvertisingParams& params); + + /// Stop advertising + /// @return 0 on success, negative on error + int stop_advertising(); + + /// Check if advertising + /// @return true if advertising + bool is_advertising() const; + + /// Run the server event loop + /// This blocks and processes events. Call from main thread or dedicated thread. + /// @return 0 on normal exit, negative on error + int run(); + + /// Stop the server event loop + void stop(); + + /// Send notification to a client + /// @param conn_handle Connection handle + /// @param char_val_handle Characteristic value handle + /// @param data Notification data + /// @return 0 on success, negative on error + int notify(uint16_t conn_handle, uint16_t char_val_handle, + const std::vector& data); + + /// Send indication to a client (with acknowledgment) + /// @param conn_handle Connection handle + /// @param char_val_handle Characteristic value handle + /// @param data Indication data + /// @return 0 on success, negative on error + int indicate(uint16_t conn_handle, uint16_t char_val_handle, + const std::vector& data); + + /// Disconnect a client + /// @param conn_handle Connection handle + /// @return 0 on success, negative on error + int disconnect(uint16_t conn_handle); + + /// Get connection state + /// @param conn_handle Connection handle + /// @return Pointer to connection state, or nullptr if not found + ConnectionState* get_connection_state(uint16_t conn_handle); + + /// Callbacks + + /// Called when a client connects + std::function on_connected; + + /// Called when a client disconnects + std::function on_disconnected; + + /// Called when MTU is exchanged + std::function on_mtu_exchanged; + + private: + std::unique_ptr transport_; + BLEAttributeDatabase db_; + + std::mutex connections_mutex_; + std::map connections_; + + bool running_; + std::mutex running_mutex_; + + // ATT PDU handlers + + /// Handle incoming ATT PDU + /// @param conn_handle Connection handle + /// @param pdu PDU data + /// @param len PDU length + void handle_att_pdu(uint16_t conn_handle, const uint8_t* pdu, size_t len); + + /// Handle MTU Exchange Request (0x02) + void handle_mtu_exchange_req(uint16_t conn_handle, const uint8_t* pdu, size_t len); + + /// Handle Find Information Request (0x04) + void handle_find_info_req(uint16_t conn_handle, const uint8_t* pdu, size_t len); + + /// Handle Find By Type Value Request (0x06) + void handle_find_by_type_value_req(uint16_t conn_handle, const uint8_t* pdu, size_t len); + + /// Handle Read By Type Request (0x08) + void handle_read_by_type_req(uint16_t conn_handle, const uint8_t* pdu, size_t len); + + /// Handle Read Request (0x0A) + void handle_read_req(uint16_t conn_handle, const uint8_t* pdu, size_t len); + + /// Handle Read Blob Request (0x0C) + void handle_read_blob_req(uint16_t conn_handle, const uint8_t* pdu, size_t len); + + /// Handle Read By Group Type Request (0x10) + void handle_read_by_group_type_req(uint16_t conn_handle, const uint8_t* pdu, size_t len); + + /// Handle Write Request (0x12) + void handle_write_req(uint16_t conn_handle, const uint8_t* pdu, size_t len); + + /// Handle Write Command (0x52) + void handle_write_cmd(uint16_t conn_handle, const uint8_t* pdu, size_t len); + + /// Handle Prepare Write Request (0x16) + void handle_prepare_write_req(uint16_t conn_handle, const uint8_t* pdu, size_t len); + + /// Handle Execute Write Request (0x18) + void handle_execute_write_req(uint16_t conn_handle, const uint8_t* pdu, size_t len); + + /// Handle Signed Write Command (0xD2) + void handle_signed_write_cmd(uint16_t conn_handle, const uint8_t* pdu, size_t len); + + // Response builders + + /// Send ATT Error Response + /// @param conn_handle Connection handle + /// @param opcode Request opcode that caused error + /// @param handle Attribute handle + /// @param error_code ATT error code + void send_error_response(uint16_t conn_handle, uint8_t opcode, + uint16_t handle, uint8_t error_code); + + /// Send MTU Exchange Response + void send_mtu_exchange_rsp(uint16_t conn_handle, uint16_t server_mtu); + + /// Send Find Information Response + void send_find_info_rsp(uint16_t conn_handle, + const std::vector& attrs); + + /// Send Read By Type Response + void send_read_by_type_rsp(uint16_t conn_handle, + const std::vector& attrs); + + /// Send Read Response + void send_read_rsp(uint16_t conn_handle, const std::vector& value); + + /// Send Read By Group Type Response + void send_read_by_group_type_rsp(uint16_t conn_handle, + const std::vector& attrs); + + /// Send Write Response + void send_write_rsp(uint16_t conn_handle); + + // Helper methods + + /// Handle CCCD write + /// @param conn_handle Connection handle + /// @param cccd_handle CCCD handle + /// @param value New CCCD value (0x0000, 0x0001, 0x0002) + void handle_cccd_write(uint16_t conn_handle, uint16_t cccd_handle, + uint16_t value); + + /// Invoke attribute read callback if present + /// @param attr Attribute + /// @param conn_handle Connection handle + /// @param offset Read offset + /// @param out_data Output data buffer + /// @return 0 on success, ATT error code on failure + int invoke_read_callback(const Attribute* attr, uint16_t conn_handle, + uint16_t offset, std::vector& out_data); + + /// Invoke attribute write callback if present + /// @param attr Attribute + /// @param conn_handle Connection handle + /// @param data Data to write + /// @return 0 on success, ATT error code on failure + int invoke_write_callback(Attribute* attr, uint16_t conn_handle, + const std::vector& data); + + /// Transport callbacks + void on_transport_connected(const ConnectionParams& params); + void on_transport_disconnected(uint16_t conn_handle); + void on_transport_data_received(uint16_t conn_handle, + const uint8_t* data, size_t len); + }; + +} // namespace BLEPP + +#endif // BLEPP_SERVER_SUPPORT +#endif // __INC_BLEPP_GATT_SERVER_H diff --git a/blepp/blepp_config.h b/blepp/blepp_config.h new file mode 100644 index 0000000..1034bae --- /dev/null +++ b/blepp/blepp_config.h @@ -0,0 +1,72 @@ +/* + * + * blepp - BLE++ Library Configuration + * + * Copyright (C) 2024 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __INC_BLEPP_CONFIG_H +#define __INC_BLEPP_CONFIG_H + +// +// Build Configuration Flags +// + +// ===== Transport Layer Support ===== + +// Enable BlueZ transport support (standard Linux Bluetooth stack) +// Uses bluetooth.h, HCI, and L2CAP sockets +// This is the default transport for most Linux systems +// +#ifndef BLEPP_BLUEZ_SUPPORT +// #define BLEPP_BLUEZ_SUPPORT +#endif + +// Enable Nimble transport support +// Uses /dev/atbm_ioctl device for Nimble-based hardware +// Can be used with or without BlueZ support +// +#ifndef BLEPP_NIMBLE_SUPPORT +// #define BLEPP_NIMBLE_SUPPORT +#endif + +// ===== Feature Support ===== + +// Enable BLE GATT Server functionality +// Define this to enable server/peripheral mode support +// If not defined, only BLE client/central mode is available +// +#ifndef BLEPP_SERVER_SUPPORT +// #define BLEPP_SERVER_SUPPORT +#endif + +// ===== Validation ===== + +// Require at least one transport +#if !defined(BLEPP_BLUEZ_SUPPORT) && !defined(BLEPP_NIMBLE_SUPPORT) + #error "At least one of BLEPP_BLUEZ_SUPPORT or BLEPP_NIMBLE_SUPPORT must be defined" +#endif + +// Server support requires at least one transport (automatically satisfied by above check) +#ifdef BLEPP_SERVER_SUPPORT + #if !defined(BLEPP_BLUEZ_SUPPORT) && !defined(BLEPP_NIMBLE_SUPPORT) + #error "BLEPP_SERVER_SUPPORT requires at least one transport" + #endif +#endif + +#endif // __INC_BLEPP_CONFIG_H diff --git a/blepp/blestatemachine.h b/blepp/blestatemachine.h index db1c6e1..47c6b58 100644 --- a/blepp/blestatemachine.h +++ b/blepp/blestatemachine.h @@ -25,13 +25,15 @@ #include #include #include +#include #include #include #include - +#ifdef BLEPP_BLUEZ_SUPPORT #include +#endif namespace BLEPP { @@ -154,7 +156,18 @@ namespace BLEPP type = BT_UUID16; value.u16 = u; } - + + // Constructor from 128-bit UUID bytes (16 bytes) + UUID(const std::vector& bytes) + { + if (bytes.size() == 16) { + type = BT_UUID128; + memcpy(&value.u128, bytes.data(), 16); + } else { + type = BT_UUID_UNSPEC; + } + } + UUID(){} UUID(const UUID&) = default; @@ -177,6 +190,19 @@ namespace BLEPP { return bt_uuid_cmp(this, &uuid) == 0; } + + bool operator!=(const UUID& uuid) const + { + return bt_uuid_cmp(this, &uuid) != 0; + } + + // Helper to convert to string + std::string str() const + { + char buf[MAX_LEN_UUID_STR]; + bt_uuid_to_string(this, buf, sizeof(buf)); + return std::string(buf); + } }; @@ -282,8 +308,10 @@ namespace BLEPP static const char* get_disconnect_string(Disconnect); private: +#ifdef BLEPP_BLUEZ_SUPPORT struct sockaddr_l2 addr; - +#endif + int sock = -1; diff --git a/blepp/bletransport.h b/blepp/bletransport.h new file mode 100644 index 0000000..d2a8944 --- /dev/null +++ b/blepp/bletransport.h @@ -0,0 +1,170 @@ +/* + * + * blepp - Implementation of the Generic ATTribute Protocol + * + * Copyright (C) 2024 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __INC_BLEPP_TRANSPORT_H +#define __INC_BLEPP_TRANSPORT_H + +#include + +#ifdef BLEPP_SERVER_SUPPORT + +#include +#include +#include +#include +#include +#include + +namespace BLEPP +{ + /// Advertising parameters for BLE peripheral mode + struct AdvertisingParams + { + std::string device_name; + std::vector service_uuids; + uint16_t appearance = 0; + + /// Advertising interval in milliseconds (min) + uint16_t min_interval_ms = 100; + + /// Advertising interval in milliseconds (max) + uint16_t max_interval_ms = 200; + + /// Service data (UUID16 + data) + uint16_t service_data_uuid16 = 0; + std::vector service_data; + + /// Raw advertising data (max 31 bytes) + uint8_t advertising_data[31]; + uint8_t advertising_data_len = 0; + + /// Raw scan response data (max 31 bytes) + uint8_t scan_response_data[31]; + uint8_t scan_response_data_len = 0; + }; + + /// Connection parameters + struct ConnectionParams + { + uint16_t conn_handle; + std::string peer_address; + uint8_t peer_address_type; + uint16_t mtu = 23; // Default ATT MTU + }; + + /// Hardware abstraction layer for BLE transport + /// This allows support for both standard BlueZ and Nimble-specific interfaces + class BLETransport + { + public: + virtual ~BLETransport() = default; + + /// Start advertising with the given parameters + /// @return 0 on success, negative error code on failure + virtual int start_advertising(const AdvertisingParams& params) = 0; + + /// Stop advertising + /// @return 0 on success, negative error code on failure + virtual int stop_advertising() = 0; + + /// Check if currently advertising + virtual bool is_advertising() const = 0; + + /// Accept an incoming connection (blocking or use get_fd() for async) + /// @return 0 on success, negative error code on failure + virtual int accept_connection() = 0; + + /// Disconnect a connection + /// @param conn_handle Connection handle to disconnect + /// @return 0 on success, negative error code on failure + virtual int disconnect(uint16_t conn_handle) = 0; + + /// Get file descriptor for select()/poll() integration + /// @return File descriptor or -1 if not supported + virtual int get_fd() const = 0; + + /// Send ATT PDU to a connected peer + /// @param conn_handle Connection handle + /// @param data PDU data + /// @param len PDU length + /// @return Number of bytes sent, or negative error code + virtual int send_pdu(uint16_t conn_handle, const uint8_t* data, size_t len) = 0; + + /// Receive ATT PDU from a connected peer + /// @param conn_handle Connection handle + /// @param buf Buffer to receive data + /// @param len Buffer size + /// @return Number of bytes received, or negative error code + virtual int recv_pdu(uint16_t conn_handle, uint8_t* buf, size_t len) = 0; + + /// Set MTU for a connection + /// @param conn_handle Connection handle + /// @param mtu MTU value (23-512) + /// @return 0 on success, negative error code on failure + virtual int set_mtu(uint16_t conn_handle, uint16_t mtu) = 0; + + /// Get current MTU for a connection + /// @param conn_handle Connection handle + /// @return MTU value + virtual uint16_t get_mtu(uint16_t conn_handle) const = 0; + + /// Process pending events (non-blocking) + /// This should be called from the event loop + /// @return 0 on success, negative error code on failure + virtual int process_events() = 0; + + // Callbacks + + /// Called when a client connects + std::function on_connected; + + /// Called when a client disconnects + std::function on_disconnected; + + /// Called when ATT data is received + std::function on_data_received; + + /// Called when MTU changes + std::function on_mtu_changed; + }; + + /// Factory function to create appropriate server transport based on build configuration + /// Tries BlueZ first (if available), then Nimble + /// @return Pointer to transport implementation (caller owns), nullptr if none available + BLETransport* create_server_transport(); + +#ifdef BLEPP_BLUEZ_SUPPORT + /// Create BlueZ server transport explicitly + /// @return Pointer to BlueZ transport implementation (caller owns) + BLETransport* create_bluez_server_transport(); +#endif + +#ifdef BLEPP_NIMBLE_SUPPORT + /// Create Nimble server transport explicitly + /// @return Pointer to Nimble transport implementation (caller owns) + BLETransport* create_nimble_server_transport(); +#endif + +} // namespace BLEPP + +#endif // BLEPP_SERVER_SUPPORT +#endif // __INC_BLEPP_TRANSPORT_H diff --git a/blepp/bluez_client_transport.h b/blepp/bluez_client_transport.h new file mode 100644 index 0000000..4a0c028 --- /dev/null +++ b/blepp/bluez_client_transport.h @@ -0,0 +1,94 @@ +/* + * + * blepp - Implementation of the Generic ATTribute Protocol + * + * Copyright (C) 2024 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __INC_BLEPP_BLUEZ_CLIENT_TRANSPORT_H +#define __INC_BLEPP_BLUEZ_CLIENT_TRANSPORT_H + +#include + +#ifdef BLEPP_BLUEZ_SUPPORT + +#include +#include +#include + +namespace BLEPP +{ + /// BlueZ-based client transport using HCI for scanning and L2CAP for connections + class BlueZClientTransport : public BLEClientTransport + { + public: + BlueZClientTransport(); + virtual ~BlueZClientTransport(); + + // Scanning operations + int start_scan(const ScanParams& params) override; + int stop_scan() override; + int get_advertisements(std::vector& ads, int timeout_ms = 0) override; + + // Connection operations + int connect(const ClientConnectionParams& params) override; + int disconnect(int fd) override; + int get_fd(int fd) const override; + + // Data transfer + int send(int fd, const uint8_t* data, size_t len) override; + int receive(int fd, uint8_t* data, size_t max_len) override; + + // MTU operations + uint16_t get_mtu(int fd) const override; + int set_mtu(int fd, uint16_t mtu) override; + + // Transport information + const char* get_transport_name() const override { return "BlueZ"; } + bool is_available() const override; + std::string get_mac_address() const override; + + private: + struct ConnectionInfo { + int fd; + uint16_t mtu; + std::string address; + }; + + int hci_dev_id_; + int hci_fd_; // HCI device for scanning + bool scanning_; + ScanParams scan_params_; + + std::set seen_devices_; // For duplicate filtering + std::map connections_; + mutable std::string mac_address_; // Cached BLE MAC address + + int open_hci_device(); + void close_hci_device(); + int set_scan_parameters(const ScanParams& params); + int set_scan_enable(bool enable, bool filter_duplicates); + int read_hci_events(std::vector& ads, int timeout_ms); + int parse_advertising_report(const uint8_t* data, size_t len, std::vector& ads); + }; + +} // namespace BLEPP + +#endif // BLEPP_BLUEZ_SUPPORT + +#endif // __INC_BLEPP_BLUEZ_CLIENT_TRANSPORT_H diff --git a/blepp/bluez_transport.h b/blepp/bluez_transport.h new file mode 100644 index 0000000..9f58bbc --- /dev/null +++ b/blepp/bluez_transport.h @@ -0,0 +1,124 @@ +/* + * + * blepp - Implementation of the Generic ATTribute Protocol + * + * Copyright (C) 2024 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __INC_BLEPP_BLUEZ_TRANSPORT_H +#define __INC_BLEPP_BLUEZ_TRANSPORT_H + +#include + +#ifdef BLEPP_SERVER_SUPPORT + +#include +#include +#include + +namespace BLEPP +{ + /// BlueZ-based BLE transport implementation + /// Uses standard Linux Bluetooth stack (HCI for advertising, L2CAP for data) + class BlueZTransport : public BLETransport + { + public: + /// Constructor + /// @param hci_dev_id HCI device ID (default: 0 for hci0) + explicit BlueZTransport(int hci_dev_id = 0); + + /// Destructor - cleans up sockets and stops advertising + ~BlueZTransport() override; + + // BLETransport interface implementation + + int start_advertising(const AdvertisingParams& params) override; + int stop_advertising() override; + bool is_advertising() const override; + + int accept_connection() override; + int disconnect(uint16_t conn_handle) override; + int get_fd() const override; + + int send_pdu(uint16_t conn_handle, const uint8_t* data, size_t len) override; + int recv_pdu(uint16_t conn_handle, uint8_t* buf, size_t len) override; + + int set_mtu(uint16_t conn_handle, uint16_t mtu) override; + uint16_t get_mtu(uint16_t conn_handle) const override; + + int process_events() override; + + private: + struct Connection + { + int fd; + uint16_t conn_handle; + std::string peer_addr; + uint16_t mtu; + }; + + int hci_dev_id_; + int hci_fd_; // HCI socket for advertising control + int l2cap_listen_fd_; // L2CAP listening socket (CID 4 - ATT) + bool advertising_; + uint16_t next_conn_handle_; + + std::map connections_; + + // Helper methods + + /// Send SSV6158 vendor command to enable ACL/Event routing + int send_ssv_acl_routing_command(int fd); + + /// Open HCI device socket + int open_hci_device(); + + /// Set up L2CAP server socket + int setup_l2cap_server(); + + /// Configure advertising parameters via HCI + int set_advertising_parameters(const AdvertisingParams& params); + + /// Set advertising data via HCI + int set_advertising_data(const AdvertisingParams& params); + + /// Set scan response data via HCI + int set_scan_response_data(const AdvertisingParams& params); + + /// Enable/disable advertising via HCI + int set_advertising_enable(bool enable); + + /// Build advertising data from parameters + int build_advertising_data(const AdvertisingParams& params, + uint8_t* data, uint8_t* len); + + /// Build scan response data from parameters + int build_scan_response_data(const AdvertisingParams& params, + uint8_t* data, uint8_t* len); + + /// Accept connection on L2CAP socket + int accept_l2cap_connection(); + + /// Close all connections and sockets + void cleanup(); + }; + +} // namespace BLEPP + +#endif // BLEPP_SERVER_SUPPORT +#endif // __INC_BLEPP_BLUEZ_TRANSPORT_H diff --git a/blepp/gatt_services.h b/blepp/gatt_services.h new file mode 100644 index 0000000..57bc41a --- /dev/null +++ b/blepp/gatt_services.h @@ -0,0 +1,231 @@ +/* + * + * blepp - Implementation of the Generic ATTribute Protocol + * + * Copyright (C) 2024 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __INC_BLEPP_GATT_SERVICES_H +#define __INC_BLEPP_GATT_SERVICES_H + +#include + +#ifdef BLEPP_SERVER_SUPPORT + +#include +#include +#include +#include +#include +#include + +namespace BLEPP +{ + // BLE-style ATT error code aliases (for NimBLE compatibility) + #define BLE_ATT_ERR_INVALID_HANDLE ATT_ECODE_INVALID_HANDLE + #define BLE_ATT_ERR_READ_NOT_PERMITTED ATT_ECODE_READ_NOT_PERM + #define BLE_ATT_ERR_READ_NOT_PERM ATT_ECODE_READ_NOT_PERM + #define BLE_ATT_ERR_WRITE_NOT_PERMITTED ATT_ECODE_WRITE_NOT_PERM + #define BLE_ATT_ERR_WRITE_NOT_PERM ATT_ECODE_WRITE_NOT_PERM + #define BLE_ATT_ERR_INVALID_PDU ATT_ECODE_INVALID_PDU + #define BLE_ATT_ERR_INSUFFICIENT_AUTHEN ATT_ECODE_AUTHENTICATION + #define BLE_ATT_ERR_REQ_NOT_SUPPORTED ATT_ECODE_REQ_NOT_SUPP + #define BLE_ATT_ERR_INVALID_OFFSET ATT_ECODE_INVALID_OFFSET + #define BLE_ATT_ERR_INSUFFICIENT_AUTHOR ATT_ECODE_AUTHORIZATION + #define BLE_ATT_ERR_PREPARE_QUEUE_FULL ATT_ECODE_PREP_QUEUE_FULL + #define BLE_ATT_ERR_ATTR_NOT_FOUND ATT_ECODE_ATTR_NOT_FOUND + #define BLE_ATT_ERR_ATTR_NOT_LONG ATT_ECODE_ATTR_NOT_LONG + #define BLE_ATT_ERR_INSUFFICIENT_KEY_SZ ATT_ECODE_INSUFF_ENCR_KEY_SIZE + #define BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN ATT_ECODE_INVAL_ATTR_VALUE_LEN + #define BLE_ATT_ERR_UNLIKELY ATT_ECODE_UNLIKELY + #define BLE_ATT_ERR_INSUFFICIENT_ENC ATT_ECODE_INSUFF_ENC + #define BLE_ATT_ERR_UNSUPPORTED_GROUP ATT_ECODE_UNSUPP_GRP_TYPE + #define BLE_ATT_ERR_UNSUPPORTED_GROUP_TYPE ATT_ECODE_UNSUPP_GRP_TYPE + #define BLE_ATT_ERR_INSUFFICIENT_RES ATT_ECODE_INSUFF_RESOURCES + + + /// GATT access callback function type + /// @param conn_handle Connection handle + /// @param op Access operation (read or write) + /// @param offset Offset for long reads/writes + /// @param data Data buffer (input for write, output for read) + /// @return 0 on success, ATT error code on failure + using GATTAccessCallback = std::function& data + )>; + + /// GATT Descriptor Definition + struct GATTDescriptorDef + { + UUID uuid; + uint8_t permissions; + GATTAccessCallback access_cb; + void* arg = nullptr; // User argument passed to callback + + uint16_t* handle_ptr = nullptr; // Filled at registration time + + GATTDescriptorDef() : permissions(0) {} + + GATTDescriptorDef(const UUID& u, uint8_t perms, + GATTAccessCallback cb = nullptr) + : uuid(u), permissions(perms), access_cb(cb) + {} + }; + + /// GATT Characteristic Definition (NimBLE-compatible) + struct GATTCharacteristicDef + { + UUID uuid; + uint16_t flags; // GATT_CHR_F_* flags + uint8_t min_key_size = 0; // Minimum required key size + GATTAccessCallback access_cb; + void* arg = nullptr; // User argument passed to callback + + std::vector descriptors; + + uint16_t* val_handle_ptr = nullptr; // Filled with value handle at registration + + GATTCharacteristicDef() : flags(0) {} + + GATTCharacteristicDef(const UUID& u, uint16_t f, + GATTAccessCallback cb = nullptr) + : uuid(u), flags(f), access_cb(cb) + {} + }; + + /// GATT Service Type + enum class GATTServiceType : uint8_t + { + PRIMARY = 1, + SECONDARY = 2 + }; + + /// GATT Service Definition (NimBLE-compatible) + struct GATTServiceDef + { + GATTServiceType type; + UUID uuid; + std::vector characteristics; + std::vector included_services; // Handles of included services + + uint16_t* handle_ptr = nullptr; // Filled with service handle at registration + + GATTServiceDef() : type(GATTServiceType::PRIMARY) {} + + GATTServiceDef(GATTServiceType t, const UUID& u) + : type(t), uuid(u) + {} + + /// Helper: Add a characteristic + GATTCharacteristicDef& add_characteristic(const UUID& uuid, + uint16_t flags, + GATTAccessCallback cb = nullptr) + { + characteristics.emplace_back(uuid, flags, cb); + return characteristics.back(); + } + + /// Helper: Add a read-only characteristic + GATTCharacteristicDef& add_read_characteristic(const UUID& uuid, + GATTAccessCallback cb = nullptr) + { + return add_characteristic(uuid, GATT_CHR_F_READ, cb); + } + + /// Helper: Add a read/write characteristic + GATTCharacteristicDef& add_read_write_characteristic(const UUID& uuid, + GATTAccessCallback cb = nullptr) + { + return add_characteristic(uuid, + GATT_CHR_F_READ | GATT_CHR_F_WRITE, + cb); + } + + /// Helper: Add a notify characteristic + GATTCharacteristicDef& add_notify_characteristic(const UUID& uuid, + GATTAccessCallback cb = nullptr) + { + return add_characteristic(uuid, + GATT_CHR_F_READ | GATT_CHR_F_NOTIFY, + cb); + } + + /// Helper: Add an indicate characteristic + GATTCharacteristicDef& add_indicate_characteristic(const UUID& uuid, + GATTAccessCallback cb = nullptr) + { + return add_characteristic(uuid, + GATT_CHR_F_READ | GATT_CHR_F_INDICATE, + cb); + } + }; + + /// Helper function: Create a simple read-only service + inline GATTServiceDef create_read_only_service(const UUID& service_uuid, + const UUID& char_uuid, + const std::vector& value) + { + GATTServiceDef service(GATTServiceType::PRIMARY, service_uuid); + + service.add_read_characteristic(char_uuid, + [value](uint16_t conn_handle, ATTAccessOp op, uint16_t offset, + std::vector& data) -> int { + if (op == ATTAccessOp::READ_CHR) { + data = value; + return 0; + } + return BLE_ATT_ERR_UNLIKELY; + } + ); + + return service; + } + + /// Helper function: Create a read/write service with callbacks + inline GATTServiceDef create_read_write_service( + const UUID& service_uuid, + const UUID& char_uuid, + std::function()> read_fn, + std::function&)> write_fn) + { + GATTServiceDef service(GATTServiceType::PRIMARY, service_uuid); + + service.add_read_write_characteristic(char_uuid, + [read_fn, write_fn](uint16_t conn_handle, ATTAccessOp op, + uint16_t offset, std::vector& data) -> int { + if (op == ATTAccessOp::READ_CHR) { + data = read_fn(); + return 0; + } else if (op == ATTAccessOp::WRITE_CHR) { + write_fn(data); + return 0; + } + return BLE_ATT_ERR_UNLIKELY; + } + ); + + return service; + } + +} // namespace BLEPP + +#endif // BLEPP_SERVER_SUPPORT +#endif // __INC_BLEPP_GATT_SERVICES_H diff --git a/blepp/lescan.h b/blepp/lescan.h index 9589058..045f9bb 100644 --- a/blepp/lescan.h +++ b/blepp/lescan.h @@ -30,9 +30,13 @@ #include #include #include -#include +#include #include //for UUID. FIXME mofo +#include + +#ifdef BLEPP_BLUEZ_SUPPORT #include +#endif namespace BLEPP { @@ -78,141 +82,180 @@ namespace BLEPP bool uuid_16_bit_complete=0; bool uuid_32_bit_complete=0; bool uuid_128_bit_complete=0; - - boost::optional local_name; - boost::optional flags; + + Name* local_name = nullptr; + Flags* flags = nullptr; std::vector> manufacturer_specific_data; std::vector> service_data; std::vector> unparsed_data_with_types; std::vector> raw_packet; - }; - /// Class for scanning for BLE devices - /// this must be run as root, because it requires getting packets from the HCI. - /// The HCI requires root since it has no permissions on setting filters, so - /// anyone with an open HCI device can sniff all data. - class HCIScanner - { - class FD + AdvertisingResponse() = default; + ~AdvertisingResponse() { + delete local_name; + delete flags; + } + + // Copy constructor + AdvertisingResponse(const AdvertisingResponse& other) + : address(other.address) + , type(other.type) + , rssi(other.rssi) + , UUIDs(other.UUIDs) + , uuid_16_bit_complete(other.uuid_16_bit_complete) + , uuid_32_bit_complete(other.uuid_32_bit_complete) + , uuid_128_bit_complete(other.uuid_128_bit_complete) + , local_name(other.local_name ? new Name(*other.local_name) : nullptr) + , flags(other.flags ? new Flags(*other.flags) : nullptr) + , manufacturer_specific_data(other.manufacturer_specific_data) + , service_data(other.service_data) + , unparsed_data_with_types(other.unparsed_data_with_types) + , raw_packet(other.raw_packet) + {} + + // Copy assignment + AdvertisingResponse& operator=(const AdvertisingResponse& other) { + if (this != &other) { + address = other.address; + type = other.type; + rssi = other.rssi; + UUIDs = other.UUIDs; + uuid_16_bit_complete = other.uuid_16_bit_complete; + uuid_32_bit_complete = other.uuid_32_bit_complete; + uuid_128_bit_complete = other.uuid_128_bit_complete; + delete local_name; + local_name = other.local_name ? new Name(*other.local_name) : nullptr; + delete flags; + flags = other.flags ? new Flags(*other.flags) : nullptr; + manufacturer_specific_data = other.manufacturer_specific_data; + service_data = other.service_data; + unparsed_data_with_types = other.unparsed_data_with_types; + raw_packet = other.raw_packet; + } + return *this; + } + + // Move constructor + AdvertisingResponse(AdvertisingResponse&& other) noexcept + : address(std::move(other.address)) + , type(other.type) + , rssi(other.rssi) + , UUIDs(std::move(other.UUIDs)) + , uuid_16_bit_complete(other.uuid_16_bit_complete) + , uuid_32_bit_complete(other.uuid_32_bit_complete) + , uuid_128_bit_complete(other.uuid_128_bit_complete) + , local_name(other.local_name) + , flags(other.flags) + , manufacturer_specific_data(std::move(other.manufacturer_specific_data)) + , service_data(std::move(other.service_data)) + , unparsed_data_with_types(std::move(other.unparsed_data_with_types)) + , raw_packet(std::move(other.raw_packet)) { - private: - int fd=-1; - - public: - operator int () const - { - return fd; - } - FD(int i) - :fd(i) - { - } - - FD()=default; - void set(int i) - { - fd = i; - } - - ~FD() - { - if(fd != -1) - close(fd); - } - }; + other.local_name = nullptr; + other.flags = nullptr; + } + // Move assignment + AdvertisingResponse& operator=(AdvertisingResponse&& other) noexcept { + if (this != &other) { + address = std::move(other.address); + type = other.type; + rssi = other.rssi; + UUIDs = std::move(other.UUIDs); + uuid_16_bit_complete = other.uuid_16_bit_complete; + uuid_32_bit_complete = other.uuid_32_bit_complete; + uuid_128_bit_complete = other.uuid_128_bit_complete; + delete local_name; + local_name = other.local_name; + other.local_name = nullptr; + delete flags; + flags = other.flags; + other.flags = nullptr; + manufacturer_specific_data = std::move(other.manufacturer_specific_data); + service_data = std::move(other.service_data); + unparsed_data_with_types = std::move(other.unparsed_data_with_types); + raw_packet = std::move(other.raw_packet); + } + return *this; + } + }; + // HCI/BLE Scanner error classes - available for all transports + + /// Generic HCI scanner error exception class + class HCIScannerError: public std::runtime_error + { public: - - enum class ScanType - { - Passive = 0x00, - Active = 0x01, - }; + HCIScannerError(const std::string& why); + }; - enum class FilterDuplicates - { - Off, //Get all events - Hardware, //Rely on hardware filtering only. Lower power draw, but can actually send - //duplicates if the device's builtin list gets overwhelmed. - Software, //Get all events from the device and filter them by hand. - Both //The best and worst of both worlds. - }; + /// HCI device spat out invalid data during parsing + class HCIParseError: public HCIScannerError + { + public: + using HCIScannerError::HCIScannerError; + }; - ///Generic error exception class - class Error: public std::runtime_error - { - public: - Error(const std::string& why); - }; + /// Parse HCI advertising packet data + /// This is a standalone function available regardless of transport backend + /// It's also accessible via HCIScanner::parse_packet() for compatibility + /// @param p Raw HCI packet data + /// @return Vector of parsed advertising responses + /// @throws HCIParseError if packet is malformed + std::vector parse_advertisement_packet(const std::vector& p); - ///Thrown only if a read() is interrupted. Only bother - ///handling if you've got a non terminating exception handler - class Interrupted: public Error - { - using Error::Error; - }; - + // Forward declaration + class BLEClientTransport; - ///IO error of some sort. Probably fatal for any bluetooth - ///based system. Or might be that the dongle was unplugged. - class IOError: public Error - { - public: - IOError(const std::string& why, int errno_val); - }; - - ///HCI device spat out invalid data. - ///This is not good. Almost certainly fatal. - class HCIError: public Error - { - using Error::Error; - }; + /// Transport-agnostic BLE Scanner class + /// Works with any BLEClientTransport implementation (BlueZ, Nimble, etc.) + /// This is the recommended scanner class for new code. + class BLEScanner + { + public: + using FilterDuplicates = ScanParams::FilterDuplicates; - HCIScanner(); - HCIScanner(bool start); - HCIScanner(bool start, FilterDuplicates duplicates, ScanType, std::string device=""); + /// Constructor + /// @param transport Pointer to transport implementation (must outlive scanner) + explicit BLEScanner(BLEClientTransport* transport); + ~BLEScanner(); - void start(); + /// Start scanning for BLE devices with custom parameters + /// @param params Scan parameters (interval, window, type, etc.) + void start(const ScanParams& params); + + /// Start scanning for BLE devices (backwards compatible) + /// @param passive If true, use passive scanning (lower power) + void start(bool passive = false); + + /// Stop scanning void stop(); - - ///get the file descriptor. - ///Use with select(), poll() or whatever. - int get_fd() const; - - ~HCIScanner(); - - ///Blocking call. Use select() on the FD if you don't want to block. - ///This reads and parses the HCI packets. - std::vector get_advertisements(); - - ///Parse an HCI advertising packet. There's probably not much - ///reason to call this yourself. - static std::vector parse_packet(const std::vector& p); - - private: - struct FilterEntry - { - explicit FilterEntry(const AdvertisingResponse&); - const std::string mac_address; - int type; - bool operator<(const FilterEntry&) const; - }; - - bool hardware_filtering; - bool software_filtering; - ScanType scan_type; - - FD hci_fd; - bool running=0; - hci_filter old_filter; - - ///Read the HCI data, but don't parse it. - std::vector read_with_retry(); - std::set scanned_devices; + + /// Get advertisements (blocking call) + /// @param timeout_ms Timeout in milliseconds (0 = no timeout) + /// @return Vector of advertising responses + std::vector get_advertisements(int timeout_ms = 0); + + /// Check if scanner is running + bool is_running() const { return running_; } + + private: + struct FilterEntry + { + explicit FilterEntry(const AdvertisingResponse&); + const std::string mac_address; + int type; + bool operator<(const FilterEntry&) const; + }; + + BLEClientTransport* transport_; + bool running_; + FilterDuplicates filter_mode_; + std::set scanned_devices_; }; + } #endif diff --git a/blepp/nimble_client_transport.h b/blepp/nimble_client_transport.h new file mode 100644 index 0000000..7d9cbef --- /dev/null +++ b/blepp/nimble_client_transport.h @@ -0,0 +1,131 @@ +/* + * + * blepp - Implementation of the Generic ATTribute Protocol + * + * Copyright (C) 2024 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __INC_BLEPP_NIMBLE_CLIENT_TRANSPORT_H +#define __INC_BLEPP_NIMBLE_CLIENT_TRANSPORT_H + +#include + +#ifdef BLEPP_NIMBLE_SUPPORT + +#include +#include +#include +#include +#include +#include +#include +#include + +// Forward declare Nimble structures +struct ble_gap_event; +struct ble_gap_disc_desc; +struct ble_gatt_access_ctxt; + +namespace BLEPP +{ + /// Nimble-based client transport using Apache Nimble BLE stack + class NimbleClientTransport : public BLEClientTransport + { + public: + NimbleClientTransport(); + virtual ~NimbleClientTransport(); + + // Scanning operations + int start_scan(const ScanParams& params) override; + int stop_scan() override; + int get_advertisements(std::vector& ads, int timeout_ms = 0) override; + + // Connection operations + int connect(const ClientConnectionParams& params) override; + int disconnect(int fd) override; + int get_fd(int fd) const override; + + // Data transfer + int send(int fd, const uint8_t* data, size_t len) override; + int receive(int fd, uint8_t* data, size_t max_len) override; + + // MTU operations + uint16_t get_mtu(int fd) const override; + int set_mtu(int fd, uint16_t mtu) override; + + // Transport information + const char* get_transport_name() const override { return "Nimble"; } + bool is_available() const override; + std::string get_mac_address() const override; + + // Nimble callbacks (static wrappers that call instance methods) + static int gap_event_callback(struct ble_gap_event* event, void* arg); + static int gatt_event_callback(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt* ctxt, void* arg); + + // Public for static callback access + std::atomic synchronized_; // True when Nimble host has synchronized + + private: + struct ConnectionInfo { + uint16_t conn_handle; // Nimble connection handle + uint16_t mtu; + std::string address; + std::queue> rx_queue; // Received data queue + bool connected; + }; + + bool initialized_; + bool scanning_; + + ScanParams scan_params_; + std::queue scan_results_; + std::mutex scan_mutex_; + std::set seen_devices_; // For duplicate filtering + + std::map connections_; // fd -> connection info + std::map handle_to_fd_; // Nimble handle -> fd + mutable std::mutex conn_mutex_; // mutable so get_mtu() can lock in const context + + int next_fd_; // For generating fake file descriptors + mutable std::string mac_address_; // Cached BLE MAC address (mutable for lazy init in const getter) + + // Nimble initialization + int initialize_nimble(); + void shutdown_nimble(); + + // Instance GAP event handlers + int handle_gap_event(struct ble_gap_event* event); + void handle_disc_event(const struct ble_gap_disc_desc* disc); + void handle_connect_event(struct ble_gap_event* event); + void handle_disconnect_event(struct ble_gap_event* event); + void handle_mtu_event(struct ble_gap_event* event); + void handle_notify_rx_event(struct ble_gap_event* event); + + // Helper functions + int allocate_fd(); + void release_fd(int fd); + std::string addr_to_string(const uint8_t addr[6]); + void string_to_addr(const std::string& str, uint8_t addr[6]); + }; + +} // namespace BLEPP + +#endif // BLEPP_NIMBLE_SUPPORT + +#endif // __INC_BLEPP_NIMBLE_CLIENT_TRANSPORT_H diff --git a/blepp/nimble_transport.h b/blepp/nimble_transport.h new file mode 100644 index 0000000..d349642 --- /dev/null +++ b/blepp/nimble_transport.h @@ -0,0 +1,167 @@ +/* + * + * blepp - Implementation of the Generic ATTribute Protocol + * + * Copyright (C) 2024 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __INC_BLEPP_NIMBLE_TRANSPORT_H +#define __INC_BLEPP_NIMBLE_TRANSPORT_H + +#include + +#ifdef BLEPP_NIMBLE_SUPPORT + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace BLEPP +{ + // Forward declarations + struct GATTServiceDef; + struct GATTCharacteristicDef; + struct GATTDescriptorDef; + /// Nimble-based BLE transport implementation + /// Uses Nimble's /dev/atbm_ioctl interface for communication with BLE controller + class NimbleTransport : public BLETransport + { + public: + /// Constructor + /// @param device_path Path to Nimble ioctl device (default: /dev/atbm_ioctl) + explicit NimbleTransport(const char* device_path = "/dev/atbm_ioctl"); + + /// Destructor - cleans up and stops event thread + ~NimbleTransport() override; + + // BLETransport interface implementation + + int start_advertising(const AdvertisingParams& params) override; + int stop_advertising() override; + bool is_advertising() const override; + + int accept_connection() override; + int disconnect(uint16_t conn_handle) override; + int get_fd() const override { return ioctl_fd_; } + + int send_pdu(uint16_t conn_handle, const uint8_t* data, size_t len) override; + int recv_pdu(uint16_t conn_handle, uint8_t* buf, size_t len) override; + + int set_mtu(uint16_t conn_handle, uint16_t mtu) override; + uint16_t get_mtu(uint16_t conn_handle) const override; + + int process_events() override; + + /// Register GATT services with NimBLE stack + /// @param services Vector of service definitions + /// @return 0 on success, negative on error + int register_services(const std::vector& services); + + /// Called from signal handler to notify event thread + void signal_event(); + + /// Convert and register services with NimBLE GATTS + /// Called from sync callback after stack initialization + int convert_and_register_services(); + + /// Restart advertising with last used parameters + /// Called from GAP event callback after disconnect + void restart_advertising(); + + private: + struct Connection + { + uint16_t conn_handle; + std::string peer_addr; + uint16_t mtu; + }; + + struct PendingPDU + { + uint16_t conn_handle; + std::vector data; + }; + + std::string device_path_; + int ioctl_fd_; + bool advertising_; + uint16_t next_conn_handle_; + bool host_task_started_; // Track if NimBLE host task has been started + + mutable std::mutex connections_mutex_; + std::map connections_; + + // Event loop thread + std::thread event_thread_; + std::atomic running_; + + // Semaphores for event synchronization + sem_t event_sem_; // Posted by signal handler, waited by event thread + sem_t ioctl_sem_; // Protects ioctl operations + + // Received PDU queue + std::mutex rx_mutex_; + std::queue rx_queue_; + + // Internal structures for Nimble ioctl communication + struct status_async { + uint8_t type; // Event type + uint8_t driver_mode; // Sub-type or reason + uint8_t list_empty; // 1 if no more events, 0 if more pending + uint8_t event_buffer[512]; + }; + + struct wsm_hdr { + uint16_t len; // Length of data following this header + uint16_t id; // Message type ID + }; + + // GATT service bridge + std::vector service_defs_; // Store original service definitions + + // Advertising parameters (stored for restart after disconnect) + AdvertisingParams adv_params_; + bool adv_params_valid_; + + // Helper methods + + /// Event loop thread function + void event_loop_thread(); + + /// Process Nimble event structure + void process_nimble_event(const struct status_async* event); + + /// Process HCI event data + void process_hci_event(const uint8_t* data, size_t len); + + /// Send HCI command + int send_hci_command(const uint8_t* cmd, size_t len); + + /// Cleanup resources + void cleanup(); + }; + +} // namespace BLEPP + +#endif // BLEPP_NIMBLE_SUPPORT +#endif // __INC_BLEPP_NIMBLE_TRANSPORT_H diff --git a/blepp/uuid.h b/blepp/uuid.h index 3dc5416..b704e4a 100644 --- a/blepp/uuid.h +++ b/blepp/uuid.h @@ -26,12 +26,23 @@ #define __BLUETOOTH_UUID_H #include +#include + +// Include bluetooth.h early if BlueZ support is enabled (provides uint128_t and other types) +#ifdef BLEPP_BLUEZ_SUPPORT #include +#endif + +// Define uint128_t ourselves only if not already defined (when BlueZ is not available) +#if !defined(BLEPP_BLUEZ_SUPPORT) && !defined(__BLUETOOTH_H) +typedef struct { + uint8_t data[16]; +} uint128_t; +#endif namespace BLEPP { #include - //#include #define GENERIC_AUDIO_UUID "00001203-0000-1000-8000-00805f9b34fb" diff --git a/configure b/configure index 5d3f0f5..6e109ee 100755 --- a/configure +++ b/configure @@ -1,9 +1,10 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for libble++ version-0.5. +# Generated by GNU Autoconf 2.72 for libble++ version-0.5. # # -# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. +# Copyright (C) 1992-1996, 1998-2017, 2020-2023 Free Software Foundation, +# Inc. # # # This configure script is free software; the Free Software Foundation @@ -16,63 +17,65 @@ # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh -if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : +if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 +then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST -else - case `(set -o) 2>/dev/null` in #( +else case e in #( + e) case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; +esac ;; esac fi + +# Reset variables that may have inherited troublesome values from +# the environment. + +# IFS needs to be set, to space, tab, and newline, in precisely that order. +# (If _AS_PATH_WALK were called with IFS unset, it would have the +# side effect of setting IFS to empty, thus disabling word splitting.) +# Quoting is to prevent editors from complaining about space-tab. as_nl=' ' export as_nl -# Printing a long string crashes Solaris 7 /usr/bin/printf. -as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' -as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo -as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo -# Prefer a ksh shell builtin over an external printf program on Solaris, -# but without wasting forks for bash or zsh. -if test -z "$BASH_VERSION$ZSH_VERSION" \ - && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then - as_echo='print -r --' - as_echo_n='print -rn --' -elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then - as_echo='printf %s\n' - as_echo_n='printf %s' -else - if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then - as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' - as_echo_n='/usr/ucb/echo -n' - else - as_echo_body='eval expr "X$1" : "X\\(.*\\)"' - as_echo_n_body='eval - arg=$1; - case $arg in #( - *"$as_nl"*) - expr "X$arg" : "X\\(.*\\)$as_nl"; - arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; - esac; - expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" - ' - export as_echo_n_body - as_echo_n='sh -c $as_echo_n_body as_echo' - fi - export as_echo_body - as_echo='sh -c $as_echo_body as_echo' -fi +IFS=" "" $as_nl" + +PS1='$ ' +PS2='> ' +PS4='+ ' + +# Ensure predictable behavior from utilities with locale-dependent output. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# We cannot yet rely on "unset" to work, but we need these variables +# to be unset--not just set to an empty or harmless value--now, to +# avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct +# also avoids known problems related to "unset" and subshell syntax +# in other old shells (e.g. bash 2.01 and pdksh 5.2.14). +for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH +do eval test \${$as_var+y} \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done + +# Ensure that fds 0, 1, and 2 are open. +if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi +if (exec 3>&2) ; then :; else exec 2>/dev/null; fi # The user is always right. -if test "${PATH_SEPARATOR+set}" != set; then +if ${PATH_SEPARATOR+false} :; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || @@ -81,13 +84,6 @@ if test "${PATH_SEPARATOR+set}" != set; then fi -# IFS -# We need space, tab and new line, in precisely that order. Quoting is -# there to prevent editors from complaining about space-tab. -# (If _AS_PATH_WALK were called with IFS unset, it would disable word -# splitting by setting IFS to empty value.) -IFS=" "" $as_nl" - # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( @@ -96,43 +92,27 @@ case $0 in #(( for as_dir in $PATH do IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + test -r "$as_dir$0" && as_myself=$as_dir$0 && break done IFS=$as_save_IFS ;; esac -# We did not find ourselves, most probably we were run as `sh COMMAND' +# We did not find ourselves, most probably we were run as 'sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then - $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + printf "%s\n" "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi -# Unset variables that we do not need and which cause bugs (e.g. in -# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" -# suppresses any "Segmentation fault" message there. '((' could -# trigger a bug in pdksh 5.2.14. -for as_var in BASH_ENV ENV MAIL MAILPATH -do eval test x\${$as_var+set} = xset \ - && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : -done -PS1='$ ' -PS2='> ' -PS4='+ ' - -# NLS nuisances. -LC_ALL=C -export LC_ALL -LANGUAGE=C -export LANGUAGE - -# CDPATH. -(unset CDPATH) >/dev/null 2>&1 && unset CDPATH # Use a proper internal environment variable to ensure we don't fall # into an infinite loop, continuously re-executing ourselves. @@ -153,26 +133,28 @@ case $- in # (((( esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail -# out after a failed `exec'. -$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 -as_fn_exit 255 +# out after a failed 'exec'. +printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 +exit 255 fi # We don't want this to propagate to other subprocesses. { _as_can_reexec=; unset _as_can_reexec;} if test "x$CONFIG_SHELL" = x; then - as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then : + as_bourne_compatible="if test \${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 +then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which # is contrary to our usage. Disable this feature. alias -g '\${1+\"\$@\"}'='\"\$@\"' setopt NO_GLOB_SUBST -else - case \`(set -o) 2>/dev/null\` in #( +else case e in #( + e) case \`(set -o) 2>/dev/null\` in #( *posix*) : set -o posix ;; #( *) : ;; +esac ;; esac fi " @@ -187,42 +169,54 @@ as_fn_success || { exitcode=1; echo as_fn_success failed.; } as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } -if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then : +if ( set x; as_fn_ret_success y && test x = \"\$1\" ) +then : -else - exitcode=1; echo positional parameters were not saved. +else case e in #( + e) exitcode=1; echo positional parameters were not saved. ;; +esac fi test x\$exitcode = x0 || exit 1 +blah=\$(echo \$(echo blah)) +test x\"\$blah\" = xblah || exit 1 test -x / || exit 1" as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && - test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1 -test \$(( 1 + 1 )) = 2 || exit 1" - if (eval "$as_required") 2>/dev/null; then : + test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1" + if (eval "$as_required") 2>/dev/null +then : as_have_required=yes -else - as_have_required=no +else case e in #( + e) as_have_required=no ;; +esac fi - if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then : + if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null +then : -else - as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +else case e in #( + e) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR as_found=false for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH do IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac as_found=: case $as_dir in #( /*) for as_base in sh bash ksh sh5; do # Try only shells that exist, to save several forks. - as_shell=$as_dir/$as_base + as_shell=$as_dir$as_base if { test -f "$as_shell" || test -f "$as_shell.exe"; } && - { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then : + as_run=a "$as_shell" -c "$as_bourne_compatible""$as_required" 2>/dev/null +then : CONFIG_SHELL=$as_shell as_have_required=yes - if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then : + if as_run=a "$as_shell" -c "$as_bourne_compatible""$as_suggested" 2>/dev/null +then : break 2 fi fi @@ -230,14 +224,22 @@ fi esac as_found=false done -$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } && - { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then : - CONFIG_SHELL=$SHELL as_have_required=yes -fi; } IFS=$as_save_IFS +if $as_found +then : + +else case e in #( + e) if { test -f "$SHELL" || test -f "$SHELL.exe"; } && + as_run=a "$SHELL" -c "$as_bourne_compatible""$as_required" 2>/dev/null +then : + CONFIG_SHELL=$SHELL as_have_required=yes +fi ;; +esac +fi - if test "x$CONFIG_SHELL" != x; then : + if test "x$CONFIG_SHELL" != x +then : export CONFIG_SHELL # We cannot yet assume a decent shell, so we have to provide a # neutralization value for shells without unset; and this also @@ -254,25 +256,27 @@ case $- in # (((( esac exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} # Admittedly, this is quite paranoid, since all the known shells bail -# out after a failed `exec'. -$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 +# out after a failed 'exec'. +printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 exit 255 fi - if test x$as_have_required = xno; then : - $as_echo "$0: This script requires a shell more modern than all" - $as_echo "$0: the shells that I found on your system." - if test x${ZSH_VERSION+set} = xset ; then - $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should" - $as_echo "$0: be upgraded to zsh 4.3.4 or later." + if test x$as_have_required = xno +then : + printf "%s\n" "$0: This script requires a shell more modern than all" + printf "%s\n" "$0: the shells that I found on your system." + if test ${ZSH_VERSION+y} ; then + printf "%s\n" "$0: In particular, zsh $ZSH_VERSION has bugs and should" + printf "%s\n" "$0: be upgraded to zsh 4.3.4 or later." else - $as_echo "$0: Please tell bug-autoconf@gnu.org about your system, + printf "%s\n" "$0: Please tell bug-autoconf@gnu.org about your system, $0: including any error possibly output before this $0: message. Then install a modern shell, or manually run $0: the script under such a shell if you do have one." fi exit 1 -fi +fi ;; +esac fi fi SHELL=${CONFIG_SHELL-/bin/sh} @@ -293,6 +297,7 @@ as_fn_unset () } as_unset=as_fn_unset + # as_fn_set_status STATUS # ----------------------- # Set $? to STATUS, without forking. @@ -324,7 +329,7 @@ as_fn_mkdir_p () as_dirs= while :; do case $as_dir in #( - *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *\'*) as_qdir=`printf "%s\n" "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" @@ -333,7 +338,7 @@ $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || -$as_echo X"$as_dir" | +printf "%s\n" X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q @@ -372,16 +377,18 @@ as_fn_executable_p () # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. -if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null +then : eval 'as_fn_append () { eval $1+=\$2 }' -else - as_fn_append () +else case e in #( + e) as_fn_append () { eval $1=\$$1\$2 - } + } ;; +esac fi # as_fn_append # as_fn_arith ARG... @@ -389,16 +396,18 @@ fi # as_fn_append # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. -if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null +then : eval 'as_fn_arith () { as_val=$(( $* )) }' -else - as_fn_arith () +else case e in #( + e) as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` - } + } ;; +esac fi # as_fn_arith @@ -412,9 +421,9 @@ as_fn_error () as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi - $as_echo "$as_me: error: $2" >&2 + printf "%s\n" "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error @@ -441,7 +450,7 @@ as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || -$as_echo X/"$0" | +printf "%s\n" X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q @@ -474,6 +483,8 @@ as_cr_alnum=$as_cr_Letters$as_cr_digits /[$]LINENO/= ' <$as_myself | sed ' + t clear + :clear s/[$]LINENO.*/&-/ t lineno b @@ -485,7 +496,7 @@ as_cr_alnum=$as_cr_Letters$as_cr_digits s/-\n.*// ' >$as_me.lineno && chmod +x "$as_me.lineno" || - { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } + { printf "%s\n" "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } # If we had to re-execute with $CONFIG_SHELL, we're ensured to have # already done that, so ensure we don't try to do so again and fall @@ -499,6 +510,10 @@ as_cr_alnum=$as_cr_Letters$as_cr_digits exit } + +# Determine whether it's possible to make 'echo' print without a newline. +# These variables are no longer used directly by Autoconf, but are AC_SUBSTed +# for compatibility with existing Makefiles. ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) @@ -512,6 +527,12 @@ case `echo -n x` in #((((( ECHO_N='-n';; esac +# For backward compatibility with old third-party macros, we provide +# the shell variables $as_echo and $as_echo_n. New code should use +# AS_ECHO(["message"]) and AS_ECHO_N(["message"]), respectively. +as_echo='printf %s\n' +as_echo_n='printf %s' + rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file @@ -523,9 +544,9 @@ if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: - # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. - # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. - # In both cases, we have to default to `cp -pR'. + # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable. + # In both cases, we have to default to 'cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then @@ -550,10 +571,12 @@ as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. -as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" +as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" +as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated # Sed expression to map a string onto a valid variable name. -as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" +as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g" +as_tr_sh="eval sed '$as_sed_sh'" # deprecated test -n "$DJDIR" || exec 7<&0 /dev/null && - as_fn_error $? "invalid feature name: $ac_useropt" + as_fn_error $? "invalid feature name: '$ac_useropt'" ac_useropt_orig=$ac_useropt - ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" @@ -824,9 +849,9 @@ do ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid feature name: $ac_useropt" + as_fn_error $? "invalid feature name: '$ac_useropt'" ac_useropt_orig=$ac_useropt - ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "enable_$ac_useropt" @@ -979,6 +1004,15 @@ do | -silent | --silent | --silen | --sile | --sil) silent=yes ;; + -runstatedir | --runstatedir | --runstatedi | --runstated \ + | --runstate | --runstat | --runsta | --runst | --runs \ + | --run | --ru | --r) + ac_prev=runstatedir ;; + -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ + | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ + | --run=* | --ru=* | --r=*) + runstatedir=$ac_optarg ;; + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ @@ -1028,9 +1062,9 @@ do ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid package name: $ac_useropt" + as_fn_error $? "invalid package name: '$ac_useropt'" ac_useropt_orig=$ac_useropt - ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" @@ -1044,9 +1078,9 @@ do ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` # Reject names that are not valid shell variable names. expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && - as_fn_error $? "invalid package name: $ac_useropt" + as_fn_error $? "invalid package name: '$ac_useropt'" ac_useropt_orig=$ac_useropt - ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` case $ac_user_opts in *" "with_$ac_useropt" @@ -1074,8 +1108,8 @@ do | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) x_libraries=$ac_optarg ;; - -*) as_fn_error $? "unrecognized option: \`$ac_option' -Try \`$0 --help' for more information" + -*) as_fn_error $? "unrecognized option: '$ac_option' +Try '$0 --help' for more information" ;; *=*) @@ -1083,16 +1117,16 @@ Try \`$0 --help' for more information" # Reject names that are not valid shell variable names. case $ac_envvar in #( '' | [0-9]* | *[!_$as_cr_alnum]* ) - as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; + as_fn_error $? "invalid variable name: '$ac_envvar'" ;; esac eval $ac_envvar=\$ac_optarg export $ac_envvar ;; *) # FIXME: should be removed in autoconf 3.0. - $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2 + printf "%s\n" "$as_me: WARNING: you should use --build, --host, --target" >&2 expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && - $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2 + printf "%s\n" "$as_me: WARNING: invalid host type: $ac_option" >&2 : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" ;; @@ -1108,7 +1142,7 @@ if test -n "$ac_unrecognized_opts"; then case $enable_option_checking in no) ;; fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; - *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; + *) printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; esac fi @@ -1116,7 +1150,7 @@ fi for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ - libdir localedir mandir + libdir localedir mandir runstatedir do eval ac_val=\$$ac_var # Remove trailing slashes. @@ -1133,7 +1167,7 @@ do as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" done -# There might be people who depend on the old broken behavior: `$host' +# There might be people who depend on the old broken behavior: '$host' # used to hold the argument of --host etc. # FIXME: To remove some day. build=$build_alias @@ -1172,7 +1206,7 @@ $as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_myself" : 'X\(//\)[^/]' \| \ X"$as_myself" : 'X\(//\)$' \| \ X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || -$as_echo X"$as_myself" | +printf "%s\n" X"$as_myself" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q @@ -1201,7 +1235,7 @@ if test ! -r "$srcdir/$ac_unique_file"; then test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" fi -ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" +ac_msg="sources are in $srcdir, but 'cd $srcdir' does not work" ac_abs_confdir=`( cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" pwd)` @@ -1229,7 +1263,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures libble++ version-0.5 to adapt to many kinds of systems. +'configure' configures libble++ version-0.5 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1243,11 +1277,11 @@ Configuration: --help=short display options specific to this package --help=recursive display the short help of all the included packages -V, --version display version information and exit - -q, --quiet, --silent do not print \`checking ...' messages + -q, --quiet, --silent do not print 'checking ...' messages --cache-file=FILE cache test results in FILE [disabled] - -C, --config-cache alias for \`--cache-file=config.cache' + -C, --config-cache alias for '--cache-file=config.cache' -n, --no-create do not create output files - --srcdir=DIR find the sources in DIR [configure dir or \`..'] + --srcdir=DIR find the sources in DIR [configure dir or '..'] Installation directories: --prefix=PREFIX install architecture-independent files in PREFIX @@ -1255,10 +1289,10 @@ Installation directories: --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX [PREFIX] -By default, \`make install' will install all the files in -\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify -an installation prefix other than \`$ac_default_prefix' using \`--prefix', -for instance \`--prefix=\$HOME'. +By default, 'make install' will install all the files in +'$ac_default_prefix/bin', '$ac_default_prefix/lib' etc. You can specify +an installation prefix other than '$ac_default_prefix' using '--prefix', +for instance '--prefix=\$HOME'. For better control, use the options below. @@ -1269,6 +1303,7 @@ Fine tuning of the installation directories: --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] @@ -1294,6 +1329,15 @@ if test -n "$ac_init_help"; then esac cat <<\_ACEOF +Optional Packages: + --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] + --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) + --with-bluez-support Build with BlueZ transport support (HCI/L2CAP) + [default=yes] + --with-nimble-support Build with Nimble transport support + (/dev/atbm_ioctl) [default=no] + --with-server-support Build with BLE GATT server support [default=no] + Some influential environment variables: CXX C++ compiler command CXXFLAGS C++ compiler flags @@ -1302,9 +1346,12 @@ Some influential environment variables: LIBS libraries to pass to the linker, e.g. -l CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if you have headers in a nonstandard directory - CXXCPP C++ preprocessor + NIMBLE_ROOT Path to Nimble BLE stack root directory (for headers) + NIMBLE_LIBDIR + Path to Nimble library directory (defaults to NIMBLE_ROOT/lib or + NIMBLE_ROOT/build) -Use these variables to override the choices made by `configure' or to help +Use these variables to override the choices made by 'configure' or to help it to find libraries and programs with nonstandard names/locations. Report bugs to the package provider. @@ -1323,9 +1370,9 @@ if test "$ac_init_help" = "recursive"; then case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) - ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + ac_dir_suffix=/`printf "%s\n" "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. - ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + ac_top_builddir_sub=`printf "%s\n" "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; @@ -1353,7 +1400,8 @@ esac ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix cd "$ac_dir" || { ac_status=$?; continue; } - # Check for guested configure. + # Check for configure.gnu first; this name is used for a wrapper for + # Metaconfig's "Configure" on case-insensitive file systems. if test -f "$ac_srcdir/configure.gnu"; then echo && $SHELL "$ac_srcdir/configure.gnu" --help=recursive @@ -1361,7 +1409,7 @@ ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix echo && $SHELL "$ac_srcdir/configure" --help=recursive else - $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 + printf "%s\n" "$as_me: WARNING: no configuration information is in $ac_dir" >&2 fi || ac_status=$? cd "$ac_pwd" || { ac_status=$?; break; } done @@ -1371,9 +1419,9 @@ test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF libble++ configure version-0.5 -generated by GNU Autoconf 2.69 +generated by GNU Autoconf 2.72 -Copyright (C) 2012 Free Software Foundation, Inc. +Copyright (C) 2023 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. @@ -1392,14 +1440,14 @@ fi ac_fn_cxx_try_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - rm -f conftest.$ac_objext + rm -f conftest.$ac_objext conftest.beam if { { ac_try="$ac_compile" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -$as_echo "$ac_try_echo"; } >&5 +printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>conftest.err ac_status=$? if test -s conftest.err; then @@ -1407,188 +1455,24 @@ $as_echo "$ac_try_echo"; } >&5 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err - } && test -s conftest.$ac_objext; then : - ac_retval=0 -else - $as_echo "$as_me: failed program was:" >&5 -sed 's/^/| /' conftest.$ac_ext >&5 - - ac_retval=1 -fi - eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno - as_fn_set_status $ac_retval - -} # ac_fn_cxx_try_compile - -# ac_fn_cxx_try_cpp LINENO -# ------------------------ -# Try to preprocess conftest.$ac_ext, and return whether this succeeded. -ac_fn_cxx_try_cpp () -{ - as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - if { { ac_try="$ac_cpp conftest.$ac_ext" -case "(($ac_try" in - *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; - *) ac_try_echo=$ac_try;; -esac -eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -$as_echo "$ac_try_echo"; } >&5 - (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err - ac_status=$? - if test -s conftest.err; then - grep -v '^ *+' conftest.err >conftest.er1 - cat conftest.er1 >&5 - mv -f conftest.er1 conftest.err - fi - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; } > conftest.i && { - test -z "$ac_cxx_preproc_warn_flag$ac_cxx_werror_flag" || - test ! -s conftest.err - }; then : + } && test -s conftest.$ac_objext +then : ac_retval=0 -else - $as_echo "$as_me: failed program was:" >&5 +else case e in #( + e) printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 - ac_retval=1 -fi - eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno - as_fn_set_status $ac_retval - -} # ac_fn_cxx_try_cpp - -# ac_fn_cxx_check_header_mongrel LINENO HEADER VAR INCLUDES -# --------------------------------------------------------- -# Tests whether HEADER exists, giving a warning if it cannot be compiled using -# the include files in INCLUDES and setting the cache variable VAR -# accordingly. -ac_fn_cxx_check_header_mongrel () -{ - as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - if eval \${$3+:} false; then : - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 -$as_echo_n "checking for $2... " >&6; } -if eval \${$3+:} false; then : - $as_echo_n "(cached) " >&6 -fi -eval ac_res=\$$3 - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } -else - # Is the header compilable? -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 usability" >&5 -$as_echo_n "checking $2 usability... " >&6; } -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -$4 -#include <$2> -_ACEOF -if ac_fn_cxx_try_compile "$LINENO"; then : - ac_header_compiler=yes -else - ac_header_compiler=no -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_compiler" >&5 -$as_echo "$ac_header_compiler" >&6; } - -# Is the header present? -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 presence" >&5 -$as_echo_n "checking $2 presence... " >&6; } -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include <$2> -_ACEOF -if ac_fn_cxx_try_cpp "$LINENO"; then : - ac_header_preproc=yes -else - ac_header_preproc=no -fi -rm -f conftest.err conftest.i conftest.$ac_ext -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5 -$as_echo "$ac_header_preproc" >&6; } - -# So? What about this header? -case $ac_header_compiler:$ac_header_preproc:$ac_cxx_preproc_warn_flag in #(( - yes:no: ) - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&5 -$as_echo "$as_me: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&2;} - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 -$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} - ;; - no:yes:* ) - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: present but cannot be compiled" >&5 -$as_echo "$as_me: WARNING: $2: present but cannot be compiled" >&2;} - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: check for missing prerequisite headers?" >&5 -$as_echo "$as_me: WARNING: $2: check for missing prerequisite headers?" >&2;} - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: see the Autoconf documentation" >&5 -$as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;} - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&5 -$as_echo "$as_me: WARNING: $2: section \"Present But Cannot Be Compiled\"" >&2;} - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5 -$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;} - ;; -esac - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 -$as_echo_n "checking for $2... " >&6; } -if eval \${$3+:} false; then : - $as_echo_n "(cached) " >&6 -else - eval "$3=\$ac_header_compiler" -fi -eval ac_res=\$$3 - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } -fi - eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno - -} # ac_fn_cxx_check_header_mongrel - -# ac_fn_cxx_try_run LINENO -# ------------------------ -# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes -# that executables *can* be run. -ac_fn_cxx_try_run () -{ - as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - if { { ac_try="$ac_link" -case "(($ac_try" in - *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; - *) ac_try_echo=$ac_try;; -esac -eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -$as_echo "$ac_try_echo"; } >&5 - (eval "$ac_link") 2>&5 - ac_status=$? - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; } && { ac_try='./conftest$ac_exeext' - { { case "(($ac_try" in - *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; - *) ac_try_echo=$ac_try;; + ac_retval=1 ;; esac -eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -$as_echo "$ac_try_echo"; } >&5 - (eval "$ac_try") 2>&5 - ac_status=$? - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; }; }; then : - ac_retval=0 -else - $as_echo "$as_me: program exited with status $ac_status" >&5 - $as_echo "$as_me: failed program was:" >&5 -sed 's/^/| /' conftest.$ac_ext >&5 - - ac_retval=$ac_status fi - rm -rf conftest.dSYM conftest_ipa8_conftest.oo eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno as_fn_set_status $ac_retval -} # ac_fn_cxx_try_run +} # ac_fn_cxx_try_compile # ac_fn_cxx_check_header_compile LINENO HEADER VAR INCLUDES # --------------------------------------------------------- @@ -1597,26 +1481,30 @@ fi ac_fn_cxx_check_header_compile () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 -$as_echo_n "checking for $2... " >&6; } -if eval \${$3+:} false; then : - $as_echo_n "(cached) " >&6 -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +printf %s "checking for $2... " >&6; } +if eval test \${$3+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ $4 #include <$2> _ACEOF -if ac_fn_cxx_try_compile "$LINENO"; then : +if ac_fn_cxx_try_compile "$LINENO" +then : eval "$3=yes" -else - eval "$3=no" +else case e in #( + e) eval "$3=no" ;; +esac fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac fi eval ac_res=\$$3 - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +printf "%s\n" "$ac_res" >&6; } eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno } # ac_fn_cxx_check_header_compile @@ -1627,14 +1515,14 @@ $as_echo "$ac_res" >&6; } ac_fn_cxx_try_link () { as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - rm -f conftest.$ac_objext conftest$ac_exeext + rm -f conftest.$ac_objext conftest.beam conftest$ac_exeext if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -$as_echo "$ac_try_echo"; } >&5 +printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link") 2>conftest.err ac_status=$? if test -s conftest.err; then @@ -1642,20 +1530,22 @@ $as_echo "$ac_try_echo"; } >&5 cat conftest.er1 >&5 mv -f conftest.er1 conftest.err fi - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } && { test -z "$ac_cxx_werror_flag" || test ! -s conftest.err } && test -s conftest$ac_exeext && { test "$cross_compiling" = yes || test -x conftest$ac_exeext - }; then : + } +then : ac_retval=0 -else - $as_echo "$as_me: failed program was:" >&5 +else case e in #( + e) printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 - ac_retval=1 + ac_retval=1 ;; +esac fi # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would @@ -1666,14 +1556,34 @@ fi as_fn_set_status $ac_retval } # ac_fn_cxx_try_link +ac_configure_args_raw= +for ac_arg +do + case $ac_arg in + *\'*) + ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + as_fn_append ac_configure_args_raw " '$ac_arg'" +done + +case $ac_configure_args_raw in + *$as_nl*) + ac_safe_unquote= ;; + *) + ac_unsafe_z='|&;<>()$`\\"*?[ '' ' # This string ends in space, tab. + ac_unsafe_a="$ac_unsafe_z#~" + ac_safe_unquote="s/ '\\([^$ac_unsafe_a][^$ac_unsafe_z]*\\)'/ \\1/g" + ac_configure_args_raw=` printf "%s\n" "$ac_configure_args_raw" | sed "$ac_safe_unquote"`;; +esac + cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. It was created by libble++ $as_me version-0.5, which was -generated by GNU Autoconf 2.69. Invocation command line was +generated by GNU Autoconf 2.72. Invocation command line was - $ $0 $@ + $ $0$ac_configure_args_raw _ACEOF exec 5>>config.log @@ -1706,8 +1616,12 @@ as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - $as_echo "PATH: $as_dir" + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + printf "%s\n" "PATH: $as_dir" done IFS=$as_save_IFS @@ -1742,7 +1656,7 @@ do | -silent | --silent | --silen | --sile | --sil) continue ;; *\'*) - ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; + ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; esac case $ac_pass in 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; @@ -1777,11 +1691,13 @@ done # WARNING: Use '\'' to represent an apostrophe within the trap. # WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. trap 'exit_status=$? + # Sanitize IFS. + IFS=" "" $as_nl" # Save into config.log some information that might help in debugging. { echo - $as_echo "## ---------------- ## + printf "%s\n" "## ---------------- ## ## Cache variables. ## ## ---------------- ##" echo @@ -1792,8 +1708,8 @@ trap 'exit_status=$? case $ac_val in #( *${as_nl}*) case $ac_var in #( - *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 -$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( @@ -1817,7 +1733,7 @@ $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; ) echo - $as_echo "## ----------------- ## + printf "%s\n" "## ----------------- ## ## Output variables. ## ## ----------------- ##" echo @@ -1825,14 +1741,14 @@ $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; do eval ac_val=\$$ac_var case $ac_val in - *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + *\'\''*) ac_val=`printf "%s\n" "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac - $as_echo "$ac_var='\''$ac_val'\''" + printf "%s\n" "$ac_var='\''$ac_val'\''" done | sort echo if test -n "$ac_subst_files"; then - $as_echo "## ------------------- ## + printf "%s\n" "## ------------------- ## ## File substitutions. ## ## ------------------- ##" echo @@ -1840,15 +1756,15 @@ $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; do eval ac_val=\$$ac_var case $ac_val in - *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + *\'\''*) ac_val=`printf "%s\n" "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; esac - $as_echo "$ac_var='\''$ac_val'\''" + printf "%s\n" "$ac_var='\''$ac_val'\''" done | sort echo fi if test -s confdefs.h; then - $as_echo "## ----------- ## + printf "%s\n" "## ----------- ## ## confdefs.h. ## ## ----------- ##" echo @@ -1856,8 +1772,8 @@ $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; echo fi test "$ac_signal" != 0 && - $as_echo "$as_me: caught signal $ac_signal" - $as_echo "$as_me: exit $exit_status" + printf "%s\n" "$as_me: caught signal $ac_signal" + printf "%s\n" "$as_me: exit $exit_status" } >&5 rm -f core *.core core.conftest.* && rm -f -r conftest* confdefs* conf$$* $ac_clean_files && @@ -1871,65 +1787,50 @@ ac_signal=0 # confdefs.h avoids OS command line length limits that DEFS can exceed. rm -f -r conftest* confdefs.h -$as_echo "/* confdefs.h */" > confdefs.h +printf "%s\n" "/* confdefs.h */" > confdefs.h # Predefined preprocessor variables. -cat >>confdefs.h <<_ACEOF -#define PACKAGE_NAME "$PACKAGE_NAME" -_ACEOF +printf "%s\n" "#define PACKAGE_NAME \"$PACKAGE_NAME\"" >>confdefs.h -cat >>confdefs.h <<_ACEOF -#define PACKAGE_TARNAME "$PACKAGE_TARNAME" -_ACEOF +printf "%s\n" "#define PACKAGE_TARNAME \"$PACKAGE_TARNAME\"" >>confdefs.h -cat >>confdefs.h <<_ACEOF -#define PACKAGE_VERSION "$PACKAGE_VERSION" -_ACEOF +printf "%s\n" "#define PACKAGE_VERSION \"$PACKAGE_VERSION\"" >>confdefs.h -cat >>confdefs.h <<_ACEOF -#define PACKAGE_STRING "$PACKAGE_STRING" -_ACEOF +printf "%s\n" "#define PACKAGE_STRING \"$PACKAGE_STRING\"" >>confdefs.h -cat >>confdefs.h <<_ACEOF -#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" -_ACEOF +printf "%s\n" "#define PACKAGE_BUGREPORT \"$PACKAGE_BUGREPORT\"" >>confdefs.h -cat >>confdefs.h <<_ACEOF -#define PACKAGE_URL "$PACKAGE_URL" -_ACEOF +printf "%s\n" "#define PACKAGE_URL \"$PACKAGE_URL\"" >>confdefs.h # Let the site file select an alternate cache file if it wants to. # Prefer an explicitly selected file to automatically selected ones. -ac_site_file1=NONE -ac_site_file2=NONE if test -n "$CONFIG_SITE"; then - # We do not want a PATH search for config.site. - case $CONFIG_SITE in #(( - -*) ac_site_file1=./$CONFIG_SITE;; - */*) ac_site_file1=$CONFIG_SITE;; - *) ac_site_file1=./$CONFIG_SITE;; - esac + ac_site_files="$CONFIG_SITE" elif test "x$prefix" != xNONE; then - ac_site_file1=$prefix/share/config.site - ac_site_file2=$prefix/etc/config.site + ac_site_files="$prefix/share/config.site $prefix/etc/config.site" else - ac_site_file1=$ac_default_prefix/share/config.site - ac_site_file2=$ac_default_prefix/etc/config.site + ac_site_files="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site" fi -for ac_site_file in "$ac_site_file1" "$ac_site_file2" + +for ac_site_file in $ac_site_files do - test "x$ac_site_file" = xNONE && continue - if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 -$as_echo "$as_me: loading site script $ac_site_file" >&6;} + case $ac_site_file in #( + */*) : + ;; #( + *) : + ac_site_file=./$ac_site_file ;; +esac + if test -f "$ac_site_file" && test -r "$ac_site_file"; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 +printf "%s\n" "$as_me: loading site script $ac_site_file" >&6;} sed 's/^/| /' "$ac_site_file" >&5 . "$ac_site_file" \ - || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} + || { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "failed to load site script $ac_site_file -See \`config.log' for more details" "$LINENO" 5; } +See 'config.log' for more details" "$LINENO" 5; } fi done @@ -1937,140 +1838,378 @@ if test -r "$cache_file"; then # Some versions of bash will fail to source /dev/null (special files # actually), so we avoid doing that. DJGPP emulates it as a regular file. if test /dev/null != "$cache_file" && test -f "$cache_file"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 -$as_echo "$as_me: loading cache $cache_file" >&6;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 +printf "%s\n" "$as_me: loading cache $cache_file" >&6;} case $cache_file in [\\/]* | ?:[\\/]* ) . "$cache_file";; *) . "./$cache_file";; esac fi else - { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 -$as_echo "$as_me: creating cache $cache_file" >&6;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 +printf "%s\n" "$as_me: creating cache $cache_file" >&6;} >$cache_file fi -# Check that the precious variables saved in the cache have kept the same -# value. -ac_cache_corrupted=false -for ac_var in $ac_precious_vars; do - eval ac_old_set=\$ac_cv_env_${ac_var}_set - eval ac_new_set=\$ac_env_${ac_var}_set - eval ac_old_val=\$ac_cv_env_${ac_var}_value - eval ac_new_val=\$ac_env_${ac_var}_value - case $ac_old_set,$ac_new_set in - set,) - { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 -$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} - ac_cache_corrupted=: ;; - ,set) - { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 -$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} - ac_cache_corrupted=: ;; - ,);; - *) - if test "x$ac_old_val" != "x$ac_new_val"; then - # differences in whitespace do not lead to failure. - ac_old_val_w=`echo x $ac_old_val` - ac_new_val_w=`echo x $ac_new_val` - if test "$ac_old_val_w" != "$ac_new_val_w"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 -$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} - ac_cache_corrupted=: - else - { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 -$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} - eval $ac_var=\$ac_old_val - fi - { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 -$as_echo "$as_me: former value: \`$ac_old_val'" >&2;} - { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 -$as_echo "$as_me: current value: \`$ac_new_val'" >&2;} - fi;; - esac - # Pass precious variables to config.status. - if test "$ac_new_set" = set; then - case $ac_new_val in - *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; - *) ac_arg=$ac_var=$ac_new_val ;; - esac - case " $ac_configure_args " in - *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. - *) as_fn_append ac_configure_args " '$ac_arg'" ;; - esac - fi -done -if $ac_cache_corrupted; then - { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} - { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 -$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;} - as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 -fi -## -------------------- ## -## Main body of script. ## -## -------------------- ## +# Test code for whether the C++ compiler supports C++98 (global declarations) +ac_cxx_conftest_cxx98_globals=' +// Does the compiler advertise C++98 conformance? +#if !defined __cplusplus || __cplusplus < 199711L +# error "Compiler does not advertise C++98 conformance" +#endif -ac_ext=c -ac_cpp='$CPP $CPPFLAGS' -ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_c_compiler_gnu +// These inclusions are to reject old compilers that +// lack the unsuffixed header files. +#include +#include +// and are *not* freestanding headers in C++98. +extern void assert (int); +namespace std { + extern int strcmp (const char *, const char *); +} +// Namespaces, exceptions, and templates were all added after "C++ 2.0". +using std::exception; +using std::strcmp; -ac_ext=cpp -ac_cpp='$CXXCPP $CPPFLAGS' -ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_cxx_compiler_gnu +namespace { -ac_ext=cpp -ac_cpp='$CXXCPP $CPPFLAGS' -ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_cxx_compiler_gnu -if test -z "$CXX"; then - if test -n "$CCC"; then - CXX=$CCC - else - if test -n "$ac_tool_prefix"; then - for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC - do - # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. -set dummy $ac_tool_prefix$ac_prog; ac_word=$2 -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -$as_echo_n "checking for $ac_word... " >&6; } -if ${ac_cv_prog_CXX+:} false; then : - $as_echo_n "(cached) " >&6 -else - if test -n "$CXX"; then +void test_exception_syntax() +{ + try { + throw "test"; + } catch (const char *s) { + // Extra parentheses suppress a warning when building autoconf itself, + // due to lint rules shared with more typical C programs. + assert (!(strcmp) (s, "test")); + } +} + +template struct test_template +{ + T const val; + explicit test_template(T t) : val(t) {} + template T add(U u) { return static_cast(u) + val; } +}; + +} // anonymous namespace +' + +# Test code for whether the C++ compiler supports C++98 (body of main) +ac_cxx_conftest_cxx98_main=' + assert (argc); + assert (! argv[0]); +{ + test_exception_syntax (); + test_template tt (2.0); + assert (tt.add (4) == 6.0); + assert (true && !false); +} +' + +# Test code for whether the C++ compiler supports C++11 (global declarations) +ac_cxx_conftest_cxx11_globals=' +// Does the compiler advertise C++ 2011 conformance? +#if !defined __cplusplus || __cplusplus < 201103L +# error "Compiler does not advertise C++11 conformance" +#endif + +namespace cxx11test +{ + constexpr int get_val() { return 20; } + + struct testinit + { + int i; + double d; + }; + + class delegate + { + public: + delegate(int n) : n(n) {} + delegate(): delegate(2354) {} + + virtual int getval() { return this->n; }; + protected: + int n; + }; + + class overridden : public delegate + { + public: + overridden(int n): delegate(n) {} + virtual int getval() override final { return this->n * 2; } + }; + + class nocopy + { + public: + nocopy(int i): i(i) {} + nocopy() = default; + nocopy(const nocopy&) = delete; + nocopy & operator=(const nocopy&) = delete; + private: + int i; + }; + + // for testing lambda expressions + template Ret eval(Fn f, Ret v) + { + return f(v); + } + + // for testing variadic templates and trailing return types + template auto sum(V first) -> V + { + return first; + } + template auto sum(V first, Args... rest) -> V + { + return first + sum(rest...); + } +} +' + +# Test code for whether the C++ compiler supports C++11 (body of main) +ac_cxx_conftest_cxx11_main=' +{ + // Test auto and decltype + auto a1 = 6538; + auto a2 = 48573953.4; + auto a3 = "String literal"; + + int total = 0; + for (auto i = a3; *i; ++i) { total += *i; } + + decltype(a2) a4 = 34895.034; +} +{ + // Test constexpr + short sa[cxx11test::get_val()] = { 0 }; +} +{ + // Test initializer lists + cxx11test::testinit il = { 4323, 435234.23544 }; +} +{ + // Test range-based for + int array[] = {9, 7, 13, 15, 4, 18, 12, 10, 5, 3, + 14, 19, 17, 8, 6, 20, 16, 2, 11, 1}; + for (auto &x : array) { x += 23; } +} +{ + // Test lambda expressions + using cxx11test::eval; + assert (eval ([](int x) { return x*2; }, 21) == 42); + double d = 2.0; + assert (eval ([&](double x) { return d += x; }, 3.0) == 5.0); + assert (d == 5.0); + assert (eval ([=](double x) mutable { return d += x; }, 4.0) == 9.0); + assert (d == 5.0); +} +{ + // Test use of variadic templates + using cxx11test::sum; + auto a = sum(1); + auto b = sum(1, 2); + auto c = sum(1.0, 2.0, 3.0); +} +{ + // Test constructor delegation + cxx11test::delegate d1; + cxx11test::delegate d2(); + cxx11test::delegate d3(45); +} +{ + // Test override and final + cxx11test::overridden o1(55464); +} +{ + // Test nullptr + char *c = nullptr; +} +{ + // Test template brackets + test_template<::test_template> v(test_template(12)); +} +{ + // Unicode literals + char const *utf8 = u8"UTF-8 string \u2500"; + char16_t const *utf16 = u"UTF-8 string \u2500"; + char32_t const *utf32 = U"UTF-32 string \u2500"; +} +' + +# Test code for whether the C compiler supports C++11 (complete). +ac_cxx_conftest_cxx11_program="${ac_cxx_conftest_cxx98_globals} +${ac_cxx_conftest_cxx11_globals} + +int +main (int argc, char **argv) +{ + int ok = 0; + ${ac_cxx_conftest_cxx98_main} + ${ac_cxx_conftest_cxx11_main} + return ok; +} +" + +# Test code for whether the C compiler supports C++98 (complete). +ac_cxx_conftest_cxx98_program="${ac_cxx_conftest_cxx98_globals} +int +main (int argc, char **argv) +{ + int ok = 0; + ${ac_cxx_conftest_cxx98_main} + return ok; +} +" + +as_fn_append ac_header_cxx_list " stdio.h stdio_h HAVE_STDIO_H" +as_fn_append ac_header_cxx_list " stdlib.h stdlib_h HAVE_STDLIB_H" +as_fn_append ac_header_cxx_list " string.h string_h HAVE_STRING_H" +as_fn_append ac_header_cxx_list " inttypes.h inttypes_h HAVE_INTTYPES_H" +as_fn_append ac_header_cxx_list " stdint.h stdint_h HAVE_STDINT_H" +as_fn_append ac_header_cxx_list " strings.h strings_h HAVE_STRINGS_H" +as_fn_append ac_header_cxx_list " sys/stat.h sys_stat_h HAVE_SYS_STAT_H" +as_fn_append ac_header_cxx_list " sys/types.h sys_types_h HAVE_SYS_TYPES_H" +as_fn_append ac_header_cxx_list " unistd.h unistd_h HAVE_UNISTD_H" +# Check that the precious variables saved in the cache have kept the same +# value. +ac_cache_corrupted=false +for ac_var in $ac_precious_vars; do + eval ac_old_set=\$ac_cv_env_${ac_var}_set + eval ac_new_set=\$ac_env_${ac_var}_set + eval ac_old_val=\$ac_cv_env_${ac_var}_value + eval ac_new_val=\$ac_env_${ac_var}_value + case $ac_old_set,$ac_new_set in + set,) + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&5 +printf "%s\n" "$as_me: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,set) + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was not set in the previous run" >&5 +printf "%s\n" "$as_me: error: '$ac_var' was not set in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,);; + *) + if test "x$ac_old_val" != "x$ac_new_val"; then + # differences in whitespace do not lead to failure. + ac_old_val_w=`echo x $ac_old_val` + ac_new_val_w=`echo x $ac_new_val` + if test "$ac_old_val_w" != "$ac_new_val_w"; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' has changed since the previous run:" >&5 +printf "%s\n" "$as_me: error: '$ac_var' has changed since the previous run:" >&2;} + ac_cache_corrupted=: + else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&5 +printf "%s\n" "$as_me: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&2;} + eval $ac_var=\$ac_old_val + fi + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: former value: '$ac_old_val'" >&5 +printf "%s\n" "$as_me: former value: '$ac_old_val'" >&2;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: current value: '$ac_new_val'" >&5 +printf "%s\n" "$as_me: current value: '$ac_new_val'" >&2;} + fi;; + esac + # Pass precious variables to config.status. + if test "$ac_new_set" = set; then + case $ac_new_val in + *\'*) ac_arg=$ac_var=`printf "%s\n" "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; + *) ac_arg=$ac_var=$ac_new_val ;; + esac + case " $ac_configure_args " in + *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. + *) as_fn_append ac_configure_args " '$ac_arg'" ;; + esac + fi +done +if $ac_cache_corrupted; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 +printf "%s\n" "$as_me: error: changes in the environment can compromise the build" >&2;} + as_fn_error $? "run '${MAKE-make} distclean' and/or 'rm $cache_file' + and start over" "$LINENO" 5 +fi +## -------------------- ## +## Main body of script. ## +## -------------------- ## + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + +ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + + + + + + +ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu +if test -z "$CXX"; then + if test -n "$CCC"; then + CXX=$CCC + else + if test -n "$ac_tool_prefix"; then + for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC clang++ + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_CXX+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$CXX"; then ac_cv_prog_CXX="$CXX" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_CXX="$ac_tool_prefix$ac_prog" - $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS -fi +fi ;; +esac fi CXX=$ac_cv_prog_CXX if test -n "$CXX"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CXX" >&5 -$as_echo "$CXX" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CXX" >&5 +printf "%s\n" "$CXX" >&6; } else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } fi @@ -2079,42 +2218,48 @@ fi fi if test -z "$CXX"; then ac_ct_CXX=$CXX - for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC + for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC clang++ do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -$as_echo_n "checking for $ac_word... " >&6; } -if ${ac_cv_prog_ac_ct_CXX+:} false; then : - $as_echo_n "(cached) " >&6 -else - if test -n "$ac_ct_CXX"; then +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_ac_ct_CXX+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$ac_ct_CXX"; then ac_cv_prog_ac_ct_CXX="$ac_ct_CXX" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CXX="$ac_prog" - $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done done IFS=$as_save_IFS -fi +fi ;; +esac fi ac_ct_CXX=$ac_cv_prog_ac_ct_CXX if test -n "$ac_ct_CXX"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CXX" >&5 -$as_echo "$ac_ct_CXX" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CXX" >&5 +printf "%s\n" "$ac_ct_CXX" >&6; } else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } fi @@ -2126,8 +2271,8 @@ done else case $cross_compiling:$ac_tool_warned in yes:) -{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 -$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} ac_tool_warned=yes ;; esac CXX=$ac_ct_CXX @@ -2137,7 +2282,7 @@ fi fi fi # Provide some information about the compiler. -$as_echo "$as_me:${as_lineno-$LINENO}: checking for C++ compiler version" >&5 +printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C++ compiler version" >&5 set X $ac_compile ac_compiler=$2 for ac_option in --version -v -V -qversion; do @@ -2147,7 +2292,7 @@ case "(($ac_try" in *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -$as_echo "$ac_try_echo"; } >&5 +printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_compiler $ac_option >&5") 2>conftest.err ac_status=$? if test -s conftest.err; then @@ -2157,7 +2302,7 @@ $as_echo "$ac_try_echo"; } >&5 cat conftest.er1 >&5 fi rm -f conftest.er1 conftest.err - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } done @@ -2165,7 +2310,7 @@ cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int -main () +main (void) { ; @@ -2177,9 +2322,9 @@ ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" # Try to create an executable without -o first, disregard a.out. # It will help us diagnose broken compilers, and finding out an intuition # of exeext. -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C++ compiler works" >&5 -$as_echo_n "checking whether the C++ compiler works... " >&6; } -ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'` +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C++ compiler works" >&5 +printf %s "checking whether the C++ compiler works... " >&6; } +ac_link_default=`printf "%s\n" "$ac_link" | sed 's/ -o *conftest[^ ]*//'` # The possible output files: ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" @@ -2200,13 +2345,14 @@ case "(($ac_try" in *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -$as_echo "$ac_try_echo"; } >&5 +printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link_default") 2>&5 ac_status=$? - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; }; then : - # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. -# So ignore a value of `no', otherwise this would lead to `EXEEXT = no' + printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +then : + # Autoconf-2.13 could set the ac_cv_exeext variable to 'no'. +# So ignore a value of 'no', otherwise this would lead to 'EXEEXT = no' # in a Makefile. We should not override ac_cv_exeext if it was cached, # so that the user can short-circuit this test for compilers unknown to # Autoconf. @@ -2221,12 +2367,12 @@ do # certainly right. break;; *.* ) - if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no; + if test ${ac_cv_exeext+y} && test "$ac_cv_exeext" != no; then :; else ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` fi # We set ac_cv_exeext here because the later test for it is not - # safe: cross compilers may not add the suffix if given an `-o' + # safe: cross compilers may not add the suffix if given an '-o' # argument, so we may need to know it at that point already. # Even if this section looks crufty: it has the advantage of # actually working. @@ -2237,48 +2383,52 @@ do done test "$ac_cv_exeext" = no && ac_cv_exeext= -else - ac_file='' +else case e in #( + e) ac_file='' ;; +esac fi -if test -z "$ac_file"; then : - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } -$as_echo "$as_me: failed program was:" >&5 +if test -z "$ac_file" +then : + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } +printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 -{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error 77 "C++ compiler cannot create executables -See \`config.log' for more details" "$LINENO" 5; } -else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -$as_echo "yes" >&6; } +See 'config.log' for more details" "$LINENO" 5; } +else case e in #( + e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf "%s\n" "yes" >&6; } ;; +esac fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C++ compiler default output file name" >&5 -$as_echo_n "checking for C++ compiler default output file name... " >&6; } -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 -$as_echo "$ac_file" >&6; } +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C++ compiler default output file name" >&5 +printf %s "checking for C++ compiler default output file name... " >&6; } +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 +printf "%s\n" "$ac_file" >&6; } ac_exeext=$ac_cv_exeext rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out ac_clean_files=$ac_clean_files_save -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 -$as_echo_n "checking for suffix of executables... " >&6; } +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 +printf %s "checking for suffix of executables... " >&6; } if { { ac_try="$ac_link" case "(($ac_try" in *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -$as_echo "$ac_try_echo"; } >&5 +printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; }; then : - # If both `conftest.exe' and `conftest' are `present' (well, observable) -# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will -# work properly (i.e., refer to `conftest.exe'), while it won't with -# `rm'. + printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +then : + # If both 'conftest.exe' and 'conftest' are 'present' (well, observable) +# catch 'conftest.exe'. For instance with Cygwin, 'ls conftest' will +# work properly (i.e., refer to 'conftest.exe'), while it won't with +# 'rm'. for ac_file in conftest.exe conftest conftest.*; do test -f "$ac_file" || continue case $ac_file in @@ -2288,15 +2438,16 @@ for ac_file in conftest.exe conftest conftest.*; do * ) break;; esac done -else - { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +else case e in #( + e) { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of executables: cannot compile and link -See \`config.log' for more details" "$LINENO" 5; } +See 'config.log' for more details" "$LINENO" 5; } ;; +esac fi rm -f conftest conftest$ac_cv_exeext -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 -$as_echo "$ac_cv_exeext" >&6; } +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 +printf "%s\n" "$ac_cv_exeext" >&6; } rm -f conftest.$ac_ext EXEEXT=$ac_cv_exeext @@ -2305,9 +2456,11 @@ cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include int -main () +main (void) { FILE *f = fopen ("conftest.out", "w"); + if (!f) + return 1; return ferror (f) || fclose (f) != 0; ; @@ -2317,8 +2470,8 @@ _ACEOF ac_clean_files="$ac_clean_files conftest.out" # Check that the compiler produces executables we can run. If not, either # the compiler is broken, or we cross compile. -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 -$as_echo_n "checking whether we are cross compiling... " >&6; } +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 +printf %s "checking whether we are cross compiling... " >&6; } if test "$cross_compiling" != yes; then { { ac_try="$ac_link" case "(($ac_try" in @@ -2326,10 +2479,10 @@ case "(($ac_try" in *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -$as_echo "$ac_try_echo"; } >&5 +printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_link") 2>&5 ac_status=$? - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; } if { ac_try='./conftest$ac_cv_exeext' { { case "(($ac_try" in @@ -2337,39 +2490,41 @@ $as_echo "$ac_try_echo"; } >&5 *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -$as_echo "$ac_try_echo"; } >&5 +printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_try") 2>&5 ac_status=$? - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 test $ac_status = 0; }; }; then cross_compiling=no else if test "$cross_compiling" = maybe; then cross_compiling=yes else - { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} -as_fn_error $? "cannot run C++ compiled programs. -If you meant to cross compile, use \`--host'. -See \`config.log' for more details" "$LINENO" 5; } + { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} +as_fn_error 77 "cannot run C++ compiled programs. +If you meant to cross compile, use '--host'. +See 'config.log' for more details" "$LINENO" 5; } fi fi fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 -$as_echo "$cross_compiling" >&6; } +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 +printf "%s\n" "$cross_compiling" >&6; } -rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out +rm -f conftest.$ac_ext conftest$ac_cv_exeext \ + conftest.o conftest.obj conftest.out ac_clean_files=$ac_clean_files_save -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 -$as_echo_n "checking for suffix of object files... " >&6; } -if ${ac_cv_objext+:} false; then : - $as_echo_n "(cached) " >&6 -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 +printf %s "checking for suffix of object files... " >&6; } +if test ${ac_cv_objext+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int -main () +main (void) { ; @@ -2383,11 +2538,12 @@ case "(($ac_try" in *) ac_try_echo=$ac_try;; esac eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" -$as_echo "$ac_try_echo"; } >&5 +printf "%s\n" "$ac_try_echo"; } >&5 (eval "$ac_compile") 2>&5 ac_status=$? - $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 - test $ac_status = 0; }; then : + printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +then : for ac_file in conftest.o conftest.obj conftest.*; do test -f "$ac_file" || continue; case $ac_file in @@ -2396,31 +2552,34 @@ $as_echo "$ac_try_echo"; } >&5 break;; esac done -else - $as_echo "$as_me: failed program was:" >&5 +else case e in #( + e) printf "%s\n" "$as_me: failed program was:" >&5 sed 's/^/| /' conftest.$ac_ext >&5 -{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} as_fn_error $? "cannot compute suffix of object files: cannot compile -See \`config.log' for more details" "$LINENO" 5; } +See 'config.log' for more details" "$LINENO" 5; } ;; +esac fi -rm -f conftest.$ac_cv_objext conftest.$ac_ext +rm -f conftest.$ac_cv_objext conftest.$ac_ext ;; +esac fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 -$as_echo "$ac_cv_objext" >&6; } +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 +printf "%s\n" "$ac_cv_objext" >&6; } OBJEXT=$ac_cv_objext ac_objext=$OBJEXT -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C++ compiler" >&5 -$as_echo_n "checking whether we are using the GNU C++ compiler... " >&6; } -if ${ac_cv_cxx_compiler_gnu+:} false; then : - $as_echo_n "(cached) " >&6 -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the compiler supports GNU C++" >&5 +printf %s "checking whether the compiler supports GNU C++... " >&6; } +if test ${ac_cv_cxx_compiler_gnu+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int -main () +main (void) { #ifndef __GNUC__ choke me @@ -2430,30 +2589,36 @@ main () return 0; } _ACEOF -if ac_fn_cxx_try_compile "$LINENO"; then : +if ac_fn_cxx_try_compile "$LINENO" +then : ac_compiler_gnu=yes -else - ac_compiler_gnu=no +else case e in #( + e) ac_compiler_gnu=no ;; +esac fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ac_cv_cxx_compiler_gnu=$ac_compiler_gnu - + ;; +esac fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_cxx_compiler_gnu" >&5 -$as_echo "$ac_cv_cxx_compiler_gnu" >&6; } +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_cxx_compiler_gnu" >&5 +printf "%s\n" "$ac_cv_cxx_compiler_gnu" >&6; } +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + if test $ac_compiler_gnu = yes; then GXX=yes else GXX= fi -ac_test_CXXFLAGS=${CXXFLAGS+set} +ac_test_CXXFLAGS=${CXXFLAGS+y} ac_save_CXXFLAGS=$CXXFLAGS -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CXX accepts -g" >&5 -$as_echo_n "checking whether $CXX accepts -g... " >&6; } -if ${ac_cv_prog_cxx_g+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_save_cxx_werror_flag=$ac_cxx_werror_flag +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CXX accepts -g" >&5 +printf %s "checking whether $CXX accepts -g... " >&6; } +if test ${ac_cv_prog_cxx_g+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_save_cxx_werror_flag=$ac_cxx_werror_flag ac_cxx_werror_flag=yes ac_cv_prog_cxx_g=no CXXFLAGS="-g" @@ -2461,57 +2626,63 @@ else /* end confdefs.h. */ int -main () +main (void) { ; return 0; } _ACEOF -if ac_fn_cxx_try_compile "$LINENO"; then : +if ac_fn_cxx_try_compile "$LINENO" +then : ac_cv_prog_cxx_g=yes -else - CXXFLAGS="" +else case e in #( + e) CXXFLAGS="" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int -main () +main (void) { ; return 0; } _ACEOF -if ac_fn_cxx_try_compile "$LINENO"; then : +if ac_fn_cxx_try_compile "$LINENO" +then : -else - ac_cxx_werror_flag=$ac_save_cxx_werror_flag +else case e in #( + e) ac_cxx_werror_flag=$ac_save_cxx_werror_flag CXXFLAGS="-g" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int -main () +main (void) { ; return 0; } _ACEOF -if ac_fn_cxx_try_compile "$LINENO"; then : +if ac_fn_cxx_try_compile "$LINENO" +then : ac_cv_prog_cxx_g=yes fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext - ac_cxx_werror_flag=$ac_save_cxx_werror_flag +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + ac_cxx_werror_flag=$ac_save_cxx_werror_flag ;; +esac fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cxx_g" >&5 -$as_echo "$ac_cv_prog_cxx_g" >&6; } -if test "$ac_test_CXXFLAGS" = set; then +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cxx_g" >&5 +printf "%s\n" "$ac_cv_prog_cxx_g" >&6; } +if test $ac_test_CXXFLAGS; then CXXFLAGS=$ac_save_CXXFLAGS elif test $ac_cv_prog_cxx_g = yes; then if test "$GXX" = yes; then @@ -2526,6 +2697,106 @@ else CXXFLAGS= fi fi +ac_prog_cxx_stdcxx=no +if test x$ac_prog_cxx_stdcxx = xno +then : + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CXX option to enable C++11 features" >&5 +printf %s "checking for $CXX option to enable C++11 features... " >&6; } +if test ${ac_cv_prog_cxx_cxx11+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_cv_prog_cxx_cxx11=no +ac_save_CXX=$CXX +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$ac_cxx_conftest_cxx11_program +_ACEOF +for ac_arg in '' -std=gnu++11 -std=gnu++0x -std=c++11 -std=c++0x -qlanglvl=extended0x -AA +do + CXX="$ac_save_CXX $ac_arg" + if ac_fn_cxx_try_compile "$LINENO" +then : + ac_cv_prog_cxx_cxx11=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam + test "x$ac_cv_prog_cxx_cxx11" != "xno" && break +done +rm -f conftest.$ac_ext +CXX=$ac_save_CXX ;; +esac +fi + +if test "x$ac_cv_prog_cxx_cxx11" = xno +then : + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +printf "%s\n" "unsupported" >&6; } +else case e in #( + e) if test "x$ac_cv_prog_cxx_cxx11" = x +then : + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +printf "%s\n" "none needed" >&6; } +else case e in #( + e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cxx_cxx11" >&5 +printf "%s\n" "$ac_cv_prog_cxx_cxx11" >&6; } + CXX="$CXX $ac_cv_prog_cxx_cxx11" ;; +esac +fi + ac_cv_prog_cxx_stdcxx=$ac_cv_prog_cxx_cxx11 + ac_prog_cxx_stdcxx=cxx11 ;; +esac +fi +fi +if test x$ac_prog_cxx_stdcxx = xno +then : + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CXX option to enable C++98 features" >&5 +printf %s "checking for $CXX option to enable C++98 features... " >&6; } +if test ${ac_cv_prog_cxx_cxx98+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_cv_prog_cxx_cxx98=no +ac_save_CXX=$CXX +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$ac_cxx_conftest_cxx98_program +_ACEOF +for ac_arg in '' -std=gnu++98 -std=c++98 -qlanglvl=extended -AA +do + CXX="$ac_save_CXX $ac_arg" + if ac_fn_cxx_try_compile "$LINENO" +then : + ac_cv_prog_cxx_cxx98=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam + test "x$ac_cv_prog_cxx_cxx98" != "xno" && break +done +rm -f conftest.$ac_ext +CXX=$ac_save_CXX ;; +esac +fi + +if test "x$ac_cv_prog_cxx_cxx98" = xno +then : + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +printf "%s\n" "unsupported" >&6; } +else case e in #( + e) if test "x$ac_cv_prog_cxx_cxx98" = x +then : + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +printf "%s\n" "none needed" >&6; } +else case e in #( + e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cxx_cxx98" >&5 +printf "%s\n" "$ac_cv_prog_cxx_cxx98" >&6; } + CXX="$CXX $ac_cv_prog_cxx_cxx98" ;; +esac +fi + ac_cv_prog_cxx_stdcxx=$ac_cv_prog_cxx_cxx98 + ac_prog_cxx_stdcxx=cxx98 ;; +esac +fi +fi + ac_ext=cpp ac_cpp='$CXXCPP $CPPFLAGS' ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' @@ -2598,11 +2869,11 @@ then if test "" == "" then - { $as_echo "$as_me:${as_lineno-$LINENO}: checking if compiler flag -Wall works" >&5 -$as_echo_n "checking if compiler flag -Wall works... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if compiler flag -Wall works" >&5 +printf %s "checking if compiler flag -Wall works... " >&6; } else - { $as_echo "$as_me:${as_lineno-$LINENO}: checking " >&5 -$as_echo_n "checking ... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking " >&5 +printf %s "checking ... " >&6; } fi save_CXXFLAGS="$CXXFLAGS" CXXFLAGS="$CXXFLAGS -Wall" @@ -2614,12 +2885,14 @@ cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ int main(){} _ACEOF -if ac_fn_cxx_try_compile "$LINENO"; then : +if ac_fn_cxx_try_compile "$LINENO" +then : cvd_conf_test=1 -else - cvd_conf_test=0 +else case e in #( + e) cvd_conf_test=0 ;; +esac fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext @@ -2627,12 +2900,12 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext if test $cvd_conf_test = 1 then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -$as_echo "yes" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf "%s\n" "yes" >&6; } ts_success=yes else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } CXXFLAGS="$save_CXXFLAGS" ts_success=no fi @@ -2640,11 +2913,11 @@ $as_echo "no" >&6; } if test "" == "" then - { $as_echo "$as_me:${as_lineno-$LINENO}: checking if compiler flag -Wextra works" >&5 -$as_echo_n "checking if compiler flag -Wextra works... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if compiler flag -Wextra works" >&5 +printf %s "checking if compiler flag -Wextra works... " >&6; } else - { $as_echo "$as_me:${as_lineno-$LINENO}: checking " >&5 -$as_echo_n "checking ... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking " >&5 +printf %s "checking ... " >&6; } fi save_CXXFLAGS="$CXXFLAGS" CXXFLAGS="$CXXFLAGS -Wextra" @@ -2655,12 +2928,14 @@ $as_echo_n "checking ... " >&6; } /* end confdefs.h. */ int main(){} _ACEOF -if ac_fn_cxx_try_compile "$LINENO"; then : +if ac_fn_cxx_try_compile "$LINENO" +then : cvd_conf_test=1 -else - cvd_conf_test=0 +else case e in #( + e) cvd_conf_test=0 ;; +esac fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext @@ -2668,12 +2943,12 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext if test $cvd_conf_test = 1 then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -$as_echo "yes" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf "%s\n" "yes" >&6; } ts_success=yes else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } CXXFLAGS="$save_CXXFLAGS" ts_success=no fi @@ -2681,11 +2956,11 @@ $as_echo "no" >&6; } if test "" == "" then - { $as_echo "$as_me:${as_lineno-$LINENO}: checking if compiler flag -W works" >&5 -$as_echo_n "checking if compiler flag -W works... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if compiler flag -W works" >&5 +printf %s "checking if compiler flag -W works... " >&6; } else - { $as_echo "$as_me:${as_lineno-$LINENO}: checking " >&5 -$as_echo_n "checking ... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking " >&5 +printf %s "checking ... " >&6; } fi save_CXXFLAGS="$CXXFLAGS" CXXFLAGS="$CXXFLAGS -W" @@ -2696,12 +2971,14 @@ $as_echo_n "checking ... " >&6; } /* end confdefs.h. */ int main(){} _ACEOF -if ac_fn_cxx_try_compile "$LINENO"; then : +if ac_fn_cxx_try_compile "$LINENO" +then : cvd_conf_test=1 -else - cvd_conf_test=0 +else case e in #( + e) cvd_conf_test=0 ;; +esac fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext @@ -2709,12 +2986,12 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext if test $cvd_conf_test = 1 then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -$as_echo "yes" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf "%s\n" "yes" >&6; } ts_success=yes else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } CXXFLAGS="$save_CXXFLAGS" ts_success=no fi @@ -2722,11 +2999,11 @@ $as_echo "no" >&6; } if test "" == "" then - { $as_echo "$as_me:${as_lineno-$LINENO}: checking if compiler flag -O3 works" >&5 -$as_echo_n "checking if compiler flag -O3 works... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if compiler flag -O3 works" >&5 +printf %s "checking if compiler flag -O3 works... " >&6; } else - { $as_echo "$as_me:${as_lineno-$LINENO}: checking " >&5 -$as_echo_n "checking ... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking " >&5 +printf %s "checking ... " >&6; } fi save_CXXFLAGS="$CXXFLAGS" CXXFLAGS="$CXXFLAGS -O3" @@ -2737,12 +3014,14 @@ $as_echo_n "checking ... " >&6; } /* end confdefs.h. */ int main(){} _ACEOF -if ac_fn_cxx_try_compile "$LINENO"; then : +if ac_fn_cxx_try_compile "$LINENO" +then : cvd_conf_test=1 -else - cvd_conf_test=0 +else case e in #( + e) cvd_conf_test=0 ;; +esac fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext @@ -2750,12 +3029,12 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext if test $cvd_conf_test = 1 then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -$as_echo "yes" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf "%s\n" "yes" >&6; } ts_success=yes else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } CXXFLAGS="$save_CXXFLAGS" ts_success=no fi @@ -2763,11 +3042,11 @@ $as_echo "no" >&6; } if test "" == "" then - { $as_echo "$as_me:${as_lineno-$LINENO}: checking if compiler flag -ggdb works" >&5 -$as_echo_n "checking if compiler flag -ggdb works... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if compiler flag -ggdb works" >&5 +printf %s "checking if compiler flag -ggdb works... " >&6; } else - { $as_echo "$as_me:${as_lineno-$LINENO}: checking " >&5 -$as_echo_n "checking ... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking " >&5 +printf %s "checking ... " >&6; } fi save_CXXFLAGS="$CXXFLAGS" CXXFLAGS="$CXXFLAGS -ggdb" @@ -2778,12 +3057,14 @@ $as_echo_n "checking ... " >&6; } /* end confdefs.h. */ int main(){} _ACEOF -if ac_fn_cxx_try_compile "$LINENO"; then : +if ac_fn_cxx_try_compile "$LINENO" +then : cvd_conf_test=1 -else - cvd_conf_test=0 +else case e in #( + e) cvd_conf_test=0 ;; +esac fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext @@ -2791,12 +3072,12 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext if test $cvd_conf_test = 1 then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -$as_echo "yes" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf "%s\n" "yes" >&6; } ts_success=yes else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } CXXFLAGS="$save_CXXFLAGS" ts_success=no fi @@ -2810,12 +3091,13 @@ ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' ac_compiler_gnu=$ac_cv_cxx_compiler_gnu ac_success=no - { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CXX supports C++14 features by default" >&5 -$as_echo_n "checking whether $CXX supports C++14 features by default... " >&6; } -if ${ax_cv_cxx_compile_cxx14+:} false; then : - $as_echo_n "(cached) " >&6 -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CXX supports C++14 features by default" >&5 +printf %s "checking whether $CXX supports C++14 features by default... " >&6; } +if test ${ax_cv_cxx_compile_cxx14+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ template @@ -2870,28 +3152,32 @@ else void noret [[noreturn]] () { throw 0; } _ACEOF -if ac_fn_cxx_try_compile "$LINENO"; then : +if ac_fn_cxx_try_compile "$LINENO" +then : ax_cv_cxx_compile_cxx14=yes -else - ax_cv_cxx_compile_cxx14=no +else case e in #( + e) ax_cv_cxx_compile_cxx14=no ;; +esac fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_cxx_compile_cxx14" >&5 -$as_echo "$ax_cv_cxx_compile_cxx14" >&6; } +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_cxx_compile_cxx14" >&5 +printf "%s\n" "$ax_cv_cxx_compile_cxx14" >&6; } if test x$ax_cv_cxx_compile_cxx14 = xyes; then ac_success=yes fi if test x$ac_success = xno; then for switch in -std=gnu++14 -std=gnu++1y; do - cachevar=`$as_echo "ax_cv_cxx_compile_cxx14_$switch" | $as_tr_sh` - { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CXX supports C++14 features with $switch" >&5 -$as_echo_n "checking whether $CXX supports C++14 features with $switch... " >&6; } -if eval \${$cachevar+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_save_CXXFLAGS="$CXXFLAGS" + cachevar=`printf "%s\n" "ax_cv_cxx_compile_cxx14_$switch" | sed "$as_sed_sh"` + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CXX supports C++14 features with $switch" >&5 +printf %s "checking whether $CXX supports C++14 features with $switch... " >&6; } +if eval test \${$cachevar+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_save_CXXFLAGS="$CXXFLAGS" CXXFLAGS="$CXXFLAGS $switch" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -2948,17 +3234,20 @@ else void noret [[noreturn]] () { throw 0; } _ACEOF -if ac_fn_cxx_try_compile "$LINENO"; then : +if ac_fn_cxx_try_compile "$LINENO" +then : eval $cachevar=yes -else - eval $cachevar=no +else case e in #( + e) eval $cachevar=no ;; +esac fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext - CXXFLAGS="$ac_save_CXXFLAGS" +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + CXXFLAGS="$ac_save_CXXFLAGS" ;; +esac fi eval ac_res=\$$cachevar - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +printf "%s\n" "$ac_res" >&6; } if eval test x\$$cachevar = xyes; then CXXFLAGS="$CXXFLAGS $switch" ac_success=yes @@ -2969,13 +3258,14 @@ $as_echo "$ac_res" >&6; } if test x$ac_success = xno; then for switch in -std=c++14 -std=c++1y +std=c++14 "-h std=c++14"; do - cachevar=`$as_echo "ax_cv_cxx_compile_cxx14_$switch" | $as_tr_sh` - { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CXX supports C++14 features with $switch" >&5 -$as_echo_n "checking whether $CXX supports C++14 features with $switch... " >&6; } -if eval \${$cachevar+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_save_CXXFLAGS="$CXXFLAGS" + cachevar=`printf "%s\n" "ax_cv_cxx_compile_cxx14_$switch" | sed "$as_sed_sh"` + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CXX supports C++14 features with $switch" >&5 +printf %s "checking whether $CXX supports C++14 features with $switch... " >&6; } +if eval test \${$cachevar+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_save_CXXFLAGS="$CXXFLAGS" CXXFLAGS="$CXXFLAGS $switch" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -3032,17 +3322,20 @@ else void noret [[noreturn]] () { throw 0; } _ACEOF -if ac_fn_cxx_try_compile "$LINENO"; then : +if ac_fn_cxx_try_compile "$LINENO" +then : eval $cachevar=yes -else - eval $cachevar=no +else case e in #( + e) eval $cachevar=no ;; +esac fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext - CXXFLAGS="$ac_save_CXXFLAGS" +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + CXXFLAGS="$ac_save_CXXFLAGS" ;; +esac fi eval ac_res=\$$cachevar - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 -$as_echo "$ac_res" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +printf "%s\n" "$ac_res" >&6; } if eval test x\$$cachevar = xyes; then CXXFLAGS="$CXXFLAGS $switch" ac_success=yes @@ -3063,12 +3356,12 @@ ac_compiler_gnu=$ac_cv_cxx_compiler_gnu else if test x$ac_success = xno; then HAVE_CXX14=0 - { $as_echo "$as_me:${as_lineno-$LINENO}: No compiler with C++14 support was found" >&5 -$as_echo "$as_me: No compiler with C++14 support was found" >&6;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: No compiler with C++14 support was found" >&5 +printf "%s\n" "$as_me: No compiler with C++14 support was found" >&6;} else HAVE_CXX14=1 -$as_echo "#define HAVE_CXX14 1" >>confdefs.h +printf "%s\n" "#define HAVE_CXX14 1" >>confdefs.h fi @@ -3082,12 +3375,13 @@ $as_echo "#define HAVE_CXX14 1" >>confdefs.h # # Extract the first word of "pkg-config", so it can be a program name with args. set dummy pkg-config; ac_word=$2 -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -$as_echo_n "checking for $ac_word... " >&6; } -if ${ac_cv_path_PKG_CONFIG+:} false; then : - $as_echo_n "(cached) " >&6 -else - case $PKG_CONFIG in +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_path_PKG_CONFIG+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) case $PKG_CONFIG in [\\/]* | ?:[\\/]*) ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path. ;; @@ -3096,11 +3390,15 @@ else for as_dir in $PATH do IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then - ac_cv_path_PKG_CONFIG="$as_dir/$ac_word$ac_exec_ext" - $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_path_PKG_CONFIG="$as_dir$ac_word$ac_exec_ext" + printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done @@ -3108,42 +3406,48 @@ done IFS=$as_save_IFS ;; +esac ;; esac fi PKG_CONFIG=$ac_cv_path_PKG_CONFIG if test -n "$PKG_CONFIG"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5 -$as_echo "$PKG_CONFIG" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5 +printf "%s\n" "$PKG_CONFIG" >&6; } else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } fi if test "x$PKG_CONFIG" = "x"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: Could not find pkg-config, will not create pc file." >&5 -$as_echo "$as_me: WARNING: Could not find pkg-config, will not create pc file." >&2;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: Could not find pkg-config, will not create pc file." >&5 +printf "%s\n" "$as_me: WARNING: Could not find pkg-config, will not create pc file." >&2;} else # we need sed to find the pkg-config lib directory # Extract the first word of "sed", so it can be a program name with args. set dummy sed; ac_word=$2 -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 -$as_echo_n "checking for $ac_word... " >&6; } -if ${ac_cv_prog_SED+:} false; then : - $as_echo_n "(cached) " >&6 -else - if test -n "$SED"; then +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_SED+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$SED"; then ac_cv_prog_SED="$SED" # Let the user override the test. else as_save_IFS=$IFS; IFS=$PATH_SEPARATOR for as_dir in $PATH do IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac for ac_exec_ext in '' $ac_executable_extensions; do - if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then ac_cv_prog_SED="sed" - $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 break 2 fi done @@ -3151,24 +3455,25 @@ done IFS=$as_save_IFS test -z "$ac_cv_prog_SED" && ac_cv_prog_SED="as_fn_error $? "You Must install sed" "$LINENO" 5" -fi +fi ;; +esac fi SED=$ac_cv_prog_SED if test -n "$SED"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $SED" >&5 -$as_echo "$SED" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $SED" >&5 +printf "%s\n" "$SED" >&6; } else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } fi - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pkg-config library dir" >&5 -$as_echo_n "checking for pkg-config library dir... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for pkg-config library dir" >&5 +printf %s "checking for pkg-config library dir... " >&6; } PKGCONFIG_LIBDIR="`echo $PKG_CONFIG | $SED -e 's~.*/bin/pkg-config$~~'`${libdir}/pkgconfig" - { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKGCONFIG_LIBDIR" >&5 -$as_echo "$PKGCONFIG_LIBDIR" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PKGCONFIG_LIBDIR" >&5 +printf "%s\n" "$PKGCONFIG_LIBDIR" >&6; } ac_config_files="$ac_config_files libblepp.pc" @@ -3178,491 +3483,355 @@ $as_echo "$PKGCONFIG_LIBDIR" >&6; } fi -a=1 -ac_ext=cpp -ac_cpp='$CXXCPP $CPPFLAGS' -ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_cxx_compiler_gnu -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C++ preprocessor" >&5 -$as_echo_n "checking how to run the C++ preprocessor... " >&6; } -if test -z "$CXXCPP"; then - if ${ac_cv_prog_CXXCPP+:} false; then : - $as_echo_n "(cached) " >&6 -else - # Double quotes because CXXCPP needs to be expanded - for CXXCPP in "$CXX -E" "/lib/cpp" - do - ac_preproc_ok=false -for ac_cxx_preproc_warn_flag in '' yes -do - # Use a header file that comes with gcc, so configuring glibc - # with a fresh cross-compiler works. - # Prefer to if __STDC__ is defined, since - # exists even on freestanding compilers. - # On the NeXT, cc -E runs the code through the compiler's parser, - # not just through cpp. "Syntax error" is here to catch this case. - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#ifdef __STDC__ -# include -#else -# include -#endif - Syntax error -_ACEOF -if ac_fn_cxx_try_cpp "$LINENO"; then : +################################################################################ +# +# Transport support options +# -else - # Broken: fails on valid input. -continue -fi -rm -f conftest.err conftest.i conftest.$ac_ext - # OK, works on sane cases. Now check whether nonexistent headers - # can be detected and how. - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include -_ACEOF -if ac_fn_cxx_try_cpp "$LINENO"; then : - # Broken: success on invalid input. -continue -else - # Passes both tests. -ac_preproc_ok=: -break -fi -rm -f conftest.err conftest.i conftest.$ac_ext - -done -# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. -rm -f conftest.i conftest.err conftest.$ac_ext -if $ac_preproc_ok; then : - break +# Check whether --with-bluez-support was given. +if test ${with_bluez_support+y} +then : + withval=$with_bluez_support; with_bluez_support=$withval +else case e in #( + e) with_bluez_support=yes ;; +esac fi - done - ac_cv_prog_CXXCPP=$CXXCPP -fi - CXXCPP=$ac_cv_prog_CXXCPP -else - ac_cv_prog_CXXCPP=$CXXCPP -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CXXCPP" >&5 -$as_echo "$CXXCPP" >&6; } -ac_preproc_ok=false -for ac_cxx_preproc_warn_flag in '' yes -do - # Use a header file that comes with gcc, so configuring glibc - # with a fresh cross-compiler works. - # Prefer to if __STDC__ is defined, since - # exists even on freestanding compilers. - # On the NeXT, cc -E runs the code through the compiler's parser, - # not just through cpp. "Syntax error" is here to catch this case. - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#ifdef __STDC__ -# include -#else -# include -#endif - Syntax error -_ACEOF -if ac_fn_cxx_try_cpp "$LINENO"; then : -else - # Broken: fails on valid input. -continue +# Check whether --with-nimble-support was given. +if test ${with_nimble_support+y} +then : + withval=$with_nimble_support; with_nimble_support=$withval +else case e in #( + e) with_nimble_support=no ;; +esac fi -rm -f conftest.err conftest.i conftest.$ac_ext - # OK, works on sane cases. Now check whether nonexistent headers - # can be detected and how. - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include -_ACEOF -if ac_fn_cxx_try_cpp "$LINENO"; then : - # Broken: success on invalid input. -continue -else - # Passes both tests. -ac_preproc_ok=: -break -fi -rm -f conftest.err conftest.i conftest.$ac_ext -done -# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped. -rm -f conftest.i conftest.err conftest.$ac_ext -if $ac_preproc_ok; then : -else - { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} -as_fn_error $? "C++ preprocessor \"$CXXCPP\" fails sanity check -See \`config.log' for more details" "$LINENO" 5; } +# Check whether --with-server-support was given. +if test ${with_server_support+y} +then : + withval=$with_server_support; with_server_support=$withval +else case e in #( + e) with_server_support=no ;; +esac fi -ac_ext=cpp -ac_cpp='$CXXCPP $CPPFLAGS' -ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' -ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' -ac_compiler_gnu=$ac_cv_cxx_compiler_gnu -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5 -$as_echo_n "checking for grep that handles long lines and -e... " >&6; } -if ${ac_cv_path_GREP+:} false; then : - $as_echo_n "(cached) " >&6 -else - if test -z "$GREP"; then - ac_path_GREP_found=false - # Loop through the user's path and test for each of PROGNAME-LIST - as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin -do - IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - for ac_prog in grep ggrep; do - for ac_exec_ext in '' $ac_executable_extensions; do - ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext" - as_fn_executable_p "$ac_path_GREP" || continue -# Check for GNU ac_path_GREP and select it if it is found. - # Check for GNU $ac_path_GREP -case `"$ac_path_GREP" --version 2>&1` in -*GNU*) - ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;; -*) - ac_count=0 - $as_echo_n 0123456789 >"conftest.in" - while : - do - cat "conftest.in" "conftest.in" >"conftest.tmp" - mv "conftest.tmp" "conftest.in" - cp "conftest.in" "conftest.nl" - $as_echo 'GREP' >> "conftest.nl" - "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break - diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break - as_fn_arith $ac_count + 1 && ac_count=$as_val - if test $ac_count -gt ${ac_path_GREP_max-0}; then - # Best one so far, save it but keep looking for a better one - ac_cv_path_GREP="$ac_path_GREP" - ac_path_GREP_max=$ac_count - fi - # 10*(2^10) chars as input seems more than enough - test $ac_count -gt 10 && break - done - rm -f conftest.in conftest.tmp conftest.nl conftest.out;; -esac - $ac_path_GREP_found && break 3 - done - done - done -IFS=$as_save_IFS - if test -z "$ac_cv_path_GREP"; then - as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 - fi -else - ac_cv_path_GREP=$GREP -fi +# Validate: require at least one transport +if test "x$with_bluez_support" != "xyes" && test "x$with_nimble_support" != "xyes"; then + as_fn_error $? "At least one of --with-bluez-support or --with-nimble-support must be enabled" "$LINENO" 5 fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5 -$as_echo "$ac_cv_path_GREP" >&6; } - GREP="$ac_cv_path_GREP" +################################################################################ +# +# BlueZ support +# -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5 -$as_echo_n "checking for egrep... " >&6; } -if ${ac_cv_path_EGREP+:} false; then : - $as_echo_n "(cached) " >&6 -else - if echo a | $GREP -E '(a|b)' >/dev/null 2>&1 - then ac_cv_path_EGREP="$GREP -E" - else - if test -z "$EGREP"; then - ac_path_EGREP_found=false - # Loop through the user's path and test for each of PROGNAME-LIST - as_save_IFS=$IFS; IFS=$PATH_SEPARATOR -for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin +if test "x$with_bluez_support" = "xyes"; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: BlueZ transport: ENABLED" >&5 +printf "%s\n" "$as_me: BlueZ transport: ENABLED" >&6;} + + bluez_ok=1 + ac_header= ac_cache= +for ac_item in $ac_header_cxx_list do - IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - for ac_prog in egrep; do - for ac_exec_ext in '' $ac_executable_extensions; do - ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext" - as_fn_executable_p "$ac_path_EGREP" || continue -# Check for GNU ac_path_EGREP and select it if it is found. - # Check for GNU $ac_path_EGREP -case `"$ac_path_EGREP" --version 2>&1` in -*GNU*) - ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;; -*) - ac_count=0 - $as_echo_n 0123456789 >"conftest.in" - while : - do - cat "conftest.in" "conftest.in" >"conftest.tmp" - mv "conftest.tmp" "conftest.in" - cp "conftest.in" "conftest.nl" - $as_echo 'EGREP' >> "conftest.nl" - "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break - diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break - as_fn_arith $ac_count + 1 && ac_count=$as_val - if test $ac_count -gt ${ac_path_EGREP_max-0}; then - # Best one so far, save it but keep looking for a better one - ac_cv_path_EGREP="$ac_path_EGREP" - ac_path_EGREP_max=$ac_count + if test $ac_cache; then + ac_fn_cxx_check_header_compile "$LINENO" $ac_header ac_cv_header_$ac_cache "$ac_includes_default" + if eval test \"x\$ac_cv_header_$ac_cache\" = xyes; then + printf "%s\n" "#define $ac_item 1" >> confdefs.h fi - # 10*(2^10) chars as input seems more than enough - test $ac_count -gt 10 && break - done - rm -f conftest.in conftest.tmp conftest.nl conftest.out;; -esac - - $ac_path_EGREP_found && break 3 - done - done - done -IFS=$as_save_IFS - if test -z "$ac_cv_path_EGREP"; then - as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5 + ac_header= ac_cache= + elif test $ac_header; then + ac_cache=$ac_item + else + ac_header=$ac_item fi -else - ac_cv_path_EGREP=$EGREP -fi - - fi -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5 -$as_echo "$ac_cv_path_EGREP" >&6; } - EGREP="$ac_cv_path_EGREP" - +done -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5 -$as_echo_n "checking for ANSI C header files... " >&6; } -if ${ac_cv_header_stdc+:} false; then : - $as_echo_n "(cached) " >&6 -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include -#include -#include -#include -int -main () -{ - ; - return 0; -} -_ACEOF -if ac_fn_cxx_try_compile "$LINENO"; then : - ac_cv_header_stdc=yes -else - ac_cv_header_stdc=no -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -if test $ac_cv_header_stdc = yes; then - # SunOS 4.x string.h does not declare mem*, contrary to ANSI. - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include -_ACEOF -if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | - $EGREP "memchr" >/dev/null 2>&1; then : -else - ac_cv_header_stdc=no -fi -rm -f conftest* -fi -if test $ac_cv_header_stdc = yes; then - # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI. - cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -#include +if test $ac_cv_header_stdlib_h = yes && test $ac_cv_header_string_h = yes +then : -_ACEOF -if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | - $EGREP "free" >/dev/null 2>&1; then : +printf "%s\n" "#define STDC_HEADERS 1" >>confdefs.h -else - ac_cv_header_stdc=no fi -rm -f conftest* +ac_fn_cxx_check_header_compile "$LINENO" "bluetooth/bluetooth.h" "ac_cv_header_bluetooth_bluetooth_h" "$ac_includes_default" +if test "x$ac_cv_header_bluetooth_bluetooth_h" = xyes +then : +else case e in #( + e) bluez_ok=0 ;; +esac fi -if test $ac_cv_header_stdc = yes; then - # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi. - if test "$cross_compiling" = yes; then : - : -else - cat confdefs.h - <<_ACEOF >conftest.$ac_ext + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for library containing hci_open_dev" >&5 +printf %s "checking for library containing hci_open_dev... " >&6; } +if test ${ac_cv_search_hci_open_dev+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_func_search_save_LIBS=$LIBS +cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ -#include -#include -#if ((' ' & 0x0FF) == 0x020) -# define ISLOWER(c) ('a' <= (c) && (c) <= 'z') -# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c)) -#else -# define ISLOWER(c) \ - (('a' <= (c) && (c) <= 'i') \ - || ('j' <= (c) && (c) <= 'r') \ - || ('s' <= (c) && (c) <= 'z')) -# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c)) -#endif -#define XOR(e, f) (((e) && !(f)) || (!(e) && (f))) +namespace conftest { + extern "C" int hci_open_dev (); +} int -main () +main (void) { - int i; - for (i = 0; i < 256; i++) - if (XOR (islower (i), ISLOWER (i)) - || toupper (i) != TOUPPER (i)) - return 2; +return conftest::hci_open_dev (); + ; return 0; } _ACEOF -if ac_fn_cxx_try_run "$LINENO"; then : - -else - ac_cv_header_stdc=no +for ac_lib in '' bluetooth +do + if test -z "$ac_lib"; then + ac_res="none required" + else + ac_res=-l$ac_lib + LIBS="-l$ac_lib $ac_func_search_save_LIBS" + fi + if ac_fn_cxx_try_link "$LINENO" +then : + ac_cv_search_hci_open_dev=$ac_res fi -rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ - conftest.$ac_objext conftest.beam conftest.$ac_ext +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext + if test ${ac_cv_search_hci_open_dev+y} +then : + break fi +done +if test ${ac_cv_search_hci_open_dev+y} +then : +else case e in #( + e) ac_cv_search_hci_open_dev=no ;; +esac fi +rm conftest.$ac_ext +LIBS=$ac_func_search_save_LIBS ;; +esac fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5 -$as_echo "$ac_cv_header_stdc" >&6; } -if test $ac_cv_header_stdc = yes; then - -$as_echo "#define STDC_HEADERS 1" >>confdefs.h +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_hci_open_dev" >&5 +printf "%s\n" "$ac_cv_search_hci_open_dev" >&6; } +ac_res=$ac_cv_search_hci_open_dev +if test "$ac_res" != no +then : + test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" +else case e in #( + e) bluez_ok=0 ;; +esac fi -# On IRIX 5.3, sys/types and inttypes.h are conflicting. -for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \ - inttypes.h stdint.h unistd.h -do : - as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` -ac_fn_cxx_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default -" -if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : - cat >>confdefs.h <<_ACEOF -#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 -_ACEOF + if test x$bluez_ok == x0; then + as_fn_error $? "BlueZ bluetooth headers/library missing. Install libbluetooth-dev or disable with --without-bluez-support" "$LINENO" 5 + fi + + BLEPP_BLUEZ_SUPPORT=1 + +else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: BlueZ transport: DISABLED" >&5 +printf "%s\n" "$as_me: BlueZ transport: DISABLED" >&6;} fi -done +################################################################################ +# +# Nimble support +# +if test "x$with_nimble_support" = "xyes"; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: Nimble transport: ENABLED" >&5 +printf "%s\n" "$as_me: Nimble transport: ENABLED" >&6;} -ac_fn_cxx_check_header_mongrel "$LINENO" "bluetooth/bluetooth.h" "ac_cv_header_bluetooth_bluetooth_h" "$ac_includes_default" -if test "x$ac_cv_header_bluetooth_bluetooth_h" = xyes; then : + # If NIMBLE_ROOT is specified, add its include paths to CPPFLAGS for header check + if test "x$NIMBLE_ROOT" != "x"; then + # Check if NIMBLE_ROOT exists + if test ! -d "$NIMBLE_ROOT"; then + as_fn_error $? "NIMBLE_ROOT not found at $NIMBLE_ROOT. Please set NIMBLE_ROOT environment variable or pass NIMBLE_ROOT=/path/to/nimble_v42 to configure" "$LINENO" 5 + fi -else - a=0 + # Convert to absolute path + NIMBLE_ROOT=$(cd "$NIMBLE_ROOT" && pwd) + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: Using Nimble headers from $NIMBLE_ROOT" >&5 +printf "%s\n" "$as_me: Using Nimble headers from $NIMBLE_ROOT" >&6;} + + # Add Nimble include paths for header check + CPPFLAGS="$CPPFLAGS -I$NIMBLE_ROOT/nimble/include -I$NIMBLE_ROOT/nimble/host/include -I$NIMBLE_ROOT/nimble/host/services/gap/include -I$NIMBLE_ROOT/nimble/host/services/gatt/include -I$NIMBLE_ROOT/nimble/host/util/include -I$NIMBLE_ROOT/porting/nimble/include -I$NIMBLE_ROOT/ext/tinycrypt/include" + fi + + # Check for Nimble headers using standard AC_CHECK_HEADER + ac_fn_cxx_check_header_compile "$LINENO" "nimble/ble.h" "ac_cv_header_nimble_ble_h" "$ac_includes_default" +if test "x$ac_cv_header_nimble_ble_h" = xyes +then : + +else case e in #( + e) + as_fn_error $? "Nimble headers not found. Please set NIMBLE_ROOT to the Nimble BLE stack directory, or ensure nimble/ble.h is in your include path." "$LINENO" 5 + ;; +esac fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing hci_open_dev" >&5 -$as_echo_n "checking for library containing hci_open_dev... " >&6; } -if ${ac_cv_search_hci_open_dev+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_func_search_save_LIBS=$LIBS + # Find Nimble library + # If NIMBLE_LIBDIR is specified, use it directly + if test "x$NIMBLE_LIBDIR" != "x"; then + if test ! -d "$NIMBLE_LIBDIR"; then + as_fn_error $? "NIMBLE_LIBDIR not found at $NIMBLE_LIBDIR" "$LINENO" 5 + fi + # Convert to absolute path + NIMBLE_LIBDIR=$(cd "$NIMBLE_LIBDIR" && pwd) + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: Using Nimble library from $NIMBLE_LIBDIR" >&5 +printf "%s\n" "$as_me: Using Nimble library from $NIMBLE_LIBDIR" >&6;} + elif test "x$NIMBLE_ROOT" != "x"; then + # Try to find library in NIMBLE_ROOT/lib or NIMBLE_ROOT/build + nimble_lib_found=no + for nimble_libdir in "$NIMBLE_ROOT/lib" "$NIMBLE_ROOT/build"; do + if test -f "$nimble_libdir/libnimble.so" || test -f "$nimble_libdir/libnimble.a"; then + NIMBLE_LIBDIR="$nimble_libdir" + nimble_lib_found=yes + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: Found Nimble library in $NIMBLE_LIBDIR" >&5 +printf "%s\n" "$as_me: Found Nimble library in $NIMBLE_LIBDIR" >&6;} + break + fi + done + + if test "x$nimble_lib_found" != "xyes"; then + as_fn_error $? "Nimble library not found. Please build Nimble first or set NIMBLE_LIBDIR. +Expected location: $NIMBLE_ROOT/lib/libnimble.so or $NIMBLE_ROOT/build/libnimble.so" "$LINENO" 5 + fi + fi + + # If NIMBLE_LIBDIR is set, add to LDFLAGS + if test "x$NIMBLE_LIBDIR" != "x"; then + LDFLAGS="$LDFLAGS -L$NIMBLE_LIBDIR -Wl,-rpath,$NIMBLE_LIBDIR" + + # Verify the library file exists + if test -f "$NIMBLE_LIBDIR/libnimble.so" || test -f "$NIMBLE_LIBDIR/libnimble.a"; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: Found Nimble library in $NIMBLE_LIBDIR" >&5 +printf "%s\n" "$as_me: Found Nimble library in $NIMBLE_LIBDIR" >&6;} + LIBS="$LIBS -lnimble" + else + as_fn_error $? "Nimble library not found in $NIMBLE_LIBDIR. Expected libnimble.so or libnimble.a" "$LINENO" 5 + fi + else + # No NIMBLE_LIBDIR specified, try to find nimble in standard paths + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for library containing ble_gap_disc" >&5 +printf %s "checking for library containing ble_gap_disc... " >&6; } +if test ${ac_cv_search_ble_gap_disc+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_func_search_save_LIBS=$LIBS cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ -/* Override any GCC internal prototype to avoid an error. - Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -#ifdef __cplusplus -extern "C" -#endif -char hci_open_dev (); +namespace conftest { + extern "C" int ble_gap_disc (); +} int -main () +main (void) { -return hci_open_dev (); +return conftest::ble_gap_disc (); ; return 0; } _ACEOF -for ac_lib in '' bluetooth; do +for ac_lib in '' nimble +do if test -z "$ac_lib"; then ac_res="none required" else ac_res=-l$ac_lib LIBS="-l$ac_lib $ac_func_search_save_LIBS" fi - if ac_fn_cxx_try_link "$LINENO"; then : - ac_cv_search_hci_open_dev=$ac_res + if ac_fn_cxx_try_link "$LINENO" +then : + ac_cv_search_ble_gap_disc=$ac_res fi -rm -f core conftest.err conftest.$ac_objext \ +rm -f core conftest.err conftest.$ac_objext conftest.beam \ conftest$ac_exeext - if ${ac_cv_search_hci_open_dev+:} false; then : + if test ${ac_cv_search_ble_gap_disc+y} +then : break fi done -if ${ac_cv_search_hci_open_dev+:} false; then : +if test ${ac_cv_search_ble_gap_disc+y} +then : -else - ac_cv_search_hci_open_dev=no +else case e in #( + e) ac_cv_search_ble_gap_disc=no ;; +esac fi rm conftest.$ac_ext -LIBS=$ac_func_search_save_LIBS +LIBS=$ac_func_search_save_LIBS ;; +esac fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_hci_open_dev" >&5 -$as_echo "$ac_cv_search_hci_open_dev" >&6; } -ac_res=$ac_cv_search_hci_open_dev -if test "$ac_res" != no; then : +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_ble_gap_disc" >&5 +printf "%s\n" "$ac_cv_search_ble_gap_disc" >&6; } +ac_res=$ac_cv_search_ble_gap_disc +if test "$ac_res" != no +then : test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" -else - a=0 +else case e in #( + e) + as_fn_error $? "Nimble library not found. Please set NIMBLE_LIBDIR or ensure libnimble is in your library path." "$LINENO" 5 + ;; +esac fi + fi + + BLEPP_NIMBLE_SUPPORT=1 -if test x$a == x0 -then - as_fn_error $? "bluez bluetooth missing" "$LINENO" 5 -fi -ac_fn_cxx_check_header_mongrel "$LINENO" "boost/optional.hpp" "ac_cv_header_boost_optional_hpp" "$ac_includes_default" -if test "x$ac_cv_header_boost_optional_hpp" = xyes; then : else - as_fn_error $? "boost::optional missing" "$LINENO" 5 + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: Nimble transport: DISABLED" >&5 +printf "%s\n" "$as_me: Nimble transport: DISABLED" >&6;} fi +################################################################################ +# +# Server support +# +if test "x$with_server_support" = "xyes"; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: Server support: ENABLED" >&5 +printf "%s\n" "$as_me: Server support: ENABLED" >&6;} + BLEPP_SERVER_SUPPORT=1 - +else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: Server support: DISABLED" >&5 +printf "%s\n" "$as_me: Server support: DISABLED" >&6;} +fi if test "" == "" then - { $as_echo "$as_me:${as_lineno-$LINENO}: checking if compiler flag -fPIC works" >&5 -$as_echo_n "checking if compiler flag -fPIC works... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if compiler flag -fPIC works" >&5 +printf %s "checking if compiler flag -fPIC works... " >&6; } else - { $as_echo "$as_me:${as_lineno-$LINENO}: checking " >&5 -$as_echo_n "checking ... " >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking " >&5 +printf %s "checking ... " >&6; } fi save_CXXFLAGS="$CXXFLAGS" CXXFLAGS="$CXXFLAGS -fPIC" @@ -3673,12 +3842,14 @@ $as_echo_n "checking ... " >&6; } /* end confdefs.h. */ int main(){} _ACEOF -if ac_fn_cxx_try_compile "$LINENO"; then : +if ac_fn_cxx_try_compile "$LINENO" +then : cvd_conf_test=1 -else - cvd_conf_test=0 +else case e in #( + e) cvd_conf_test=0 ;; +esac fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext @@ -3686,12 +3857,12 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext if test $cvd_conf_test = 1 then - { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -$as_echo "yes" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf "%s\n" "yes" >&6; } ts_success=yes else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } CXXFLAGS="$save_CXXFLAGS" ts_success=no fi @@ -3709,8 +3880,8 @@ cat >confcache <<\_ACEOF # config.status only pays attention to the cache file if you give it # the --recheck option to rerun configure. # -# `ac_cv_env_foo' variables (set or unset) will be overridden when -# loading this file, other *unset* `ac_cv_foo' will be assigned the +# 'ac_cv_env_foo' variables (set or unset) will be overridden when +# loading this file, other *unset* 'ac_cv_foo' will be assigned the # following values. _ACEOF @@ -3726,8 +3897,8 @@ _ACEOF case $ac_val in #( *${as_nl}*) case $ac_var in #( - *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 -$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; esac case $ac_var in #( _ | IFS | as_nl) ;; #( @@ -3740,14 +3911,14 @@ $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; (set) 2>&1 | case $as_nl`(ac_space=' '; set) 2>&1` in #( *${as_nl}ac_space=\ *) - # `set' does not quote correctly, so add quotes: double-quote + # 'set' does not quote correctly, so add quotes: double-quote # substitution turns \\\\ into \\, and sed turns \\ into \. sed -n \ "s/'/'\\\\''/g; s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" ;; #( *) - # `set' quotes correctly as required by POSIX, so do not add quotes. + # 'set' quotes correctly as required by POSIX, so do not add quotes. sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" ;; esac | @@ -3757,15 +3928,15 @@ $as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; /^ac_cv_env_/b end t clear :clear - s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ + s/^\([^=]*\)=\(.*[{}].*\)$/test ${\1+y} || &/ t end s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ :end' >>confcache if diff "$cache_file" confcache >/dev/null 2>&1; then :; else if test -w "$cache_file"; then if test "x$cache_file" != "x/dev/null"; then - { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 -$as_echo "$as_me: updating cache $cache_file" >&6;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 +printf "%s\n" "$as_me: updating cache $cache_file" >&6;} if test ! -f "$cache_file" || test -h "$cache_file"; then cat confcache >"$cache_file" else @@ -3779,8 +3950,8 @@ $as_echo "$as_me: updating cache $cache_file" >&6;} fi fi else - { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 -$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 +printf "%s\n" "$as_me: not updating unwritable cache $cache_file" >&6;} fi fi rm -f confcache @@ -3811,9 +3982,7 @@ s/^[ ]*#[ ]*define[ ][ ]*\([^ ][^ ]*\)[ ]*\(.*\)/-D\1=\2/g t quote b any :quote -s/[ `~#$^&*(){}\\|;'\''"<>?]/\\&/g -s/\[/\\&/g -s/\]/\\&/g +s/[][ `~#$^&*(){}\\|;'\''"<>?]/\\&/g s/\$/$$/g H :any @@ -3833,7 +4002,7 @@ U= for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue # 1. Remove the extension, and $U if already installed. ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' - ac_i=`$as_echo "$ac_i" | sed "$ac_script"` + ac_i=`printf "%s\n" "$ac_i" | sed "$ac_script"` # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR # will be set to the directory where LIBOBJS objects are built. as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" @@ -3849,8 +4018,8 @@ LTLIBOBJS=$ac_ltlibobjs ac_write_fail=0 ac_clean_files_save=$ac_clean_files ac_clean_files="$ac_clean_files $CONFIG_STATUS" -{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 -$as_echo "$as_me: creating $CONFIG_STATUS" >&6;} +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 +printf "%s\n" "$as_me: creating $CONFIG_STATUS" >&6;} as_write_fail=0 cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 #! $SHELL @@ -3873,63 +4042,65 @@ cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 # Be more Bourne compatible DUALCASE=1; export DUALCASE # for MKS sh -if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : +if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 +then : emulate sh NULLCMD=: # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which # is contrary to our usage. Disable this feature. alias -g '${1+"$@"}'='"$@"' setopt NO_GLOB_SUBST -else - case `(set -o) 2>/dev/null` in #( +else case e in #( + e) case `(set -o) 2>/dev/null` in #( *posix*) : set -o posix ;; #( *) : ;; +esac ;; esac fi + +# Reset variables that may have inherited troublesome values from +# the environment. + +# IFS needs to be set, to space, tab, and newline, in precisely that order. +# (If _AS_PATH_WALK were called with IFS unset, it would have the +# side effect of setting IFS to empty, thus disabling word splitting.) +# Quoting is to prevent editors from complaining about space-tab. as_nl=' ' export as_nl -# Printing a long string crashes Solaris 7 /usr/bin/printf. -as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' -as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo -as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo -# Prefer a ksh shell builtin over an external printf program on Solaris, -# but without wasting forks for bash or zsh. -if test -z "$BASH_VERSION$ZSH_VERSION" \ - && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then - as_echo='print -r --' - as_echo_n='print -rn --' -elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then - as_echo='printf %s\n' - as_echo_n='printf %s' -else - if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then - as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' - as_echo_n='/usr/ucb/echo -n' - else - as_echo_body='eval expr "X$1" : "X\\(.*\\)"' - as_echo_n_body='eval - arg=$1; - case $arg in #( - *"$as_nl"*) - expr "X$arg" : "X\\(.*\\)$as_nl"; - arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; - esac; - expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" - ' - export as_echo_n_body - as_echo_n='sh -c $as_echo_n_body as_echo' - fi - export as_echo_body - as_echo='sh -c $as_echo_body as_echo' -fi +IFS=" "" $as_nl" + +PS1='$ ' +PS2='> ' +PS4='+ ' + +# Ensure predictable behavior from utilities with locale-dependent output. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# We cannot yet rely on "unset" to work, but we need these variables +# to be unset--not just set to an empty or harmless value--now, to +# avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct +# also avoids known problems related to "unset" and subshell syntax +# in other old shells (e.g. bash 2.01 and pdksh 5.2.14). +for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH +do eval test \${$as_var+y} \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done + +# Ensure that fds 0, 1, and 2 are open. +if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi +if (exec 3>&2) ; then :; else exec 2>/dev/null; fi # The user is always right. -if test "${PATH_SEPARATOR+set}" != set; then +if ${PATH_SEPARATOR+false} :; then PATH_SEPARATOR=: (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || @@ -3938,13 +4109,6 @@ if test "${PATH_SEPARATOR+set}" != set; then fi -# IFS -# We need space, tab and new line, in precisely that order. Quoting is -# there to prevent editors from complaining about space-tab. -# (If _AS_PATH_WALK were called with IFS unset, it would disable word -# splitting by setting IFS to empty value.) -IFS=" "" $as_nl" - # Find who we are. Look in the path if we contain no directory separator. as_myself= case $0 in #(( @@ -3953,43 +4117,27 @@ case $0 in #(( for as_dir in $PATH do IFS=$as_save_IFS - test -z "$as_dir" && as_dir=. - test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + test -r "$as_dir$0" && as_myself=$as_dir$0 && break done IFS=$as_save_IFS ;; esac -# We did not find ourselves, most probably we were run as `sh COMMAND' +# We did not find ourselves, most probably we were run as 'sh COMMAND' # in which case we are not to be found in the path. if test "x$as_myself" = x; then as_myself=$0 fi if test ! -f "$as_myself"; then - $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + printf "%s\n" "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 exit 1 fi -# Unset variables that we do not need and which cause bugs (e.g. in -# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" -# suppresses any "Segmentation fault" message there. '((' could -# trigger a bug in pdksh 5.2.14. -for as_var in BASH_ENV ENV MAIL MAILPATH -do eval test x\${$as_var+set} = xset \ - && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : -done -PS1='$ ' -PS2='> ' -PS4='+ ' - -# NLS nuisances. -LC_ALL=C -export LC_ALL -LANGUAGE=C -export LANGUAGE - -# CDPATH. -(unset CDPATH) >/dev/null 2>&1 && unset CDPATH # as_fn_error STATUS ERROR [LINENO LOG_FD] @@ -4002,9 +4150,9 @@ as_fn_error () as_status=$1; test $as_status -eq 0 && as_status=1 if test "$4"; then as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack - $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 fi - $as_echo "$as_me: error: $2" >&2 + printf "%s\n" "$as_me: error: $2" >&2 as_fn_exit $as_status } # as_fn_error @@ -4035,22 +4183,25 @@ as_fn_unset () { eval $1=; unset $1;} } as_unset=as_fn_unset + # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take # advantage of any shell optimizations that allow amortized linear growth over # repeated appends, instead of the typical quadratic growth present in naive # implementations. -if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null +then : eval 'as_fn_append () { eval $1+=\$2 }' -else - as_fn_append () +else case e in #( + e) as_fn_append () { eval $1=\$$1\$2 - } + } ;; +esac fi # as_fn_append # as_fn_arith ARG... @@ -4058,16 +4209,18 @@ fi # as_fn_append # Perform arithmetic evaluation on the ARGs, and store the result in the # global $as_val. Take advantage of shells that can avoid forks. The arguments # must be portable across $(()) and expr. -if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null +then : eval 'as_fn_arith () { as_val=$(( $* )) }' -else - as_fn_arith () +else case e in #( + e) as_fn_arith () { as_val=`expr "$@" || test $? -eq 1` - } + } ;; +esac fi # as_fn_arith @@ -4094,7 +4247,7 @@ as_me=`$as_basename -- "$0" || $as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ X"$0" : 'X\(//\)$' \| \ X"$0" : 'X\(/\)' \| . 2>/dev/null || -$as_echo X/"$0" | +printf "%s\n" X/"$0" | sed '/^.*\/\([^/][^/]*\)\/*$/{ s//\1/ q @@ -4116,6 +4269,10 @@ as_cr_Letters=$as_cr_letters$as_cr_LETTERS as_cr_digits='0123456789' as_cr_alnum=$as_cr_Letters$as_cr_digits + +# Determine whether it's possible to make 'echo' print without a newline. +# These variables are no longer used directly by Autoconf, but are AC_SUBSTed +# for compatibility with existing Makefiles. ECHO_C= ECHO_N= ECHO_T= case `echo -n x` in #((((( -n*) @@ -4129,6 +4286,12 @@ case `echo -n x` in #((((( ECHO_N='-n';; esac +# For backward compatibility with old third-party macros, we provide +# the shell variables $as_echo and $as_echo_n. New code should use +# AS_ECHO(["message"]) and AS_ECHO_N(["message"]), respectively. +as_echo='printf %s\n' +as_echo_n='printf %s' + rm -f conf$$ conf$$.exe conf$$.file if test -d conf$$.dir; then rm -f conf$$.dir/conf$$.file @@ -4140,9 +4303,9 @@ if (echo >conf$$.file) 2>/dev/null; then if ln -s conf$$.file conf$$ 2>/dev/null; then as_ln_s='ln -s' # ... but there are two gotchas: - # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. - # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. - # In both cases, we have to default to `cp -pR'. + # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable. + # In both cases, we have to default to 'cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then @@ -4170,7 +4333,7 @@ as_fn_mkdir_p () as_dirs= while :; do case $as_dir in #( - *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *\'*) as_qdir=`printf "%s\n" "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( *) as_qdir=$as_dir;; esac as_dirs="'$as_qdir' $as_dirs" @@ -4179,7 +4342,7 @@ $as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$as_dir" : 'X\(//\)[^/]' \| \ X"$as_dir" : 'X\(//\)$' \| \ X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || -$as_echo X"$as_dir" | +printf "%s\n" X"$as_dir" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q @@ -4223,10 +4386,12 @@ as_test_x='test -x' as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. -as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" +as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" +as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated # Sed expression to map a string onto a valid variable name. -as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" +as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g" +as_tr_sh="eval sed '$as_sed_sh'" # deprecated exec 6>&1 @@ -4242,7 +4407,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # values after options handling. ac_log=" This file was extended by libble++ $as_me version-0.5, which was -generated by GNU Autoconf 2.69. Invocation command line was +generated by GNU Autoconf 2.72. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS @@ -4269,7 +4434,7 @@ _ACEOF cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 ac_cs_usage="\ -\`$as_me' instantiates files and other configuration actions +'$as_me' instantiates files and other configuration actions from templates according to the current configuration. Unless the files and actions are specified as TAGs, all are instantiated by default. @@ -4291,14 +4456,16 @@ $config_files Report bugs to the package provider." _ACEOF +ac_cs_config=`printf "%s\n" "$ac_configure_args" | sed "$ac_safe_unquote"` +ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\''/g"` cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 -ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" +ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ libble++ config.status version-0.5 -configured by $0, generated by GNU Autoconf 2.69, +configured by $0, generated by GNU Autoconf 2.72, with options \\"\$ac_cs_config\\" -Copyright (C) 2012 Free Software Foundation, Inc. +Copyright (C) 2023 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." @@ -4335,28 +4502,28 @@ do -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) ac_cs_recheck=: ;; --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) - $as_echo "$ac_cs_version"; exit ;; + printf "%s\n" "$ac_cs_version"; exit ;; --config | --confi | --conf | --con | --co | --c ) - $as_echo "$ac_cs_config"; exit ;; + printf "%s\n" "$ac_cs_config"; exit ;; --debug | --debu | --deb | --de | --d | -d ) debug=: ;; --file | --fil | --fi | --f ) $ac_shift case $ac_optarg in - *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + *\'*) ac_optarg=`printf "%s\n" "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; '') as_fn_error $? "missing file argument" ;; esac as_fn_append CONFIG_FILES " '$ac_optarg'" ac_need_defaults=false;; --he | --h | --help | --hel | -h ) - $as_echo "$ac_cs_usage"; exit ;; + printf "%s\n" "$ac_cs_usage"; exit ;; -q | -quiet | --quiet | --quie | --qui | --qu | --q \ | -silent | --silent | --silen | --sile | --sil | --si | --s) ac_cs_silent=: ;; # This is an error. - -*) as_fn_error $? "unrecognized option: \`$1' -Try \`$0 --help' for more information." ;; + -*) as_fn_error $? "unrecognized option: '$1' +Try '$0 --help' for more information." ;; *) as_fn_append ac_config_targets " $1" ac_need_defaults=false ;; @@ -4377,7 +4544,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 if \$ac_cs_recheck; then set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion shift - \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 + \printf "%s\n" "running CONFIG_SHELL=$SHELL \$*" >&6 CONFIG_SHELL='$SHELL' export CONFIG_SHELL exec "\$@" @@ -4391,7 +4558,7 @@ exec 5>>config.log sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX ## Running $as_me. ## _ASBOX - $as_echo "$ac_log" + printf "%s\n" "$ac_log" } >&5 _ACEOF @@ -4407,7 +4574,7 @@ do "libblepp.pc") CONFIG_FILES="$CONFIG_FILES libblepp.pc" ;; "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; - *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; + *) as_fn_error $? "invalid argument: '$ac_config_target'" "$LINENO" 5;; esac done @@ -4417,7 +4584,7 @@ done # We use the long form for the default assignment because of an extremely # bizarre bug on SunOS 4.1.3. if $ac_need_defaults; then - test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files + test ${CONFIG_FILES+y} || CONFIG_FILES=$config_files fi # Have a temporary directory for convenience. Make it in the build tree @@ -4425,7 +4592,7 @@ fi # creating and moving files from /tmp can sometimes cause problems. # Hook for its removal unless debugging. # Note that there is a small window in which the directory will not be cleaned: -# after its creation but before its name has been assigned to `$tmp'. +# after its creation but before its name has been assigned to '$tmp'. $debug || { tmp= ac_tmp= @@ -4449,7 +4616,7 @@ ac_tmp=$tmp # Set up the scripts for CONFIG_FILES section. # No need to generate them if there are no CONFIG_FILES. -# This happens for instance with `./config.status config.h'. +# This happens for instance with './config.status config.h'. if test -n "$CONFIG_FILES"; then @@ -4615,7 +4782,7 @@ do esac case $ac_mode$ac_tag in :[FHL]*:*);; - :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; + :L* | :C*:*) as_fn_error $? "invalid tag '$ac_tag'" "$LINENO" 5;; :[FH]-) ac_tag=-:-;; :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; esac @@ -4637,33 +4804,33 @@ do -) ac_f="$ac_tmp/stdin";; *) # Look for the file first in the build tree, then in the source tree # (if the path is not absolute). The absolute path cannot be DOS-style, - # because $ac_f cannot contain `:'. + # because $ac_f cannot contain ':'. test -f "$ac_f" || case $ac_f in [\\/$]*) false;; *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; esac || - as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; + as_fn_error 1 "cannot find input file: '$ac_f'" "$LINENO" 5;; esac - case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac + case $ac_f in *\'*) ac_f=`printf "%s\n" "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac as_fn_append ac_file_inputs " '$ac_f'" done - # Let's still pretend it is `configure' which instantiates (i.e., don't + # Let's still pretend it is 'configure' which instantiates (i.e., don't # use $as_me), people would be surprised to read: # /* config.h. Generated by config.status. */ configure_input='Generated from '` - $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' + printf "%s\n" "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' `' by configure.' if test x"$ac_file" != x-; then configure_input="$ac_file. $configure_input" - { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 -$as_echo "$as_me: creating $ac_file" >&6;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 +printf "%s\n" "$as_me: creating $ac_file" >&6;} fi # Neutralize special characters interpreted by sed in replacement strings. case $configure_input in #( *\&* | *\|* | *\\* ) - ac_sed_conf_input=`$as_echo "$configure_input" | + ac_sed_conf_input=`printf "%s\n" "$configure_input" | sed 's/[\\\\&|]/\\\\&/g'`;; #( *) ac_sed_conf_input=$configure_input;; esac @@ -4680,7 +4847,7 @@ $as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ X"$ac_file" : 'X\(//\)[^/]' \| \ X"$ac_file" : 'X\(//\)$' \| \ X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || -$as_echo X"$ac_file" | +printf "%s\n" X"$ac_file" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q @@ -4704,9 +4871,9 @@ $as_echo X"$ac_file" | case "$ac_dir" in .) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; *) - ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + ac_dir_suffix=/`printf "%s\n" "$ac_dir" | sed 's|^\.[\\/]||'` # A ".." for each directory in $ac_dir_suffix. - ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + ac_top_builddir_sub=`printf "%s\n" "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` case $ac_top_builddir_sub in "") ac_top_builddir_sub=. ac_top_build_prefix= ;; *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; @@ -4759,8 +4926,8 @@ ac_sed_dataroot=' case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in *datarootdir*) ac_datarootdir_seen=yes;; *@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 -$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 +printf "%s\n" "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_datarootdir_hack=' @@ -4773,7 +4940,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 esac _ACEOF -# Neutralize VPATH when `$srcdir' = `.'. +# Neutralize VPATH when '$srcdir' = '.'. # Shell code in configure.ac might set extrasub. # FIXME: do we really want to maintain this feature? cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 @@ -4802,9 +4969,9 @@ test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ "$ac_tmp/out"`; test -z "$ac_out"; } && - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable 'datarootdir' which seems to be undefined. Please make sure it is defined" >&5 -$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' +printf "%s\n" "$as_me: WARNING: $ac_file contains a reference to the variable 'datarootdir' which seems to be undefined. Please make sure it is defined" >&2;} rm -f "$ac_tmp/stdin" @@ -4851,8 +5018,8 @@ if test "$no_create" != yes; then $ac_cs_success || as_fn_exit 1 fi if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then - { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 -$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 +printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} fi diff --git a/configure.ac b/configure.ac index 56907e9..a104e05 100644 --- a/configure.ac +++ b/configure.ac @@ -21,15 +21,15 @@ define(APPEND, [$1="$$1 $2"]) dnl TEST_AND_SET_CXXFLAG(flag, [program], [run]) dnl dnl This attempts to compile a and run program with a certain compiler flag. -dnl If no program is given, then the minimal C++ program is compiled, and -dnl this tests just the validity of the compiler flag. +dnl If no program is given, then the minimal C++ program is compiled, and +dnl this tests just the validity of the compiler flag. dnl define([TEST_AND_SET_CXXFLAG],[ if test "$3" == "" then - AC_MSG_CHECKING([if compiler flag $1 works]) + AC_MSG_CHECKING([if compiler flag $1 works]) else - AC_MSG_CHECKING([$3]) + AC_MSG_CHECKING([$3]) fi save_CXXFLAGS="$CXXFLAGS" APPEND(CXXFLAGS, [$1]) @@ -40,8 +40,8 @@ define([TEST_AND_SET_CXXFLAG],[ [AC_RUN_IFELSE([AC_LANG_SOURCE([prog])], [cvd_conf_test=1],[cvd_conf_test=0], [cvd_conf_test=0])], [AC_COMPILE_IFELSE([AC_LANG_SOURCE([prog])], [cvd_conf_test=1],[cvd_conf_test=0])] ) - - + + popdef([prog]) if test $cvd_conf_test = 1 @@ -59,7 +59,7 @@ define([TEST_AND_SET_CXXFLAG],[ dnl Add flags, but only if the flags weren't specified. if test "$CXXFLAGS" == "-g -O2" then - + TEST_AND_SET_CXXFLAG(-Wall) TEST_AND_SET_CXXFLAG(-Wextra) TEST_AND_SET_CXXFLAG(-W) @@ -91,21 +91,151 @@ else AC_SUBST(VERSION) fi -a=1 -AC_CHECK_HEADER(bluetooth/bluetooth.h, [ ], [a=0]) -AC_SEARCH_LIBS(hci_open_dev, bluetooth, [ ], [a=0]) +################################################################################ +# +# Transport support options +# -if test x$a == x0 -then - AC_ERROR([bluez bluetooth missing]) +AC_ARG_WITH([bluez-support], + [AS_HELP_STRING([--with-bluez-support], [Build with BlueZ transport support (HCI/L2CAP) @<:@default=yes@:>@])], + [with_bluez_support=$withval], + [with_bluez_support=yes]) + +AC_ARG_WITH([nimble-support], + [AS_HELP_STRING([--with-nimble-support], [Build with Nimble transport support (/dev/atbm_ioctl) @<:@default=no@:>@])], + [with_nimble_support=$withval], + [with_nimble_support=no]) + +AC_ARG_WITH([server-support], + [AS_HELP_STRING([--with-server-support], [Build with BLE GATT server support @<:@default=no@:>@])], + [with_server_support=$withval], + [with_server_support=no]) + +AC_ARG_VAR([NIMBLE_ROOT], [Path to Nimble BLE stack root directory (for headers)]) +AC_ARG_VAR([NIMBLE_LIBDIR], [Path to Nimble library directory (defaults to NIMBLE_ROOT/lib or NIMBLE_ROOT/build)]) + +# Validate: require at least one transport +if test "x$with_bluez_support" != "xyes" && test "x$with_nimble_support" != "xyes"; then + AC_MSG_ERROR([At least one of --with-bluez-support or --with-nimble-support must be enabled]) +fi + +################################################################################ +# +# BlueZ support +# + +if test "x$with_bluez_support" = "xyes"; then + AC_MSG_NOTICE([BlueZ transport: ENABLED]) + + bluez_ok=1 + AC_CHECK_HEADER(bluetooth/bluetooth.h, [ ], [bluez_ok=0]) + AC_SEARCH_LIBS(hci_open_dev, bluetooth, [ ], [bluez_ok=0]) + + if test x$bluez_ok == x0; then + AC_MSG_ERROR([BlueZ bluetooth headers/library missing. Install libbluetooth-dev or disable with --without-bluez-support]) + fi + + BLEPP_BLUEZ_SUPPORT=1 + AC_SUBST(BLEPP_BLUEZ_SUPPORT) +else + AC_MSG_NOTICE([BlueZ transport: DISABLED]) fi -AC_CHECK_HEADER(boost/optional.hpp, [ ], [AC_ERROR([boost::optional missing])]) +################################################################################ +# +# Nimble support +# + +if test "x$with_nimble_support" = "xyes"; then + AC_MSG_NOTICE([Nimble transport: ENABLED]) + + # If NIMBLE_ROOT is specified, add its include paths to CPPFLAGS for header check + if test "x$NIMBLE_ROOT" != "x"; then + # Check if NIMBLE_ROOT exists + if test ! -d "$NIMBLE_ROOT"; then + AC_MSG_ERROR([NIMBLE_ROOT not found at $NIMBLE_ROOT. Please set NIMBLE_ROOT environment variable or pass NIMBLE_ROOT=/path/to/nimble_v42 to configure]) + fi + + # Convert to absolute path + NIMBLE_ROOT=$(cd "$NIMBLE_ROOT" && pwd) + AC_MSG_NOTICE([Using Nimble headers from $NIMBLE_ROOT]) + + # Add Nimble include paths for header check + CPPFLAGS="$CPPFLAGS -I$NIMBLE_ROOT/nimble/include -I$NIMBLE_ROOT/nimble/host/include -I$NIMBLE_ROOT/nimble/host/services/gap/include -I$NIMBLE_ROOT/nimble/host/services/gatt/include -I$NIMBLE_ROOT/nimble/host/util/include -I$NIMBLE_ROOT/porting/nimble/include -I$NIMBLE_ROOT/ext/tinycrypt/include" + fi + + # Check for Nimble headers using standard AC_CHECK_HEADER + AC_CHECK_HEADER([nimble/ble.h], [], [ + AC_MSG_ERROR([Nimble headers not found. Please set NIMBLE_ROOT to the Nimble BLE stack directory, or ensure nimble/ble.h is in your include path.]) + ]) + + # Find Nimble library + # If NIMBLE_LIBDIR is specified, use it directly + if test "x$NIMBLE_LIBDIR" != "x"; then + if test ! -d "$NIMBLE_LIBDIR"; then + AC_MSG_ERROR([NIMBLE_LIBDIR not found at $NIMBLE_LIBDIR]) + fi + # Convert to absolute path + NIMBLE_LIBDIR=$(cd "$NIMBLE_LIBDIR" && pwd) + AC_MSG_NOTICE([Using Nimble library from $NIMBLE_LIBDIR]) + elif test "x$NIMBLE_ROOT" != "x"; then + # Try to find library in NIMBLE_ROOT/lib or NIMBLE_ROOT/build + nimble_lib_found=no + for nimble_libdir in "$NIMBLE_ROOT/lib" "$NIMBLE_ROOT/build"; do + if test -f "$nimble_libdir/libnimble.so" || test -f "$nimble_libdir/libnimble.a"; then + NIMBLE_LIBDIR="$nimble_libdir" + nimble_lib_found=yes + AC_MSG_NOTICE([Found Nimble library in $NIMBLE_LIBDIR]) + break + fi + done + + if test "x$nimble_lib_found" != "xyes"; then + AC_MSG_ERROR([Nimble library not found. Please build Nimble first or set NIMBLE_LIBDIR. +Expected location: $NIMBLE_ROOT/lib/libnimble.so or $NIMBLE_ROOT/build/libnimble.so]) + fi + fi + + # If NIMBLE_LIBDIR is set, add to LDFLAGS + if test "x$NIMBLE_LIBDIR" != "x"; then + LDFLAGS="$LDFLAGS -L$NIMBLE_LIBDIR -Wl,-rpath,$NIMBLE_LIBDIR" + + # Verify the library file exists + if test -f "$NIMBLE_LIBDIR/libnimble.so" || test -f "$NIMBLE_LIBDIR/libnimble.a"; then + AC_MSG_NOTICE([Found Nimble library in $NIMBLE_LIBDIR]) + LIBS="$LIBS -lnimble" + else + AC_MSG_ERROR([Nimble library not found in $NIMBLE_LIBDIR. Expected libnimble.so or libnimble.a]) + fi + else + # No NIMBLE_LIBDIR specified, try to find nimble in standard paths + AC_SEARCH_LIBS([ble_gap_disc], [nimble], [], [ + AC_MSG_ERROR([Nimble library not found. Please set NIMBLE_LIBDIR or ensure libnimble is in your library path.]) + ]) + fi + BLEPP_NIMBLE_SUPPORT=1 + AC_SUBST(BLEPP_NIMBLE_SUPPORT) + AC_SUBST(NIMBLE_ROOT) + AC_SUBST(NIMBLE_LIBDIR) +else + AC_MSG_NOTICE([Nimble transport: DISABLED]) +fi +################################################################################ +# +# Server support +# + +if test "x$with_server_support" = "xyes"; then + AC_MSG_NOTICE([Server support: ENABLED]) + BLEPP_SERVER_SUPPORT=1 + AC_SUBST(BLEPP_SERVER_SUPPORT) +else + AC_MSG_NOTICE([Server support: DISABLED]) +fi TEST_AND_SET_CXXFLAG(-fPIC) dnl TEST_AND_SET_CXXFLAG(-Werror) AC_OUTPUT(Makefile) - diff --git a/configure~ b/configure~ new file mode 100755 index 0000000..fff539a --- /dev/null +++ b/configure~ @@ -0,0 +1,5016 @@ +#! /bin/sh +# Guess values for system-dependent variables and create Makefiles. +# Generated by GNU Autoconf 2.72 for libble++ version-0.5. +# +# +# Copyright (C) 1992-1996, 1998-2017, 2020-2023 Free Software Foundation, +# Inc. +# +# +# This configure script is free software; the Free Software Foundation +# gives unlimited permission to copy, distribute and modify it. +# +# E. Rosten, 2016, the BlueZ contributors to 2016 +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 +then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else case e in #( + e) case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac ;; +esac +fi + + + +# Reset variables that may have inherited troublesome values from +# the environment. + +# IFS needs to be set, to space, tab, and newline, in precisely that order. +# (If _AS_PATH_WALK were called with IFS unset, it would have the +# side effect of setting IFS to empty, thus disabling word splitting.) +# Quoting is to prevent editors from complaining about space-tab. +as_nl=' +' +export as_nl +IFS=" "" $as_nl" + +PS1='$ ' +PS2='> ' +PS4='+ ' + +# Ensure predictable behavior from utilities with locale-dependent output. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# We cannot yet rely on "unset" to work, but we need these variables +# to be unset--not just set to an empty or harmless value--now, to +# avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct +# also avoids known problems related to "unset" and subshell syntax +# in other old shells (e.g. bash 2.01 and pdksh 5.2.14). +for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH +do eval test \${$as_var+y} \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done + +# Ensure that fds 0, 1, and 2 are open. +if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi +if (exec 3>&2) ; then :; else exec 2>/dev/null; fi + +# The user is always right. +if ${PATH_SEPARATOR+false} :; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + test -r "$as_dir$0" && as_myself=$as_dir$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as 'sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + printf "%s\n" "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + + +# Use a proper internal environment variable to ensure we don't fall + # into an infinite loop, continuously re-executing ourselves. + if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then + _as_can_reexec=no; export _as_can_reexec; + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed 'exec'. +printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 +exit 255 + fi + # We don't want this to propagate to other subprocesses. + { _as_can_reexec=; unset _as_can_reexec;} +if test "x$CONFIG_SHELL" = x; then + as_bourne_compatible="if test \${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 +then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which + # is contrary to our usage. Disable this feature. + alias -g '\${1+\"\$@\"}'='\"\$@\"' + setopt NO_GLOB_SUBST +else case e in #( + e) case \`(set -o) 2>/dev/null\` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac ;; +esac +fi +" + as_required="as_fn_return () { (exit \$1); } +as_fn_success () { as_fn_return 0; } +as_fn_failure () { as_fn_return 1; } +as_fn_ret_success () { return 0; } +as_fn_ret_failure () { return 1; } + +exitcode=0 +as_fn_success || { exitcode=1; echo as_fn_success failed.; } +as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } +as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } +as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } +if ( set x; as_fn_ret_success y && test x = \"\$1\" ) +then : + +else case e in #( + e) exitcode=1; echo positional parameters were not saved. ;; +esac +fi +test x\$exitcode = x0 || exit 1 +blah=\$(echo \$(echo blah)) +test x\"\$blah\" = xblah || exit 1 +test -x / || exit 1" + as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO + as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO + eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && + test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1" + if (eval "$as_required") 2>/dev/null +then : + as_have_required=yes +else case e in #( + e) as_have_required=no ;; +esac +fi + if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null +then : + +else case e in #( + e) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +as_found=false +for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + as_found=: + case $as_dir in #( + /*) + for as_base in sh bash ksh sh5; do + # Try only shells that exist, to save several forks. + as_shell=$as_dir$as_base + if { test -f "$as_shell" || test -f "$as_shell.exe"; } && + as_run=a "$as_shell" -c "$as_bourne_compatible""$as_required" 2>/dev/null +then : + CONFIG_SHELL=$as_shell as_have_required=yes + if as_run=a "$as_shell" -c "$as_bourne_compatible""$as_suggested" 2>/dev/null +then : + break 2 +fi +fi + done;; + esac + as_found=false +done +IFS=$as_save_IFS +if $as_found +then : + +else case e in #( + e) if { test -f "$SHELL" || test -f "$SHELL.exe"; } && + as_run=a "$SHELL" -c "$as_bourne_compatible""$as_required" 2>/dev/null +then : + CONFIG_SHELL=$SHELL as_have_required=yes +fi ;; +esac +fi + + + if test "x$CONFIG_SHELL" != x +then : + export CONFIG_SHELL + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed 'exec'. +printf "%s\n" "$0: could not re-execute with $CONFIG_SHELL" >&2 +exit 255 +fi + + if test x$as_have_required = xno +then : + printf "%s\n" "$0: This script requires a shell more modern than all" + printf "%s\n" "$0: the shells that I found on your system." + if test ${ZSH_VERSION+y} ; then + printf "%s\n" "$0: In particular, zsh $ZSH_VERSION has bugs and should" + printf "%s\n" "$0: be upgraded to zsh 4.3.4 or later." + else + printf "%s\n" "$0: Please tell bug-autoconf@gnu.org about your system, +$0: including any error possibly output before this +$0: message. Then install a modern shell, or manually run +$0: the script under such a shell if you do have one." + fi + exit 1 +fi ;; +esac +fi +fi +SHELL=${CONFIG_SHELL-/bin/sh} +export SHELL +# Unset more variables known to interfere with behavior of common tools. +CLICOLOR_FORCE= GREP_OPTIONS= +unset CLICOLOR_FORCE GREP_OPTIONS + +## --------------------- ## +## M4sh Shell Functions. ## +## --------------------- ## +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset + + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`printf "%s\n" "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +printf "%s\n" X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null +then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else case e in #( + e) as_fn_append () + { + eval $1=\$$1\$2 + } ;; +esac +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null +then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else case e in #( + e) as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } ;; +esac +fi # as_fn_arith + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + printf "%s\n" "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +printf "%s\n" X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + + + as_lineno_1=$LINENO as_lineno_1a=$LINENO + as_lineno_2=$LINENO as_lineno_2a=$LINENO + eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && + test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { + # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) + sed -n ' + p + /[$]LINENO/= + ' <$as_myself | + sed ' + t clear + :clear + s/[$]LINENO.*/&-/ + t lineno + b + :lineno + N + :loop + s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ + t loop + s/-\n.*// + ' >$as_me.lineno && + chmod +x "$as_me.lineno" || + { printf "%s\n" "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } + + # If we had to re-execute with $CONFIG_SHELL, we're ensured to have + # already done that, so ensure we don't try to do so again and fall + # in an infinite loop. This has already happened in practice. + _as_can_reexec=no; export _as_can_reexec + # Don't try to exec as it changes $[0], causing all sort of problems + # (the dirname of $[0] is not the place where we might find the + # original and so on. Autoconf is especially sensitive to this). + . "./$as_me.lineno" + # Exit status is that of the last command. + exit +} + + +# Determine whether it's possible to make 'echo' print without a newline. +# These variables are no longer used directly by Autoconf, but are AC_SUBSTed +# for compatibility with existing Makefiles. +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +# For backward compatibility with old third-party macros, we provide +# the shell variables $as_echo and $as_echo_n. New code should use +# AS_ECHO(["message"]) and AS_ECHO_N(["message"]), respectively. +as_echo='printf %s\n' +as_echo_n='printf %s' + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable. + # In both cases, we have to default to 'cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -pR' + fi +else + as_ln_s='cp -pR' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + +as_test_x='test -x' +as_executable_p=as_fn_executable_p + +# Sed expression to map a string onto a valid CPP name. +as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" +as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated + +# Sed expression to map a string onto a valid variable name. +as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g" +as_tr_sh="eval sed '$as_sed_sh'" # deprecated + + +test -n "$DJDIR" || exec 7<&0 &1 + +# Name of the host. +# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, +# so uname gets run too. +ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` + +# +# Initializations. +# +ac_default_prefix=/usr/local +ac_clean_files= +ac_config_libobj_dir=. +LIBOBJS= +cross_compiling=no +subdirs= +MFLAGS= +MAKEFLAGS= + +# Identity of this package. +PACKAGE_NAME='libble++' +PACKAGE_TARNAME='libble--' +PACKAGE_VERSION='version-0.5' +PACKAGE_STRING='libble++ version-0.5' +PACKAGE_BUGREPORT='' +PACKAGE_URL='' + +# Factoring default headers for most tests. +ac_includes_default="\ +#include +#ifdef HAVE_STDIO_H +# include +#endif +#ifdef HAVE_STDLIB_H +# include +#endif +#ifdef HAVE_STRING_H +# include +#endif +#ifdef HAVE_INTTYPES_H +# include +#endif +#ifdef HAVE_STDINT_H +# include +#endif +#ifdef HAVE_STRINGS_H +# include +#endif +#ifdef HAVE_SYS_TYPES_H +# include +#endif +#ifdef HAVE_SYS_STAT_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif" + +ac_header_cxx_list= +ac_subst_vars='LTLIBOBJS +LIBOBJS +BLEPP_SERVER_SUPPORT +BLEPP_NIMBLE_SUPPORT +BLEPP_BLUEZ_SUPPORT +NIMBLE_LIBDIR +NIMBLE_ROOT +VERSION +PKGCONFIG_LIBDIR +SED +PKG_CONFIG +HAVE_CXX14 +OBJEXT +EXEEXT +ac_ct_CXX +CPPFLAGS +LDFLAGS +CXXFLAGS +CXX +target_alias +host_alias +build_alias +LIBS +ECHO_T +ECHO_N +ECHO_C +DEFS +mandir +localedir +libdir +psdir +pdfdir +dvidir +htmldir +infodir +docdir +oldincludedir +includedir +runstatedir +localstatedir +sharedstatedir +sysconfdir +datadir +datarootdir +libexecdir +sbindir +bindir +program_transform_name +prefix +exec_prefix +PACKAGE_URL +PACKAGE_BUGREPORT +PACKAGE_STRING +PACKAGE_VERSION +PACKAGE_TARNAME +PACKAGE_NAME +PATH_SEPARATOR +SHELL' +ac_subst_files='' +ac_user_opts=' +enable_option_checking +with_bluez_support +with_nimble_support +with_server_support +' + ac_precious_vars='build_alias +host_alias +target_alias +CXX +CXXFLAGS +LDFLAGS +LIBS +CPPFLAGS +CCC +NIMBLE_ROOT +NIMBLE_LIBDIR' + + +# Initialize some variables set by options. +ac_init_help= +ac_init_version=false +ac_unrecognized_opts= +ac_unrecognized_sep= +# The variables have the same names as the options, with +# dashes changed to underlines. +cache_file=/dev/null +exec_prefix=NONE +no_create= +no_recursion= +prefix=NONE +program_prefix=NONE +program_suffix=NONE +program_transform_name=s,x,x, +silent= +site= +srcdir= +verbose= +x_includes=NONE +x_libraries=NONE + +# Installation directory options. +# These are left unexpanded so users can "make install exec_prefix=/foo" +# and all the variables that are supposed to be based on exec_prefix +# by default will actually change. +# Use braces instead of parens because sh, perl, etc. also accept them. +# (The list follows the same order as the GNU Coding Standards.) +bindir='${exec_prefix}/bin' +sbindir='${exec_prefix}/sbin' +libexecdir='${exec_prefix}/libexec' +datarootdir='${prefix}/share' +datadir='${datarootdir}' +sysconfdir='${prefix}/etc' +sharedstatedir='${prefix}/com' +localstatedir='${prefix}/var' +runstatedir='${localstatedir}/run' +includedir='${prefix}/include' +oldincludedir='/usr/include' +docdir='${datarootdir}/doc/${PACKAGE_TARNAME}' +infodir='${datarootdir}/info' +htmldir='${docdir}' +dvidir='${docdir}' +pdfdir='${docdir}' +psdir='${docdir}' +libdir='${exec_prefix}/lib' +localedir='${datarootdir}/locale' +mandir='${datarootdir}/man' + +ac_prev= +ac_dashdash= +for ac_option +do + # If the previous option needs an argument, assign it. + if test -n "$ac_prev"; then + eval $ac_prev=\$ac_option + ac_prev= + continue + fi + + case $ac_option in + *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; + *=) ac_optarg= ;; + *) ac_optarg=yes ;; + esac + + case $ac_dashdash$ac_option in + --) + ac_dashdash=yes ;; + + -bindir | --bindir | --bindi | --bind | --bin | --bi) + ac_prev=bindir ;; + -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) + bindir=$ac_optarg ;; + + -build | --build | --buil | --bui | --bu) + ac_prev=build_alias ;; + -build=* | --build=* | --buil=* | --bui=* | --bu=*) + build_alias=$ac_optarg ;; + + -cache-file | --cache-file | --cache-fil | --cache-fi \ + | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) + ac_prev=cache_file ;; + -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ + | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) + cache_file=$ac_optarg ;; + + --config-cache | -C) + cache_file=config.cache ;; + + -datadir | --datadir | --datadi | --datad) + ac_prev=datadir ;; + -datadir=* | --datadir=* | --datadi=* | --datad=*) + datadir=$ac_optarg ;; + + -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ + | --dataroo | --dataro | --datar) + ac_prev=datarootdir ;; + -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ + | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) + datarootdir=$ac_optarg ;; + + -disable-* | --disable-*) + ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: '$ac_useropt'" + ac_useropt_orig=$ac_useropt + ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=no ;; + + -docdir | --docdir | --docdi | --doc | --do) + ac_prev=docdir ;; + -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) + docdir=$ac_optarg ;; + + -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) + ac_prev=dvidir ;; + -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) + dvidir=$ac_optarg ;; + + -enable-* | --enable-*) + ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: '$ac_useropt'" + ac_useropt_orig=$ac_useropt + ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=\$ac_optarg ;; + + -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ + | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ + | --exec | --exe | --ex) + ac_prev=exec_prefix ;; + -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ + | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ + | --exec=* | --exe=* | --ex=*) + exec_prefix=$ac_optarg ;; + + -gas | --gas | --ga | --g) + # Obsolete; use --with-gas. + with_gas=yes ;; + + -help | --help | --hel | --he | -h) + ac_init_help=long ;; + -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) + ac_init_help=recursive ;; + -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) + ac_init_help=short ;; + + -host | --host | --hos | --ho) + ac_prev=host_alias ;; + -host=* | --host=* | --hos=* | --ho=*) + host_alias=$ac_optarg ;; + + -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) + ac_prev=htmldir ;; + -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ + | --ht=*) + htmldir=$ac_optarg ;; + + -includedir | --includedir | --includedi | --included | --include \ + | --includ | --inclu | --incl | --inc) + ac_prev=includedir ;; + -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ + | --includ=* | --inclu=* | --incl=* | --inc=*) + includedir=$ac_optarg ;; + + -infodir | --infodir | --infodi | --infod | --info | --inf) + ac_prev=infodir ;; + -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) + infodir=$ac_optarg ;; + + -libdir | --libdir | --libdi | --libd) + ac_prev=libdir ;; + -libdir=* | --libdir=* | --libdi=* | --libd=*) + libdir=$ac_optarg ;; + + -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ + | --libexe | --libex | --libe) + ac_prev=libexecdir ;; + -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ + | --libexe=* | --libex=* | --libe=*) + libexecdir=$ac_optarg ;; + + -localedir | --localedir | --localedi | --localed | --locale) + ac_prev=localedir ;; + -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) + localedir=$ac_optarg ;; + + -localstatedir | --localstatedir | --localstatedi | --localstated \ + | --localstate | --localstat | --localsta | --localst | --locals) + ac_prev=localstatedir ;; + -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ + | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) + localstatedir=$ac_optarg ;; + + -mandir | --mandir | --mandi | --mand | --man | --ma | --m) + ac_prev=mandir ;; + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) + mandir=$ac_optarg ;; + + -nfp | --nfp | --nf) + # Obsolete; use --without-fp. + with_fp=no ;; + + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c | -n) + no_create=yes ;; + + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) + no_recursion=yes ;; + + -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ + | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ + | --oldin | --oldi | --old | --ol | --o) + ac_prev=oldincludedir ;; + -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ + | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ + | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) + oldincludedir=$ac_optarg ;; + + -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) + ac_prev=prefix ;; + -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) + prefix=$ac_optarg ;; + + -program-prefix | --program-prefix | --program-prefi | --program-pref \ + | --program-pre | --program-pr | --program-p) + ac_prev=program_prefix ;; + -program-prefix=* | --program-prefix=* | --program-prefi=* \ + | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) + program_prefix=$ac_optarg ;; + + -program-suffix | --program-suffix | --program-suffi | --program-suff \ + | --program-suf | --program-su | --program-s) + ac_prev=program_suffix ;; + -program-suffix=* | --program-suffix=* | --program-suffi=* \ + | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) + program_suffix=$ac_optarg ;; + + -program-transform-name | --program-transform-name \ + | --program-transform-nam | --program-transform-na \ + | --program-transform-n | --program-transform- \ + | --program-transform | --program-transfor \ + | --program-transfo | --program-transf \ + | --program-trans | --program-tran \ + | --progr-tra | --program-tr | --program-t) + ac_prev=program_transform_name ;; + -program-transform-name=* | --program-transform-name=* \ + | --program-transform-nam=* | --program-transform-na=* \ + | --program-transform-n=* | --program-transform-=* \ + | --program-transform=* | --program-transfor=* \ + | --program-transfo=* | --program-transf=* \ + | --program-trans=* | --program-tran=* \ + | --progr-tra=* | --program-tr=* | --program-t=*) + program_transform_name=$ac_optarg ;; + + -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) + ac_prev=pdfdir ;; + -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) + pdfdir=$ac_optarg ;; + + -psdir | --psdir | --psdi | --psd | --ps) + ac_prev=psdir ;; + -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) + psdir=$ac_optarg ;; + + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + silent=yes ;; + + -runstatedir | --runstatedir | --runstatedi | --runstated \ + | --runstate | --runstat | --runsta | --runst | --runs \ + | --run | --ru | --r) + ac_prev=runstatedir ;; + -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ + | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ + | --run=* | --ru=* | --r=*) + runstatedir=$ac_optarg ;; + + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) + ac_prev=sbindir ;; + -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ + | --sbi=* | --sb=*) + sbindir=$ac_optarg ;; + + -sharedstatedir | --sharedstatedir | --sharedstatedi \ + | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ + | --sharedst | --shareds | --shared | --share | --shar \ + | --sha | --sh) + ac_prev=sharedstatedir ;; + -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ + | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ + | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ + | --sha=* | --sh=*) + sharedstatedir=$ac_optarg ;; + + -site | --site | --sit) + ac_prev=site ;; + -site=* | --site=* | --sit=*) + site=$ac_optarg ;; + + -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) + ac_prev=srcdir ;; + -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) + srcdir=$ac_optarg ;; + + -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ + | --syscon | --sysco | --sysc | --sys | --sy) + ac_prev=sysconfdir ;; + -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ + | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) + sysconfdir=$ac_optarg ;; + + -target | --target | --targe | --targ | --tar | --ta | --t) + ac_prev=target_alias ;; + -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) + target_alias=$ac_optarg ;; + + -v | -verbose | --verbose | --verbos | --verbo | --verb) + verbose=yes ;; + + -version | --version | --versio | --versi | --vers | -V) + ac_init_version=: ;; + + -with-* | --with-*) + ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: '$ac_useropt'" + ac_useropt_orig=$ac_useropt + ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=\$ac_optarg ;; + + -without-* | --without-*) + ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: '$ac_useropt'" + ac_useropt_orig=$ac_useropt + ac_useropt=`printf "%s\n" "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=no ;; + + --x) + # Obsolete; use --with-x. + with_x=yes ;; + + -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ + | --x-incl | --x-inc | --x-in | --x-i) + ac_prev=x_includes ;; + -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ + | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) + x_includes=$ac_optarg ;; + + -x-libraries | --x-libraries | --x-librarie | --x-librari \ + | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) + ac_prev=x_libraries ;; + -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ + | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) + x_libraries=$ac_optarg ;; + + -*) as_fn_error $? "unrecognized option: '$ac_option' +Try '$0 --help' for more information" + ;; + + *=*) + ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` + # Reject names that are not valid shell variable names. + case $ac_envvar in #( + '' | [0-9]* | *[!_$as_cr_alnum]* ) + as_fn_error $? "invalid variable name: '$ac_envvar'" ;; + esac + eval $ac_envvar=\$ac_optarg + export $ac_envvar ;; + + *) + # FIXME: should be removed in autoconf 3.0. + printf "%s\n" "$as_me: WARNING: you should use --build, --host, --target" >&2 + expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && + printf "%s\n" "$as_me: WARNING: invalid host type: $ac_option" >&2 + : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" + ;; + + esac +done + +if test -n "$ac_prev"; then + ac_option=--`echo $ac_prev | sed 's/_/-/g'` + as_fn_error $? "missing argument to $ac_option" +fi + +if test -n "$ac_unrecognized_opts"; then + case $enable_option_checking in + no) ;; + fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; + *) printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; + esac +fi + +# Check all directory arguments for consistency. +for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ + datadir sysconfdir sharedstatedir localstatedir includedir \ + oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ + libdir localedir mandir runstatedir +do + eval ac_val=\$$ac_var + # Remove trailing slashes. + case $ac_val in + */ ) + ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` + eval $ac_var=\$ac_val;; + esac + # Be sure to have absolute directory names. + case $ac_val in + [\\/$]* | ?:[\\/]* ) continue;; + NONE | '' ) case $ac_var in *prefix ) continue;; esac;; + esac + as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" +done + +# There might be people who depend on the old broken behavior: '$host' +# used to hold the argument of --host etc. +# FIXME: To remove some day. +build=$build_alias +host=$host_alias +target=$target_alias + +# FIXME: To remove some day. +if test "x$host_alias" != x; then + if test "x$build_alias" = x; then + cross_compiling=maybe + elif test "x$build_alias" != "x$host_alias"; then + cross_compiling=yes + fi +fi + +ac_tool_prefix= +test -n "$host_alias" && ac_tool_prefix=$host_alias- + +test "$silent" = yes && exec 6>/dev/null + + +ac_pwd=`pwd` && test -n "$ac_pwd" && +ac_ls_di=`ls -di .` && +ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || + as_fn_error $? "working directory cannot be determined" +test "X$ac_ls_di" = "X$ac_pwd_ls_di" || + as_fn_error $? "pwd does not report name of working directory" + + +# Find the source files, if location was not specified. +if test -z "$srcdir"; then + ac_srcdir_defaulted=yes + # Try the directory containing this script, then the parent directory. + ac_confdir=`$as_dirname -- "$as_myself" || +$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_myself" : 'X\(//\)[^/]' \| \ + X"$as_myself" : 'X\(//\)$' \| \ + X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || +printf "%s\n" X"$as_myself" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + srcdir=$ac_confdir + if test ! -r "$srcdir/$ac_unique_file"; then + srcdir=.. + fi +else + ac_srcdir_defaulted=no +fi +if test ! -r "$srcdir/$ac_unique_file"; then + test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." + as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" +fi +ac_msg="sources are in $srcdir, but 'cd $srcdir' does not work" +ac_abs_confdir=`( + cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" + pwd)` +# When building in place, set srcdir=. +if test "$ac_abs_confdir" = "$ac_pwd"; then + srcdir=. +fi +# Remove unnecessary trailing slashes from srcdir. +# Double slashes in file names in object file debugging info +# mess up M-x gdb in Emacs. +case $srcdir in +*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; +esac +for ac_var in $ac_precious_vars; do + eval ac_env_${ac_var}_set=\${${ac_var}+set} + eval ac_env_${ac_var}_value=\$${ac_var} + eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} + eval ac_cv_env_${ac_var}_value=\$${ac_var} +done + +# +# Report the --help message. +# +if test "$ac_init_help" = "long"; then + # Omit some internal or obsolete options to make the list less imposing. + # This message is too long to be a string in the A/UX 3.1 sh. + cat <<_ACEOF +'configure' configures libble++ version-0.5 to adapt to many kinds of systems. + +Usage: $0 [OPTION]... [VAR=VALUE]... + +To assign environment variables (e.g., CC, CFLAGS...), specify them as +VAR=VALUE. See below for descriptions of some of the useful variables. + +Defaults for the options are specified in brackets. + +Configuration: + -h, --help display this help and exit + --help=short display options specific to this package + --help=recursive display the short help of all the included packages + -V, --version display version information and exit + -q, --quiet, --silent do not print 'checking ...' messages + --cache-file=FILE cache test results in FILE [disabled] + -C, --config-cache alias for '--cache-file=config.cache' + -n, --no-create do not create output files + --srcdir=DIR find the sources in DIR [configure dir or '..'] + +Installation directories: + --prefix=PREFIX install architecture-independent files in PREFIX + [$ac_default_prefix] + --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX + [PREFIX] + +By default, 'make install' will install all the files in +'$ac_default_prefix/bin', '$ac_default_prefix/lib' etc. You can specify +an installation prefix other than '$ac_default_prefix' using '--prefix', +for instance '--prefix=\$HOME'. + +For better control, use the options below. + +Fine tuning of the installation directories: + --bindir=DIR user executables [EPREFIX/bin] + --sbindir=DIR system admin executables [EPREFIX/sbin] + --libexecdir=DIR program executables [EPREFIX/libexec] + --sysconfdir=DIR read-only single-machine data [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] + --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] + --libdir=DIR object code libraries [EPREFIX/lib] + --includedir=DIR C header files [PREFIX/include] + --oldincludedir=DIR C header files for non-gcc [/usr/include] + --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] + --datadir=DIR read-only architecture-independent data [DATAROOTDIR] + --infodir=DIR info documentation [DATAROOTDIR/info] + --localedir=DIR locale-dependent data [DATAROOTDIR/locale] + --mandir=DIR man documentation [DATAROOTDIR/man] + --docdir=DIR documentation root [DATAROOTDIR/doc/libble--] + --htmldir=DIR html documentation [DOCDIR] + --dvidir=DIR dvi documentation [DOCDIR] + --pdfdir=DIR pdf documentation [DOCDIR] + --psdir=DIR ps documentation [DOCDIR] +_ACEOF + + cat <<\_ACEOF +_ACEOF +fi + +if test -n "$ac_init_help"; then + case $ac_init_help in + short | recursive ) echo "Configuration of libble++ version-0.5:";; + esac + cat <<\_ACEOF + +Optional Packages: + --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] + --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) + --with-bluez-support Build with BlueZ transport support (HCI/L2CAP) + [default=yes] + --with-nimble-support Build with Nimble transport support + (/dev/atbm_ioctl) [default=no] + --with-server-support Build with BLE GATT server support [default=no] + +Some influential environment variables: + CXX C++ compiler command + CXXFLAGS C++ compiler flags + LDFLAGS linker flags, e.g. -L if you have libraries in a + nonstandard directory + LIBS libraries to pass to the linker, e.g. -l + CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I if + you have headers in a nonstandard directory + NIMBLE_ROOT Path to Nimble BLE stack root directory (for headers) + NIMBLE_LIBDIR + Path to Nimble library directory (defaults to NIMBLE_ROOT/lib or + NIMBLE_ROOT/build) + +Use these variables to override the choices made by 'configure' or to help +it to find libraries and programs with nonstandard names/locations. + +Report bugs to the package provider. +_ACEOF +ac_status=$? +fi + +if test "$ac_init_help" = "recursive"; then + # If there are subdirs, report their specific --help. + for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue + test -d "$ac_dir" || + { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || + continue + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`printf "%s\n" "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`printf "%s\n" "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + cd "$ac_dir" || { ac_status=$?; continue; } + # Check for configure.gnu first; this name is used for a wrapper for + # Metaconfig's "Configure" on case-insensitive file systems. + if test -f "$ac_srcdir/configure.gnu"; then + echo && + $SHELL "$ac_srcdir/configure.gnu" --help=recursive + elif test -f "$ac_srcdir/configure"; then + echo && + $SHELL "$ac_srcdir/configure" --help=recursive + else + printf "%s\n" "$as_me: WARNING: no configuration information is in $ac_dir" >&2 + fi || ac_status=$? + cd "$ac_pwd" || { ac_status=$?; break; } + done +fi + +test -n "$ac_init_help" && exit $ac_status +if $ac_init_version; then + cat <<\_ACEOF +libble++ configure version-0.5 +generated by GNU Autoconf 2.72 + +Copyright (C) 2023 Free Software Foundation, Inc. +This configure script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it. + +E. Rosten, 2016, the BlueZ contributors to 2016 +_ACEOF + exit +fi + +## ------------------------ ## +## Autoconf initialization. ## +## ------------------------ ## + +# ac_fn_cxx_try_compile LINENO +# ---------------------------- +# Try to compile conftest.$ac_ext, and return whether this succeeded. +ac_fn_cxx_try_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext conftest.beam + if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +printf "%s\n" "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_cxx_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext +then : + ac_retval=0 +else case e in #( + e) printf "%s\n" "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 ;; +esac +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_cxx_try_compile + +# ac_fn_cxx_check_header_compile LINENO HEADER VAR INCLUDES +# --------------------------------------------------------- +# Tests whether HEADER exists and can be compiled using the include files in +# INCLUDES, setting the cache variable VAR accordingly. +ac_fn_cxx_check_header_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $2" >&5 +printf %s "checking for $2... " >&6; } +if eval test \${$3+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$4 +#include <$2> +_ACEOF +if ac_fn_cxx_try_compile "$LINENO" +then : + eval "$3=yes" +else case e in #( + e) eval "$3=no" ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +eval ac_res=\$$3 + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +printf "%s\n" "$ac_res" >&6; } + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + +} # ac_fn_cxx_check_header_compile + +# ac_fn_cxx_try_link LINENO +# ------------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. +ac_fn_cxx_try_link () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext conftest.beam conftest$ac_exeext + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +printf "%s\n" "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_cxx_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + test -x conftest$ac_exeext + } +then : + ac_retval=0 +else case e in #( + e) printf "%s\n" "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 ;; +esac +fi + # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information + # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would + # interfere with the next link command; also delete a directory that is + # left behind by Apple's compiler. We do this before executing the actions. + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_cxx_try_link +ac_configure_args_raw= +for ac_arg +do + case $ac_arg in + *\'*) + ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + as_fn_append ac_configure_args_raw " '$ac_arg'" +done + +case $ac_configure_args_raw in + *$as_nl*) + ac_safe_unquote= ;; + *) + ac_unsafe_z='|&;<>()$`\\"*?[ '' ' # This string ends in space, tab. + ac_unsafe_a="$ac_unsafe_z#~" + ac_safe_unquote="s/ '\\([^$ac_unsafe_a][^$ac_unsafe_z]*\\)'/ \\1/g" + ac_configure_args_raw=` printf "%s\n" "$ac_configure_args_raw" | sed "$ac_safe_unquote"`;; +esac + +cat >config.log <<_ACEOF +This file contains any messages produced by compilers while +running configure, to aid debugging if configure makes a mistake. + +It was created by libble++ $as_me version-0.5, which was +generated by GNU Autoconf 2.72. Invocation command line was + + $ $0$ac_configure_args_raw + +_ACEOF +exec 5>>config.log +{ +cat <<_ASUNAME +## --------- ## +## Platform. ## +## --------- ## + +hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` + +/bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` +/usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` +/bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` +/bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` + +_ASUNAME + +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + printf "%s\n" "PATH: $as_dir" + done +IFS=$as_save_IFS + +} >&5 + +cat >&5 <<_ACEOF + + +## ----------- ## +## Core tests. ## +## ----------- ## + +_ACEOF + + +# Keep a trace of the command line. +# Strip out --no-create and --no-recursion so they do not pile up. +# Strip out --silent because we don't want to record it for future runs. +# Also quote any args containing shell meta-characters. +# Make two passes to allow for proper duplicate-argument suppression. +ac_configure_args= +ac_configure_args0= +ac_configure_args1= +ac_must_keep_next=false +for ac_pass in 1 2 +do + for ac_arg + do + case $ac_arg in + -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + continue ;; + *\'*) + ac_arg=`printf "%s\n" "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + case $ac_pass in + 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; + 2) + as_fn_append ac_configure_args1 " '$ac_arg'" + if test $ac_must_keep_next = true; then + ac_must_keep_next=false # Got value, back to normal. + else + case $ac_arg in + *=* | --config-cache | -C | -disable-* | --disable-* \ + | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ + | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ + | -with-* | --with-* | -without-* | --without-* | --x) + case "$ac_configure_args0 " in + "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; + esac + ;; + -* ) ac_must_keep_next=true ;; + esac + fi + as_fn_append ac_configure_args " '$ac_arg'" + ;; + esac + done +done +{ ac_configure_args0=; unset ac_configure_args0;} +{ ac_configure_args1=; unset ac_configure_args1;} + +# When interrupted or exit'd, cleanup temporary files, and complete +# config.log. We remove comments because anyway the quotes in there +# would cause problems or look ugly. +# WARNING: Use '\'' to represent an apostrophe within the trap. +# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. +trap 'exit_status=$? + # Sanitize IFS. + IFS=" "" $as_nl" + # Save into config.log some information that might help in debugging. + { + echo + + printf "%s\n" "## ---------------- ## +## Cache variables. ## +## ---------------- ##" + echo + # The following way of writing the cache mishandles newlines in values, +( + for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + (set) 2>&1 | + case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + sed -n \ + "s/'\''/'\''\\\\'\'''\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" + ;; #( + *) + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) + echo + + printf "%s\n" "## ----------------- ## +## Output variables. ## +## ----------------- ##" + echo + for ac_var in $ac_subst_vars + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`printf "%s\n" "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + printf "%s\n" "$ac_var='\''$ac_val'\''" + done | sort + echo + + if test -n "$ac_subst_files"; then + printf "%s\n" "## ------------------- ## +## File substitutions. ## +## ------------------- ##" + echo + for ac_var in $ac_subst_files + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`printf "%s\n" "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + printf "%s\n" "$ac_var='\''$ac_val'\''" + done | sort + echo + fi + + if test -s confdefs.h; then + printf "%s\n" "## ----------- ## +## confdefs.h. ## +## ----------- ##" + echo + cat confdefs.h + echo + fi + test "$ac_signal" != 0 && + printf "%s\n" "$as_me: caught signal $ac_signal" + printf "%s\n" "$as_me: exit $exit_status" + } >&5 + rm -f core *.core core.conftest.* && + rm -f -r conftest* confdefs* conf$$* $ac_clean_files && + exit $exit_status +' 0 +for ac_signal in 1 2 13 15; do + trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal +done +ac_signal=0 + +# confdefs.h avoids OS command line length limits that DEFS can exceed. +rm -f -r conftest* confdefs.h + +printf "%s\n" "/* confdefs.h */" > confdefs.h + +# Predefined preprocessor variables. + +printf "%s\n" "#define PACKAGE_NAME \"$PACKAGE_NAME\"" >>confdefs.h + +printf "%s\n" "#define PACKAGE_TARNAME \"$PACKAGE_TARNAME\"" >>confdefs.h + +printf "%s\n" "#define PACKAGE_VERSION \"$PACKAGE_VERSION\"" >>confdefs.h + +printf "%s\n" "#define PACKAGE_STRING \"$PACKAGE_STRING\"" >>confdefs.h + +printf "%s\n" "#define PACKAGE_BUGREPORT \"$PACKAGE_BUGREPORT\"" >>confdefs.h + +printf "%s\n" "#define PACKAGE_URL \"$PACKAGE_URL\"" >>confdefs.h + + +# Let the site file select an alternate cache file if it wants to. +# Prefer an explicitly selected file to automatically selected ones. +if test -n "$CONFIG_SITE"; then + ac_site_files="$CONFIG_SITE" +elif test "x$prefix" != xNONE; then + ac_site_files="$prefix/share/config.site $prefix/etc/config.site" +else + ac_site_files="$ac_default_prefix/share/config.site $ac_default_prefix/etc/config.site" +fi + +for ac_site_file in $ac_site_files +do + case $ac_site_file in #( + */*) : + ;; #( + *) : + ac_site_file=./$ac_site_file ;; +esac + if test -f "$ac_site_file" && test -r "$ac_site_file"; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 +printf "%s\n" "$as_me: loading site script $ac_site_file" >&6;} + sed 's/^/| /' "$ac_site_file" >&5 + . "$ac_site_file" \ + || { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} +as_fn_error $? "failed to load site script $ac_site_file +See 'config.log' for more details" "$LINENO" 5; } + fi +done + +if test -r "$cache_file"; then + # Some versions of bash will fail to source /dev/null (special files + # actually), so we avoid doing that. DJGPP emulates it as a regular file. + if test /dev/null != "$cache_file" && test -f "$cache_file"; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 +printf "%s\n" "$as_me: loading cache $cache_file" >&6;} + case $cache_file in + [\\/]* | ?:[\\/]* ) . "$cache_file";; + *) . "./$cache_file";; + esac + fi +else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 +printf "%s\n" "$as_me: creating cache $cache_file" >&6;} + >$cache_file +fi + +# Test code for whether the C++ compiler supports C++98 (global declarations) +ac_cxx_conftest_cxx98_globals=' +// Does the compiler advertise C++98 conformance? +#if !defined __cplusplus || __cplusplus < 199711L +# error "Compiler does not advertise C++98 conformance" +#endif + +// These inclusions are to reject old compilers that +// lack the unsuffixed header files. +#include +#include + +// and are *not* freestanding headers in C++98. +extern void assert (int); +namespace std { + extern int strcmp (const char *, const char *); +} + +// Namespaces, exceptions, and templates were all added after "C++ 2.0". +using std::exception; +using std::strcmp; + +namespace { + +void test_exception_syntax() +{ + try { + throw "test"; + } catch (const char *s) { + // Extra parentheses suppress a warning when building autoconf itself, + // due to lint rules shared with more typical C programs. + assert (!(strcmp) (s, "test")); + } +} + +template struct test_template +{ + T const val; + explicit test_template(T t) : val(t) {} + template T add(U u) { return static_cast(u) + val; } +}; + +} // anonymous namespace +' + +# Test code for whether the C++ compiler supports C++98 (body of main) +ac_cxx_conftest_cxx98_main=' + assert (argc); + assert (! argv[0]); +{ + test_exception_syntax (); + test_template tt (2.0); + assert (tt.add (4) == 6.0); + assert (true && !false); +} +' + +# Test code for whether the C++ compiler supports C++11 (global declarations) +ac_cxx_conftest_cxx11_globals=' +// Does the compiler advertise C++ 2011 conformance? +#if !defined __cplusplus || __cplusplus < 201103L +# error "Compiler does not advertise C++11 conformance" +#endif + +namespace cxx11test +{ + constexpr int get_val() { return 20; } + + struct testinit + { + int i; + double d; + }; + + class delegate + { + public: + delegate(int n) : n(n) {} + delegate(): delegate(2354) {} + + virtual int getval() { return this->n; }; + protected: + int n; + }; + + class overridden : public delegate + { + public: + overridden(int n): delegate(n) {} + virtual int getval() override final { return this->n * 2; } + }; + + class nocopy + { + public: + nocopy(int i): i(i) {} + nocopy() = default; + nocopy(const nocopy&) = delete; + nocopy & operator=(const nocopy&) = delete; + private: + int i; + }; + + // for testing lambda expressions + template Ret eval(Fn f, Ret v) + { + return f(v); + } + + // for testing variadic templates and trailing return types + template auto sum(V first) -> V + { + return first; + } + template auto sum(V first, Args... rest) -> V + { + return first + sum(rest...); + } +} +' + +# Test code for whether the C++ compiler supports C++11 (body of main) +ac_cxx_conftest_cxx11_main=' +{ + // Test auto and decltype + auto a1 = 6538; + auto a2 = 48573953.4; + auto a3 = "String literal"; + + int total = 0; + for (auto i = a3; *i; ++i) { total += *i; } + + decltype(a2) a4 = 34895.034; +} +{ + // Test constexpr + short sa[cxx11test::get_val()] = { 0 }; +} +{ + // Test initializer lists + cxx11test::testinit il = { 4323, 435234.23544 }; +} +{ + // Test range-based for + int array[] = {9, 7, 13, 15, 4, 18, 12, 10, 5, 3, + 14, 19, 17, 8, 6, 20, 16, 2, 11, 1}; + for (auto &x : array) { x += 23; } +} +{ + // Test lambda expressions + using cxx11test::eval; + assert (eval ([](int x) { return x*2; }, 21) == 42); + double d = 2.0; + assert (eval ([&](double x) { return d += x; }, 3.0) == 5.0); + assert (d == 5.0); + assert (eval ([=](double x) mutable { return d += x; }, 4.0) == 9.0); + assert (d == 5.0); +} +{ + // Test use of variadic templates + using cxx11test::sum; + auto a = sum(1); + auto b = sum(1, 2); + auto c = sum(1.0, 2.0, 3.0); +} +{ + // Test constructor delegation + cxx11test::delegate d1; + cxx11test::delegate d2(); + cxx11test::delegate d3(45); +} +{ + // Test override and final + cxx11test::overridden o1(55464); +} +{ + // Test nullptr + char *c = nullptr; +} +{ + // Test template brackets + test_template<::test_template> v(test_template(12)); +} +{ + // Unicode literals + char const *utf8 = u8"UTF-8 string \u2500"; + char16_t const *utf16 = u"UTF-8 string \u2500"; + char32_t const *utf32 = U"UTF-32 string \u2500"; +} +' + +# Test code for whether the C compiler supports C++11 (complete). +ac_cxx_conftest_cxx11_program="${ac_cxx_conftest_cxx98_globals} +${ac_cxx_conftest_cxx11_globals} + +int +main (int argc, char **argv) +{ + int ok = 0; + ${ac_cxx_conftest_cxx98_main} + ${ac_cxx_conftest_cxx11_main} + return ok; +} +" + +# Test code for whether the C compiler supports C++98 (complete). +ac_cxx_conftest_cxx98_program="${ac_cxx_conftest_cxx98_globals} +int +main (int argc, char **argv) +{ + int ok = 0; + ${ac_cxx_conftest_cxx98_main} + return ok; +} +" + +as_fn_append ac_header_cxx_list " stdio.h stdio_h HAVE_STDIO_H" +as_fn_append ac_header_cxx_list " stdlib.h stdlib_h HAVE_STDLIB_H" +as_fn_append ac_header_cxx_list " string.h string_h HAVE_STRING_H" +as_fn_append ac_header_cxx_list " inttypes.h inttypes_h HAVE_INTTYPES_H" +as_fn_append ac_header_cxx_list " stdint.h stdint_h HAVE_STDINT_H" +as_fn_append ac_header_cxx_list " strings.h strings_h HAVE_STRINGS_H" +as_fn_append ac_header_cxx_list " sys/stat.h sys_stat_h HAVE_SYS_STAT_H" +as_fn_append ac_header_cxx_list " sys/types.h sys_types_h HAVE_SYS_TYPES_H" +as_fn_append ac_header_cxx_list " unistd.h unistd_h HAVE_UNISTD_H" +# Check that the precious variables saved in the cache have kept the same +# value. +ac_cache_corrupted=false +for ac_var in $ac_precious_vars; do + eval ac_old_set=\$ac_cv_env_${ac_var}_set + eval ac_new_set=\$ac_env_${ac_var}_set + eval ac_old_val=\$ac_cv_env_${ac_var}_value + eval ac_new_val=\$ac_env_${ac_var}_value + case $ac_old_set,$ac_new_set in + set,) + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&5 +printf "%s\n" "$as_me: error: '$ac_var' was set to '$ac_old_val' in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,set) + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' was not set in the previous run" >&5 +printf "%s\n" "$as_me: error: '$ac_var' was not set in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,);; + *) + if test "x$ac_old_val" != "x$ac_new_val"; then + # differences in whitespace do not lead to failure. + ac_old_val_w=`echo x $ac_old_val` + ac_new_val_w=`echo x $ac_new_val` + if test "$ac_old_val_w" != "$ac_new_val_w"; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: '$ac_var' has changed since the previous run:" >&5 +printf "%s\n" "$as_me: error: '$ac_var' has changed since the previous run:" >&2;} + ac_cache_corrupted=: + else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&5 +printf "%s\n" "$as_me: warning: ignoring whitespace changes in '$ac_var' since the previous run:" >&2;} + eval $ac_var=\$ac_old_val + fi + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: former value: '$ac_old_val'" >&5 +printf "%s\n" "$as_me: former value: '$ac_old_val'" >&2;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: current value: '$ac_new_val'" >&5 +printf "%s\n" "$as_me: current value: '$ac_new_val'" >&2;} + fi;; + esac + # Pass precious variables to config.status. + if test "$ac_new_set" = set; then + case $ac_new_val in + *\'*) ac_arg=$ac_var=`printf "%s\n" "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; + *) ac_arg=$ac_var=$ac_new_val ;; + esac + case " $ac_configure_args " in + *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. + *) as_fn_append ac_configure_args " '$ac_arg'" ;; + esac + fi +done +if $ac_cache_corrupted; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 +printf "%s\n" "$as_me: error: changes in the environment can compromise the build" >&2;} + as_fn_error $? "run '${MAKE-make} distclean' and/or 'rm $cache_file' + and start over" "$LINENO" 5 +fi +## -------------------- ## +## Main body of script. ## +## -------------------- ## + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + +ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + + + + + + +ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu +if test -z "$CXX"; then + if test -n "$CCC"; then + CXX=$CCC + else + if test -n "$ac_tool_prefix"; then + for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC clang++ + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_CXX+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$CXX"; then + ac_cv_prog_CXX="$CXX" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_CXX="$ac_tool_prefix$ac_prog" + printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi ;; +esac +fi +CXX=$ac_cv_prog_CXX +if test -n "$CXX"; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $CXX" >&5 +printf "%s\n" "$CXX" >&6; } +else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } +fi + + + test -n "$CXX" && break + done +fi +if test -z "$CXX"; then + ac_ct_CXX=$CXX + for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC clang++ +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_ac_ct_CXX+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$ac_ct_CXX"; then + ac_cv_prog_ac_ct_CXX="$ac_ct_CXX" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CXX="$ac_prog" + printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi ;; +esac +fi +ac_ct_CXX=$ac_cv_prog_ac_ct_CXX +if test -n "$ac_ct_CXX"; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CXX" >&5 +printf "%s\n" "$ac_ct_CXX" >&6; } +else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } +fi + + + test -n "$ac_ct_CXX" && break +done + + if test "x$ac_ct_CXX" = x; then + CXX="g++" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +printf "%s\n" "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CXX=$ac_ct_CXX + fi +fi + + fi +fi +# Provide some information about the compiler. +printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C++ compiler version" >&5 +set X $ac_compile +ac_compiler=$2 +for ac_option in --version -v -V -qversion; do + { { ac_try="$ac_compiler $ac_option >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +printf "%s\n" "$ac_try_echo"; } >&5 + (eval "$ac_compiler $ac_option >&5") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + sed '10a\ +... rest of stderr output deleted ... + 10q' conftest.err >conftest.er1 + cat conftest.er1 >&5 + fi + rm -f conftest.er1 conftest.err + printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +done + +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" +# Try to create an executable without -o first, disregard a.out. +# It will help us diagnose broken compilers, and finding out an intuition +# of exeext. +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the C++ compiler works" >&5 +printf %s "checking whether the C++ compiler works... " >&6; } +ac_link_default=`printf "%s\n" "$ac_link" | sed 's/ -o *conftest[^ ]*//'` + +# The possible output files: +ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" + +ac_rmfiles= +for ac_file in $ac_files +do + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + * ) ac_rmfiles="$ac_rmfiles $ac_file";; + esac +done +rm -f $ac_rmfiles + +if { { ac_try="$ac_link_default" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +printf "%s\n" "$ac_try_echo"; } >&5 + (eval "$ac_link_default") 2>&5 + ac_status=$? + printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +then : + # Autoconf-2.13 could set the ac_cv_exeext variable to 'no'. +# So ignore a value of 'no', otherwise this would lead to 'EXEEXT = no' +# in a Makefile. We should not override ac_cv_exeext if it was cached, +# so that the user can short-circuit this test for compilers unknown to +# Autoconf. +for ac_file in $ac_files '' +do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) + ;; + [ab].out ) + # We found the default executable, but exeext='' is most + # certainly right. + break;; + *.* ) + if test ${ac_cv_exeext+y} && test "$ac_cv_exeext" != no; + then :; else + ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + fi + # We set ac_cv_exeext here because the later test for it is not + # safe: cross compilers may not add the suffix if given an '-o' + # argument, so we may need to know it at that point already. + # Even if this section looks crufty: it has the advantage of + # actually working. + break;; + * ) + break;; + esac +done +test "$ac_cv_exeext" = no && ac_cv_exeext= + +else case e in #( + e) ac_file='' ;; +esac +fi +if test -z "$ac_file" +then : + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } +printf "%s\n" "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} +as_fn_error 77 "C++ compiler cannot create executables +See 'config.log' for more details" "$LINENO" 5; } +else case e in #( + e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf "%s\n" "yes" >&6; } ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for C++ compiler default output file name" >&5 +printf %s "checking for C++ compiler default output file name... " >&6; } +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 +printf "%s\n" "$ac_file" >&6; } +ac_exeext=$ac_cv_exeext + +rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out +ac_clean_files=$ac_clean_files_save +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 +printf %s "checking for suffix of executables... " >&6; } +if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +printf "%s\n" "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +then : + # If both 'conftest.exe' and 'conftest' are 'present' (well, observable) +# catch 'conftest.exe'. For instance with Cygwin, 'ls conftest' will +# work properly (i.e., refer to 'conftest.exe'), while it won't with +# 'rm'. +for ac_file in conftest.exe conftest conftest.*; do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + break;; + * ) break;; + esac +done +else case e in #( + e) { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of executables: cannot compile and link +See 'config.log' for more details" "$LINENO" 5; } ;; +esac +fi +rm -f conftest conftest$ac_cv_exeext +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 +printf "%s\n" "$ac_cv_exeext" >&6; } + +rm -f conftest.$ac_ext +EXEEXT=$ac_cv_exeext +ac_exeext=$EXEEXT +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main (void) +{ +FILE *f = fopen ("conftest.out", "w"); + if (!f) + return 1; + return ferror (f) || fclose (f) != 0; + + ; + return 0; +} +_ACEOF +ac_clean_files="$ac_clean_files conftest.out" +# Check that the compiler produces executables we can run. If not, either +# the compiler is broken, or we cross compile. +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 +printf %s "checking whether we are cross compiling... " >&6; } +if test "$cross_compiling" != yes; then + { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +printf "%s\n" "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + if { ac_try='./conftest$ac_cv_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +printf "%s\n" "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then + cross_compiling=no + else + if test "$cross_compiling" = maybe; then + cross_compiling=yes + else + { { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} +as_fn_error 77 "cannot run C++ compiled programs. +If you meant to cross compile, use '--host'. +See 'config.log' for more details" "$LINENO" 5; } + fi + fi +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 +printf "%s\n" "$cross_compiling" >&6; } + +rm -f conftest.$ac_ext conftest$ac_cv_exeext \ + conftest.o conftest.obj conftest.out +ac_clean_files=$ac_clean_files_save +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 +printf %s "checking for suffix of object files... " >&6; } +if test ${ac_cv_objext+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.o conftest.obj +if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +printf "%s\n" "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>&5 + ac_status=$? + printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +then : + for ac_file in conftest.o conftest.obj conftest.*; do + test -f "$ac_file" || continue; + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; + *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` + break;; + esac +done +else case e in #( + e) printf "%s\n" "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: in '$ac_pwd':" >&5 +printf "%s\n" "$as_me: error: in '$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of object files: cannot compile +See 'config.log' for more details" "$LINENO" 5; } ;; +esac +fi +rm -f conftest.$ac_cv_objext conftest.$ac_ext ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 +printf "%s\n" "$ac_cv_objext" >&6; } +OBJEXT=$ac_cv_objext +ac_objext=$OBJEXT +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether the compiler supports GNU C++" >&5 +printf %s "checking whether the compiler supports GNU C++... " >&6; } +if test ${ac_cv_cxx_compiler_gnu+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ +#ifndef __GNUC__ + choke me +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO" +then : + ac_compiler_gnu=yes +else case e in #( + e) ac_compiler_gnu=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext +ac_cv_cxx_compiler_gnu=$ac_compiler_gnu + ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_cxx_compiler_gnu" >&5 +printf "%s\n" "$ac_cv_cxx_compiler_gnu" >&6; } +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + +if test $ac_compiler_gnu = yes; then + GXX=yes +else + GXX= +fi +ac_test_CXXFLAGS=${CXXFLAGS+y} +ac_save_CXXFLAGS=$CXXFLAGS +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CXX accepts -g" >&5 +printf %s "checking whether $CXX accepts -g... " >&6; } +if test ${ac_cv_prog_cxx_g+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_save_cxx_werror_flag=$ac_cxx_werror_flag + ac_cxx_werror_flag=yes + ac_cv_prog_cxx_g=no + CXXFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO" +then : + ac_cv_prog_cxx_g=yes +else case e in #( + e) CXXFLAGS="" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO" +then : + +else case e in #( + e) ac_cxx_werror_flag=$ac_save_cxx_werror_flag + CXXFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main (void) +{ + + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO" +then : + ac_cv_prog_cxx_g=yes +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + ac_cxx_werror_flag=$ac_save_cxx_werror_flag ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cxx_g" >&5 +printf "%s\n" "$ac_cv_prog_cxx_g" >&6; } +if test $ac_test_CXXFLAGS; then + CXXFLAGS=$ac_save_CXXFLAGS +elif test $ac_cv_prog_cxx_g = yes; then + if test "$GXX" = yes; then + CXXFLAGS="-g -O2" + else + CXXFLAGS="-g" + fi +else + if test "$GXX" = yes; then + CXXFLAGS="-O2" + else + CXXFLAGS= + fi +fi +ac_prog_cxx_stdcxx=no +if test x$ac_prog_cxx_stdcxx = xno +then : + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CXX option to enable C++11 features" >&5 +printf %s "checking for $CXX option to enable C++11 features... " >&6; } +if test ${ac_cv_prog_cxx_cxx11+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_cv_prog_cxx_cxx11=no +ac_save_CXX=$CXX +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$ac_cxx_conftest_cxx11_program +_ACEOF +for ac_arg in '' -std=gnu++11 -std=gnu++0x -std=c++11 -std=c++0x -qlanglvl=extended0x -AA +do + CXX="$ac_save_CXX $ac_arg" + if ac_fn_cxx_try_compile "$LINENO" +then : + ac_cv_prog_cxx_cxx11=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam + test "x$ac_cv_prog_cxx_cxx11" != "xno" && break +done +rm -f conftest.$ac_ext +CXX=$ac_save_CXX ;; +esac +fi + +if test "x$ac_cv_prog_cxx_cxx11" = xno +then : + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +printf "%s\n" "unsupported" >&6; } +else case e in #( + e) if test "x$ac_cv_prog_cxx_cxx11" = x +then : + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +printf "%s\n" "none needed" >&6; } +else case e in #( + e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cxx_cxx11" >&5 +printf "%s\n" "$ac_cv_prog_cxx_cxx11" >&6; } + CXX="$CXX $ac_cv_prog_cxx_cxx11" ;; +esac +fi + ac_cv_prog_cxx_stdcxx=$ac_cv_prog_cxx_cxx11 + ac_prog_cxx_stdcxx=cxx11 ;; +esac +fi +fi +if test x$ac_prog_cxx_stdcxx = xno +then : + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $CXX option to enable C++98 features" >&5 +printf %s "checking for $CXX option to enable C++98 features... " >&6; } +if test ${ac_cv_prog_cxx_cxx98+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_cv_prog_cxx_cxx98=no +ac_save_CXX=$CXX +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +$ac_cxx_conftest_cxx98_program +_ACEOF +for ac_arg in '' -std=gnu++98 -std=c++98 -qlanglvl=extended -AA +do + CXX="$ac_save_CXX $ac_arg" + if ac_fn_cxx_try_compile "$LINENO" +then : + ac_cv_prog_cxx_cxx98=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam + test "x$ac_cv_prog_cxx_cxx98" != "xno" && break +done +rm -f conftest.$ac_ext +CXX=$ac_save_CXX ;; +esac +fi + +if test "x$ac_cv_prog_cxx_cxx98" = xno +then : + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +printf "%s\n" "unsupported" >&6; } +else case e in #( + e) if test "x$ac_cv_prog_cxx_cxx98" = x +then : + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +printf "%s\n" "none needed" >&6; } +else case e in #( + e) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cxx_cxx98" >&5 +printf "%s\n" "$ac_cv_prog_cxx_cxx98" >&6; } + CXX="$CXX $ac_cv_prog_cxx_cxx98" ;; +esac +fi + ac_cv_prog_cxx_stdcxx=$ac_cv_prog_cxx_cxx98 + ac_prog_cxx_stdcxx=cxx98 ;; +esac +fi +fi + +ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + + +# ============================================================================ +# http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_14.html +# ============================================================================ +# +# SYNOPSIS +# +# AX_CXX_COMPILE_STDCXX_14([ext|noext],[mandatory|optional]) +# +# DESCRIPTION +# +# Check for baseline language coverage in the compiler for the C++14 +# standard; if necessary, add switches to CXXFLAGS to enable support. +# +# The first argument, if specified, indicates whether you insist on an +# extended mode (e.g. -std=gnu++14) or a strict conformance mode (e.g. +# -std=c++14). If neither is specified, you get whatever works, with +# preference for an extended mode. +# +# The second argument, if specified 'mandatory' or if left unspecified, +# indicates that baseline C++14 support is required and that the macro +# should error out if no mode with that support is found. If specified +# 'optional', then configuration proceeds regardless, after defining +# HAVE_CXX14 if and only if a supporting mode is found. +# +# LICENSE +# +# Copyright (c) 2008 Benjamin Kosnik +# Copyright (c) 2012 Zack Weinberg +# Copyright (c) 2013 Roy Stogner +# Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov +# Copyright (c) 2015 Paul Norman +# Copyright (c) 2016 Edward Rosten +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 13 + + + + + + + +################################################################################ +# +# Useful macros +# + + + + + + + + +if test "$CXXFLAGS" == "-g -O2" +then + + + if test "" == "" + then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if compiler flag -Wall works" >&5 +printf %s "checking if compiler flag -Wall works... " >&6; } + else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking " >&5 +printf %s "checking ... " >&6; } + fi + save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS -Wall" + + + + +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +int main(){} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO" +then : + cvd_conf_test=1 +else case e in #( + e) cvd_conf_test=0 ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + + + + + + if test $cvd_conf_test = 1 + then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf "%s\n" "yes" >&6; } + ts_success=yes + else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } + CXXFLAGS="$save_CXXFLAGS" + ts_success=no + fi + + + if test "" == "" + then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if compiler flag -Wextra works" >&5 +printf %s "checking if compiler flag -Wextra works... " >&6; } + else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking " >&5 +printf %s "checking ... " >&6; } + fi + save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS -Wextra" + + + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +int main(){} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO" +then : + cvd_conf_test=1 +else case e in #( + e) cvd_conf_test=0 ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + + + + + + if test $cvd_conf_test = 1 + then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf "%s\n" "yes" >&6; } + ts_success=yes + else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } + CXXFLAGS="$save_CXXFLAGS" + ts_success=no + fi + + + if test "" == "" + then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if compiler flag -W works" >&5 +printf %s "checking if compiler flag -W works... " >&6; } + else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking " >&5 +printf %s "checking ... " >&6; } + fi + save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS -W" + + + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +int main(){} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO" +then : + cvd_conf_test=1 +else case e in #( + e) cvd_conf_test=0 ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + + + + + + if test $cvd_conf_test = 1 + then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf "%s\n" "yes" >&6; } + ts_success=yes + else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } + CXXFLAGS="$save_CXXFLAGS" + ts_success=no + fi + + + if test "" == "" + then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if compiler flag -O3 works" >&5 +printf %s "checking if compiler flag -O3 works... " >&6; } + else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking " >&5 +printf %s "checking ... " >&6; } + fi + save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS -O3" + + + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +int main(){} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO" +then : + cvd_conf_test=1 +else case e in #( + e) cvd_conf_test=0 ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + + + + + + if test $cvd_conf_test = 1 + then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf "%s\n" "yes" >&6; } + ts_success=yes + else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } + CXXFLAGS="$save_CXXFLAGS" + ts_success=no + fi + + + if test "" == "" + then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if compiler flag -ggdb works" >&5 +printf %s "checking if compiler flag -ggdb works... " >&6; } + else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking " >&5 +printf %s "checking ... " >&6; } + fi + save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS -ggdb" + + + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +int main(){} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO" +then : + cvd_conf_test=1 +else case e in #( + e) cvd_conf_test=0 ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + + + + + + if test $cvd_conf_test = 1 + then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf "%s\n" "yes" >&6; } + ts_success=yes + else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } + CXXFLAGS="$save_CXXFLAGS" + ts_success=no + fi + +fi + + ax_cxx_compile_cxx14_required=true + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + ac_success=no + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CXX supports C++14 features by default" >&5 +printf %s "checking whether $CXX supports C++14 features by default... " >&6; } +if test ${ax_cv_cxx_compile_cxx14+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + template + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + struct Base { + virtual void f() {} + }; + struct Child : public Base { + virtual void f() override {} + }; + + typedef check> right_angle_brackets; + + int a; + decltype(a) b; + + typedef check check_type; + check_type c; + check_type&& cr = static_cast(c); + + auto d = a; + auto l = [](){}; + // Prevent Clang error: unused variable 'l' [-Werror,-Wunused-variable] + struct use_l { use_l() { l(); } }; + + // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae + // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function because of this + namespace test_template_alias_sfinae { + struct foo {}; + + template + using member = typename T::member_type; + + template + void func(...) {} + + template + void func(member*) {} + + void test(); + + void test() { + func(0); + } + } + + // Check for C++14 attribute support + void noret [[noreturn]] () { throw 0; } + +_ACEOF +if ac_fn_cxx_try_compile "$LINENO" +then : + ax_cv_cxx_compile_cxx14=yes +else case e in #( + e) ax_cv_cxx_compile_cxx14=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_cxx_compile_cxx14" >&5 +printf "%s\n" "$ax_cv_cxx_compile_cxx14" >&6; } + if test x$ax_cv_cxx_compile_cxx14 = xyes; then + ac_success=yes + fi + + if test x$ac_success = xno; then + for switch in -std=gnu++14 -std=gnu++1y; do + cachevar=`printf "%s\n" "ax_cv_cxx_compile_cxx14_$switch" | sed "$as_sed_sh"` + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CXX supports C++14 features with $switch" >&5 +printf %s "checking whether $CXX supports C++14 features with $switch... " >&6; } +if eval test \${$cachevar+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS $switch" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + template + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + struct Base { + virtual void f() {} + }; + struct Child : public Base { + virtual void f() override {} + }; + + typedef check> right_angle_brackets; + + int a; + decltype(a) b; + + typedef check check_type; + check_type c; + check_type&& cr = static_cast(c); + + auto d = a; + auto l = [](){}; + // Prevent Clang error: unused variable 'l' [-Werror,-Wunused-variable] + struct use_l { use_l() { l(); } }; + + // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae + // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function because of this + namespace test_template_alias_sfinae { + struct foo {}; + + template + using member = typename T::member_type; + + template + void func(...) {} + + template + void func(member*) {} + + void test(); + + void test() { + func(0); + } + } + + // Check for C++14 attribute support + void noret [[noreturn]] () { throw 0; } + +_ACEOF +if ac_fn_cxx_try_compile "$LINENO" +then : + eval $cachevar=yes +else case e in #( + e) eval $cachevar=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + CXXFLAGS="$ac_save_CXXFLAGS" ;; +esac +fi +eval ac_res=\$$cachevar + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +printf "%s\n" "$ac_res" >&6; } + if eval test x\$$cachevar = xyes; then + CXXFLAGS="$CXXFLAGS $switch" + ac_success=yes + break + fi + done + fi + + if test x$ac_success = xno; then + for switch in -std=c++14 -std=c++1y +std=c++14 "-h std=c++14"; do + cachevar=`printf "%s\n" "ax_cv_cxx_compile_cxx14_$switch" | sed "$as_sed_sh"` + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether $CXX supports C++14 features with $switch" >&5 +printf %s "checking whether $CXX supports C++14 features with $switch... " >&6; } +if eval test \${$cachevar+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS $switch" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + template + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + struct Base { + virtual void f() {} + }; + struct Child : public Base { + virtual void f() override {} + }; + + typedef check> right_angle_brackets; + + int a; + decltype(a) b; + + typedef check check_type; + check_type c; + check_type&& cr = static_cast(c); + + auto d = a; + auto l = [](){}; + // Prevent Clang error: unused variable 'l' [-Werror,-Wunused-variable] + struct use_l { use_l() { l(); } }; + + // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae + // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function because of this + namespace test_template_alias_sfinae { + struct foo {}; + + template + using member = typename T::member_type; + + template + void func(...) {} + + template + void func(member*) {} + + void test(); + + void test() { + func(0); + } + } + + // Check for C++14 attribute support + void noret [[noreturn]] () { throw 0; } + +_ACEOF +if ac_fn_cxx_try_compile "$LINENO" +then : + eval $cachevar=yes +else case e in #( + e) eval $cachevar=no ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + CXXFLAGS="$ac_save_CXXFLAGS" ;; +esac +fi +eval ac_res=\$$cachevar + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +printf "%s\n" "$ac_res" >&6; } + if eval test x\$$cachevar = xyes; then + CXXFLAGS="$CXXFLAGS $switch" + ac_success=yes + break + fi + done + fi + ac_ext=cpp +ac_cpp='$CXXCPP $CPPFLAGS' +ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_cxx_compiler_gnu + + if test x$ax_cxx_compile_cxx14_required = xtrue; then + if test x$ac_success = xno; then + as_fn_error $? "*** A compiler with support for C++14 language features is required." "$LINENO" 5 + fi + else + if test x$ac_success = xno; then + HAVE_CXX14=0 + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: No compiler with C++14 support was found" >&5 +printf "%s\n" "$as_me: No compiler with C++14 support was found" >&6;} + else + HAVE_CXX14=1 + +printf "%s\n" "#define HAVE_CXX14 1" >>confdefs.h + + fi + + + fi + + + +# +# Test for Package Config +# +# Extract the first word of "pkg-config", so it can be a program name with args. +set dummy pkg-config; ac_word=$2 +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_path_PKG_CONFIG+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) case $PKG_CONFIG in + [\\/]* | ?:[\\/]*) + ac_cv_path_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_path_PKG_CONFIG="$as_dir$ac_word$ac_exec_ext" + printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac ;; +esac +fi +PKG_CONFIG=$ac_cv_path_PKG_CONFIG +if test -n "$PKG_CONFIG"; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5 +printf "%s\n" "$PKG_CONFIG" >&6; } +else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } +fi + + +if test "x$PKG_CONFIG" = "x"; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: Could not find pkg-config, will not create pc file." >&5 +printf "%s\n" "$as_me: WARNING: Could not find pkg-config, will not create pc file." >&2;} +else + # we need sed to find the pkg-config lib directory + # Extract the first word of "sed", so it can be a program name with args. +set dummy sed; ac_word=$2 +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +printf %s "checking for $ac_word... " >&6; } +if test ${ac_cv_prog_SED+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) if test -n "$SED"; then + ac_cv_prog_SED="$SED" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir$ac_word$ac_exec_ext"; then + ac_cv_prog_SED="sed" + printf "%s\n" "$as_me:${as_lineno-$LINENO}: found $as_dir$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + test -z "$ac_cv_prog_SED" && ac_cv_prog_SED="as_fn_error $? "You Must install sed" "$LINENO" 5" +fi ;; +esac +fi +SED=$ac_cv_prog_SED +if test -n "$SED"; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $SED" >&5 +printf "%s\n" "$SED" >&6; } +else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } +fi + + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for pkg-config library dir" >&5 +printf %s "checking for pkg-config library dir... " >&6; } + PKGCONFIG_LIBDIR="`echo $PKG_CONFIG | $SED -e 's~.*/bin/pkg-config$~~'`${libdir}/pkgconfig" + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $PKGCONFIG_LIBDIR" >&5 +printf "%s\n" "$PKGCONFIG_LIBDIR" >&6; } + + ac_config_files="$ac_config_files libblepp.pc" + + + # This will be put into the pc file + VERSION=version-0.5 + +fi + +################################################################################ +# +# Transport support options +# + + +# Check whether --with-bluez-support was given. +if test ${with_bluez_support+y} +then : + withval=$with_bluez_support; with_bluez_support=$withval +else case e in #( + e) with_bluez_support=yes ;; +esac +fi + + + +# Check whether --with-nimble-support was given. +if test ${with_nimble_support+y} +then : + withval=$with_nimble_support; with_nimble_support=$withval +else case e in #( + e) with_nimble_support=no ;; +esac +fi + + + +# Check whether --with-server-support was given. +if test ${with_server_support+y} +then : + withval=$with_server_support; with_server_support=$withval +else case e in #( + e) with_server_support=no ;; +esac +fi + + + + + +# Validate: require at least one transport +if test "x$with_bluez_support" != "xyes" && test "x$with_nimble_support" != "xyes"; then + as_fn_error $? "At least one of --with-bluez-support or --with-nimble-support must be enabled" "$LINENO" 5 +fi + +################################################################################ +# +# BlueZ support +# + +if test "x$with_bluez_support" = "xyes"; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: BlueZ transport: ENABLED" >&5 +printf "%s\n" "$as_me: BlueZ transport: ENABLED" >&6;} + + bluez_ok=1 + ac_header= ac_cache= +for ac_item in $ac_header_cxx_list +do + if test $ac_cache; then + ac_fn_cxx_check_header_compile "$LINENO" $ac_header ac_cv_header_$ac_cache "$ac_includes_default" + if eval test \"x\$ac_cv_header_$ac_cache\" = xyes; then + printf "%s\n" "#define $ac_item 1" >> confdefs.h + fi + ac_header= ac_cache= + elif test $ac_header; then + ac_cache=$ac_item + else + ac_header=$ac_item + fi +done + + + + + + + + +if test $ac_cv_header_stdlib_h = yes && test $ac_cv_header_string_h = yes +then : + +printf "%s\n" "#define STDC_HEADERS 1" >>confdefs.h + +fi +ac_fn_cxx_check_header_compile "$LINENO" "bluetooth/bluetooth.h" "ac_cv_header_bluetooth_bluetooth_h" "$ac_includes_default" +if test "x$ac_cv_header_bluetooth_bluetooth_h" = xyes +then : + +else case e in #( + e) bluez_ok=0 ;; +esac +fi + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for library containing hci_open_dev" >&5 +printf %s "checking for library containing hci_open_dev... " >&6; } +if test ${ac_cv_search_hci_open_dev+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_func_search_save_LIBS=$LIBS +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +namespace conftest { + extern "C" int hci_open_dev (); +} +int +main (void) +{ +return conftest::hci_open_dev (); + ; + return 0; +} +_ACEOF +for ac_lib in '' bluetooth +do + if test -z "$ac_lib"; then + ac_res="none required" + else + ac_res=-l$ac_lib + LIBS="-l$ac_lib $ac_func_search_save_LIBS" + fi + if ac_fn_cxx_try_link "$LINENO" +then : + ac_cv_search_hci_open_dev=$ac_res +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext + if test ${ac_cv_search_hci_open_dev+y} +then : + break +fi +done +if test ${ac_cv_search_hci_open_dev+y} +then : + +else case e in #( + e) ac_cv_search_hci_open_dev=no ;; +esac +fi +rm conftest.$ac_ext +LIBS=$ac_func_search_save_LIBS ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_hci_open_dev" >&5 +printf "%s\n" "$ac_cv_search_hci_open_dev" >&6; } +ac_res=$ac_cv_search_hci_open_dev +if test "$ac_res" != no +then : + test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" + +else case e in #( + e) bluez_ok=0 ;; +esac +fi + + + if test x$bluez_ok == x0; then + as_fn_error $? "BlueZ bluetooth headers/library missing. Install libbluetooth-dev or disable with --without-bluez-support" "$LINENO" 5 + fi + + BLEPP_BLUEZ_SUPPORT=1 + +else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: BlueZ transport: DISABLED" >&5 +printf "%s\n" "$as_me: BlueZ transport: DISABLED" >&6;} +fi + +################################################################################ +# +# Nimble support +# + +if test "x$with_nimble_support" = "xyes"; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: Nimble transport: ENABLED" >&5 +printf "%s\n" "$as_me: Nimble transport: ENABLED" >&6;} + + # If NIMBLE_ROOT is specified, add its include paths to CPPFLAGS for header check + if test "x$NIMBLE_ROOT" != "x"; then + # Check if NIMBLE_ROOT exists + if test ! -d "$NIMBLE_ROOT"; then + as_fn_error $? "NIMBLE_ROOT not found at $NIMBLE_ROOT. Please set NIMBLE_ROOT environment variable or pass NIMBLE_ROOT=/path/to/nimble_v42 to configure" "$LINENO" 5 + fi + + # Convert to absolute path + NIMBLE_ROOT=$(cd "$NIMBLE_ROOT" && pwd) + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: Using Nimble headers from $NIMBLE_ROOT" >&5 +printf "%s\n" "$as_me: Using Nimble headers from $NIMBLE_ROOT" >&6;} + + # Add Nimble include paths for header check + CPPFLAGS="$CPPFLAGS -I$NIMBLE_ROOT/nimble/include -I$NIMBLE_ROOT/nimble/host/include -I$NIMBLE_ROOT/nimble/host/services/gap/include -I$NIMBLE_ROOT/nimble/host/services/gatt/include -I$NIMBLE_ROOT/nimble/host/util/include -I$NIMBLE_ROOT/porting/nimble/include -I$NIMBLE_ROOT/ext/tinycrypt/include" + fi + + # Check for Nimble headers using standard AC_CHECK_HEADER + ac_fn_cxx_check_header_compile "$LINENO" "nimble/ble.h" "ac_cv_header_nimble_ble_h" "$ac_includes_default" +if test "x$ac_cv_header_nimble_ble_h" = xyes +then : + +else case e in #( + e) + as_fn_error $? "Nimble headers not found. Please set NIMBLE_ROOT to the Nimble BLE stack directory, or ensure nimble/ble.h is in your include path." "$LINENO" 5 + ;; +esac +fi + + + # Find Nimble library + # If NIMBLE_LIBDIR is specified, use it directly + if test "x$NIMBLE_LIBDIR" != "x"; then + if test ! -d "$NIMBLE_LIBDIR"; then + as_fn_error $? "NIMBLE_LIBDIR not found at $NIMBLE_LIBDIR" "$LINENO" 5 + fi + # Convert to absolute path + NIMBLE_LIBDIR=$(cd "$NIMBLE_LIBDIR" && pwd) + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: Using Nimble library from $NIMBLE_LIBDIR" >&5 +printf "%s\n" "$as_me: Using Nimble library from $NIMBLE_LIBDIR" >&6;} + elif test "x$NIMBLE_ROOT" != "x"; then + # Try to find library in NIMBLE_ROOT/lib or NIMBLE_ROOT/build + nimble_lib_found=no + for nimble_libdir in "$NIMBLE_ROOT/lib" "$NIMBLE_ROOT/build"; do + if test -f "$nimble_libdir/libnimble.so" || test -f "$nimble_libdir/libnimble.a"; then + NIMBLE_LIBDIR="$nimble_libdir" + nimble_lib_found=yes + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: Found Nimble library in $NIMBLE_LIBDIR" >&5 +printf "%s\n" "$as_me: Found Nimble library in $NIMBLE_LIBDIR" >&6;} + break + fi + done + + if test "x$nimble_lib_found" != "xyes"; then + as_fn_error $? "Nimble library not found. Please build Nimble first or set NIMBLE_LIBDIR. +Expected location: $NIMBLE_ROOT/lib/libnimble.so or $NIMBLE_ROOT/build/libnimble.so" "$LINENO" 5 + fi + fi + + # If NIMBLE_LIBDIR is set, add to LDFLAGS and check for library + if test "x$NIMBLE_LIBDIR" != "x"; then + LDFLAGS="$LDFLAGS -L$NIMBLE_LIBDIR -Wl,-rpath,$NIMBLE_LIBDIR" + fi + + # Check for nimble library + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for library containing ble_gap_disc" >&5 +printf %s "checking for library containing ble_gap_disc... " >&6; } +if test ${ac_cv_search_ble_gap_disc+y} +then : + printf %s "(cached) " >&6 +else case e in #( + e) ac_func_search_save_LIBS=$LIBS +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +namespace conftest { + extern "C" int ble_gap_disc (); +} +int +main (void) +{ +return conftest::ble_gap_disc (); + ; + return 0; +} +_ACEOF +for ac_lib in '' nimble +do + if test -z "$ac_lib"; then + ac_res="none required" + else + ac_res=-l$ac_lib + LIBS="-l$ac_lib $ac_func_search_save_LIBS" + fi + if ac_fn_cxx_try_link "$LINENO" +then : + ac_cv_search_ble_gap_disc=$ac_res +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam \ + conftest$ac_exeext + if test ${ac_cv_search_ble_gap_disc+y} +then : + break +fi +done +if test ${ac_cv_search_ble_gap_disc+y} +then : + +else case e in #( + e) ac_cv_search_ble_gap_disc=no ;; +esac +fi +rm conftest.$ac_ext +LIBS=$ac_func_search_save_LIBS ;; +esac +fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_ble_gap_disc" >&5 +printf "%s\n" "$ac_cv_search_ble_gap_disc" >&6; } +ac_res=$ac_cv_search_ble_gap_disc +if test "$ac_res" != no +then : + test "$ac_res" = "none required" || LIBS="$ac_res $LIBS" + +else case e in #( + e) + as_fn_error $? "Nimble library not found. Please set NIMBLE_LIBDIR or ensure libnimble is in your library path." "$LINENO" 5 + ;; +esac +fi + + + BLEPP_NIMBLE_SUPPORT=1 + + + +else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: Nimble transport: DISABLED" >&5 +printf "%s\n" "$as_me: Nimble transport: DISABLED" >&6;} +fi + +################################################################################ +# +# Server support +# + +if test "x$with_server_support" = "xyes"; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: Server support: ENABLED" >&5 +printf "%s\n" "$as_me: Server support: ENABLED" >&6;} + BLEPP_SERVER_SUPPORT=1 + +else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: Server support: DISABLED" >&5 +printf "%s\n" "$as_me: Server support: DISABLED" >&6;} +fi + + + if test "" == "" + then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking if compiler flag -fPIC works" >&5 +printf %s "checking if compiler flag -fPIC works... " >&6; } + else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking " >&5 +printf %s "checking ... " >&6; } + fi + save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS -fPIC" + + + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +int main(){} +_ACEOF +if ac_fn_cxx_try_compile "$LINENO" +then : + cvd_conf_test=1 +else case e in #( + e) cvd_conf_test=0 ;; +esac +fi +rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + + + + + + if test $cvd_conf_test = 1 + then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf "%s\n" "yes" >&6; } + ts_success=yes + else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } + CXXFLAGS="$save_CXXFLAGS" + ts_success=no + fi + + +ac_config_files="$ac_config_files Makefile" + +cat >confcache <<\_ACEOF +# This file is a shell script that caches the results of configure +# tests run on this system so they can be shared between configure +# scripts and configure runs, see configure's option --config-cache. +# It is not useful on other systems. If it contains results you don't +# want to keep, you may remove or edit it. +# +# config.status only pays attention to the cache file if you give it +# the --recheck option to rerun configure. +# +# 'ac_cv_env_foo' variables (set or unset) will be overridden when +# loading this file, other *unset* 'ac_cv_foo' will be assigned the +# following values. + +_ACEOF + +# The following way of writing the cache mishandles newlines in values, +# but we know of no workaround that is simple, portable, and efficient. +# So, we kill variables containing newlines. +# Ultrix sh set writes to stderr and can't be redirected directly, +# and sets the high bit in the cache file unless we assign to the vars. +( + for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +printf "%s\n" "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + + (set) 2>&1 | + case $as_nl`(ac_space=' '; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + # 'set' does not quote correctly, so add quotes: double-quote + # substitution turns \\\\ into \\, and sed turns \\ into \. + sed -n \ + "s/'/'\\\\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" + ;; #( + *) + # 'set' quotes correctly as required by POSIX, so do not add quotes. + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) | + sed ' + /^ac_cv_env_/b end + t clear + :clear + s/^\([^=]*\)=\(.*[{}].*\)$/test ${\1+y} || &/ + t end + s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ + :end' >>confcache +if diff "$cache_file" confcache >/dev/null 2>&1; then :; else + if test -w "$cache_file"; then + if test "x$cache_file" != "x/dev/null"; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 +printf "%s\n" "$as_me: updating cache $cache_file" >&6;} + if test ! -f "$cache_file" || test -h "$cache_file"; then + cat confcache >"$cache_file" + else + case $cache_file in #( + */* | ?:*) + mv -f confcache "$cache_file"$$ && + mv -f "$cache_file"$$ "$cache_file" ;; #( + *) + mv -f confcache "$cache_file" ;; + esac + fi + fi + else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 +printf "%s\n" "$as_me: not updating unwritable cache $cache_file" >&6;} + fi +fi +rm -f confcache + +test "x$prefix" = xNONE && prefix=$ac_default_prefix +# Let make expand exec_prefix. +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' + +# Transform confdefs.h into DEFS. +# Protect against shell expansion while executing Makefile rules. +# Protect against Makefile macro expansion. +# +# If the first sed substitution is executed (which looks for macros that +# take arguments), then branch to the quote section. Otherwise, +# look for a macro that doesn't take arguments. +ac_script=' +:mline +/\\$/{ + N + s,\\\n,, + b mline +} +t clear +:clear +s/^[ ]*#[ ]*define[ ][ ]*\([^ (][^ (]*([^)]*)\)[ ]*\(.*\)/-D\1=\2/g +t quote +s/^[ ]*#[ ]*define[ ][ ]*\([^ ][^ ]*\)[ ]*\(.*\)/-D\1=\2/g +t quote +b any +:quote +s/[][ `~#$^&*(){}\\|;'\''"<>?]/\\&/g +s/\$/$$/g +H +:any +${ + g + s/^\n// + s/\n/ /g + p +} +' +DEFS=`sed -n "$ac_script" confdefs.h` + + +ac_libobjs= +ac_ltlibobjs= +U= +for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue + # 1. Remove the extension, and $U if already installed. + ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' + ac_i=`printf "%s\n" "$ac_i" | sed "$ac_script"` + # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR + # will be set to the directory where LIBOBJS objects are built. + as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" + as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' +done +LIBOBJS=$ac_libobjs + +LTLIBOBJS=$ac_ltlibobjs + + + +: "${CONFIG_STATUS=./config.status}" +ac_write_fail=0 +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files $CONFIG_STATUS" +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 +printf "%s\n" "$as_me: creating $CONFIG_STATUS" >&6;} +as_write_fail=0 +cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 +#! $SHELL +# Generated by $as_me. +# Run this file to recreate the current configuration. +# Compiler output produced by configure, useful for debugging +# configure, is in config.log if it exists. + +debug=false +ac_cs_recheck=false +ac_cs_silent=false + +SHELL=\${CONFIG_SHELL-$SHELL} +export SHELL +_ASEOF +cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test ${ZSH_VERSION+y} && (emulate sh) >/dev/null 2>&1 +then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else case e in #( + e) case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac ;; +esac +fi + + + +# Reset variables that may have inherited troublesome values from +# the environment. + +# IFS needs to be set, to space, tab, and newline, in precisely that order. +# (If _AS_PATH_WALK were called with IFS unset, it would have the +# side effect of setting IFS to empty, thus disabling word splitting.) +# Quoting is to prevent editors from complaining about space-tab. +as_nl=' +' +export as_nl +IFS=" "" $as_nl" + +PS1='$ ' +PS2='> ' +PS4='+ ' + +# Ensure predictable behavior from utilities with locale-dependent output. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# We cannot yet rely on "unset" to work, but we need these variables +# to be unset--not just set to an empty or harmless value--now, to +# avoid bugs in old shells (e.g. pre-3.0 UWIN ksh). This construct +# also avoids known problems related to "unset" and subshell syntax +# in other old shells (e.g. bash 2.01 and pdksh 5.2.14). +for as_var in BASH_ENV ENV MAIL MAILPATH CDPATH +do eval test \${$as_var+y} \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done + +# Ensure that fds 0, 1, and 2 are open. +if (exec 3>&0) 2>/dev/null; then :; else exec 0&1) 2>/dev/null; then :; else exec 1>/dev/null; fi +if (exec 3>&2) ; then :; else exec 2>/dev/null; fi + +# The user is always right. +if ${PATH_SEPARATOR+false} :; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + case $as_dir in #((( + '') as_dir=./ ;; + */) ;; + *) as_dir=$as_dir/ ;; + esac + test -r "$as_dir$0" && as_myself=$as_dir$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as 'sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + printf "%s\n" "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + printf "%s\n" "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + printf "%s\n" "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset + +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null +then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else case e in #( + e) as_fn_append () + { + eval $1=\$$1\$2 + } ;; +esac +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null +then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else case e in #( + e) as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } ;; +esac +fi # as_fn_arith + + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +printf "%s\n" X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + + +# Determine whether it's possible to make 'echo' print without a newline. +# These variables are no longer used directly by Autoconf, but are AC_SUBSTed +# for compatibility with existing Makefiles. +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +# For backward compatibility with old third-party macros, we provide +# the shell variables $as_echo and $as_echo_n. New code should use +# AS_ECHO(["message"]) and AS_ECHO_N(["message"]), respectively. +as_echo='printf %s\n' +as_echo_n='printf %s' + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both 'ln -s file dir' and 'ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; 'ln -s' creates a wrapper executable. + # In both cases, we have to default to 'cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -pR' + fi +else + as_ln_s='cp -pR' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`printf "%s\n" "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +printf "%s\n" X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +as_test_x='test -x' +as_executable_p=as_fn_executable_p + +# Sed expression to map a string onto a valid CPP name. +as_sed_cpp="y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g" +as_tr_cpp="eval sed '$as_sed_cpp'" # deprecated + +# Sed expression to map a string onto a valid variable name. +as_sed_sh="y%*+%pp%;s%[^_$as_cr_alnum]%_%g" +as_tr_sh="eval sed '$as_sed_sh'" # deprecated + + +exec 6>&1 +## ----------------------------------- ## +## Main body of $CONFIG_STATUS script. ## +## ----------------------------------- ## +_ASEOF +test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# Save the log message, to keep $0 and so on meaningful, and to +# report actual input values of CONFIG_FILES etc. instead of their +# values after options handling. +ac_log=" +This file was extended by libble++ $as_me version-0.5, which was +generated by GNU Autoconf 2.72. Invocation command line was + + CONFIG_FILES = $CONFIG_FILES + CONFIG_HEADERS = $CONFIG_HEADERS + CONFIG_LINKS = $CONFIG_LINKS + CONFIG_COMMANDS = $CONFIG_COMMANDS + $ $0 $@ + +on `(hostname || uname -n) 2>/dev/null | sed 1q` +" + +_ACEOF + +case $ac_config_files in *" +"*) set x $ac_config_files; shift; ac_config_files=$*;; +esac + + + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +# Files that config.status was made for. +config_files="$ac_config_files" + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +ac_cs_usage="\ +'$as_me' instantiates files and other configuration actions +from templates according to the current configuration. Unless the files +and actions are specified as TAGs, all are instantiated by default. + +Usage: $0 [OPTION]... [TAG]... + + -h, --help print this help, then exit + -V, --version print version number and configuration settings, then exit + --config print configuration, then exit + -q, --quiet, --silent + do not print progress messages + -d, --debug don't remove temporary files + --recheck update $as_me by reconfiguring in the same conditions + --file=FILE[:TEMPLATE] + instantiate the configuration file FILE + +Configuration files: +$config_files + +Report bugs to the package provider." + +_ACEOF +ac_cs_config=`printf "%s\n" "$ac_configure_args" | sed "$ac_safe_unquote"` +ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\''/g"` +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_cs_config='$ac_cs_config_escaped' +ac_cs_version="\\ +libble++ config.status version-0.5 +configured by $0, generated by GNU Autoconf 2.72, + with options \\"\$ac_cs_config\\" + +Copyright (C) 2023 Free Software Foundation, Inc. +This config.status script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it." + +ac_pwd='$ac_pwd' +srcdir='$srcdir' +test -n "\$AWK" || AWK=awk +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# The default lists apply if the user does not specify any file. +ac_need_defaults=: +while test $# != 0 +do + case $1 in + --*=?*) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` + ac_shift=: + ;; + --*=) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg= + ac_shift=: + ;; + *) + ac_option=$1 + ac_optarg=$2 + ac_shift=shift + ;; + esac + + case $ac_option in + # Handling of the options. + -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) + ac_cs_recheck=: ;; + --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) + printf "%s\n" "$ac_cs_version"; exit ;; + --config | --confi | --conf | --con | --co | --c ) + printf "%s\n" "$ac_cs_config"; exit ;; + --debug | --debu | --deb | --de | --d | -d ) + debug=: ;; + --file | --fil | --fi | --f ) + $ac_shift + case $ac_optarg in + *\'*) ac_optarg=`printf "%s\n" "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + '') as_fn_error $? "missing file argument" ;; + esac + as_fn_append CONFIG_FILES " '$ac_optarg'" + ac_need_defaults=false;; + --he | --h | --help | --hel | -h ) + printf "%s\n" "$ac_cs_usage"; exit ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil | --si | --s) + ac_cs_silent=: ;; + + # This is an error. + -*) as_fn_error $? "unrecognized option: '$1' +Try '$0 --help' for more information." ;; + + *) as_fn_append ac_config_targets " $1" + ac_need_defaults=false ;; + + esac + shift +done + +ac_configure_extra_args= + +if $ac_cs_silent; then + exec 6>/dev/null + ac_configure_extra_args="$ac_configure_extra_args --silent" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +if \$ac_cs_recheck; then + set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion + shift + \printf "%s\n" "running CONFIG_SHELL=$SHELL \$*" >&6 + CONFIG_SHELL='$SHELL' + export CONFIG_SHELL + exec "\$@" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +exec 5>>config.log +{ + echo + sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX +## Running $as_me. ## +_ASBOX + printf "%s\n" "$ac_log" +} >&5 + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 + +# Handling of arguments. +for ac_config_target in $ac_config_targets +do + case $ac_config_target in + "libblepp.pc") CONFIG_FILES="$CONFIG_FILES libblepp.pc" ;; + "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; + + *) as_fn_error $? "invalid argument: '$ac_config_target'" "$LINENO" 5;; + esac +done + + +# If the user did not use the arguments to specify the items to instantiate, +# then the envvar interface is used. Set only those that are not. +# We use the long form for the default assignment because of an extremely +# bizarre bug on SunOS 4.1.3. +if $ac_need_defaults; then + test ${CONFIG_FILES+y} || CONFIG_FILES=$config_files +fi + +# Have a temporary directory for convenience. Make it in the build tree +# simply because there is no reason against having it here, and in addition, +# creating and moving files from /tmp can sometimes cause problems. +# Hook for its removal unless debugging. +# Note that there is a small window in which the directory will not be cleaned: +# after its creation but before its name has been assigned to '$tmp'. +$debug || +{ + tmp= ac_tmp= + trap 'exit_status=$? + : "${ac_tmp:=$tmp}" + { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status +' 0 + trap 'as_fn_exit 1' 1 2 13 15 +} +# Create a (secure) tmp directory for tmp files. + +{ + tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && + test -d "$tmp" +} || +{ + tmp=./conf$$-$RANDOM + (umask 077 && mkdir "$tmp") +} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 +ac_tmp=$tmp + +# Set up the scripts for CONFIG_FILES section. +# No need to generate them if there are no CONFIG_FILES. +# This happens for instance with './config.status config.h'. +if test -n "$CONFIG_FILES"; then + + +ac_cr=`echo X | tr X '\015'` +# On cygwin, bash can eat \r inside `` if the user requested igncr. +# But we know of no other shell where ac_cr would be empty at this +# point, so we can use a bashism as a fallback. +if test "x$ac_cr" = x; then + eval ac_cr=\$\'\\r\' +fi +ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' /dev/null` +if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then + ac_cs_awk_cr='\\r' +else + ac_cs_awk_cr=$ac_cr +fi + +echo 'BEGIN {' >"$ac_tmp/subs1.awk" && +_ACEOF + + +{ + echo "cat >conf$$subs.awk <<_ACEOF" && + echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && + echo "_ACEOF" +} >conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 +ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` +ac_delim='%!_!# ' +for ac_last_try in false false false false false :; do + . ./conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + + ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` + if test $ac_delim_n = $ac_delim_num; then + break + elif $ac_last_try; then + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + else + ac_delim="$ac_delim!$ac_delim _$ac_delim!! " + fi +done +rm -f conf$$subs.sh + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && +_ACEOF +sed -n ' +h +s/^/S["/; s/!.*/"]=/ +p +g +s/^[^!]*!// +:repl +t repl +s/'"$ac_delim"'$// +t delim +:nl +h +s/\(.\{148\}\)..*/\1/ +t more1 +s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ +p +n +b repl +:more1 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t nl +:delim +h +s/\(.\{148\}\)..*/\1/ +t more2 +s/["\\]/\\&/g; s/^/"/; s/$/"/ +p +b +:more2 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t delim +' >$CONFIG_STATUS || ac_write_fail=1 +rm -f conf$$subs.awk +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +_ACAWK +cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && + for (key in S) S_is_set[key] = 1 + FS = "" + +} +{ + line = $ 0 + nfields = split(line, field, "@") + substed = 0 + len = length(field[1]) + for (i = 2; i < nfields; i++) { + key = field[i] + keylen = length(key) + if (S_is_set[key]) { + value = S[key] + line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) + len += length(value) + length(field[++i]) + substed = 1 + } else + len += 1 + keylen + } + + print line +} + +_ACAWK +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then + sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" +else + cat +fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ + || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 +_ACEOF + +# VPATH may cause trouble with some makes, so we remove sole $(srcdir), +# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and +# trailing colons and then remove the whole line if VPATH becomes empty +# (actually we leave an empty line to preserve line numbers). +if test "x$srcdir" = x.; then + ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ +h +s/// +s/^/:/ +s/[ ]*$/:/ +s/:\$(srcdir):/:/g +s/:\${srcdir}:/:/g +s/:@srcdir@:/:/g +s/^:*// +s/:*$// +x +s/\(=[ ]*\).*/\1/ +G +s/\n// +s/^[^=]*=[ ]*$// +}' +fi + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +fi # test -n "$CONFIG_FILES" + + +eval set X " :F $CONFIG_FILES " +shift +for ac_tag +do + case $ac_tag in + :[FHLC]) ac_mode=$ac_tag; continue;; + esac + case $ac_mode$ac_tag in + :[FHL]*:*);; + :L* | :C*:*) as_fn_error $? "invalid tag '$ac_tag'" "$LINENO" 5;; + :[FH]-) ac_tag=-:-;; + :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; + esac + ac_save_IFS=$IFS + IFS=: + set x $ac_tag + IFS=$ac_save_IFS + shift + ac_file=$1 + shift + + case $ac_mode in + :L) ac_source=$1;; + :[FH]) + ac_file_inputs= + for ac_f + do + case $ac_f in + -) ac_f="$ac_tmp/stdin";; + *) # Look for the file first in the build tree, then in the source tree + # (if the path is not absolute). The absolute path cannot be DOS-style, + # because $ac_f cannot contain ':'. + test -f "$ac_f" || + case $ac_f in + [\\/$]*) false;; + *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; + esac || + as_fn_error 1 "cannot find input file: '$ac_f'" "$LINENO" 5;; + esac + case $ac_f in *\'*) ac_f=`printf "%s\n" "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac + as_fn_append ac_file_inputs " '$ac_f'" + done + + # Let's still pretend it is 'configure' which instantiates (i.e., don't + # use $as_me), people would be surprised to read: + # /* config.h. Generated by config.status. */ + configure_input='Generated from '` + printf "%s\n" "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' + `' by configure.' + if test x"$ac_file" != x-; then + configure_input="$ac_file. $configure_input" + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 +printf "%s\n" "$as_me: creating $ac_file" >&6;} + fi + # Neutralize special characters interpreted by sed in replacement strings. + case $configure_input in #( + *\&* | *\|* | *\\* ) + ac_sed_conf_input=`printf "%s\n" "$configure_input" | + sed 's/[\\\\&|]/\\\\&/g'`;; #( + *) ac_sed_conf_input=$configure_input;; + esac + + case $ac_tag in + *:-:* | *:-) cat >"$ac_tmp/stdin" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; + esac + ;; + esac + + ac_dir=`$as_dirname -- "$ac_file" || +$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$ac_file" : 'X\(//\)[^/]' \| \ + X"$ac_file" : 'X\(//\)$' \| \ + X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || +printf "%s\n" X"$ac_file" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + as_dir="$ac_dir"; as_fn_mkdir_p + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`printf "%s\n" "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`printf "%s\n" "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + + case $ac_mode in + :F) + # + # CONFIG_FILE + # + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# If the template does not know about datarootdir, expand it. +# FIXME: This hack should be removed a few years after 2.60. +ac_datarootdir_hack=; ac_datarootdir_seen= +ac_sed_dataroot=' +/datarootdir/ { + p + q +} +/@datadir@/p +/@docdir@/p +/@infodir@/p +/@localedir@/p +/@mandir@/p' +case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in +*datarootdir*) ac_datarootdir_seen=yes;; +*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 +printf "%s\n" "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 + ac_datarootdir_hack=' + s&@datadir@&$datadir&g + s&@docdir@&$docdir&g + s&@infodir@&$infodir&g + s&@localedir@&$localedir&g + s&@mandir@&$mandir&g + s&\\\${datarootdir}&$datarootdir&g' ;; +esac +_ACEOF + +# Neutralize VPATH when '$srcdir' = '.'. +# Shell code in configure.ac might set extrasub. +# FIXME: do we really want to maintain this feature? +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_sed_extra="$ac_vpsub +$extrasub +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +:t +/@[a-zA-Z_][a-zA-Z_0-9]*@/!b +s|@configure_input@|$ac_sed_conf_input|;t t +s&@top_builddir@&$ac_top_builddir_sub&;t t +s&@top_build_prefix@&$ac_top_build_prefix&;t t +s&@srcdir@&$ac_srcdir&;t t +s&@abs_srcdir@&$ac_abs_srcdir&;t t +s&@top_srcdir@&$ac_top_srcdir&;t t +s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t +s&@builddir@&$ac_builddir&;t t +s&@abs_builddir@&$ac_abs_builddir&;t t +s&@abs_top_builddir@&$ac_abs_top_builddir&;t t +$ac_datarootdir_hack +" +eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ + >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + +test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && + { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && + { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ + "$ac_tmp/out"`; test -z "$ac_out"; } && + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable 'datarootdir' +which seems to be undefined. Please make sure it is defined" >&5 +printf "%s\n" "$as_me: WARNING: $ac_file contains a reference to the variable 'datarootdir' +which seems to be undefined. Please make sure it is defined" >&2;} + + rm -f "$ac_tmp/stdin" + case $ac_file in + -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; + *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; + esac \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + ;; + + + + esac + +done # for ac_tag + + +as_fn_exit 0 +_ACEOF +ac_clean_files=$ac_clean_files_save + +test $ac_write_fail = 0 || + as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 + + +# configure is writing to config.log, and then calls config.status. +# config.status does its own redirection, appending to config.log. +# Unfortunately, on DOS this fails, as config.log is still kept open +# by configure, so config.status won't be able to write to it; its +# output is simply discarded. So we exec the FD to /dev/null, +# effectively closing config.log, so it can be properly (re)opened and +# appended to by config.status. When coming back to configure, we +# need to make the FD available again. +if test "$no_create" != yes; then + ac_cs_success=: + ac_config_status_args= + test "$silent" = yes && + ac_config_status_args="$ac_config_status_args --quiet" + exec 5>/dev/null + $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false + exec 5>>config.log + # Use ||, not &&, to avoid exiting from the if with $? = 1, which + # would make configure fail if this is the last instruction. + $ac_cs_success || as_fn_exit 1 +fi +if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 +printf "%s\n" "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} +fi + + diff --git a/docs/ATBM_IOCTL_API.md b/docs/ATBM_IOCTL_API.md new file mode 100644 index 0000000..3484bcc --- /dev/null +++ b/docs/ATBM_IOCTL_API.md @@ -0,0 +1,378 @@ +# ATBM ioctl API Reference + +## Overview + +This document describes the ATBM BLE ioctl API extracted from the `ble_host` project. The ATBM hardware uses a custom `/dev/atbm_ioctl` device for BLE communication instead of standard BlueZ HCI. + +--- + +## Device Node + +**Path:** `/dev/atbm_ioctl` + +**Major Number:** 121 (defined as `ATBM_IOCTL`) + +--- + +## ioctl Commands + +All ioctl commands use the base number `ATBM_IOCTL (121)`. + +### 1. ATBM_AT_CMD_DIRECT + +```c +#define ATBM_AT_CMD_DIRECT _IOW(ATBM_IOCTL, 0, unsigned int) +``` + +**Purpose:** Send AT commands directly to the device. + +**Parameter Structure:** +```c +struct at_cmd_direct { + uint32_t len; + uint8_t cmd[1500]; +}; +``` + +**Usage:** Used for WiFi AT commands in the WiFi+BLE combined mode. + +--- + +### 2. ATBM_BLE_SMART + +```c +#define ATBM_BLE_SMART _IOW(ATBM_IOCTL, 1, unsigned int) +``` + +**Purpose:** Control BLE smart configuration mode. + +**Parameter:** Same as `at_cmd_direct` structure. + +**Known Commands:** +- `"ble_smt_start"` - Start BLE smart configuration + +--- + +### 3. ATBM_BLE_COEXIST_START + +```c +#define ATBM_BLE_COEXIST_START _IOW(ATBM_IOCTL, 0, unsigned int) +``` + +**Purpose:** Start BLE coexistence mode (initialize BLE subsystem). + +**Parameter:** Pointer to ioctl data buffer (may be empty). + +**When to call:** Before any BLE operations, during initialization. + +--- + +### 4. ATBM_BLE_COEXIST_STOP + +```c +#define ATBM_BLE_COEXIST_STOP _IOW(ATBM_IOCTL, 1, unsigned int) +``` + +**Purpose:** Stop BLE coexistence mode (shutdown BLE subsystem). + +**Parameter:** Pointer to ioctl data buffer (may be empty). + +**When to call:** During cleanup/shutdown. + +--- + +### 5. ATBM_BLE_SET_ADV_DATA + +```c +#define ATBM_BLE_SET_ADV_DATA _IOW(ATBM_IOCTL, 2, unsigned int) +``` + +**Purpose:** Set BLE advertising data. + +**Parameter:** Pointer to advertising data buffer (format TBD from NimBLE code). + +--- + +### 6. ATBM_BLE_ADV_RESP_MODE_START + +```c +#define ATBM_BLE_ADV_RESP_MODE_START _IOW(ATBM_IOCTL, 3, unsigned int) +``` + +**Purpose:** Start advertising response mode. + +**Parameter:** Configuration buffer. + +--- + +### 7. ATBM_BLE_SET_RESP_DATA + +```c +#define ATBM_BLE_SET_RESP_DATA _IOW(ATBM_IOCTL, 4, unsigned int) +``` + +**Purpose:** Set scan response data. + +**Parameter:** Pointer to scan response data buffer. + +--- + +### 8. ATBM_BLE_HIF_TXDATA + +```c +#define ATBM_BLE_HIF_TXDATA _IOW(ATBM_IOCTL, 5, unsigned int) +``` + +**Purpose:** Transmit HCI data to the BLE controller. + +**Parameter:** Pointer to HCI packet buffer. + +**Buffer Format:** +```c +struct { + uint16_t len; // Total length of HCI packet + uint8_t type; // BLE_HCI_HIF_CMD (0x01) or BLE_HCI_HIF_ACL (0x02) + uint8_t data[...]; // HCI packet data +}; +``` + +**HCI Packet Types:** +- `BLE_HCI_HIF_CMD (0x01)` - HCI command +- `BLE_HCI_HIF_ACL (0x02)` - ACL data +- `BLE_HCI_HIF_EVT (0x04)` - Event (received only) + +--- + +## Asynchronous Event Handling + +The ATBM device uses **SIGIO signals** for asynchronous event notifications. + +### Setup + +1. Open `/dev/atbm_ioctl` +2. Set file owner: `fcntl(fd, F_SETOWN, getpid())` +3. Enable async mode: `fcntl(fd, F_SETFL, flags | FASYNC)` +4. Register signal handler: `signal(SIGIO, event_handler)` + +### Reading Events + +When SIGIO is received, read events using `read()`: + +```c +struct status_async { + uint8_t type; // Event type (0-6) + uint8_t driver_mode; // Sub-type or reason + uint8_t list_empty; // 1 if no more events, 0 if more pending + uint8_t event_buffer[MAX_SYNC_EVENT_BUFFER_LEN]; // 512 bytes +}; +``` + +### Event Buffer Format + +The `event_buffer` contains a WSM (WiFi-to-Host Message) header followed by HCI data: + +```c +struct wsm_hdr { + uint16_t len; // Length of data following this header + uint16_t id; // Message type ID +}; + +// Followed by actual HCI packet data +``` + +**Message Type IDs:** +- `BLE_MSG_TYPE_ACK (value TBD)` - Acknowledgment/command complete +- `BLE_MSG_TYPE_EVT (value TBD)` - Asynchronous event + +### Event Processing + +```c +struct wsm_hdr *wsm = (struct wsm_hdr *)status.event_buffer; +uint8_t *hci_data = (uint8_t *)(wsm + 1); + +if (wsm->id == BLE_MSG_TYPE_ACK) { + // Process acknowledgment + ble_hci_trans_hs_rx(1, hci_data, wsm->len); +} else if (wsm->id == BLE_MSG_TYPE_EVT) { + // Process event + ble_hci_trans_hs_rx(0, hci_data, wsm->len); +} +``` + +--- + +## HCI Packet Format + +### Transmit (to controller) + +**Command Packet:** +``` +[length:2][type:1][opcode:2][param_len:1][params...] +``` + +**ACL Data Packet:** +``` +[length:2][type:1][handle:2][data_len:2][data...] +``` + +### Receive (from controller) + +**Event Packet:** +``` +[type:1][event_code:1][param_len:1][params...] +``` + +**ACL Data Packet:** +``` +[type:1][handle:2][data_len:2][data...] +``` + +--- + +## Initialization Sequence + +1. Open `/dev/atbm_ioctl` +2. Configure async I/O (fcntl + signal) +3. Call `ATBM_BLE_COEXIST_START` ioctl +4. Initialize NimBLE stack +5. Register HCI transport callbacks +6. Start event processing thread + +--- + +## Event Loop Pattern + +The ATBM implementation uses a dedicated thread to read events: + +```c +void *event_thread(void *arg) { + while (!quit) { + sem_wait(&event_sem); // Wait for SIGIO + + do { + struct status_async event; + ssize_t len = read(atbm_fd, &event, sizeof(event)); + + if (len == sizeof(event)) { + process_event(&event); + } + } while (!event.list_empty); // Keep reading if more events pending + } +} +``` + +--- + +## TX Data Path + +### Sending HCI Commands/Data + +```c +int send_hci_packet(uint8_t type, const uint8_t *data, size_t len) { + uint8_t buffer[2048]; + uint16_t *plen = (uint16_t *)buffer; + + *plen = len + 1; // Total length including type byte + buffer[2] = type; // BLE_HCI_HIF_CMD or BLE_HCI_HIF_ACL + memcpy(&buffer[3], data, len); + + return ioctl(atbm_fd, ATBM_BLE_HIF_TXDATA, (unsigned long)buffer); +} +``` + +### Thread Safety + +The ioctl operations should be protected with a semaphore to prevent concurrent access: + +```c +sem_wait(&ioctl_sem); +ioctl(atbm_fd, ATBM_BLE_HIF_TXDATA, buffer); +sem_post(&ioctl_sem); +``` + +--- + +## NimBLE Integration + +The ATBM transport layer integrates with Apache NimBLE v4.2: + +### Key Functions + +**TX from Host Stack:** +- `ble_hci_trans_hs_cmd_tx()` - Send HCI command +- `ble_hci_trans_hs_acl_tx()` - Send ACL data + +**RX to Host Stack:** +- `ble_hci_trans_hs_rx()` - Deliver received HCI data to stack + +**Buffer Management:** +- `ble_hci_trans_buf_alloc()` - Allocate HCI buffer +- `ble_hci_trans_buf_free()` - Free HCI buffer +- `ble_hci_trans_acl_buf_alloc()` - Allocate ACL mbuf + +--- + +## Error Codes + +Standard Linux errno values are used: +- `0` - Success +- `-ENOMEM` - Out of memory +- `-EINVAL` - Invalid argument +- `-EIO` - I/O error +- `-ENOTTY` - Inappropriate ioctl for device + +--- + +## Constants + +```c +#define ATBM_IOCTL (121) +#define MAX_SYNC_EVENT_BUFFER_LEN 512 +#define HCI_ACL_SHARE_SIZE 1538 + +// HCI packet types +#define BLE_HCI_HIF_NONE 0x00 +#define BLE_HCI_HIF_CMD 0x01 +#define BLE_HCI_HIF_ACL 0x02 +#define BLE_HCI_HIF_SCO 0x03 +#define BLE_HCI_HIF_EVT 0x04 +#define BLE_HCI_HIF_ISO 0x05 + +// Message IDs (in WSM header) +#define HI_MSG_ID_BLE_BASE 0xC00 +#define HI_MSG_ID_BLE_EVENT (HI_MSG_ID_BLE_BASE + 0x01) +#define HI_MSG_ID_BLE_ACK (HI_MSG_ID_BLE_BASE + 0x02) + +// Alternative IDs for combined WiFi+BLE mode +#define HI_MSG_ID_BLE_BIT BIT(8) +#define HI_MSG_ID_BLE_BASE_COMB (0x800 + HI_MSG_ID_BLE_BIT) +#define HI_MSG_ID_BLE_EVENT_COMB (HI_MSG_ID_BLE_BASE_COMB + 0x03) +#define HI_MSG_ID_BLE_ACK_COMB (HI_MSG_ID_BLE_BASE_COMB + 0x04) +``` + +--- + +## Example Usage + +See `ble_hci_hif.c` for complete implementation example: +- `/Users/yinzara/github/atbm-wifi/ble_host/nimble_v42/nimble/transport/ioctl/ble_hci_hif.c` + +--- + +## References + +- **Transport Implementation:** `ble_host/nimble_v42/nimble/transport/ioctl/ble_hci_hif.c` +- **ioctl Definitions:** `ble_host/os/linux/atbm_os_api.c` (lines 275-277, 557-562) +- **Header Structures:** `ble_host/nimble_v42/nimble/transport/ioctl/ble_hci_ioctl.h` +- **NimBLE HCI API:** `ble_host/nimble_v42/nimble/host/include/host/ble_hci_trans.h` + +--- + +## Notes + +1. The ATBM device requires `ATBM_BLE_COEXIST_START` to be called before any BLE operations. +2. All HCI packets are wrapped in a length prefix (2 bytes) when transmitted. +3. Event notification uses Linux async I/O (SIGIO signal). +4. Events may be queued; check `list_empty` field to drain the queue. +5. The WSM header format is specific to ATBM and wraps standard HCI packets. +6. Thread synchronization is required for both TX and RX paths. diff --git a/docs/BUILD_OPTIONS.md b/docs/BUILD_OPTIONS.md new file mode 100644 index 0000000..36994d4 --- /dev/null +++ b/docs/BUILD_OPTIONS.md @@ -0,0 +1,220 @@ +# Build Options Reference + +## Preprocessor Flags + +### `BLEPP_SERVER_SUPPORT` + +Enables BLE GATT Server functionality. + +**Default:** Not defined (disabled) + +**Effect:** +- Compiles `src/bluez_transport.cc` or `src/nimble_transport.cc` +- Adds server-related headers to installation +- Adds ~50-80 KB to library size + +**Usage:** +```bash +./configure && make BLEPP_SERVER_SUPPORT=1 +``` +--- + +### `BLEPP_NIMBLE_SUPPORT` + +Enables NIMBLE ioctl transport support. + +**Default:** Not defined (disabled) + +**Effect:** +- Compiles `src/nimble_client_transport.cc` +- Adds NIMBLE-specific headers + +**Usage:** +```bash +./configure && make BLEPP_SERVER_SUPPORT=1 BLEPP_NIMBLE_SUPPORT=1 +``` + +### `BLEPP_BLUEZ_SUPPORT` + +Enables BlueZ HCI/L2CAP transport support + +**Default:** Not defined (disabled) + +**Effect:** +- Compiles `src/bluez_client_transport.cc` + +**Usage:** +```bash +./configure && make BLEPP_SERVER_SUPPORT=1 BLEPP_BLUEZ_SUPPORT=1 + +--- +--- + +## Build Configurations + +### Client-Only (Default) + +**Size:** Baseline (~150 KB) + +**Compile:** +```bash +./configure +make +``` + +**Features:** +- ✅ BLE Central/Client mode +- ✅ Service discovery +- ✅ Read/Write characteristics +- ✅ Receive notifications +- ✅ BLE Peripheral/Server mode +- ✅ Advertising +- ✅ Accept connections + +--- + +### Client + Server (BlueZ) + +**Size:** ~200-230 KB (+50-80 KB) + +**Compile:** +```bash +./configure +make BLEPP_SERVER_SUPPORT=1 +``` + +**Features:** +- ✅ All client features +- ✅ BLE Peripheral/Server mode +- ✅ Advertising +- ✅ Accept connections +- ✅ Handle read/write requests +- ✅ Send notifications/indications +- ✅ BlueZ HCI/L2CAP transport +- ✅ NIMBLE transport + +--- + +### Client + Server + NIMBLE + +**Size:** ~220-360 KB (+70-110 KB) + +**Compile:** +```bash +./configure +make BLEPP_SERVER_SUPPORT=1 BLEPP_NIMBLE_SUPPORT=1 +``` + +**Features:** +- ✅ All client + server features +- ✅ NIMBLE ioctl transport +- ✅ Hardware-specific optimizations + +--- + +## Makefile Variables + +### Setting Variables + +**Command line:** +```bash +make BLEPP_SERVER_SUPPORT=1 BLEPP_NIMBLE_SUPPORT=1 +``` + +**Environment:** +```bash +export BLEPP_SERVER_SUPPORT=1 +export BLEPP_NIMBLE_SUPPORT=1 +make +``` + +**Makefile override:** +```makefile +# At top of Makefile or Makefile.local +BLEPP_SERVER_SUPPORT = 1 +BLEPP_NIMBLE_SUPPORT = 1 +``` +--- + +**Build:** +```bash +cmake -DWITH_SERVER_SUPPORT=ON -DWITH_NIMBLE_SUPPORT=OFF .. +make +``` + +--- + +## Verification + +### Check Compiled Features + +```bash +# Check for server symbols +nm -D libble++.so | grep -i "bluez\|transport" + +# Check for NIMBLE symbols +nm -D libble++.so | grep -i "NIMBLE" +``` + +### Runtime Check + +```cpp +#ifdef BLEPP_SERVER_SUPPORT + std::cout << "Server support: ENABLED" << std::endl; +#else + std::cout << "Server support: DISABLED" << std::endl; +#endif + +#ifdef BLEPP_NIMBLE_SUPPORT + std::cout << "NIMBLE support: ENABLED" << std::endl; +#else + std::cout << "NIMBLE support: DISABLED" << std::endl; +#endif + +#ifdef BLEPP_BLUEZ_SUPPORT + std::cout << "BLUEZ support: ENABLED" << std::endl; +#else + std::cout << "BLUEZ support: DISABLED" << std::endl; +#endif +``` + +--- + +## Dependencies + +### BlueZ +- `libbluetooth.so` (BlueZ 5.0+) +- Root privileges or `CAP_NET_ADMIN` + `CAP_NET_RAW` capabilities + +### NIMBLE +- NIMBLE driver loaded +- `/dev/NIMBLE_ioctl` device accessible +- Appropriate permissions on ioctl device + +--- +--- + +## Troubleshooting + +### "undefined reference to BlueZTransport" + +**Cause:** Trying to use BlueZ features without defining `BLEPP_BLUEZ_SUPPORT` + +**Fix:** +```bash +g++ -DBLEPP_SERVER_SUPPORT your_code.cpp -lblepp +``` +### "/dev/NIMBLE_ioctl: No such file or directory" + +**Cause:** NIMBLE driver not loaded + +**Fix:** +```bash +# Load NIMBLE driver +sudo modprobe NIMBLE_wifi_ble + +# Check device +ls -l /dev/NIMBLE_ioctl + +Where NIMBLE is your drivers slug ('atbm' for Altobeam) +``` diff --git a/docs/CLIENT_TRANSPORT_ABSTRACTION.md b/docs/CLIENT_TRANSPORT_ABSTRACTION.md new file mode 100644 index 0000000..69eeabd --- /dev/null +++ b/docs/CLIENT_TRANSPORT_ABSTRACTION.md @@ -0,0 +1,293 @@ +# Client Transport Abstraction + +## Overview + +The client functionality of libblepp has been refactored to support multiple transport layers through a unified abstraction. This allows the library to work with both standard BlueZ (HCI/L2CAP) and NIMBLE-specific hardware (/dev/atbm_ioctl). + +--- + +## Build Configuration + +### Preprocessor Flags + +Three main flags control the build: + +1. **`BLEPP_BLUEZ_SUPPORT`** - Enable BlueZ transport (HCI/L2CAP) + - Uses `bluetooth.h`, HCI sockets, L2CAP sockets + - Standard Linux Bluetooth stack + - Default for most systems + +2. **`BLEPP_NIMBLE_SUPPORT`** - Enable NIMBLE transport (/dev/atbm_ioctl) + - Uses NIMBLE-specific ioctl interface + - For NIMBLE WiFi/BLE combo chips + - Can be used with or without BlueZ + +3. **`BLEPP_SERVER_SUPPORT`** - Enable server/peripheral mode + - Optional feature + - Requires at least one transport + +### Build Configurations + +**Configuration Matrix:** + +| BlueZ | NIMBLE | Server | Result | +|-------|------|--------|--------| +| ON | OFF | OFF | Client-only with BlueZ | +| ON | OFF | ON | Client + Server with BlueZ | +| OFF | ON | OFF | Client-only with NIMBLE | +| OFF | ON | ON | Client + Server with NIMBLE | +| ON | ON | OFF | Client with both (runtime select) | +| ON | ON | ON | Full featured with both transports | +| OFF | OFF | any | **COMPILE ERROR** - need at least one transport | + +### Build Examples + +#### BlueZ Only (Default) +```bash +# Makefile +make BLEPP_BLUEZ_SUPPORT=1 + +# CMake +cmake -DWITH_BLUEZ_SUPPORT=ON .. +make +``` + +#### NIMBLE Only +```bash +# Makefile +make BLEPP_NIMBLE_SUPPORT=1 + +# CMake +cmake -DWITH_NIMBLE_SUPPORT=ON .. +make +``` + +#### Both Transports +```bash +# Makefile +make BLEPP_BLUEZ_SUPPORT=1 BLEPP_NIMBLE_SUPPORT=1 + +# CMake +cmake -DWITH_BLUEZ_SUPPORT=ON -DWITH_NIMBLE_SUPPORT=ON .. +make +``` + +#### Full Build (Both Transports + Server) +```bash +# Makefile +make BLEPP_BLUEZ_SUPPORT=1 BLEPP_NIMBLE_SUPPORT=1 BLEPP_SERVER_SUPPORT=1 + +# CMake +cmake -DWITH_BLUEZ_SUPPORT=ON -DWITH_NIMBLE_SUPPORT=ON -DWITH_SERVER_SUPPORT=ON .. +make +``` + +--- + +## Architecture + +### Transport Abstraction Layer + +``` +┌─────────────────────────────────────────────┐ +│ User Application │ +└──────────────────┬──────────────────────────┘ + │ + ┌─────────┴─────────┐ + │ │ + ┌────▼────┐ ┌──────▼──────┐ + │ Scanner │ │ GATT Client │ + └────┬────┘ └──────┬──────┘ + │ │ + └─────────┬─────────┘ + │ + ┌─────────▼─────────────┐ + │ BLEClientTransport │ ◄── Abstract Interface + │ (Pure Virtual) │ + └─────────┬─────────────┘ + │ + ┌────────────┴────────────┐ + │ │ +┌─────▼──────────┐ ┌─────────▼─────────┐ +│ BlueZ │ │ NIMBLE │ +│ Client │ │ Client │ +│ Transport │ │ Transport │ +└─────┬──────────┘ └─────────┬─────────┘ + │ │ + │ │ +┌─────▼──────────┐ ┌─────────▼─────────┐ +│ HCI + L2CAP │ │ /dev/atbm_ioctl │ +│ Sockets │ │ + SIGIO Events │ +└────────────────┘ └───────────────────┘ +``` + +### Key Classes + +#### `BLEClientTransport` (Abstract Interface) +- Pure virtual base class +- Defines scanning, connection, and data transfer operations +- Located in: `blepp/bleclienttransport.h` + +#### `BlueZClientTransport` +- Implements BLEClientTransport using BlueZ +- Uses HCI for scanning, L2CAP for connections +- Gated by `#ifdef BLEPP_BLUEZ_SUPPORT` +- Located in: `blepp/bluez_client_transport.h` / `src/bluez_client_transport.cc` + +#### `NIMBLEClientTransport` +- Implements BLEClientTransport using NIMBLE ioctl +- Uses /dev/atbm_ioctl for all operations +- Event-driven with SIGIO signal handling +- Gated by `#ifdef BLEPP_NIMBLE_SUPPORT` +- Located in: `blepp/atbm_client_transport.h` / `src/atbm_client_transport.cc` + +--- + +## API + +### Scanning + +```cpp +#include + +// Create transport +BLEClientTransport* transport = create_client_transport(); + +// Configure scan +ScanParams params; +params.scan_type = ScanParams::ScanType::Active; +params.interval_ms = 100; +params.window_ms = 50; + +// Start scanning +transport->start_scan(params); + +// Get results +std::vector ads; +while (running) { + int count = transport->get_advertisements(ads, 1000); // 1 sec timeout + for (const auto& ad : ads) { + std::cout << "Device: " << ad.address << " RSSI: " << (int)ad.rssi << std::endl; + } +} + +transport->stop_scan(); +``` + +### Connecting + +```cpp +// Connect to device +ClientConnectionParams conn_params; +conn_params.peer_address = "AA:BB:CC:DD:EE:FF"; +conn_params.peer_address_type = 0; // Public address + +int fd = transport->connect(conn_params); +if (fd < 0) { + // Error +} + +// Send ATT data +uint8_t pdu[] = {0x02, 0x17, 0x00}; // MTU exchange request +transport->send(fd, pdu, sizeof(pdu)); + +// Receive response +uint8_t buf[512]; +int len = transport->receive(fd, buf, sizeof(buf)); + +// Disconnect +transport->disconnect(fd); +``` + +--- + +## Migration Guide + +### For Existing Code Using BlueZ Directly + +**Before:** +```cpp +#include + +BLEGATTStateMachine gatt; +gatt.connect_blocking("AA:BB:CC:DD:EE:FF"); +``` + +**After:** +No changes needed! The BLEGATTStateMachine class has been updated internally to use the transport abstraction, but the public API remains the same for backward compatibility. + +### For Code That Needs Both Transports + +```cpp +#ifdef BLEPP_BLUEZ_SUPPORT + transport = new BlueZClientTransport(); +#elif defined(BLEPP_NIMBLE_SUPPORT) + transport = new NIMBLEClientTransport(); +#endif + +// Or use factory: +transport = create_client_transport(); // Returns appropriate transport +``` + +--- + +## Files Modified/Created + +### New Files + +**Headers:** +- `blepp/bleclienttransport.h` - Abstract transport interface +- `blepp/bluez_client_transport.h` - BlueZ implementation +- `blepp/nimble_client_transport.h` - NIMBLE implementation + +**Implementation:** +- `src/bluez_client_transport.cc` - BlueZ transport +- `src/nimble_client_transport.cc` - NIMBLE transport + +### Modified Files + +**Configuration:** +- `blepp/blepp_config.h` - Added BLUEZ_SUPPORT and NIMBLE_SUPPORT flags +- `CMakeLists.txt` - Added WITH_BLUEZ_SUPPORT and WITH_NIMBLE_SUPPORT options +- `Makefile.in` - Added conditional compilation for both transports + +**Core Classes (Internal Changes Only):** +- `blepp/blestatemachine.h` - Uses BLEClientTransport internally +- `src/blestatemachine.cc` - Refactored to use transport +- `blepp/lescan.h` - Uses BLEClientTransport for scanning +- `src/lescan.cc` - Refactored scanner implementation + +--- + +## Testing + +### Test BlueZ Transport +```bash +make clean +make BLEPP_BLUEZ_SUPPORT=1 BLEPP_EXAMPLES=1 +sudo ./examples/lescan_simple +``` + +### Test NIMBLE Transport +```bash +make clean +make BLEPP_NIMBLE_SUPPORT=1 BLEPP_EXAMPLES=1 +sudo ./examples/lescan_simple +``` + +### Test Both +```bash +make clean +make BLEPP_BLUEZ_SUPPORT=1 BLEPP_NIMBLE_SUPPORT=1 BLEPP_EXAMPLES=1 +# Will use default transport (BlueZ if available, NIMBLE otherwise) +sudo ./examples/lescan_simple +``` + +--- + +## Related Documentation + +- `BUILD_OPTIONS.md` - Makefile build options +- `CMAKE_BUILD_GUIDE.md` - CMake build guide +- `CONDITIONAL_COMPILATION.md` - Preprocessor flags reference +- `NIMBLE_IOCTL_API.md` - NIMBLE ioctl interface specification diff --git a/docs/CMAKE_BUILD_GUIDE.md b/docs/CMAKE_BUILD_GUIDE.md new file mode 100644 index 0000000..e484663 --- /dev/null +++ b/docs/CMAKE_BUILD_GUIDE.md @@ -0,0 +1,493 @@ +# CMake Build Guide + +## Overview + +libblepp now supports CMake for building the library with optional server and NIMBLE transport support. This guide covers all CMake build configurations and options. + +--- + +## Quick Start + +### Client-Only Build (Default) + +```bash +mkdir build +cd build +cmake .. +make +sudo make install +``` + +### Server Support Build + +```bash +mkdir build +cd build +cmake -DWITH_SERVER_SUPPORT=ON .. +make +sudo make install +``` + +### Server + NIMBLE Support Build + +```bash +mkdir build +cd build +cmake -DWITH_SERVER_SUPPORT=ON -DWITH_NIMBLE_SUPPORT=ON .. +make +sudo make install +``` + +--- + +## CMake Options + +### `WITH_EXAMPLES` + +**Description:** Build example applications + +**Default:** `OFF` + +**Usage:** +```bash +cmake -DWITH_EXAMPLES=ON .. +``` + +--- + +### `WITH_SERVER_SUPPORT` + +**Description:** Enable BLE GATT server (peripheral) functionality + +**Default:** `OFF` + +**Effect:** +- Compiles `src/bluez_transport.cc` +- Compiles `src/bleattributedb.cc` +- Compiles `src/blegattserver.cc` +- Adds `-DBLEPP_SERVER_SUPPORT` to compiler flags +- Links with `pthread` library +- Installs server headers + +**Usage:** +```bash +cmake -DWITH_SERVER_SUPPORT=ON .. +``` + +--- + +### `WITH_NIMBLE_SUPPORT` + +**Description:** Enable NIMBLE ioctl transport + +**Default:** `OFF` + +**Requires:** `WITH_SERVER_SUPPORT=ON` + +**Effect:** +- Compiles `src/nimble_transport.cc` +- Adds `-DBLEPP_NIMBLE_SUPPORT` to compiler flags +- Installs NIMBLE headers + +**Usage:** +```bash +cmake -DWITH_SERVER_SUPPORT=ON -DWITH_NIMBLE_SUPPORT=ON .. +``` + +--- + +## Build Configurations + +### Configuration Matrix + +| Configuration | Command | Headers | Objects | Size | Features | +|---------------|---------|---------|---------|------|----------| +| **Client-only** | `cmake ..` | 12 | 9 | ~150 KB | Client only | +| **+ Server** | `cmake -DWITH_SERVER_SUPPORT=ON ..` | 17 | 12 | ~230 KB | Client + Server (BlueZ) | +| **+ NIMBLE** | `cmake -DWITH_SERVER_SUPPORT=ON -DWITH_NIMBLE_SUPPORT=ON ..` | 18 | 13 | ~260 KB | Client + Server + NIMBLE | +| **+ Examples** | `cmake -DWITH_EXAMPLES=ON ..` | - | - | - | Build examples too | + +--- + +## Build Types + +### Debug Build + +```bash +cmake -DCMAKE_BUILD_TYPE=Debug .. +make +``` + +**Features:** +- Debug symbols included +- No optimization +- Larger binaries +- Better for debugging + +### Release Build (Default) + +```bash +cmake -DCMAKE_BUILD_TYPE=Release .. +make +``` + +**Features:** +- Optimized for performance +- Smaller binaries +- No debug symbols +- Production-ready + +--- + +## Installation Paths + +### Default Install Locations + +On Linux: +- Libraries: `/usr/local/lib/libble++.so` +- Headers: `/usr/local/include/blepp/` +- pkg-config: `/usr/local/lib/pkgconfig/libblepp.pc` + +### Custom Install Prefix + +```bash +cmake -DCMAKE_INSTALL_PREFIX=/opt/libblepp .. +make +sudo make install +``` + +Results in: +- `/opt/libblepp/lib/libble++.so` +- `/opt/libblepp/include/blepp/` + +--- + +## Complete Build Examples + +### Example 1: Client + Server + Examples + +```bash +mkdir build && cd build +cmake -DWITH_SERVER_SUPPORT=ON -DWITH_EXAMPLES=ON .. +make -j$(nproc) +sudo make install +``` + +### Example 2: Debug Build with Server + +```bash +mkdir build-debug && cd build-debug +cmake -DCMAKE_BUILD_TYPE=Debug -DWITH_SERVER_SUPPORT=ON .. +make +``` + +### Example 3: NIMBLE Build with Custom Prefix + +```bash +mkdir build && cd build +cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DWITH_SERVER_SUPPORT=ON \ + -DWITH_NIMBLE_SUPPORT=ON \ + -DCMAKE_INSTALL_PREFIX=$HOME/opt/libblepp \ + .. +make -j$(nproc) +make install # No sudo needed for user prefix +``` + +### Example 4: Cross-Compilation for ARM + +```bash +mkdir build-arm && cd build-arm +cmake \ + -DCMAKE_TOOLCHAIN_FILE=../cmake/arm-toolchain.cmake \ + -DWITH_SERVER_SUPPORT=ON \ + .. +make -j$(nproc) +``` + +--- + +## Using libblepp in Your CMake Project + +### Method 1: Find Package (After Installation) + +```cmake +cmake_minimum_required(VERSION 3.4) +project(my_ble_app) + +# Find the installed library +find_library(BLEPP_LIB ble++ REQUIRED) +find_path(BLEPP_INCLUDE blepp REQUIRED) + +# Check for server support +try_compile(HAS_SERVER_SUPPORT + ${CMAKE_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/check_server.cpp + CMAKE_FLAGS "-DINCLUDE_DIRECTORIES=${BLEPP_INCLUDE}" + LINK_LIBRARIES ${BLEPP_LIB}) + +if(HAS_SERVER_SUPPORT) + add_definitions(-DBLEPP_SERVER_SUPPORT) + message(STATUS "libblepp has server support") +endif() + +add_executable(my_app main.cpp) +target_include_directories(my_app PRIVATE ${BLEPP_INCLUDE}) +target_link_libraries(my_app ${BLEPP_LIB} bluetooth pthread) +``` + +### Method 2: pkg-config + +```cmake +cmake_minimum_required(VERSION 3.4) +project(my_ble_app) + +find_package(PkgConfig REQUIRED) +pkg_check_modules(BLEPP REQUIRED libblepp) + +add_executable(my_app main.cpp) +target_include_directories(my_app PRIVATE ${BLEPP_INCLUDE_DIRS}) +target_link_libraries(my_app ${BLEPP_LIBRARIES}) +``` + +### Method 3: Add as Subdirectory + +```cmake +cmake_minimum_required(VERSION 3.4) +project(my_ble_app) + +# Add libblepp as subdirectory +set(WITH_SERVER_SUPPORT ON CACHE BOOL "" FORCE) +add_subdirectory(external/libblepp) + +add_executable(my_app main.cpp) +target_link_libraries(my_app ble++) +``` + +--- + +## Verification + +### Check Build Configuration + +After building, verify what was compiled: + +```bash +# Check for server symbols +nm -D libble++.so | grep -i "BLEGATTServer\|BlueZTransport" + +# Check for NIMBLE symbols +nm -D libble++.so | grep -i "NIMBLETransport" + +# List all headers that will be installed +find blepp -name "*.h" -type f +``` + +### Check Preprocessor Defines + +Create a test file: + +```cpp +// test_config.cpp +#include +#include + +int main() { +#ifdef BLEPP_SERVER_SUPPORT + std::cout << "Server support: YES" << std::endl; +#else + std::cout << "Server support: NO" << std::endl; +#endif + +#ifdef BLEPP_NIMBLE_SUPPORT + std::cout << "NIMBLE support: YES" << std::endl; +#else + std::cout << "NIMBLE support: NO" << std::endl; +#endif + return 0; +} +``` + +Compile and run: +```bash +g++ -I/usr/local/include test_config.cpp -o test_config +./test_config +``` + +--- + +## Out-of-Source Builds + +It's recommended to use out-of-source builds: + +```bash +# Good: Build directory separate from source +mkdir build +cd build +cmake .. +make + +# Bad: In-source build +cd libblepp +cmake . +make +``` + +Multiple build configurations: + +```bash +# Client-only +mkdir build-client && cd build-client +cmake .. +make + +# Server +mkdir build-server && cd build-server +cmake -DWITH_SERVER_SUPPORT=ON .. +make + +# NIMBLE +mkdir build-nimble && cd build-nimble +cmake -DWITH_SERVER_SUPPORT=ON -DWITH_NIMBLE_SUPPORT=ON .. +make +``` + +--- + +## Cleaning Builds + +### Clean build artifacts + +```bash +cd build +make clean +``` + +### Complete rebuild + +```bash +rm -rf build +mkdir build +cd build +cmake .. +make +``` + +### Clean and rebuild specific target + +```bash +cd build +make clean +make ble++ +``` + +--- + +## Advanced CMake Options + +### Compiler Selection + +```bash +cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ .. +``` + +### Additional Compiler Flags + +```bash +cmake -DCMAKE_CXX_FLAGS="-Wall -Wextra -O3" .. +``` + +### Verbose Build + +```bash +make VERBOSE=1 +``` + +Or: + +```bash +cmake -DCMAKE_VERBOSE_MAKEFILE=ON .. +make +``` + +### Parallel Build + +```bash +make -j$(nproc) # Use all CPU cores +make -j4 # Use 4 cores +``` + +--- + +## Troubleshooting + +### "Bluez not found" + +**Solution:** +```bash +# Ubuntu/Debian +sudo apt-get install libbluetooth-dev + +# Fedora/RHEL +sudo dnf install bluez-libs-devel + +# Arch +sudo pacman -S bluez-libs +``` + +**Solution:** +```bash +cmake -DWITH_SERVER_SUPPORT=ON -DWITH_NIMBLE_SUPPORT=ON .. +``` + +### Headers not found after install + +**Cause:** Non-standard install prefix + +**Solution:** +```bash +# Add to pkg-config path +export PKG_CONFIG_PATH=/opt/libblepp/lib/pkgconfig:$PKG_CONFIG_PATH + +# Or use full path +g++ -I/opt/libblepp/include myapp.cpp -L/opt/libblepp/lib -lble++ +``` + +## Comparison: Make vs CMake + +| Feature | Makefile | CMake | +|---------|----------|-------| +| **Build Options** | `make BLEPP_SERVER_SUPPORT=1` | `cmake -DWITH_SERVER_SUPPORT=ON` | +| **Installation** | `make install` | `cmake .. && make install` | +| **Clean** | `make clean` | `make clean` or `rm -rf build` | +| **Parallel** | `make -j4` | `make -j4` | +| **Cross-compile** | Manual | Toolchain files | +| **IDE Support** | Limited | Excellent | +| **Windows** | Difficult | Native | + +--- + +## IDE Integration + +### CLion + +1. Open libblepp directory in CLion +2. CMake options appear automatically +3. Set options in `File → Settings → Build → CMake` +4. Add profiles for different configurations + +### Visual Studio Code + +1. Install CMake Tools extension +2. Open libblepp directory +3. Select configure preset: + - `Ctrl+Shift+P` → "CMake: Configure" +4. Build: `Ctrl+Shift+P` → "CMake: Build" + +### Qt Creator + +1. `File → Open File or Project` +2. Select `CMakeLists.txt` +3. Configure build settings +4. Build and run \ No newline at end of file diff --git a/examples/gatt_server.cc b/examples/gatt_server.cc new file mode 100644 index 0000000..006e86c --- /dev/null +++ b/examples/gatt_server.cc @@ -0,0 +1,258 @@ +/* + * Simple GATT Server Example + * + * This example demonstrates creating a simple BLE peripheral with: + * - Battery Service (0x180F) + * - Device Information Service (0x180A) + * - A custom notification service + * + * Compile with server support: + * cmake -DWITH_SERVER_SUPPORT=ON -DWITH_EXAMPLES=ON .. + * make + * + * Run: + * sudo ./examples/gatt_server + */ + +#ifdef BLEPP_SERVER_SUPPORT + +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace BLEPP; + +// Global server instance for signal handler +static BLEGATTServer* g_server = nullptr; + +// Signal handler for clean shutdown +void signal_handler(int signum) +{ + cerr << "\nReceived signal " << signum << ", shutting down..." << endl; + if (g_server) { + g_server->stop(); + } +} + +int main(int argc, char** argv) +{ + log_level = LogLevels::Info; + + // Device name (can be overridden by command line) + string device_name = "LibBLE++ Example"; + if (argc > 1) { + device_name = argv[1]; + } + + cout << "Creating BLE GATT Server: " << device_name << endl; + + // Create server instance + BLEGATTServer server; + g_server = &server; + + // Set up signal handlers for graceful shutdown + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + + // + // Add Battery Service (0x180F) + // + auto battery_service = server.add_service(UUID("180F")); + + // Battery Level characteristic (0x2A19) - Read + Notify + auto battery_level_char = battery_service->add_characteristic( + UUID("2A19"), + CharacteristicProperty::Read | CharacteristicProperty::Notify + ); + + // Simulated battery level (will decrease over time) + uint8_t battery_level = 100; + + // Read callback for battery level + battery_level_char->set_read_callback([&battery_level](uint16_t conn_handle) { + cout << "Battery level read by connection " << conn_handle + << ": " << (int)battery_level << "%" << endl; + return vector{battery_level}; + }); + + // + // Add Device Information Service (0x180A) + // + auto device_info_service = server.add_service(UUID("180A")); + + // Manufacturer Name String (0x2A29) + auto manufacturer_char = device_info_service->add_characteristic( + UUID("2A29"), + CharacteristicProperty::Read + ); + manufacturer_char->set_read_callback([](uint16_t conn_handle) { + string manufacturer = "LibBLE++ Project"; + return vector(manufacturer.begin(), manufacturer.end()); + }); + + // Model Number String (0x2A24) + auto model_char = device_info_service->add_characteristic( + UUID("2A24"), + CharacteristicProperty::Read + ); + model_char->set_read_callback([](uint16_t conn_handle) { + string model = "v1.0"; + return vector(model.begin(), model.end()); + }); + + // + // Add Custom Service for demonstrating write operations + // UUID: 12345678-1234-5678-1234-56789abcdef0 + // + auto custom_service = server.add_service( + UUID("12345678-1234-5678-1234-56789abcdef0") + ); + + // LED Control characteristic (write) + // UUID: 12345678-1234-5678-1234-56789abcdef1 + auto led_control_char = custom_service->add_characteristic( + UUID("12345678-1234-5678-1234-56789abcdef1"), + CharacteristicProperty::Write | CharacteristicProperty::Read + ); + + uint8_t led_state = 0; // 0 = off, 1 = on + + led_control_char->set_write_callback([&led_state](uint16_t conn_handle, const vector& data) { + if (!data.empty()) { + led_state = data[0]; + cout << "LED state changed to: " << (led_state ? "ON" : "OFF") + << " by connection " << conn_handle << endl; + } + }); + + led_control_char->set_read_callback([&led_state](uint16_t conn_handle) { + return vector{led_state}; + }); + + // Counter characteristic (notify) + // UUID: 12345678-1234-5678-1234-56789abcdef2 + auto counter_char = custom_service->add_characteristic( + UUID("12345678-1234-5678-1234-56789abcdef2"), + CharacteristicProperty::Read | CharacteristicProperty::Notify + ); + + uint32_t counter = 0; + + counter_char->set_read_callback([&counter](uint16_t conn_handle) { + // Return counter as 4-byte little-endian value + return vector{ + static_cast(counter & 0xFF), + static_cast((counter >> 8) & 0xFF), + static_cast((counter >> 16) & 0xFF), + static_cast((counter >> 24) & 0xFF) + }; + }); + + // + // Set up connection callbacks + // + server.on_connected = [](const ConnectionParams& params) { + cout << "Device connected: " << params.peer_address + << " (handle: " << params.connection_handle << ")" << endl; + }; + + server.on_disconnected = [](uint16_t conn_handle, uint8_t reason) { + cout << "Device disconnected (handle: " << conn_handle + << ", reason: " << (int)reason << ")" << endl; + }; + + // + // Configure advertising + // + AdvertisingParams adv_params; + adv_params.device_name = device_name; + adv_params.service_uuids = { + UUID("180F"), // Battery Service + UUID("180A"), // Device Information + UUID("12345678-1234-5678-1234-56789abcdef0") // Custom Service + }; + adv_params.connectable = true; + adv_params.interval_min_ms = 100; + adv_params.interval_max_ms = 200; + + // + // Start advertising + // + cout << "Starting advertising as: " << device_name << endl; + if (server.start_advertising(adv_params) < 0) { + cerr << "Failed to start advertising" << endl; + return 1; + } + + cout << "Server running. Press Ctrl+C to stop." << endl; + cout << "\nServices available:" << endl; + cout << " - Battery Service (0x180F)" << endl; + cout << " - Device Information (0x180A)" << endl; + cout << " - Custom Service (12345678-1234-5678-1234-56789abcdef0)" << endl; + cout << " - LED Control (write 0/1 to turn off/on)" << endl; + cout << " - Counter (read or subscribe for notifications)" << endl; + cout << endl; + + // + // Main event loop with periodic tasks + // + auto last_update = chrono::steady_clock::now(); + auto last_battery_update = chrono::steady_clock::now(); + + while (server.is_running()) { + auto now = chrono::steady_clock::now(); + + // Update counter every second and send notifications + if (chrono::duration_cast(now - last_update).count() >= 1) { + counter++; + + // Send notification to all subscribed clients + vector counter_data{ + static_cast(counter & 0xFF), + static_cast((counter >> 8) & 0xFF), + static_cast((counter >> 16) & 0xFF), + static_cast((counter >> 24) & 0xFF) + }; + counter_char->notify(counter_data); + + last_update = now; + } + + // Decrease battery level every 10 seconds + if (chrono::duration_cast(now - last_battery_update).count() >= 10) { + if (battery_level > 0) { + battery_level -= 5; + if (battery_level > 100) battery_level = 0; // Handle underflow + + cout << "Battery level decreased to " << (int)battery_level << "%" << endl; + + // Send notification to subscribed clients + battery_level_char->notify(vector{battery_level}); + } + last_battery_update = now; + } + + // Process events and sleep briefly + server.process_events(100); // 100ms timeout + } + + cout << "Server stopped." << endl; + g_server = nullptr; + + return 0; +} + +#else // !BLEPP_SERVER_SUPPORT + +#include +int main() { + std::cerr << "This example requires server support." << std::endl; + std::cerr << "Build with: cmake -DWITH_SERVER_SUPPORT=ON -DWITH_EXAMPLES=ON .." << std::endl; + return 1; +} + +#endif // BLEPP_SERVER_SUPPORT diff --git a/examples/lescan.cc b/examples/lescan.cc index 4da5641..eece5e3 100644 --- a/examples/lescan.cc +++ b/examples/lescan.cc @@ -9,7 +9,6 @@ #include #include #include -#include #include diff --git a/examples/lescan_simple.cc b/examples/lescan_simple.cc index bf2f79d..6dd5aea 100644 --- a/examples/lescan_simple.cc +++ b/examples/lescan_simple.cc @@ -1,10 +1,23 @@ #include +#include int main() { BLEPP::log_level = BLEPP::LogLevels::Info; - BLEPP::HCIScanner scanner; + + // Create transport (auto-selects available transport) + BLEPP::BLEClientTransport* transport = BLEPP::create_client_transport(); + if (!transport) { + return 1; + } + + // Create scanner + BLEPP::BLEScanner scanner(transport); + scanner.start(); + while (1) { std::vector ads = scanner.get_advertisements(); } + + delete transport; } diff --git a/examples/lescan_transport.cc b/examples/lescan_transport.cc new file mode 100644 index 0000000..af59667 --- /dev/null +++ b/examples/lescan_transport.cc @@ -0,0 +1,155 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include //for UUID. FIXME mofo +#include + +using namespace std; +using namespace BLEPP; + +void catch_function(int) +{ + cerr << "\nInterrupted!\n"; +} + +int main(int argc, char** argv) +{ + ScanParams::ScanType type = ScanParams::ScanType::Active; + bool filter_duplicates = true; + int c; + string help = R"X(-[pdhH]: + -p passive scan + -d show duplicates (no filtering, default is to filter) + -h show this message + -H use hardware filtering (not supported on all transports) +)X"; + while((c=getopt(argc, argv, "pdhH")) != -1) + { + if(c == 'p') + type = ScanParams::ScanType::Passive; + else if(c == 'd') + filter_duplicates = false; + else if(c == 'H') + { + // Hardware filtering - note: may not be supported by all transports + cerr << "Warning: hardware filtering may not be supported by all transports" << endl; + } + else if(c == 'h') + { + cout << "Usage: " << argv[0] << " " << help; + return 0; + } + else + { + cerr << argv[0] << ": unknown option " << c << endl; + return 1; + } + } + + log_level = LogLevels::Warning; + + // Create transport using factory (will select BlueZ or ATBM based on availability) + BLEClientTransport* transport = create_client_transport(); + if (!transport) + { + cerr << "Failed to create BLE client transport. No transports available." << endl; + return 1; + } + + cout << "Using transport: " << transport->get_transport_name() << endl; + + // Configure scan parameters + ScanParams params; + params.scan_type = type; + params.filter_duplicates = filter_duplicates; + params.interval_ms = 10; + params.window_ms = 10; + + // Start scanning + if (transport->start_scan(params) < 0) + { + cerr << "Failed to start scanning" << endl; + delete transport; + return 1; + } + + //Catch the interrupt signal + signal(SIGINT, catch_function); + + //Something to print to demonstrate the timeout. + string throbber="/|\\-"; + + //hide cursor, to make the throbber look nicer. + cout << "[?25l" << flush; + + int i=0; + while (1) + { + // Get advertisements with 300ms timeout + vector ads; + int result = transport->get_advertisements(ads, 300); + + //Interrupted, so quit and clean up properly. + if(result < 0 && errno == EINTR) + break; + + if (result > 0 && !ads.empty()) + { + for(const auto& ad: ads) + { + cout << "Found device: " << ad.address << " "; + + // Decode event type + if(ad.event_type == 0x00) + cout << "Connectable undirected (ADV_IND)" << endl; + else if(ad.event_type == 0x01) + cout << "Connectable directed (ADV_DIRECT_IND)" << endl; + else if(ad.event_type == 0x02) + cout << "Scannable (ADV_SCAN_IND)" << endl; + else if(ad.event_type == 0x03) + cout << "Non connectable (ADV_NONCONN_IND)" << endl; + else if(ad.event_type == 0x04) + cout << "Scan response (SCAN_RSP)" << endl; + else + cout << "Unknown event type: " << (int)ad.event_type << endl; + + // Parse advertisement data for UUIDs and names + // TODO: Parse ad.data for UUIDs, local name, etc. + // For now, just show raw data length + cout << " Data length: " << ad.data.size() << " bytes" << endl; + + if(ad.rssi == 127) + cout << " RSSI: unavailable" << endl; + else if(ad.rssi <= 20) + cout << " RSSI = " << (int) ad.rssi << " dBm" << endl; + else + cout << " RSSI = " << to_hex((uint8_t)ad.rssi) << " unknown" << endl; + } + } + else + { + cout << throbber[i%4] << "\b" << flush; + } + i++; + } + + // Stop scanning and cleanup + transport->stop_scan(); + delete transport; + + //show cursor + cout << "[?25h" << flush; + + return 0; +} diff --git a/libblepp.pc.in b/libblepp.pc.in deleted file mode 100644 index be29768..0000000 --- a/libblepp.pc.in +++ /dev/null @@ -1,8 +0,0 @@ -prefix=@prefix@ -includedir=@includedir@ - -Name: libble++ -Description: Bluetooth LE interface for C++ -Version: @VERSION@ -Libs: -lble++ @LIBS@ -Cflags: -I${includedir} diff --git a/src/att.cc b/src/att.cc index dfc6165..209a1b2 100644 --- a/src/att.cc +++ b/src/att.cc @@ -55,8 +55,6 @@ #include #include -#include - #include #include diff --git a/src/bleattributedb.cc b/src/bleattributedb.cc new file mode 100644 index 0000000..925dffd --- /dev/null +++ b/src/bleattributedb.cc @@ -0,0 +1,609 @@ +/* + * + * blepp - Implementation of the Generic ATTribute Protocol + * + * Copyright (C) 2024 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +#ifdef BLEPP_SERVER_SUPPORT + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace BLEPP +{ + +// Standard GATT UUIDs +static const UUID UUID_PRIMARY_SERVICE(0x2800); +static const UUID UUID_SECONDARY_SERVICE(0x2801); +static const UUID UUID_INCLUDE(0x2802); +static const UUID UUID_CHARACTERISTIC(0x2803); +static const UUID UUID_CCCD(0x2902); // Client Characteristic Configuration Descriptor + +BLEAttributeDatabase::BLEAttributeDatabase() + : next_handle_(1) // Handles start at 1 (0 is invalid) +{ + ENTER(); +} + +BLEAttributeDatabase::~BLEAttributeDatabase() +{ + ENTER(); + clear(); +} + +uint16_t BLEAttributeDatabase::allocate_handle() +{ + if (next_handle_ == 0xFFFF) { + LOG(Error, "Handle space exhausted!"); + return 0; + } + return next_handle_++; +} + +uint8_t BLEAttributeDatabase::flags_to_properties(uint16_t flags) +{ + uint8_t props = 0; + + if (flags & GATT_CHR_F_BROADCAST) props |= GATT_CHR_PROP_BROADCAST; + if (flags & GATT_CHR_F_READ) props |= GATT_CHR_PROP_READ; + if (flags & GATT_CHR_F_WRITE_NO_RSP) props |= GATT_CHR_PROP_WRITE_NO_RSP; + if (flags & GATT_CHR_F_WRITE) props |= GATT_CHR_PROP_WRITE; + if (flags & GATT_CHR_F_NOTIFY) props |= GATT_CHR_PROP_NOTIFY; + if (flags & GATT_CHR_F_INDICATE) props |= GATT_CHR_PROP_INDICATE; + if (flags & GATT_CHR_F_AUTH_SIGN_WRITE) props |= GATT_CHR_PROP_AUTH_WRITE; + + return props; +} + +uint8_t BLEAttributeDatabase::flags_to_permissions(uint16_t flags) +{ + uint8_t perms = 0; + + if (flags & GATT_CHR_F_READ) perms |= ATT_PERM_READ; + if (flags & (GATT_CHR_F_WRITE | GATT_CHR_F_WRITE_NO_RSP)) perms |= ATT_PERM_WRITE; + if (flags & GATT_CHR_F_READ_ENC) perms |= ATT_PERM_READ_ENCRYPT; + if (flags & GATT_CHR_F_WRITE_ENC) perms |= ATT_PERM_WRITE_ENCRYPT; + if (flags & GATT_CHR_F_READ_AUTHEN) perms |= ATT_PERM_READ_AUTHEN; + if (flags & GATT_CHR_F_WRITE_AUTHEN) perms |= ATT_PERM_WRITE_AUTHEN; + + return perms; +} + +uint16_t BLEAttributeDatabase::add_primary_service(const UUID& uuid) +{ + ENTER(); + + uint16_t handle = allocate_handle(); + if (handle == 0) return 0; + + Attribute attr = {}; // Zero-initialize all fields + attr.handle = handle; + attr.type = AttributeType::PRIMARY_SERVICE; + attr.uuid = UUID_PRIMARY_SERVICE; + attr.permissions = ATT_PERM_READ; + attr.end_group_handle = handle; // Initialize to service handle, will be updated later + + // Value is the service UUID + if (uuid.type == BT_UUID16) { + uint16_t val = uuid.value.u16; + attr.value = {(uint8_t)(val & 0xFF), (uint8_t)((val >> 8) & 0xFF)}; + } else { + // 128-bit UUID - Our UUID parsing stores in little-endian, ATT also requires little-endian + // So we just copy directly without reversing + attr.value.resize(16); + memcpy(attr.value.data(), uuid.value.u128.data, 16); + + // Debug: log the UUID bytes + std::stringstream uuid_hex; + uuid_hex << std::hex << std::setfill('0'); + for (int i = 0; i < 16; i++) { + uuid_hex << std::setw(2) << (int)attr.value[i] << " "; + } + LOG(Info, "Stored primary service UUID bytes (little-endian for ATT): " << uuid_hex.str()); + } + + attributes_[handle] = attr; + + // Track service for end_group_handle updates + services_.push_back({handle, handle}); + + LOG(Info, "Added primary service " << uuid.str() << " at handle " << handle); + return handle; +} + +uint16_t BLEAttributeDatabase::add_secondary_service(const UUID& uuid) +{ + ENTER(); + + uint16_t handle = allocate_handle(); + if (handle == 0) return 0; + + Attribute attr = {}; // Zero-initialize all fields + attr.handle = handle; + attr.type = AttributeType::SECONDARY_SERVICE; + attr.uuid = UUID_SECONDARY_SERVICE; + attr.permissions = ATT_PERM_READ; + attr.end_group_handle = handle; // Initialize to service handle, will be updated later + + // Value is the service UUID + if (uuid.type == BT_UUID16) { + uint16_t val = uuid.value.u16; + attr.value = {(uint8_t)(val & 0xFF), (uint8_t)((val >> 8) & 0xFF)}; + } else { + // 128-bit UUID - ATT requires little-endian, which is how we store it + attr.value.resize(16); + memcpy(attr.value.data(), uuid.value.u128.data, 16); + + // Debug: log the UUID bytes + std::stringstream uuid_hex; + uuid_hex << std::hex << std::setfill('0'); + for (int i = 0; i < 16; i++) { + uuid_hex << std::setw(2) << (int)attr.value[i] << " "; + } + LOG(Info, "Stored secondary service UUID bytes (little-endian for ATT): " << uuid_hex.str()); + } + + attributes_[handle] = attr; + + services_.push_back({handle, handle}); + + LOG(Info, "Added secondary service " << uuid.str() << " at handle " << handle); + return handle; +} + +uint16_t BLEAttributeDatabase::add_include(uint16_t service_handle, + uint16_t included_service_handle) +{ + ENTER(); + + uint16_t handle = allocate_handle(); + if (handle == 0) return 0; + + // Find the included service to get its end handle and UUID + auto inc_svc = get_attribute(included_service_handle); + if (!inc_svc) { + LOG(Error, "Included service handle " << included_service_handle << " not found"); + return 0; + } + + Attribute attr; + attr.handle = handle; + attr.type = AttributeType::INCLUDE; + attr.uuid = UUID_INCLUDE; + attr.permissions = ATT_PERM_READ; + + // Value format: included_handle (2 bytes) + end_group_handle (2 bytes) + UUID (0 or 2 bytes) + attr.value.resize(4); + attr.value[0] = included_service_handle & 0xFF; + attr.value[1] = (included_service_handle >> 8) & 0xFF; + attr.value[2] = inc_svc->end_group_handle & 0xFF; + attr.value[3] = (inc_svc->end_group_handle >> 8) & 0xFF; + + // If the included service has a 16-bit UUID, append it + if (inc_svc->uuid.type == BT_UUID16) { + uint16_t uuid16 = inc_svc->uuid.value.u16; + attr.value.push_back(uuid16 & 0xFF); + attr.value.push_back((uuid16 >> 8) & 0xFF); + } + + attributes_[handle] = attr; + + update_service_end_handle(service_handle, handle); + + LOG(Info, "Added include at handle " << handle); + return handle; +} + +uint16_t BLEAttributeDatabase::add_characteristic(uint16_t service_handle, + const UUID& uuid, + uint8_t properties, + uint8_t permissions) +{ + ENTER(); + + // Allocate handles for: + // 1. Characteristic declaration + // 2. Characteristic value + uint16_t decl_handle = allocate_handle(); + uint16_t value_handle = allocate_handle(); + + if (decl_handle == 0 || value_handle == 0) return 0; + + // 1. Add characteristic declaration + Attribute decl_attr; + decl_attr.handle = decl_handle; + decl_attr.type = AttributeType::CHARACTERISTIC; + decl_attr.uuid = UUID_CHARACTERISTIC; + decl_attr.permissions = ATT_PERM_READ; + decl_attr.properties = properties; + decl_attr.value_handle = value_handle; + + // Value format: properties (1 byte) + value_handle (2 bytes) + UUID + decl_attr.value.push_back(properties); + decl_attr.value.push_back(value_handle & 0xFF); + decl_attr.value.push_back((value_handle >> 8) & 0xFF); + + if (uuid.type == BT_UUID16) { + uint16_t val = uuid.value.u16; + decl_attr.value.push_back(val & 0xFF); + decl_attr.value.push_back((val >> 8) & 0xFF); + } else { + // 128-bit UUID - ATT requires little-endian, which is how we store it + size_t start_pos = decl_attr.value.size(); + decl_attr.value.resize(start_pos + 16); + memcpy(&decl_attr.value[start_pos], uuid.value.u128.data, 16); + } + + attributes_[decl_handle] = decl_attr; + + // 2. Add characteristic value + Attribute value_attr; + value_attr.handle = value_handle; + value_attr.type = AttributeType::CHARACTERISTIC_VALUE; + value_attr.uuid = uuid; + value_attr.permissions = permissions; + value_attr.properties = properties; + + attributes_[value_handle] = value_attr; + + update_service_end_handle(service_handle, value_handle); + + // 3. Auto-add CCCD if notify or indicate is enabled + if (properties & (GATT_CHR_PROP_NOTIFY | GATT_CHR_PROP_INDICATE)) { + uint16_t cccd_handle = add_cccd(value_handle); + if (cccd_handle) { + update_service_end_handle(service_handle, cccd_handle); + } + } + + LOG(Info, "Added characteristic " << uuid.str() << " (decl=" << decl_handle + << ", value=" << value_handle << ")"); + + return decl_handle; +} + +uint16_t BLEAttributeDatabase::add_descriptor(uint16_t char_handle, + const UUID& uuid, + uint8_t permissions) +{ + ENTER(); + + uint16_t handle = allocate_handle(); + if (handle == 0) return 0; + + Attribute attr; + attr.handle = handle; + attr.type = AttributeType::DESCRIPTOR; + attr.uuid = uuid; + attr.permissions = permissions; + + attributes_[handle] = attr; + + // Find the service this belongs to and update end handle + // (char_handle should be a characteristic value, so search backwards for service) + for (auto rit = services_.rbegin(); rit != services_.rend(); ++rit) { + if (char_handle >= rit->start_handle && char_handle <= rit->end_handle) { + rit->end_handle = handle; + // Update the service attribute's end_group_handle + auto svc_attr = get_attribute(rit->start_handle); + if (svc_attr) { + svc_attr->end_group_handle = handle; + } + break; + } + } + + LOG(Info, "Added descriptor " << uuid.str() << " at handle " << handle); + return handle; +} + +uint16_t BLEAttributeDatabase::add_cccd(uint16_t char_value_handle) +{ + ENTER(); + + uint16_t handle = add_descriptor(char_value_handle, UUID_CCCD, + ATT_PERM_READ | ATT_PERM_WRITE); + + if (handle) { + // Initialize CCCD value to 0x0000 (notifications and indications disabled) + auto attr = get_attribute(handle); + if (attr) { + attr->value = {0x00, 0x00}; + } + LOG(Debug, "Auto-added CCCD at handle " << handle + << " for characteristic " << char_value_handle); + } + + return handle; +} + +void BLEAttributeDatabase::update_service_end_handle(uint16_t service_handle, + uint16_t last_handle) +{ + // Update the service info + for (auto& svc : services_) { + if (svc.start_handle == service_handle) { + svc.end_handle = last_handle; + break; + } + } + + // Update the service attribute itself + auto attr = get_attribute(service_handle); + if (attr) { + attr->end_group_handle = last_handle; + } +} + +int BLEAttributeDatabase::register_services(const std::vector& services) +{ + ENTER(); + + for (const auto& svc_def : services) { + // Add service + uint16_t svc_handle; + if (svc_def.type == GATTServiceType::PRIMARY) { + svc_handle = add_primary_service(svc_def.uuid); + } else { + svc_handle = add_secondary_service(svc_def.uuid); + } + + if (svc_handle == 0) { + LOG(Error, "Failed to add service " << svc_def.uuid.str()); + return -1; + } + + // Fill in handle pointer if provided + if (svc_def.handle_ptr) { + *svc_def.handle_ptr = svc_handle; + } + + // Add included services + for (uint16_t inc_handle : svc_def.included_services) { + add_include(svc_handle, inc_handle); + } + + // Add characteristics + for (const auto& char_def : svc_def.characteristics) { + uint8_t properties = flags_to_properties(char_def.flags); + uint8_t permissions = flags_to_permissions(char_def.flags); + + uint16_t char_decl_handle = add_characteristic( + svc_handle, + char_def.uuid, + properties, + permissions + ); + + if (char_decl_handle == 0) { + LOG(Error, "Failed to add characteristic " << char_def.uuid.str()); + return -1; + } + + // The value handle is always declaration handle + 1 + uint16_t char_value_handle = char_decl_handle + 1; + + // Fill in handle pointer if provided + if (char_def.val_handle_ptr) { + *char_def.val_handle_ptr = char_value_handle; + } + + // Set access callback + if (char_def.access_cb) { + auto value_attr = get_attribute(char_value_handle); + if (value_attr) { + value_attr->read_cb = [char_def](uint16_t conn_handle, uint16_t offset, + std::vector& out_data) -> int { + return char_def.access_cb(conn_handle, ATTAccessOp::READ_CHR, offset, out_data); + }; + + value_attr->write_cb = [char_def](uint16_t conn_handle, + const std::vector& data) -> int { + std::vector mutable_data = data; + return char_def.access_cb(conn_handle, ATTAccessOp::WRITE_CHR, 0, mutable_data); + }; + } + } + + // Add descriptors + for (const auto& dsc_def : char_def.descriptors) { + uint16_t dsc_handle = add_descriptor(char_value_handle, + dsc_def.uuid, + dsc_def.permissions); + + if (dsc_handle == 0) { + LOG(Error, "Failed to add descriptor " << dsc_def.uuid.str()); + return -1; + } + + // Fill in handle pointer if provided + if (dsc_def.handle_ptr) { + *dsc_def.handle_ptr = dsc_handle; + } + + // Set access callback + if (dsc_def.access_cb) { + auto dsc_attr = get_attribute(dsc_handle); + if (dsc_attr) { + dsc_attr->read_cb = [dsc_def](uint16_t conn_handle, uint16_t offset, + std::vector& out_data) -> int { + return dsc_def.access_cb(conn_handle, ATTAccessOp::READ_DSC, offset, out_data); + }; + + dsc_attr->write_cb = [dsc_def](uint16_t conn_handle, + const std::vector& data) -> int { + std::vector mutable_data = data; + return dsc_def.access_cb(conn_handle, ATTAccessOp::WRITE_DSC, 0, mutable_data); + }; + } + } + } + } + } + + LOG(Info, "Registered " << services.size() << " services, total attributes: " << attributes_.size()); + return 0; +} + +Attribute* BLEAttributeDatabase::get_attribute(uint16_t handle) +{ + auto it = attributes_.find(handle); + return (it != attributes_.end()) ? &it->second : nullptr; +} + +const Attribute* BLEAttributeDatabase::get_attribute(uint16_t handle) const +{ + auto it = attributes_.find(handle); + return (it != attributes_.end()) ? &it->second : nullptr; +} + +std::vector BLEAttributeDatabase::find_by_type( + uint16_t start_handle, + uint16_t end_handle, + const UUID& type) const +{ + ENTER(); + LOG(Debug, "Searching for type=" << type.str() << " in range [" << start_handle << ", " << end_handle << "]"); + LOG(Debug, "Total attributes in database: " << attributes_.size()); + + std::vector results; + + for (const auto& pair : attributes_) { + const auto& attr = pair.second; + if (attr.handle >= start_handle && attr.handle <= end_handle) { + LOG(Debug, "Checking handle " << attr.handle << " with UUID " << attr.uuid.str()); + if (attr.uuid == type) { + LOG(Debug, " -> MATCH!"); + results.push_back(&attr); + } + } + } + + LOG(Debug, "Found " << results.size() << " matching attributes"); + return results; +} + +std::vector BLEAttributeDatabase::find_by_type_value( + uint16_t start_handle, + uint16_t end_handle, + const UUID& type, + const std::vector& value) const +{ + std::vector results; + + for (const auto& pair : attributes_) { + const auto& attr = pair.second; + if (attr.handle >= start_handle && attr.handle <= end_handle) { + if (attr.uuid == type && attr.value == value) { + results.push_back(&attr); + } + } + } + + return results; +} + +std::vector BLEAttributeDatabase::get_range( + uint16_t start_handle, + uint16_t end_handle) const +{ + std::vector results; + + for (const auto& pair : attributes_) { + const auto& attr = pair.second; + if (attr.handle >= start_handle && attr.handle <= end_handle) { + results.push_back(&attr); + } + } + + return results; +} + +void BLEAttributeDatabase::clear() +{ + attributes_.clear(); + services_.clear(); + next_handle_ = 1; +} + +int BLEAttributeDatabase::set_characteristic_value(uint16_t char_value_handle, + const std::vector& value) +{ + auto attr = get_attribute(char_value_handle); + if (!attr) { + LOG(Warning, "Characteristic value handle " << char_value_handle << " not found"); + return -1; + } + + if (attr->type != AttributeType::CHARACTERISTIC_VALUE) { + LOG(Warning, "Handle " << char_value_handle << " is not a characteristic value"); + return -1; + } + + attr->value = value; + return 0; +} + +std::vector BLEAttributeDatabase::get_characteristic_value(uint16_t char_value_handle) const +{ + auto attr = get_attribute(char_value_handle); + if (!attr) { + return {}; + } + + if (attr->type != AttributeType::CHARACTERISTIC_VALUE) { + return {}; + } + + return attr->value; +} + +int BLEAttributeDatabase::set_read_callback(uint16_t char_value_handle, + std::function& out_data)> cb) +{ + auto attr = get_attribute(char_value_handle); + if (!attr) return -1; + + attr->read_cb = cb; + return 0; +} + +int BLEAttributeDatabase::set_write_callback(uint16_t char_value_handle, + std::function& data)> cb) +{ + auto attr = get_attribute(char_value_handle); + if (!attr) return -1; + + attr->write_cb = cb; + return 0; +} + +} // namespace BLEPP + +#endif // BLEPP_SERVER_SUPPORT diff --git a/src/bleclienttransport.cc b/src/bleclienttransport.cc new file mode 100644 index 0000000..3eefa19 --- /dev/null +++ b/src/bleclienttransport.cc @@ -0,0 +1,168 @@ +/* + * + * blepp - Implementation of the Generic ATTribute Protocol + * + * Copyright (C) 2024 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include + +#ifdef BLEPP_SERVER_SUPPORT +#include +#endif + +#ifdef BLEPP_BLUEZ_SUPPORT +#include +#ifdef BLEPP_SERVER_SUPPORT +#include +#endif +#endif + +#ifdef BLEPP_NIMBLE_SUPPORT +#include +#ifdef BLEPP_SERVER_SUPPORT +#include +#endif +#endif + +namespace BLEPP +{ + +BLEClientTransport* create_client_transport() +{ + ENTER(); + LOG(Info, "create_client_transport() called - selecting BLE client transport"); + + // Prefer BlueZ if available and working, fall back to Nimble +#ifdef BLEPP_BLUEZ_SUPPORT + { + LOG(Debug, "Trying BlueZ client transport..."); + BlueZClientTransport* transport = new BlueZClientTransport(); + LOG(Debug, "BlueZ client transport created, checking availability..."); + if (transport->is_available()) { + LOG(Info, "Using BlueZ client transport"); + return transport; + } + delete transport; + LOG(Warning, "BlueZ transport not available, trying next option"); + } +#else + LOG(Debug, "BlueZ support not compiled in (BLEPP_BLUEZ_SUPPORT not defined)"); +#endif + +#ifdef BLEPP_NIMBLE_SUPPORT + { + LOG(Debug, "Trying Nimble client transport..."); + NimbleClientTransport* transport = new NimbleClientTransport(); + LOG(Debug, "Nimble client transport created, checking availability..."); + if (transport->is_available()) { + LOG(Info, "Using Nimble client transport"); + return transport; + } + delete transport; + LOG(Warning, "Nimble transport not available"); + } +#else + LOG(Debug, "Nimble support not compiled in (BLEPP_NIMBLE_SUPPORT not defined)"); +#endif + + LOG(Error, "No BLE client transport available - all transports failed"); + return nullptr; +} + +#ifdef BLEPP_BLUEZ_SUPPORT +BLEClientTransport* create_bluez_client_transport() +{ + ENTER(); + LOG(Info, "Creating BlueZ client transport"); + return new BlueZClientTransport(); +} +#endif + +#ifdef BLEPP_NIMBLE_SUPPORT +BLEClientTransport* create_nimble_client_transport() +{ + ENTER(); + LOG(Info, "Creating Nimble client transport"); + return new NimbleClientTransport(); +} +#endif + +// =================================================================== +// Server Transport Factory Functions +// =================================================================== + +#ifdef BLEPP_SERVER_SUPPORT + +BLETransport* create_server_transport() +{ + ENTER(); + + // Prefer BlueZ if available and working, fall back to Nimble +#ifdef BLEPP_BLUEZ_SUPPORT + { + BlueZTransport* transport = new BlueZTransport(); + // BlueZ server doesn't have is_available(), so just check if it was created + if (transport) { + LOG(Info, "Using BlueZ server transport"); + return transport; + } + delete transport; + LOG(Warning, "BlueZ server transport not available"); + } +#endif + +#ifdef BLEPP_NIMBLE_SUPPORT + { + NimbleTransport* transport = new NimbleTransport(); + // Nimble server doesn't have is_available() either + if (transport) { + LOG(Info, "Using Nimble server transport"); + return transport; + } + delete transport; + LOG(Warning, "Nimble server transport not available"); + } +#endif + + LOG(Error, "No BLE server transport available"); + return nullptr; +} + +#ifdef BLEPP_BLUEZ_SUPPORT +BLETransport* create_bluez_server_transport() +{ + ENTER(); + LOG(Info, "Creating BlueZ server transport"); + return new BlueZTransport(); +} +#endif + +#ifdef BLEPP_NIMBLE_SUPPORT +BLETransport* create_nimble_server_transport() +{ + ENTER(); + LOG(Info, "Creating Nimble server transport"); + return new NimbleTransport(); +} +#endif + +#endif // BLEPP_SERVER_SUPPORT + +} // namespace BLEPP diff --git a/src/bledevice.cc b/src/bledevice.cc index c8a9688..2488065 100644 --- a/src/bledevice.cc +++ b/src/bledevice.cc @@ -23,13 +23,10 @@ #include "blepp/logging.h" #include "blepp/att_pdu.h" - -#include -#include - #include - #include +#include +#include namespace BLEPP { diff --git a/src/blegattserver.cc b/src/blegattserver.cc new file mode 100644 index 0000000..dcc9073 --- /dev/null +++ b/src/blegattserver.cc @@ -0,0 +1,1140 @@ +/* + * + * blepp - Implementation of the Generic ATTribute Protocol + * + * Copyright (C) 2024 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +#ifdef BLEPP_SERVER_SUPPORT + +#include +#include +#include + +#ifdef BLEPP_NIMBLE_SUPPORT +#include +#endif + +#include +#include +#include +#include +#include +#include +#include // for usleep() + +namespace BLEPP +{ + +// ATT Opcodes (from att.h, but repeated here for clarity) +#define ATT_OP_ERROR 0x01 +#define ATT_OP_MTU_REQ 0x02 +#define ATT_OP_MTU_RSP 0x03 +#define ATT_OP_FIND_INFO_REQ 0x04 +#define ATT_OP_FIND_INFO_RSP 0x05 +#define ATT_OP_FIND_BY_TYPE_VALUE_REQ 0x06 +#define ATT_OP_FIND_BY_TYPE_VALUE_RSP 0x07 +#define ATT_OP_READ_BY_TYPE_REQ 0x08 +#define ATT_OP_READ_BY_TYPE_RSP 0x09 +#define ATT_OP_READ_REQ 0x0A +#define ATT_OP_READ_RSP 0x0B +#define ATT_OP_READ_BLOB_REQ 0x0C +#define ATT_OP_READ_BLOB_RSP 0x0D +#define ATT_OP_READ_MULTIPLE_REQ 0x0E +#define ATT_OP_READ_MULTIPLE_RSP 0x0F +#define ATT_OP_READ_BY_GROUP_TYPE_REQ 0x10 +#define ATT_OP_READ_BY_GROUP_TYPE_RSP 0x11 +#define ATT_OP_WRITE_REQ 0x12 +#define ATT_OP_WRITE_RSP 0x13 +#define ATT_OP_WRITE_CMD 0x52 +#define ATT_OP_PREPARE_WRITE_REQ 0x16 +#define ATT_OP_PREPARE_WRITE_RSP 0x17 +#define ATT_OP_EXECUTE_WRITE_REQ 0x18 +#define ATT_OP_EXECUTE_WRITE_RSP 0x19 +#define ATT_OP_HANDLE_NOTIFY 0x1B +#define ATT_OP_HANDLE_INDICATE 0x1D +#define ATT_OP_HANDLE_CONFIRM 0x1E +#define ATT_OP_SIGNED_WRITE_CMD 0xD2 + +// Default MTU +#define ATT_DEFAULT_MTU 23 +#define ATT_MAX_MTU 517 + +BLEGATTServer::BLEGATTServer(std::unique_ptr transport) + : transport_(std::move(transport)) + , running_(false) +{ + ENTER(); + + // Set up transport callbacks + transport_->on_connected = [this](const ConnectionParams& params) { + this->on_transport_connected(params); + }; + + transport_->on_disconnected = [this](uint16_t conn_handle) { + this->on_transport_disconnected(conn_handle); + }; + + transport_->on_data_received = [this](uint16_t conn_handle, + const uint8_t* data, size_t len) { + this->on_transport_data_received(conn_handle, data, len); + }; + + LOG(Info, "BLEGATTServer created"); +} + +BLEGATTServer::~BLEGATTServer() +{ + ENTER(); + stop(); +} + +int BLEGATTServer::register_services(const std::vector& services) +{ + ENTER(); + + // Register with attribute database (for BlueZ transport) + int rc = db_.register_services(services); + if (rc != 0) { + return rc; + } + + // For NimbleTransport, also register with NimBLE GATTS +#ifdef BLEPP_NIMBLE_SUPPORT + NimbleTransport* nimble_transport = dynamic_cast(transport_.get()); + if (nimble_transport) { + LOG(Info, "Registering services with NimbleTransport"); + rc = nimble_transport->register_services(services); + if (rc != 0) { + LOG(Error, "Failed to register services with NimbleTransport: " << rc); + return rc; + } + } +#endif + + return 0; +} + +int BLEGATTServer::start_advertising(const AdvertisingParams& params) +{ + ENTER(); + return transport_->start_advertising(params); +} + +int BLEGATTServer::stop_advertising() +{ + ENTER(); + return transport_->stop_advertising(); +} + +bool BLEGATTServer::is_advertising() const +{ + return transport_->is_advertising(); +} + +int BLEGATTServer::run() +{ + ENTER(); + + { + std::lock_guard lock(running_mutex_); + running_ = true; + } + + LOG(Info, "GATT server running"); + + // Accept connections and process events + while (running_) { + // Accept new connections (non-blocking or with timeout) + transport_->accept_connection(); + + // Process transport events + transport_->process_events(); + + // Small sleep to prevent busy-wait + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + + LOG(Info, "GATT server stopped"); + return 0; +} + +void BLEGATTServer::stop() +{ + std::lock_guard lock(running_mutex_); + running_ = false; +} + +int BLEGATTServer::notify(uint16_t conn_handle, uint16_t char_val_handle, + const std::vector& data) +{ + std::lock_guard lock(connections_mutex_); + + auto it = connections_.find(conn_handle); + if (it == connections_.end()) { + LOG(Error, "Connection " << conn_handle << " not found"); + return -1; + } + + // Check if client enabled notifications + uint16_t cccd = it->second.cccd_values[char_val_handle]; + if (!(cccd & 0x0001)) { + LOG(Warning, "Notifications not enabled for handle " << char_val_handle); + return -1; + } + + // Build ATT_OP_HANDLE_NOTIFY PDU + std::vector pdu; + pdu.reserve(3 + data.size()); + pdu.push_back(ATT_OP_HANDLE_NOTIFY); + pdu.push_back(char_val_handle & 0xFF); + pdu.push_back((char_val_handle >> 8) & 0xFF); + pdu.insert(pdu.end(), data.begin(), data.end()); + + return transport_->send_pdu(conn_handle, pdu.data(), pdu.size()); +} + +int BLEGATTServer::indicate(uint16_t conn_handle, uint16_t char_val_handle, + const std::vector& data) +{ + std::lock_guard lock(connections_mutex_); + + auto it = connections_.find(conn_handle); + if (it == connections_.end()) { + LOG(Error, "Connection " << conn_handle << " not found"); + return -1; + } + + // Check if client enabled indications + uint16_t cccd = it->second.cccd_values[char_val_handle]; + if (!(cccd & 0x0002)) { + LOG(Warning, "Indications not enabled for handle " << char_val_handle); + return -1; + } + + // Build ATT_OP_HANDLE_INDICATE PDU + std::vector pdu; + pdu.reserve(3 + data.size()); + pdu.push_back(ATT_OP_HANDLE_INDICATE); + pdu.push_back(char_val_handle & 0xFF); + pdu.push_back((char_val_handle >> 8) & 0xFF); + pdu.insert(pdu.end(), data.begin(), data.end()); + + // TODO: Wait for ATT_OP_HANDLE_CONFIRM from client + return transport_->send_pdu(conn_handle, pdu.data(), pdu.size()); +} + +int BLEGATTServer::disconnect(uint16_t conn_handle) +{ + return transport_->disconnect(conn_handle); +} + +ConnectionState* BLEGATTServer::get_connection_state(uint16_t conn_handle) +{ + std::lock_guard lock(connections_mutex_); + + auto it = connections_.find(conn_handle); + if (it == connections_.end()) { + return nullptr; + } + + return &it->second; +} + +// Transport callbacks + +void BLEGATTServer::on_transport_connected(const ConnectionParams& params) +{ + ENTER(); + + std::lock_guard lock(connections_mutex_); + + ConnectionState state; + state.conn_handle = params.conn_handle; + state.mtu = ATT_DEFAULT_MTU; + state.connected = true; + + connections_[params.conn_handle] = state; + + LOG(Info, "Client connected: handle=" << params.conn_handle + << " addr=" << params.peer_address); + + // Call user callback + if (on_connected) { + on_connected(params.conn_handle, params.peer_address); + } +} + +void BLEGATTServer::on_transport_disconnected(uint16_t conn_handle) +{ + ENTER(); + + { + std::lock_guard lock(connections_mutex_); + connections_.erase(conn_handle); + } + + LOG(Info, "Client disconnected: handle=" << conn_handle); + + // Call user callback + if (on_disconnected) { + on_disconnected(conn_handle); + } +} + +void BLEGATTServer::on_transport_data_received(uint16_t conn_handle, + const uint8_t* data, size_t len) +{ + if (len < 1) { + LOG(Error, "Received empty PDU"); + return; + } + + handle_att_pdu(conn_handle, data, len); +} + +// ATT PDU dispatcher + +void BLEGATTServer::handle_att_pdu(uint16_t conn_handle, + const uint8_t* pdu, size_t len) +{ + uint8_t opcode = pdu[0]; + + LOG(Debug, "ATT PDU: conn=" << conn_handle << " opcode=0x" + << std::hex << (int)opcode << std::dec << " len=" << len); + + switch (opcode) { + case ATT_OP_MTU_REQ: + handle_mtu_exchange_req(conn_handle, pdu, len); + break; + + case ATT_OP_FIND_INFO_REQ: + handle_find_info_req(conn_handle, pdu, len); + break; + + case ATT_OP_FIND_BY_TYPE_VALUE_REQ: + handle_find_by_type_value_req(conn_handle, pdu, len); + break; + + case ATT_OP_READ_BY_TYPE_REQ: + handle_read_by_type_req(conn_handle, pdu, len); + break; + + case ATT_OP_READ_REQ: + handle_read_req(conn_handle, pdu, len); + break; + + case ATT_OP_READ_BLOB_REQ: + handle_read_blob_req(conn_handle, pdu, len); + break; + + case ATT_OP_READ_BY_GROUP_TYPE_REQ: + handle_read_by_group_type_req(conn_handle, pdu, len); + break; + + case ATT_OP_WRITE_REQ: + handle_write_req(conn_handle, pdu, len); + break; + + case ATT_OP_WRITE_CMD: + handle_write_cmd(conn_handle, pdu, len); + break; + + case ATT_OP_PREPARE_WRITE_REQ: + handle_prepare_write_req(conn_handle, pdu, len); + break; + + case ATT_OP_EXECUTE_WRITE_REQ: + handle_execute_write_req(conn_handle, pdu, len); + break; + + case ATT_OP_SIGNED_WRITE_CMD: + handle_signed_write_cmd(conn_handle, pdu, len); + break; + + case ATT_OP_HANDLE_CONFIRM: + // Confirmation for indication - just acknowledge + LOG(Debug, "Received indication confirmation"); + break; + + default: + // Log hex dump of unknown opcode + std::stringstream hex_dump; + hex_dump << std::hex << std::setfill('0'); + for (size_t i = 0; i < len && i < 32; i++) { + hex_dump << std::setw(2) << (int)pdu[i] << " "; + } + LOG(Warning, "Unsupported ATT opcode: 0x" << std::hex << (int)opcode + << " PDU: " << hex_dump.str()); + send_error_response(conn_handle, opcode, 0x0000, BLE_ATT_ERR_REQ_NOT_SUPPORTED); + break; + } +} + +// MTU Exchange + +void BLEGATTServer::handle_mtu_exchange_req(uint16_t conn_handle, + const uint8_t* pdu, size_t len) +{ + if (len < 3) { + send_error_response(conn_handle, ATT_OP_MTU_REQ, 0x0000, + BLE_ATT_ERR_INVALID_PDU); + return; + } + + uint16_t client_mtu = pdu[1] | (pdu[2] << 8); + + LOG(Debug, "MTU Exchange: client=" << client_mtu); + + // Negotiate MTU (minimum of client and server MTU) + uint16_t server_mtu = ATT_MAX_MTU; + uint16_t negotiated_mtu = std::min(client_mtu, server_mtu); + + // Update connection state + { + std::lock_guard lock(connections_mutex_); + auto it = connections_.find(conn_handle); + if (it != connections_.end()) { + it->second.mtu = negotiated_mtu; + } + } + + // Update transport MTU + transport_->set_mtu(conn_handle, negotiated_mtu); + + // Send response + send_mtu_exchange_rsp(conn_handle, server_mtu); + + LOG(Info, "MTU negotiated: " << negotiated_mtu); + + // Call user callback + if (on_mtu_exchanged) { + on_mtu_exchanged(conn_handle, negotiated_mtu); + } +} + +void BLEGATTServer::send_mtu_exchange_rsp(uint16_t conn_handle, uint16_t server_mtu) +{ + uint8_t rsp[3]; + rsp[0] = ATT_OP_MTU_RSP; + rsp[1] = server_mtu & 0xFF; + rsp[2] = (server_mtu >> 8) & 0xFF; + + transport_->send_pdu(conn_handle, rsp, sizeof(rsp)); +} + +// Find Information (UUID discovery) + +void BLEGATTServer::handle_find_info_req(uint16_t conn_handle, + const uint8_t* pdu, size_t len) +{ + if (len < 5) { + send_error_response(conn_handle, ATT_OP_FIND_INFO_REQ, 0x0000, + BLE_ATT_ERR_INVALID_PDU); + return; + } + + uint16_t start_handle = pdu[1] | (pdu[2] << 8); + uint16_t end_handle = pdu[3] | (pdu[4] << 8); + + LOG(Debug, "Find Information: start=0x" << std::hex << start_handle + << " end=0x" << end_handle << std::dec); + + if (start_handle == 0 || start_handle > end_handle) { + send_error_response(conn_handle, ATT_OP_FIND_INFO_REQ, start_handle, + BLE_ATT_ERR_INVALID_HANDLE); + return; + } + + // Get all attributes in range + auto attrs = db_.get_range(start_handle, end_handle); + + if (attrs.empty()) { + send_error_response(conn_handle, ATT_OP_FIND_INFO_REQ, start_handle, + BLE_ATT_ERR_ATTR_NOT_FOUND); + return; + } + + send_find_info_rsp(conn_handle, attrs); +} + +void BLEGATTServer::send_find_info_rsp(uint16_t conn_handle, + const std::vector& attrs) +{ + if (attrs.empty()) return; + + // Determine format (0x01 = 16-bit UUIDs, 0x02 = 128-bit UUIDs) + uint8_t format = attrs[0]->uuid.type == BT_UUID16 ? 0x01 : 0x02; + uint8_t handle_uuid_len = (format == 0x01) ? 4 : 18; + + std::vector rsp; + rsp.reserve(2 + attrs.size() * handle_uuid_len); + rsp.push_back(ATT_OP_FIND_INFO_RSP); + rsp.push_back(format); + + uint16_t mtu = transport_->get_mtu(conn_handle); + size_t max_data = mtu - 2; // Opcode + format + + for (const auto* attr : attrs) { + // All UUIDs in response must be same size + if ((attr->uuid.type == BT_UUID16 && format != 0x01) || + (attr->uuid.type == BT_UUID128 && format != 0x02)) { + break; + } + + // Check if we have space + if (rsp.size() + handle_uuid_len > max_data) { + break; + } + + // Add handle + rsp.push_back(attr->handle & 0xFF); + rsp.push_back((attr->handle >> 8) & 0xFF); + + // Add UUID + if (format == 0x01) { + uint16_t uuid16 = attr->uuid.value.u16; + rsp.push_back(uuid16 & 0xFF); + rsp.push_back((uuid16 >> 8) & 0xFF); + } else { + const uint8_t* uuid128_data = reinterpret_cast(&attr->uuid.value.u128); + rsp.insert(rsp.end(), uuid128_data, uuid128_data + 16); + } + } + + transport_->send_pdu(conn_handle, rsp.data(), rsp.size()); +} + +// Read By Type (characteristic/descriptor discovery) + +void BLEGATTServer::handle_read_by_type_req(uint16_t conn_handle, + const uint8_t* pdu, size_t len) +{ + if (len < 7) { + send_error_response(conn_handle, ATT_OP_READ_BY_TYPE_REQ, 0x0000, + BLE_ATT_ERR_INVALID_PDU); + return; + } + + uint16_t start_handle = pdu[1] | (pdu[2] << 8); + uint16_t end_handle = pdu[3] | (pdu[4] << 8); + + // Parse UUID (16-bit or 128-bit) + UUID type_uuid; + if (len == 7) { + // 16-bit UUID + type_uuid = UUID(pdu[5] | (pdu[6] << 8)); + } else if (len == 21) { + // 128-bit UUID + type_uuid = UUID(std::vector(pdu + 5, pdu + 21)); + } else { + send_error_response(conn_handle, ATT_OP_READ_BY_TYPE_REQ, 0x0000, + BLE_ATT_ERR_INVALID_PDU); + return; + } + + LOG(Debug, "Read By Type: start=0x" << std::hex << start_handle + << " end=0x" << end_handle << " type=" << type_uuid.str() << std::dec); + + if (start_handle == 0 || start_handle > end_handle) { + send_error_response(conn_handle, ATT_OP_READ_BY_TYPE_REQ, start_handle, + BLE_ATT_ERR_INVALID_HANDLE); + return; + } + + // Find attributes matching type in range + auto attrs = db_.find_by_type(start_handle, end_handle, type_uuid); + + if (attrs.empty()) { + send_error_response(conn_handle, ATT_OP_READ_BY_TYPE_REQ, start_handle, + BLE_ATT_ERR_ATTR_NOT_FOUND); + return; + } + + send_read_by_type_rsp(conn_handle, attrs); +} + +void BLEGATTServer::send_read_by_type_rsp(uint16_t conn_handle, + const std::vector& attrs) +{ + if (attrs.empty()) return; + + std::vector rsp; + rsp.push_back(ATT_OP_READ_BY_TYPE_RSP); + + uint16_t mtu = transport_->get_mtu(conn_handle); + + // Determine pair length from first attribute + std::vector first_value; + const auto* first_attr = attrs[0]; + + // Read attribute value + if (invoke_read_callback(first_attr, conn_handle, 0, first_value) != 0) { + first_value = first_attr->value; + } + + uint8_t pair_len = 2 + first_value.size(); // Handle + value + rsp.push_back(pair_len); + + size_t max_data = mtu - 2; // Opcode + length + + for (const auto* attr : attrs) { + if (rsp.size() + pair_len > max_data) { + break; + } + + // Add handle + rsp.push_back(attr->handle & 0xFF); + rsp.push_back((attr->handle >> 8) & 0xFF); + + // Add value + std::vector value; + if (invoke_read_callback(attr, conn_handle, 0, value) != 0) { + value = attr->value; + } + + // Truncate to pair_len if necessary + size_t value_len = std::min(value.size(), (size_t)(pair_len - 2)); + rsp.insert(rsp.end(), value.begin(), value.begin() + value_len); + } + + transport_->send_pdu(conn_handle, rsp.data(), rsp.size()); +} + +// Read By Group Type (primary service discovery) + +void BLEGATTServer::handle_read_by_group_type_req(uint16_t conn_handle, + const uint8_t* pdu, size_t len) +{ + if (len < 7) { + send_error_response(conn_handle, ATT_OP_READ_BY_GROUP_TYPE_REQ, 0x0000, + BLE_ATT_ERR_INVALID_PDU); + return; + } + + uint16_t start_handle = pdu[1] | (pdu[2] << 8); + uint16_t end_handle = pdu[3] | (pdu[4] << 8); + + // Parse UUID + UUID type_uuid; + if (len == 7) { + type_uuid = UUID(pdu[5] | (pdu[6] << 8)); + } else if (len == 21) { + type_uuid = UUID(std::vector(pdu + 5, pdu + 21)); + } else { + send_error_response(conn_handle, ATT_OP_READ_BY_GROUP_TYPE_REQ, 0x0000, + BLE_ATT_ERR_INVALID_PDU); + return; + } + + // Log request bytes + std::stringstream req_hex; + req_hex << std::hex << std::setfill('0'); + for (size_t i = 0; i < len; i++) { + req_hex << std::setw(2) << (int)pdu[i] << " "; + } + + LOG(Debug, "Read By Group Type: start=0x" << std::hex << start_handle + << " end=0x" << end_handle << " type=" << type_uuid.str() << std::dec + << " [" << req_hex.str() << "]"); + + // Only Primary Service (0x2800) is a grouping attribute + if (type_uuid != UUID(0x2800)) { + send_error_response(conn_handle, ATT_OP_READ_BY_GROUP_TYPE_REQ, + start_handle, BLE_ATT_ERR_UNSUPPORTED_GROUP_TYPE); + return; + } + + // Find primary services in range + auto attrs = db_.find_by_type(start_handle, end_handle, type_uuid); + + LOG(Debug, "Found " << attrs.size() << " services matching type " << type_uuid.str()); + + if (attrs.empty()) { + LOG(Debug, "No services found in range, sending Attribute Not Found error"); + send_error_response(conn_handle, ATT_OP_READ_BY_GROUP_TYPE_REQ, + start_handle, BLE_ATT_ERR_ATTR_NOT_FOUND); + return; + } + + // Check if this is a continuation request (start_handle > 1) + // If so, and we found services, this means we're continuing discovery + if (start_handle > 1) { + LOG(Debug, "Continuation request from handle " << start_handle); + } + + send_read_by_group_type_rsp(conn_handle, attrs); +} + +void BLEGATTServer::send_read_by_group_type_rsp(uint16_t conn_handle, + const std::vector& attrs) +{ + if (attrs.empty()) return; + + std::vector rsp; + rsp.push_back(ATT_OP_READ_BY_GROUP_TYPE_RSP); + + // Determine pair length from first attribute + // Format: start_handle(2) + end_handle(2) + uuid(2 or 16) + const auto* first_attr = attrs[0]; + // Check the service UUID size from the value (not the attribute UUID itself) + uint8_t uuid_size = first_attr->value.size(); + uint8_t pair_len = 4 + uuid_size; + rsp.push_back(pair_len); + + uint16_t mtu = transport_->get_mtu(conn_handle); + + LOG(Debug, "Building Read By Group Type response: uuid_size=" << (int)uuid_size + << " pair_len=" << (int)pair_len << " mtu=" << mtu); + + for (const auto* attr : attrs) { + // Check if we have room for another pair + if (rsp.size() + pair_len > mtu) { + LOG(Debug, "MTU limit reached: rsp.size()=" << rsp.size() << " pair_len=" << (int)pair_len << " mtu=" << mtu); + break; + } + + LOG(Debug, "Adding service: handle=" << attr->handle + << " end_handle=" << attr->end_group_handle + << " value_size=" << attr->value.size()); + + // Start handle + rsp.push_back(attr->handle & 0xFF); + rsp.push_back((attr->handle >> 8) & 0xFF); + + // End group handle + rsp.push_back(attr->end_group_handle & 0xFF); + rsp.push_back((attr->end_group_handle >> 8) & 0xFF); + + // Service UUID (from attribute value) + if (attr->value.size() >= uuid_size) { + rsp.insert(rsp.end(), attr->value.begin(), attr->value.begin() + uuid_size); + } else { + // Value is smaller than expected - pad with zeros or send error + LOG(Warning, "Service value size mismatch: expected=" << (int)uuid_size << " actual=" << attr->value.size()); + rsp.insert(rsp.end(), attr->value.begin(), attr->value.end()); + // Pad with zeros if needed + for (size_t i = attr->value.size(); i < uuid_size; i++) { + rsp.push_back(0); + } + } + } + + // Validate response format before sending + size_t expected_size = 2 + (pair_len * (rsp.size() - 2) / pair_len); + if (rsp.size() != expected_size && rsp.size() >= 2) { + LOG(Warning, "Response size mismatch: actual=" << rsp.size() + << " expected=" << expected_size + << " (may indicate incomplete service data)"); + } + + // Log hex dump of response + std::stringstream hex_dump; + hex_dump << std::hex << std::setfill('0'); + for (size_t i = 0; i < rsp.size(); i++) { + hex_dump << std::setw(2) << (int)rsp[i] << " "; + } + LOG(Debug, "Sending Read By Group Type response: " << rsp.size() << " bytes: " << hex_dump.str()); + + // Additional validation: Check that data length matches what length field claims + if (rsp.size() >= 2) { + uint8_t length_field = rsp[1]; + size_t num_entries = (rsp.size() - 2) / length_field; + size_t expected_total = 2 + (num_entries * length_field); + LOG(Debug, "Response validation: length_field=" << (int)length_field + << " num_entries=" << num_entries + << " calculated_size=" << expected_total + << " actual_size=" << rsp.size()); + } + + // CRITICAL FIX: Add small delay before sending response + // Android GATT client has a race condition where it queues the command AFTER sending the request. + // If our response arrives before Android queues the command (lines 497-498 in att_protocol.cc), + // gatt_cmd_dequeue() returns NULL and the response is silently dropped. + // This causes a 5-second timeout and retry. + // Delay ensures Android has time to queue the command before our response arrives. + usleep(20000); // 20ms delay + + transport_->send_pdu(conn_handle, rsp.data(), rsp.size()); +} + +// Read Request + +void BLEGATTServer::handle_read_req(uint16_t conn_handle, + const uint8_t* pdu, size_t len) +{ + if (len < 3) { + send_error_response(conn_handle, ATT_OP_READ_REQ, 0x0000, + BLE_ATT_ERR_INVALID_PDU); + return; + } + + uint16_t handle = pdu[1] | (pdu[2] << 8); + + LOG(Debug, "Read Request: handle=0x" << std::hex << handle << std::dec); + + // Get attribute + auto* attr = db_.get_attribute(handle); + if (!attr) { + send_error_response(conn_handle, ATT_OP_READ_REQ, handle, + BLE_ATT_ERR_INVALID_HANDLE); + return; + } + + // Check read permissions + if (!(attr->permissions & ATT_PERM_READ)) { + send_error_response(conn_handle, ATT_OP_READ_REQ, handle, + BLE_ATT_ERR_READ_NOT_PERM); + return; + } + + // Read value + std::vector value; + int result = invoke_read_callback(attr, conn_handle, 0, value); + + if (result != 0) { + send_error_response(conn_handle, ATT_OP_READ_REQ, handle, result); + return; + } + + send_read_rsp(conn_handle, value); +} + +void BLEGATTServer::send_read_rsp(uint16_t conn_handle, + const std::vector& value) +{ + uint16_t mtu = transport_->get_mtu(conn_handle); + size_t max_data = mtu - 1; // Opcode + + std::vector rsp; + rsp.reserve(1 + std::min(value.size(), max_data)); + rsp.push_back(ATT_OP_READ_RSP); + + // Truncate to MTU if necessary + size_t send_len = std::min(value.size(), max_data); + rsp.insert(rsp.end(), value.begin(), value.begin() + send_len); + + transport_->send_pdu(conn_handle, rsp.data(), rsp.size()); +} + +// Read Blob Request (for long attributes) + +void BLEGATTServer::handle_read_blob_req(uint16_t conn_handle, + const uint8_t* pdu, size_t len) +{ + if (len < 5) { + send_error_response(conn_handle, ATT_OP_READ_BLOB_REQ, 0x0000, + BLE_ATT_ERR_INVALID_PDU); + return; + } + + uint16_t handle = pdu[1] | (pdu[2] << 8); + uint16_t offset = pdu[3] | (pdu[4] << 8); + + LOG(Debug, "Read Blob Request: handle=0x" << std::hex << handle + << " offset=" << std::dec << offset); + + // Get attribute + auto* attr = db_.get_attribute(handle); + if (!attr) { + send_error_response(conn_handle, ATT_OP_READ_BLOB_REQ, handle, + BLE_ATT_ERR_INVALID_HANDLE); + return; + } + + // Check read permissions + if (!(attr->permissions & ATT_PERM_READ)) { + send_error_response(conn_handle, ATT_OP_READ_BLOB_REQ, handle, + BLE_ATT_ERR_READ_NOT_PERM); + return; + } + + // Read value + std::vector value; + int result = invoke_read_callback(attr, conn_handle, offset, value); + + if (result != 0) { + send_error_response(conn_handle, ATT_OP_READ_BLOB_REQ, handle, result); + return; + } + + // Check offset + if (offset >= value.size()) { + send_error_response(conn_handle, ATT_OP_READ_BLOB_REQ, handle, + BLE_ATT_ERR_INVALID_OFFSET); + return; + } + + // Send from offset + std::vector blob_value(value.begin() + offset, value.end()); + send_read_rsp(conn_handle, blob_value); +} + +// Write Request + +void BLEGATTServer::handle_write_req(uint16_t conn_handle, + const uint8_t* pdu, size_t len) +{ + if (len < 3) { + send_error_response(conn_handle, ATT_OP_WRITE_REQ, 0x0000, + BLE_ATT_ERR_INVALID_PDU); + return; + } + + uint16_t handle = pdu[1] | (pdu[2] << 8); + std::vector value(pdu + 3, pdu + len); + + LOG(Debug, "Write Request: handle=0x" << std::hex << handle + << std::dec << " len=" << value.size()); + + // Get attribute + auto* attr = db_.get_attribute(handle); + if (!attr) { + send_error_response(conn_handle, ATT_OP_WRITE_REQ, handle, + BLE_ATT_ERR_INVALID_HANDLE); + return; + } + + // Check write permissions + if (!(attr->permissions & ATT_PERM_WRITE)) { + send_error_response(conn_handle, ATT_OP_WRITE_REQ, handle, + BLE_ATT_ERR_WRITE_NOT_PERM); + return; + } + + // Special handling for CCCD (0x2902) + if (attr->uuid == UUID(0x2902) && value.size() == 2) { + uint16_t cccd_value = value[0] | (value[1] << 8); + handle_cccd_write(conn_handle, handle, cccd_value); + } + + // Write value + int result = invoke_write_callback(attr, conn_handle, value); + + if (result != 0) { + send_error_response(conn_handle, ATT_OP_WRITE_REQ, handle, result); + return; + } + + send_write_rsp(conn_handle); +} + +void BLEGATTServer::send_write_rsp(uint16_t conn_handle) +{ + uint8_t rsp = ATT_OP_WRITE_RSP; + transport_->send_pdu(conn_handle, &rsp, 1); +} + +// Write Command (no response) + +void BLEGATTServer::handle_write_cmd(uint16_t conn_handle, + const uint8_t* pdu, size_t len) +{ + if (len < 3) { + // Write command has no response, just ignore + return; + } + + uint16_t handle = pdu[1] | (pdu[2] << 8); + std::vector value(pdu + 3, pdu + len); + + LOG(Debug, "Write Command: handle=0x" << std::hex << handle + << std::dec << " len=" << value.size()); + + // Get attribute + auto* attr = db_.get_attribute(handle); + if (!attr) { + // No response for write command + return; + } + + // Check write permissions + if (!(attr->permissions & ATT_PERM_WRITE)) { + return; + } + + // Write value (ignore errors for write command) + invoke_write_callback(attr, conn_handle, value); +} + +// Prepare/Execute Write (for long writes) + +void BLEGATTServer::handle_prepare_write_req(uint16_t conn_handle, + const uint8_t* pdu, size_t len) +{ + // TODO: Implement prepared write queue + send_error_response(conn_handle, ATT_OP_PREPARE_WRITE_REQ, 0x0000, + BLE_ATT_ERR_REQ_NOT_SUPPORTED); +} + +void BLEGATTServer::handle_execute_write_req(uint16_t conn_handle, + const uint8_t* pdu, size_t len) +{ + // TODO: Implement prepared write queue + send_error_response(conn_handle, ATT_OP_EXECUTE_WRITE_REQ, 0x0000, + BLE_ATT_ERR_REQ_NOT_SUPPORTED); +} + +void BLEGATTServer::handle_signed_write_cmd(uint16_t conn_handle, + const uint8_t* pdu, size_t len) +{ + // TODO: Implement signature verification + LOG(Warning, "Signed write command not yet supported"); +} + +// Find By Type Value (for specific service discovery) + +void BLEGATTServer::handle_find_by_type_value_req(uint16_t conn_handle, + const uint8_t* pdu, size_t len) +{ + if (len < 7) { + send_error_response(conn_handle, ATT_OP_FIND_BY_TYPE_VALUE_REQ, 0x0000, + BLE_ATT_ERR_INVALID_PDU); + return; + } + + uint16_t start_handle = pdu[1] | (pdu[2] << 8); + uint16_t end_handle = pdu[3] | (pdu[4] << 8); + uint16_t type = pdu[5] | (pdu[6] << 8); + std::vector value(pdu + 7, pdu + len); + + LOG(Debug, "Find By Type Value: start=0x" << std::hex << start_handle + << " end=0x" << end_handle << " type=0x" << type << std::dec); + + // Find attributes + auto attrs = db_.find_by_type_value(start_handle, end_handle, + UUID(type), value); + + if (attrs.empty()) { + send_error_response(conn_handle, ATT_OP_FIND_BY_TYPE_VALUE_REQ, + start_handle, BLE_ATT_ERR_ATTR_NOT_FOUND); + return; + } + + // Build response + std::vector rsp; + rsp.push_back(ATT_OP_FIND_BY_TYPE_VALUE_RSP); + + uint16_t mtu = transport_->get_mtu(conn_handle); + size_t max_data = mtu - 1; + + for (const auto* attr : attrs) { + if (rsp.size() + 4 > max_data) { + break; + } + + // Found handle + rsp.push_back(attr->handle & 0xFF); + rsp.push_back((attr->handle >> 8) & 0xFF); + + // Group end handle + rsp.push_back(attr->end_group_handle & 0xFF); + rsp.push_back((attr->end_group_handle >> 8) & 0xFF); + } + + transport_->send_pdu(conn_handle, rsp.data(), rsp.size()); +} + +// Error Response + +void BLEGATTServer::send_error_response(uint16_t conn_handle, uint8_t opcode, + uint16_t handle, uint8_t error_code) +{ + uint8_t rsp[5]; + rsp[0] = ATT_OP_ERROR; + rsp[1] = opcode; + rsp[2] = handle & 0xFF; + rsp[3] = (handle >> 8) & 0xFF; + rsp[4] = error_code; + + transport_->send_pdu(conn_handle, rsp, sizeof(rsp)); + + LOG(Debug, "ATT Error: opcode=0x" << std::hex << (int)opcode + << " handle=0x" << handle + << " error=0x" << (int)error_code << std::dec); +} + +// CCCD handling + +void BLEGATTServer::handle_cccd_write(uint16_t conn_handle, uint16_t cccd_handle, + uint16_t value) +{ + LOG(Debug, "CCCD write: handle=0x" << std::hex << cccd_handle + << " value=0x" << value << std::dec); + + // Store CCCD value for this connection + std::lock_guard lock(connections_mutex_); + + auto it = connections_.find(conn_handle); + if (it == connections_.end()) { + return; + } + + // CCCD handle is always characteristic value handle + 1 + uint16_t char_handle = cccd_handle - 1; + it->second.cccd_values[char_handle] = value; + + if (value & 0x0001) { + LOG(Info, "Notifications enabled for characteristic 0x" + << std::hex << char_handle << std::dec); + } + if (value & 0x0002) { + LOG(Info, "Indications enabled for characteristic 0x" + << std::hex << char_handle << std::dec); + } + if (value == 0x0000) { + LOG(Info, "Notifications/indications disabled for characteristic 0x" + << std::hex << char_handle << std::dec); + } +} + +// Callback invocation helpers + +int BLEGATTServer::invoke_read_callback(const Attribute* attr, uint16_t conn_handle, + uint16_t offset, std::vector& out_data) +{ + if (attr->read_cb) { + return attr->read_cb(conn_handle, offset, out_data); + } + + // No callback - use static value + if (offset >= attr->value.size()) { + return BLE_ATT_ERR_INVALID_OFFSET; + } + + out_data.assign(attr->value.begin() + offset, attr->value.end()); + return 0; +} + +int BLEGATTServer::invoke_write_callback(Attribute* attr, uint16_t conn_handle, + const std::vector& data) +{ + if (attr->write_cb) { + return attr->write_cb(conn_handle, data); + } + + // No callback - update static value + attr->value = data; + return 0; +} + +} // namespace BLEPP + +#endif // BLEPP_SERVER_SUPPORT diff --git a/src/blestatemachine.cc b/src/blestatemachine.cc index 8096346..9104bc6 100644 --- a/src/blestatemachine.cc +++ b/src/blestatemachine.cc @@ -31,11 +31,15 @@ #include #include #include +#include +#include +#ifdef BLEPP_BLUEZ_SUPPORT #include #include #include #include +#endif #define log_fd(X) log_fd_(X, __LINE__, __FILE__) @@ -157,6 +161,7 @@ namespace BLEPP } +#ifdef BLEPP_BLUEZ_SUPPORT int log_l2cap_options(int sock) { //Read and log the socket setup. @@ -178,9 +183,10 @@ namespace BLEPP LOGVAR(Info,options.fcs); LOGVAR(Info,options.max_tx); LOGVAR(Info,options.txwin_size); - + return 0; } +#endif BLEGATTStateMachine::~BLEGATTStateMachine() { @@ -196,6 +202,7 @@ namespace BLEPP buf.resize(bufsize); } +#ifdef BLEPP_BLUEZ_SUPPORT void BLEGATTStateMachine::connect_blocking(const std::string& address) { connect(address, true); @@ -323,6 +330,7 @@ namespace BLEPP throw SocketConnectFailed(strerror(errno)); } } +#endif // BLEPP_BLUEZ_SUPPORT diff --git a/src/bluez_client_transport.cc b/src/bluez_client_transport.cc new file mode 100644 index 0000000..e5617c5 --- /dev/null +++ b/src/bluez_client_transport.cc @@ -0,0 +1,651 @@ +/* + * + * blepp - Implementation of the Generic ATTribute Protocol + * + * Copyright (C) 2024 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +#ifdef BLEPP_BLUEZ_SUPPORT + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace BLEPP +{ + +BlueZClientTransport::BlueZClientTransport() + : hci_dev_id_(-1) + , hci_fd_(-1) + , scanning_(false) +{ + ENTER(); +} + +BlueZClientTransport::~BlueZClientTransport() +{ + ENTER(); + + if (scanning_) { + stop_scan(); + } + + // Close all connections + for (auto& conn : connections_) { + if (conn.second.fd >= 0) { + close(conn.second.fd); + } + } + connections_.clear(); + + close_hci_device(); +} + +bool BlueZClientTransport::is_available() const +{ + LOG(Debug, "BlueZClientTransport::is_available() - checking availability"); + + // Check if we can open HCI device + int dev_id = hci_get_route(nullptr); + LOG(Debug, "hci_get_route(nullptr) returned: " << dev_id); + + if (dev_id < 0) { + LOG(Debug, "hci_get_route failed with errno=" << errno << " (" << strerror(errno) << "), trying device 0"); + dev_id = 0; + } + + // Try to bring up the device + int ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); + if (ctl < 0) { + LOG(Error, "Failed to create HCI socket for HCIDEVUP: " << strerror(errno)); + } else { + LOG(Debug, "Created HCI control socket, attempting HCIDEVUP on device " << dev_id); + if (ioctl(ctl, HCIDEVUP, dev_id) < 0) { + if (errno == EALREADY) { + LOG(Debug, "Device hci" << dev_id << " already up"); + } else { + LOG(Warning, "HCIDEVUP failed for hci" << dev_id << ": " << strerror(errno)); + } + } else { + LOG(Info, "Successfully brought up hci" << dev_id); + } + close(ctl); + + // After bringing up the device, get the route again to confirm it's available + LOG(Debug, "Re-checking hci_get_route() after HCIDEVUP..."); + int new_dev_id = hci_get_route(nullptr); + if (new_dev_id >= 0) { + dev_id = new_dev_id; + LOG(Debug, "hci_get_route() now returns: " << dev_id); + } + } + + // Try to open the device + LOG(Debug, "Attempting to open HCI device " << dev_id); + int fd = hci_open_dev(dev_id); + if (fd < 0) { + LOG(Error, "hci_open_dev(" << dev_id << ") failed: " << strerror(errno)); + return false; + } + + LOG(Info, "BlueZ transport is available (hci" << dev_id << ")"); + close(fd); + return true; +} + +int BlueZClientTransport::open_hci_device() +{ + ENTER(); + + if (hci_fd_ >= 0) { + return 0; // Already open + } + + // Get HCI device ID + hci_dev_id_ = hci_get_route(nullptr); + if (hci_dev_id_ < 0) { + // hci_get_route failed - try device 0 directly + LOG(Debug, "hci_get_route failed, trying hci0 directly"); + hci_dev_id_ = 0; + } + + // Bring up the HCI device if it's down + // This is equivalent to "hciconfig hci0 up" + int ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); + if (ctl >= 0) { + if (ioctl(ctl, HCIDEVUP, hci_dev_id_) < 0) { + if (errno != EALREADY) { + LOG(Warning, "Failed to bring up HCI device " << hci_dev_id_ + << ": " << strerror(errno)); + } + } else { + LOG(Info, "Brought up HCI device " << hci_dev_id_); + } + close(ctl); + + // After bringing up the device, get the route again to confirm it's available + int new_dev_id = hci_get_route(nullptr); + if (new_dev_id >= 0) { + hci_dev_id_ = new_dev_id; + LOG(Debug, "hci_get_route() now returns: " << hci_dev_id_); + } + } + + // Now try to open the device + hci_fd_ = hci_open_dev(hci_dev_id_); + if (hci_fd_ < 0) { + LOG(Error, "Failed to open HCI device " << hci_dev_id_ + << ": " << strerror(errno)); + return -1; + } + + // Set up HCI filter to receive LE meta events + struct hci_filter flt; + hci_filter_clear(&flt); + hci_filter_set_ptype(HCI_EVENT_PKT, &flt); + hci_filter_set_event(EVT_LE_META_EVENT, &flt); + hci_filter_set_event(EVT_CMD_COMPLETE, &flt); + hci_filter_set_event(EVT_CMD_STATUS, &flt); + + if (setsockopt(hci_fd_, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)) < 0) { + LOG(Warning, "Failed to set HCI filter: " << strerror(errno)); + } else { + LOG(Debug, "HCI filter set to receive LE meta events"); + } + + LOG(Debug, "Opened HCI device " << hci_dev_id_ << " (fd=" << hci_fd_ << ")"); + return 0; +} + +void BlueZClientTransport::close_hci_device() +{ + ENTER(); + + if (hci_fd_ >= 0) { + hci_close_dev(hci_fd_); + hci_fd_ = -1; + hci_dev_id_ = -1; + } +} + +int BlueZClientTransport::start_scan(const ScanParams& params) +{ + ENTER(); + LOG(Info, "start_scan() called"); + + if (scanning_) { + LOG(Warning, "Already scanning"); + return 0; + } + + LOG(Debug, "Opening HCI device for scanning..."); + if (open_hci_device() < 0) { + LOG(Error, "Failed to open HCI device"); + return -1; + } + + scan_params_ = params; + LOG(Debug, "Scan params: type=" << (int)params.scan_type + << " interval=" << params.interval_ms << "ms" + << " window=" << params.window_ms << "ms" + << " filter_duplicates=" << (int)params.filter_duplicates); + + // Set scan parameters + LOG(Debug, "Setting scan parameters..."); + if (set_scan_parameters(params) < 0) { + LOG(Error, "Failed to set scan parameters"); + return -1; + } + + // Enable scanning - hardware filtering only when Hardware mode is selected + LOG(Debug, "Enabling scanning..."); + bool hw_filter = (params.filter_duplicates == ScanParams::FilterDuplicates::Hardware); + if (set_scan_enable(true, hw_filter) < 0) { + LOG(Error, "Failed to enable scanning"); + return -1; + } + + scanning_ = true; + seen_devices_.clear(); + + LOG(Info, "BLE scanning started successfully on hci" << hci_dev_id_ << " (fd=" << hci_fd_ << ")"); + return 0; +} + +int BlueZClientTransport::stop_scan() +{ + ENTER(); + + if (!scanning_) { + return 0; + } + + if (set_scan_enable(false, false) < 0) { + LOG(Warning, "Failed to disable scanning"); + } + + scanning_ = false; + close_hci_device(); + LOG(Info, "BLE scanning stopped and HCI device closed"); + return 0; +} + +int BlueZClientTransport::set_scan_parameters(const ScanParams& params) +{ + ENTER(); + + uint8_t scan_type = static_cast(params.scan_type); + uint16_t interval = params.interval_ms * 1000 / 625; // Convert to 0.625ms units + uint16_t window = params.window_ms * 1000 / 625; + uint8_t own_type = 0x00; // Public address + uint8_t filter = static_cast(params.filter_policy); + + if (hci_le_set_scan_parameters(hci_fd_, scan_type, htobs(interval), + htobs(window), own_type, filter, 1000) < 0) { + LOG(Error, "Failed to set scan parameters: " << strerror(errno)); + return -1; + } + + LOG(Debug, "Set scan parameters: interval=" << params.interval_ms + << "ms window=" << params.window_ms << "ms"); + return 0; +} + +int BlueZClientTransport::set_scan_enable(bool enable, bool filter_duplicates) +{ + ENTER(); + + uint8_t enable_val = enable ? 0x01 : 0x00; + uint8_t filter_dup = filter_duplicates ? 0x01 : 0x00; + + if (hci_le_set_scan_enable(hci_fd_, enable_val, filter_dup, 1000) < 0) { + LOG(Error, "Failed to " << (enable ? "enable" : "disable") + << " scanning: " << strerror(errno)); + return -1; + } + + return 0; +} + +int BlueZClientTransport::get_advertisements(std::vector& ads, int timeout_ms) +{ + ENTER(); + static int call_count = 0; + static auto last_log = std::chrono::steady_clock::now(); + call_count++; + + // Log every 30 seconds + auto now = std::chrono::steady_clock::now(); + if (std::chrono::duration_cast(now - last_log).count() >= 30) { + LOG(Debug, "Scanner status: " << call_count << " polls, scanning=" << scanning_); + last_log = now; + } + + if (!scanning_) { + LOG(Error, "Not scanning - scan state is false!"); + return -1; + } + + ads.clear(); + return read_hci_events(ads, timeout_ms); +} + +int BlueZClientTransport::read_hci_events(std::vector& ads, int timeout_ms) +{ + ENTER(); + static int select_count = 0; + static int event_count = 0; + static int ad_count = 0; + + select_count++; + + fd_set rfds; + struct timeval tv; + + FD_ZERO(&rfds); + FD_SET(hci_fd_, &rfds); + + if (timeout_ms >= 0) { + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + } + + int ret = select(hci_fd_ + 1, &rfds, nullptr, nullptr, + timeout_ms >= 0 ? &tv : nullptr); + + if (ret < 0) { + LOG(Error, "select() failed: " << strerror(errno)); + return -1; + } + + if (ret == 0) { + // Timeout - no events available + return 0; + } + + // Read HCI event + uint8_t buf[HCI_MAX_EVENT_SIZE]; + ssize_t len = read(hci_fd_, buf, sizeof(buf)); + + if (len < 0) { + LOG(Error, "read() failed: " << strerror(errno)); + return -1; + } + + event_count++; + + // The first byte is the HCI packet type (0x04 = HCI_EVENT_PKT) + // Skip it and parse the actual event starting at buf[1] + if (len < 1 + HCI_EVENT_HDR_SIZE) { + LOG(Warning, "Packet too short: " << len << " bytes"); + return 0; + } + + // Parse HCI event (skip the packet type byte) + hci_event_hdr* hdr = (hci_event_hdr*)(buf + 1); + len -= 1; // Adjust length to account for skipped packet type byte + + // Log occasionally for debugging + if (event_count % 1000 == 1) { + LOG(Debug, "HCI event stats: " << event_count << " events, " + << ad_count << " advertisements received"); + } + + if (hdr->evt != EVT_LE_META_EVENT) { + // Not an LE meta event + return 0; + } + + // Parse LE meta event + // buf now points to: [Event Code][Param Len][Subevent Code][Data...] + // hdr = (hci_event_hdr*)(buf + 1), so: + // hdr->evt = buf[1] = Event Code (0x3e) + // hdr->plen = buf[2] = Param Length + // Subevent starts at buf[1] + sizeof(hci_event_hdr) = buf[1] + 2 = buf[3] + // So we need: buf + 1 + 2 for the subevent data + int num_ads = parse_advertising_report(buf + 3, // Skip: pkt_type(1) + evt(1) + plen(1) + len - 2, ads); // Adjust length + if (num_ads > 0) { + ad_count += num_ads; + LOG(Debug, "Received " << num_ads << " advertisement(s), total=" << ad_count); + } + return num_ads; +} + +int BlueZClientTransport::parse_advertising_report(const uint8_t* data, size_t len, + std::vector& ads) +{ + if (len < 1) { + LOG(Warning, "parse_advertising_report: packet too short: " << len << " bytes"); + return 0; + } + + uint8_t subevent = data[0]; + + if (subevent != EVT_LE_ADVERTISING_REPORT) { + LOG(Debug, "Ignoring non-advertising LE subevent: 0x" << std::hex << (int)subevent << std::dec); + return 0; + } + + if (len < 2) return 0; + + uint8_t num_reports = data[1]; + const uint8_t* ptr = data + 2; + const uint8_t* end = data + len; + + for (uint8_t i = 0; i < num_reports && ptr < end; i++) { + if (ptr + 10 > end) break; // Minimum report size + + AdvertisementData ad; + ad.event_type = ptr[0]; + ad.address_type = ptr[1]; + + // Parse address (6 bytes, little-endian) + char addr_str[18]; + ba2str((bdaddr_t*)(ptr + 2), addr_str); + ad.address = addr_str; + + uint8_t data_len = ptr[8]; + ptr += 9; + + if (ptr + data_len + 1 > end) break; + + // Copy advertising data + ad.data.assign(ptr, ptr + data_len); + ptr += data_len; + + // RSSI + ad.rssi = (int8_t)(*ptr); + ptr++; + + // Apply software duplicate filtering if Software mode is selected + if (scan_params_.filter_duplicates == ScanParams::FilterDuplicates::Software) { + if (seen_devices_.count(ad.address) > 0) { + continue; // Skip duplicate + } + seen_devices_.insert(ad.address); + } + + ads.push_back(ad); + + // Call callback if set + if (on_advertisement) { + on_advertisement(ad); + } + } + + return ads.size(); +} + +int BlueZClientTransport::connect(const ClientConnectionParams& params) +{ + ENTER(); + + // Create L2CAP socket + int sock = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (sock < 0) { + LOG(Error, "Failed to create L2CAP socket: " << strerror(errno)); + return -1; + } + + // Set up address + struct sockaddr_l2 addr = {}; + addr.l2_family = AF_BLUETOOTH; + addr.l2_cid = htobs(4); // ATT CID + addr.l2_bdaddr_type = params.peer_address_type; + + // Parse address string to bdaddr_t + if (str2ba(params.peer_address.c_str(), &addr.l2_bdaddr) < 0) { + LOG(Error, "Invalid Bluetooth address: " << params.peer_address); + close(sock); + return -1; + } + + // Connect + if (::connect(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + LOG(Error, "Failed to connect to " << params.peer_address + << ": " << strerror(errno)); + close(sock); + return -1; + } + + // Store connection info + ConnectionInfo info; + info.fd = sock; + info.mtu = 23; // Default ATT MTU + info.address = params.peer_address; + + connections_[sock] = info; + + LOG(Info, "Connected to " << params.peer_address << " (fd=" << sock << ")"); + + // Call callback if set + if (on_connected) { + on_connected(sock); + } + + return sock; +} + +int BlueZClientTransport::disconnect(int fd) +{ + ENTER(); + + auto it = connections_.find(fd); + if (it == connections_.end()) { + LOG(Warning, "Unknown connection fd=" << fd); + return -1; + } + + std::string addr = it->second.address; + close(fd); + connections_.erase(it); + + LOG(Info, "Disconnected from " << addr << " (fd=" << fd << ")"); + + // Call callback if set + if (on_disconnected) { + on_disconnected(fd); + } + + return 0; +} + +int BlueZClientTransport::get_fd(int fd) const +{ + // In BlueZ, the connection fd IS the file descriptor for select/poll + return fd; +} + +int BlueZClientTransport::send(int fd, const uint8_t* data, size_t len) +{ + auto it = connections_.find(fd); + if (it == connections_.end()) { + LOG(Error, "Invalid connection fd=" << fd); + return -1; + } + + ssize_t sent = write(fd, data, len); + if (sent < 0) { + LOG(Error, "Failed to send data on fd=" << fd << ": " << strerror(errno)); + return -1; + } + + return sent; +} + +int BlueZClientTransport::receive(int fd, uint8_t* data, size_t max_len) +{ + auto it = connections_.find(fd); + if (it == connections_.end()) { + LOG(Error, "Invalid connection fd=" << fd); + return -1; + } + + ssize_t received = read(fd, data, max_len); + if (received < 0) { + LOG(Error, "Failed to receive data on fd=" << fd << ": " << strerror(errno)); + return -1; + } + + if (received == 0) { + // Connection closed + LOG(Info, "Connection closed by peer (fd=" << fd << ")"); + return 0; + } + + // Call callback if set + if (on_data_received) { + on_data_received(fd, data, received); + } + + return received; +} + +uint16_t BlueZClientTransport::get_mtu(int fd) const +{ + auto it = connections_.find(fd); + if (it == connections_.end()) { + return 23; // Default ATT MTU + } + + return it->second.mtu; +} + +int BlueZClientTransport::set_mtu(int fd, uint16_t mtu) +{ + auto it = connections_.find(fd); + if (it == connections_.end()) { + LOG(Error, "Invalid connection fd=" << fd); + return -1; + } + + it->second.mtu = mtu; + LOG(Debug, "Set MTU=" << mtu << " for fd=" << fd); + return 0; +} + +// ============================================================================ +// MAC Address Operations +// ============================================================================ + +std::string BlueZClientTransport::get_mac_address() const +{ + ENTER(); + + // Return cached address if available + if (!mac_address_.empty()) { + return mac_address_; + } + + // Read address from HCI device + bdaddr_t bdaddr; + int rc = hci_devba(hci_dev_id_, &bdaddr); + if (rc < 0) { + LOG(Error, "Failed to read device address"); + return ""; + } + + // Convert bdaddr_t to string (BlueZ format is little-endian) + char addr_str[18]; + ba2str(&bdaddr, addr_str); + mac_address_ = addr_str; + + return mac_address_; +} + +} // namespace BLEPP + +#endif // BLEPP_BLUEZ_SUPPORT diff --git a/src/bluez_transport.cc b/src/bluez_transport.cc new file mode 100644 index 0000000..3db416a --- /dev/null +++ b/src/bluez_transport.cc @@ -0,0 +1,706 @@ +/* + * + * blepp - Implementation of the Generic ATTribute Protocol + * + * Copyright (C) 2024 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +#ifdef BLEPP_SERVER_SUPPORT + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace BLEPP +{ + +BlueZTransport::BlueZTransport(int hci_dev_id) + : hci_dev_id_(hci_dev_id) + , hci_fd_(-1) + , l2cap_listen_fd_(-1) + , advertising_(false) + , next_conn_handle_(1) +{ + ENTER(); + + // Open HCI device + hci_fd_ = open_hci_device(); + if (hci_fd_ < 0) { + throw std::runtime_error("Failed to open HCI device"); + } + + // Set up L2CAP server + if (setup_l2cap_server() < 0) { + close(hci_fd_); + throw std::runtime_error("Failed to set up L2CAP server"); + } + + LOG(Info, "BlueZTransport initialized on hci" << hci_dev_id_); +} + +BlueZTransport::~BlueZTransport() +{ + ENTER(); + cleanup(); +} + +int BlueZTransport::send_ssv_acl_routing_command(int fd) +{ + ENTER(); + + LOG(Info, "Sending SSV6158 ACL routing command (0xFC06)..."); + + // SSV6158 vendor-specific HCI command: 0xFC06 + // This command tells the SSV6158 firmware to route ACL data and events + // to/from the external host instead of handling them internally. + // Without this command, the firmware accepts outgoing ACL packets but + // doesn't actually transmit them over the BLE radio. + // + // Command structure: + // OGF: 0x3F (vendor-specific) + // OCF: 0x06 (SSV_ACL_EVT_TO_EXTERNAL_HOST) + // Parameters: none + + struct hci_request rq; + memset(&rq, 0, sizeof(rq)); + rq.ogf = 0x3F; // OGF_VENDOR_CMD + rq.ocf = 0x06; // SSV_ACL_EVT_TO_EXTERNAL_HOST + rq.cparam = nullptr; + rq.clen = 0; + rq.rparam = nullptr; + rq.rlen = 0; + + int ret = hci_send_req(fd, &rq, 1000); // 1 second timeout + if (ret < 0) { + LOG(Warning, "Failed to send SSV ACL routing command: " << strerror(errno)); + return -1; + } + + LOG(Info, "SSV6158 ACL routing command sent successfully"); + + // Give firmware a moment to process the command + usleep(100000); // 100ms delay + + return 0; +} + +int BlueZTransport::open_hci_device() +{ + ENTER(); + + // Get device ID if not specified + int dev_id = hci_dev_id_; + if (dev_id < 0) { + dev_id = hci_get_route(nullptr); + if (dev_id < 0) { + LOG(Error, "No Bluetooth adapter found"); + return -1; + } + } + + // Open HCI socket + int fd = hci_open_dev(dev_id); + if (fd < 0) { + LOG(Error, "Failed to open HCI device hci" << dev_id << ": " << strerror(errno)); + return -1; + } + + LOG(Info, "Opened HCI device hci" << dev_id << " (fd=" << fd << ")"); + + // Send SSV6158 vendor command to enable ACL/Event routing to external host + // This is required for SSV6158 firmware to actually transmit ACL data packets + // over the air when operating as a BLE peripheral/server. + // Command: 0xFC06 (OGF=0x3F vendor-specific, OCF=0x06) + LOG(Info, "About to call send_ssv_acl_routing_command..."); + int ssv_result = send_ssv_acl_routing_command(fd); + LOG(Info, "send_ssv_acl_routing_command returned: " << ssv_result); + if (ssv_result < 0) { + LOG(Warning, "Failed to send SSV ACL routing command (not SSV6158 chip?)"); + // Don't fail initialization - this is only needed for SSV6158 + } + + return fd; +} + +int BlueZTransport::setup_l2cap_server() +{ + ENTER(); + + // Create L2CAP socket + l2cap_listen_fd_ = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (l2cap_listen_fd_ < 0) { + LOG(Error, "Failed to create L2CAP socket: " << strerror(errno)); + return -1; + } + + // Bind to ATT CID (Channel ID 4) + struct sockaddr_l2 addr = {}; + addr.l2_family = AF_BLUETOOTH; + bdaddr_t any_addr = {{0, 0, 0, 0, 0, 0}}; + bacpy(&addr.l2_bdaddr, &any_addr); + addr.l2_cid = htobs(4); // ATT protocol CID + addr.l2_bdaddr_type = BDADDR_LE_PUBLIC; + + if (bind(l2cap_listen_fd_, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + LOG(Error, "Failed to bind L2CAP socket: " << strerror(errno)); + close(l2cap_listen_fd_); + l2cap_listen_fd_ = -1; + return -1; + } + + // Listen for connections + if (listen(l2cap_listen_fd_, 5) < 0) { + LOG(Error, "Failed to listen on L2CAP socket: " << strerror(errno)); + close(l2cap_listen_fd_); + l2cap_listen_fd_ = -1; + return -1; + } + + // Set non-blocking + int flags = fcntl(l2cap_listen_fd_, F_GETFL, 0); + fcntl(l2cap_listen_fd_, F_SETFL, flags | O_NONBLOCK); + + LOG(Info, "L2CAP server listening on CID 4 (fd=" << l2cap_listen_fd_ << ")"); + return 0; +} + +int BlueZTransport::start_advertising(const AdvertisingParams& params) +{ + ENTER(); + + if (advertising_) { + LOG(Warning, "Already advertising"); + return 0; + } + + // Set advertising parameters + if (set_advertising_parameters(params) < 0) { + LOG(Error, "Failed to set advertising parameters"); + return -1; + } + + // Set advertising data + if (set_advertising_data(params) < 0) { + LOG(Error, "Failed to set advertising data"); + return -1; + } + + // Set scan response data + if (set_scan_response_data(params) < 0) { + LOG(Error, "Failed to set scan response data"); + return -1; + } + + // Enable advertising + if (set_advertising_enable(true) < 0) { + LOG(Error, "Failed to enable advertising"); + return -1; + } + + advertising_ = true; + LOG(Info, "Advertising started: " << params.device_name); + return 0; +} + +int BlueZTransport::stop_advertising() +{ + ENTER(); + + if (!advertising_) { + return 0; + } + + if (set_advertising_enable(false) < 0) { + LOG(Error, "Failed to disable advertising"); + return -1; + } + + advertising_ = false; + LOG(Info, "Advertising stopped"); + return 0; +} + +bool BlueZTransport::is_advertising() const +{ + return advertising_; +} + +int BlueZTransport::set_advertising_parameters(const AdvertisingParams& params) +{ + ENTER(); + + // HCI LE Set Advertising Parameters command + le_set_advertising_parameters_cp cmd; + cmd.min_interval = htobs(params.min_interval_ms * 1000 / 625); // Convert ms to 0.625ms units + cmd.max_interval = htobs(params.max_interval_ms * 1000 / 625); + cmd.advtype = 0x00; // ADV_IND - connectable undirected advertising + cmd.own_bdaddr_type = LE_PUBLIC_ADDRESS; + cmd.direct_bdaddr_type = LE_PUBLIC_ADDRESS; + memset(&cmd.direct_bdaddr, 0, sizeof(cmd.direct_bdaddr)); + cmd.chan_map = 0x07; // All channels + cmd.filter = 0x00; // No filter policy + + struct hci_request rq; + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LE_CTL; + rq.ocf = OCF_LE_SET_ADVERTISING_PARAMETERS; + rq.cparam = &cmd; + rq.clen = LE_SET_ADVERTISING_PARAMETERS_CP_SIZE; + rq.rparam = nullptr; + rq.rlen = 0; + + int ret = hci_send_req(hci_fd_, &rq, 1000); + if (ret < 0) { + LOG(Error, "hci_le_set_advertising_parameters failed: " << strerror(errno)); + return -1; + } + + LOG(Debug, "Set advertising interval: " << params.min_interval_ms << "-" << params.max_interval_ms << "ms"); + return 0; +} + +int BlueZTransport::build_advertising_data(const AdvertisingParams& params, + uint8_t* data, uint8_t* len) +{ + ENTER(); + + uint8_t* ptr = data; + + // If custom advertising data is provided, use it + if (params.advertising_data_len > 0) { + memcpy(data, params.advertising_data, params.advertising_data_len); + *len = params.advertising_data_len; + return 0; + } + + // Otherwise build standard advertising data + + // Flags + *ptr++ = 0x02; // Length + *ptr++ = 0x01; // Type: Flags + *ptr++ = 0x06; // LE General Discoverable, BR/EDR not supported + + // Complete list of 16-bit Service UUIDs (if any) + // Note: 128-bit UUIDs are placed in scan response data due to size constraints + if (!params.service_uuids.empty()) { + uint8_t* len_ptr = ptr++; + *ptr++ = 0x03; // Type: Complete list of 16-bit UUIDs + + uint8_t uuid_count = 0; + for (const auto& uuid : params.service_uuids) { + if (uuid.type == BT_UUID16) { + *ptr++ = uuid.value.u16 & 0xFF; + *ptr++ = (uuid.value.u16 >> 8) & 0xFF; + uuid_count++; + } + } + + // Only keep this section if we actually added UUIDs + if (uuid_count > 0) { + *len_ptr = 1 + uuid_count * 2; // Type byte + UUIDs + } else { + // No 16-bit UUIDs, revert the pointer + ptr = len_ptr; + } + } + + // Device name + if (!params.device_name.empty()) { + size_t name_len = params.device_name.length(); + if (name_len > 29 - (ptr - data)) { + name_len = 29 - (ptr - data); // Truncate if needed + } + + *ptr++ = 1 + name_len; // Length + *ptr++ = 0x09; // Type: Complete local name + memcpy(ptr, params.device_name.c_str(), name_len); + ptr += name_len; + } + + *len = ptr - data; + LOG(Debug, "Built advertising data: " << (int)*len << " bytes"); + return 0; +} + +int BlueZTransport::set_advertising_data(const AdvertisingParams& params) +{ + ENTER(); + + uint8_t data[31]; + uint8_t len; + + if (build_advertising_data(params, data, &len) < 0) { + return -1; + } + + // HCI LE Set Advertising Data command + le_set_advertising_data_cp cmd; + cmd.length = len; + memset(cmd.data, 0, sizeof(cmd.data)); + memcpy(cmd.data, data, len); + + struct hci_request rq; + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LE_CTL; + rq.ocf = OCF_LE_SET_ADVERTISING_DATA; + rq.cparam = &cmd; + rq.clen = LE_SET_ADVERTISING_DATA_CP_SIZE; + rq.rparam = nullptr; + rq.rlen = 0; + + int ret = hci_send_req(hci_fd_, &rq, 1000); + if (ret < 0) { + LOG(Error, "hci_le_set_advertising_data failed: " << strerror(errno)); + return -1; + } + + return 0; +} + +int BlueZTransport::build_scan_response_data(const AdvertisingParams& params, + uint8_t* data, uint8_t* len) +{ + ENTER(); + + uint8_t* ptr = data; + + // If custom scan response data is provided, use it + if (params.scan_response_data_len > 0) { + memcpy(data, params.scan_response_data, params.scan_response_data_len); + *len = params.scan_response_data_len; + return 0; + } + + // Otherwise, build scan response with 128-bit UUIDs if present + std::vector uuid128_list; + for (const auto& uuid : params.service_uuids) { + if (uuid.type == BT_UUID128) { + uuid128_list.push_back(uuid); + } + } + + if (!uuid128_list.empty()) { + // Add 128-bit UUIDs to scan response + size_t space_needed = 2 + uuid128_list.size() * 16; // Length + Type + UUIDs + if (space_needed <= 31) { + uint8_t* len_ptr = ptr++; + *ptr++ = 0x07; // Type: Complete list of 128-bit UUIDs + + for (const auto& uuid : uuid128_list) { + // 128-bit UUIDs in advertising are in little-endian (different from ATT!) + // So we use the data as-is + memcpy(ptr, uuid.value.u128.data, 16); + ptr += 16; + } + + *len_ptr = 1 + uuid128_list.size() * 16; // Type byte + UUIDs + } else { + LOG(Warning, "Not enough space for 128-bit UUIDs in scan response"); + } + } + + *len = ptr - data; + LOG(Debug, "Built scan response data: " << (int)*len << " bytes"); + return 0; +} + +int BlueZTransport::set_scan_response_data(const AdvertisingParams& params) +{ + ENTER(); + + uint8_t data[31]; + uint8_t len; + + if (build_scan_response_data(params, data, &len) < 0) { + return -1; + } + + // Only send scan response if there's data + if (len > 0) { + le_set_scan_response_data_cp cmd; + cmd.length = len; + memset(cmd.data, 0, sizeof(cmd.data)); + memcpy(cmd.data, data, len); + + struct hci_request rq; + memset(&rq, 0, sizeof(rq)); + rq.ogf = OGF_LE_CTL; + rq.ocf = OCF_LE_SET_SCAN_RESPONSE_DATA; + rq.cparam = &cmd; + rq.clen = LE_SET_SCAN_RESPONSE_DATA_CP_SIZE; + rq.rparam = nullptr; + rq.rlen = 0; + + int ret = hci_send_req(hci_fd_, &rq, 1000); + if (ret < 0) { + LOG(Error, "hci_le_set_scan_response_data failed: " << strerror(errno)); + return -1; + } + } + + return 0; +} + +int BlueZTransport::set_advertising_enable(bool enable) +{ + ENTER(); + + int ret = hci_le_set_advertise_enable(hci_fd_, enable ? 1 : 0, 1000); + if (ret < 0) { + LOG(Error, "hci_le_set_advertise_enable failed: " << strerror(errno)); + return -1; + } + + LOG(Debug, "Advertising " << (enable ? "enabled" : "disabled")); + return 0; +} + +int BlueZTransport::accept_connection() +{ + ENTER(); + return accept_l2cap_connection(); +} + +int BlueZTransport::accept_l2cap_connection() +{ + ENTER(); + + struct sockaddr_l2 addr = {}; + socklen_t addr_len = sizeof(addr); + + int client_fd = accept(l2cap_listen_fd_, (struct sockaddr*)&addr, &addr_len); + if (client_fd < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return 0; // No connection pending + } + LOG(Error, "accept() failed: " << strerror(errno)); + return -1; + } + + // Get peer address + char peer_addr[18]; + ba2str(&addr.l2_bdaddr, peer_addr); + + // Create connection entry + uint16_t conn_handle = next_conn_handle_++; + Connection conn = { + .fd = client_fd, + .conn_handle = conn_handle, + .peer_addr = peer_addr, + .mtu = 23 // Default ATT MTU + }; + + connections_[conn_handle] = conn; + + LOG(Info, "Client connected: " << peer_addr << " (handle=" << conn_handle << ")"); + + // Notify callback + if (on_connected) { + ConnectionParams params; + params.conn_handle = conn_handle; + params.peer_address = peer_addr; + params.peer_address_type = addr.l2_bdaddr_type; + params.mtu = 23; + on_connected(params); + } + + return 0; +} + +int BlueZTransport::disconnect(uint16_t conn_handle) +{ + ENTER(); + + auto it = connections_.find(conn_handle); + if (it == connections_.end()) { + LOG(Warning, "Connection handle " << conn_handle << " not found"); + return -1; + } + + close(it->second.fd); + connections_.erase(it); + + LOG(Info, "Disconnected connection handle " << conn_handle); + + if (on_disconnected) { + on_disconnected(conn_handle); + } + + return 0; +} + +int BlueZTransport::get_fd() const +{ + return l2cap_listen_fd_; +} + +int BlueZTransport::send_pdu(uint16_t conn_handle, const uint8_t* data, size_t len) +{ + auto it = connections_.find(conn_handle); + if (it == connections_.end()) { + LOG(Error, "Connection handle " << conn_handle << " not found"); + return -1; + } + + // Log hex dump of data being sent + std::stringstream hex_dump; + hex_dump << std::hex << std::setfill('0'); + for (size_t i = 0; i < len; i++) { + hex_dump << std::setw(2) << (int)data[i] << " "; + } + LOG(Debug, "Sending " << std::dec << len << " bytes: " << hex_dump.str()); + + ssize_t sent = send(it->second.fd, data, len, 0); + if (sent < 0) { + LOG(Error, "send() failed: " << strerror(errno)); + return -1; + } + + if ((size_t)sent != len) { + LOG(Warning, "Partial send: sent=" << sent << " expected=" << len); + } else { + LOG(Debug, "Successfully sent " << sent << " bytes to connection " << conn_handle); + } + + return sent; +} + +int BlueZTransport::recv_pdu(uint16_t conn_handle, uint8_t* buf, size_t len) +{ + auto it = connections_.find(conn_handle); + if (it == connections_.end()) { + LOG(Error, "Connection handle " << conn_handle << " not found"); + return -1; + } + + ssize_t received = recv(it->second.fd, buf, len, 0); + if (received < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return 0; // No data available + } + // Connection errors - disconnect and stop trying + if (errno == ENOTCONN || errno == ECONNRESET || errno == EPIPE) { + LOG(Info, "Connection " << conn_handle << " error: " << strerror(errno) << " - disconnecting"); + disconnect(conn_handle); + return 0; + } + LOG(Error, "recv() failed: " << strerror(errno)); + return -1; + } + + if (received == 0) { + LOG(Info, "Connection " << conn_handle << " closed by peer"); + disconnect(conn_handle); + return 0; + } + + LOG(Debug, "Received " << received << " bytes from connection " << conn_handle); + return received; +} + +int BlueZTransport::set_mtu(uint16_t conn_handle, uint16_t mtu) +{ + auto it = connections_.find(conn_handle); + if (it == connections_.end()) { + return -1; + } + + it->second.mtu = mtu; + LOG(Debug, "Set MTU to " << mtu << " for connection " << conn_handle); + + if (on_mtu_changed) { + on_mtu_changed(conn_handle, mtu); + } + + return 0; +} + +uint16_t BlueZTransport::get_mtu(uint16_t conn_handle) const +{ + auto it = connections_.find(conn_handle); + if (it == connections_.end()) { + return 23; // Default + } + return it->second.mtu; +} + +int BlueZTransport::process_events() +{ + // Check for incoming connections + accept_l2cap_connection(); + + // Check for data on existing connections + for (auto& pair : connections_) { + uint8_t buf[512]; + int received = recv_pdu(pair.first, buf, sizeof(buf)); + if (received > 0 && on_data_received) { + on_data_received(pair.first, buf, received); + } + } + + return 0; +} + +void BlueZTransport::cleanup() +{ + ENTER(); + + // Stop advertising + if (advertising_) { + stop_advertising(); + } + + // Close all connections + for (auto& pair : connections_) { + close(pair.second.fd); + } + connections_.clear(); + + // Close sockets + if (l2cap_listen_fd_ >= 0) { + close(l2cap_listen_fd_); + l2cap_listen_fd_ = -1; + } + + if (hci_fd_ >= 0) { + close(hci_fd_); + hci_fd_ = -1; + } + + LOG(Info, "BlueZTransport cleaned up"); +} + +} // namespace BLEPP + +#endif // BLEPP_SERVER_SUPPORT diff --git a/src/lescan.cc b/src/lescan.cc index 46aab09..0d996e2 100644 --- a/src/lescan.cc +++ b/src/lescan.cc @@ -1,13 +1,27 @@ #include "blepp/lescan.h" +#include "blepp/bleclienttransport.h" #include "blepp/pretty_printers.h" #include "blepp/gap.h" -#include #include #include #include #include +#ifdef BLEPP_BLUEZ_SUPPORT +#include +#endif + +// HCI packet types (standard Bluetooth HCI constants) +#ifndef HCI_EVENT_PKT +#define HCI_EVENT_PKT 0x04 +#endif + +// HCI event codes (standard Bluetooth HCI constants) +#ifndef EVT_LE_META_EVENT +#define EVT_LE_META_EVENT 0x3E +#endif + namespace BLEPP { class Span @@ -97,366 +111,153 @@ namespace BLEPP return to_hex(s.data(), s.size()); } - HCIScanner::Error::Error(const std::string& why) + // HCI Scanner error implementation - always available + HCIScannerError::HCIScannerError(const std::string& why) :std::runtime_error(why) - { - LOG(LogLevels::Error, why); - } - - HCIScanner::IOError::IOError(const std::string& why, int errno_val) - :Error(why + ": " + strerror(errno_val)) - { - } - - HCIScanner::HCIScanner(bool start_scan) - :HCIScanner(start_scan, FilterDuplicates::Both, ScanType::Active) { + LOG(LogLevels::Error, why); } + // =================================================================== + // BLEScanner - Transport-agnostic scanner implementation + // =================================================================== - HCIScanner::HCIScanner(bool start_scan, FilterDuplicates filtering, ScanType st, std::string device) + BLEScanner::FilterEntry::FilterEntry(const AdvertisingResponse& ad) + :mac_address(ad.address), type(static_cast(ad.type)) { - if(filtering == FilterDuplicates::Hardware || filtering == FilterDuplicates::Both) - hardware_filtering = true; - else - hardware_filtering = false; - - if(filtering == FilterDuplicates::Software || filtering == FilterDuplicates::Both) - software_filtering = true; - else - software_filtering = false; - - scan_type=st; - - int dev_id = 0; - if (device == "") { - //Get a route to any(?) BTLE adapter (?) - dev_id = hci_get_route(NULL); - } - else { - dev_id = hci_devid(device.c_str()); - } - if (dev_id < 0) { - throw HCIError("Error obtaining HCI device ID"); - } - - //Open the device - //FIXME check errors - hci_fd.set(hci_open_dev(dev_id)); - - if(start_scan) - start(); } - HCIScanner::HCIScanner() - :HCIScanner(true) + bool BLEScanner::FilterEntry::operator<(const FilterEntry& e) const { - + if(mac_address < e.mac_address) + return true; + else if(mac_address > e.mac_address) + return false; + else + return type < e.type; } - void HCIScanner::start() + BLEScanner::BLEScanner(BLEClientTransport* transport) + : transport_(transport) + , running_(false) + , filter_mode_(FilterDuplicates::Off) { - ENTER(); - if(running) - { - LOG(Trace, "Scanner is already running"); - return; + if (!transport_) { + throw std::invalid_argument("BLEScanner: transport cannot be null"); } + } - //Cadged from the hcitool sources. No idea what - //these mean - uint16_t interval = htobs(0x0010); - uint16_t window = htobs(0x0010); - - //Address for the adapter (I think). Use a public address. - //other option is random. Works either way it seems. - uint8_t own_type = LE_PUBLIC_ADDRESS; - - //Don't use a whitelist (?) - uint8_t filter_policy = 0x00; - - - //The 10,000 thing seems to be some sort of retry logic timeout - //thing. Number of miliseconds, but there are multiple tries - //where it gets reduced by 10ms each time. It's a bit odd. - int err = hci_le_set_scan_parameters(hci_fd, static_cast(scan_type), interval, window, - own_type, filter_policy, 10000); - if(err < 0) - { - if(errno != EIO) - throw IOError("Setting scan parameters", errno); - else - { - //If the BLE device is already set to scanning, then we get an IO error. So - //try turning it off and trying again. This bad state would happen, if, to pick - //like a *totally* hypothetical example, the program segged-out during scanning - //and so never cleaned up properly. - LOG(LogLevels::Warning, "Received I/O error while setting scan parameters."); - LOG(LogLevels::Warning, "Switching off HCI scanner"); - err = hci_le_set_scan_enable(hci_fd, 0x00, 0x00, 10000); - if(err < 0) - throw IOError("Error disabling scan:", errno); - - - err = hci_le_set_scan_parameters(hci_fd, static_cast(scan_type), interval, window, own_type, filter_policy, 10000); - if(err < 0) - throw IOError("Error disabling scan:", errno); - else - LOG(LogLevels::Warning, "Setting scan parameters worked this time."); - - + BLEScanner::~BLEScanner() + { + if (running_) { + try { + stop(); + } catch (...) { + // Suppress exceptions in destructor } } - - LOG(LogLevels::Info, "Starting scanner"); - scanned_devices.clear(); - - //Removal of duplicates done on the adapter itself - uint8_t filter_dup = hardware_filtering?0x01:0x00; - - - //Set up the filters. - socklen_t olen = sizeof(old_filter); - if (getsockopt(hci_fd, SOL_HCI, HCI_FILTER, &old_filter, &olen) < 0) - throw IOError("Getting HCI filter socket options", errno); - - - //Magic incantations to get scan events - struct hci_filter nf; - hci_filter_clear(&nf); - hci_filter_set_ptype(HCI_EVENT_PKT, &nf); - hci_filter_set_event(EVT_LE_META_EVENT, &nf); - if (setsockopt(hci_fd, SOL_HCI, HCI_FILTER, &nf, sizeof(nf)) < 0) - throw IOError("Setting HCI filter socket options", errno); - - - //device disable/enable duplictes ???? - err = hci_le_set_scan_enable(hci_fd, 0x01, filter_dup, 10000); - if(err < 0) - throw IOError("Enabling scan", errno); - - running=true; } - void HCIScanner::stop() + void BLEScanner::start(const ScanParams& params) { ENTER(); - if(!running) - { + if (running_) { + LOG(Trace, "Scanner is already running"); return; } - LOG(LogLevels::Info, "Cleaning up HCI scanner"); - int err = hci_le_set_scan_enable(hci_fd, 0x00, 0x00, 10000); - - if(err < 0) - throw IOError("Error disabling scan:", errno); - - err = setsockopt(hci_fd, SOL_HCI, HCI_FILTER, &old_filter, sizeof(old_filter)); + // Store the filter mode from params for use in get_advertisements() + filter_mode_ = params.filter_duplicates; - if(err < 0) - throw IOError("Error resetting HCI socket:", errno); + int result = transport_->start_scan(params); + if (result < 0) { + throw HCIScannerError("Failed to start scan"); + } - running = false; + scanned_devices_.clear(); + running_ = true; + LOG(Info, "BLE scanner started"); } - int HCIScanner::get_fd() const + void BLEScanner::start(bool passive) { - return hci_fd; + ScanParams params; + params.scan_type = passive ? ScanParams::ScanType::Passive : ScanParams::ScanType::Active; + params.interval_ms = 500; // 500ms for WiFi coexistence + params.window_ms = 150; // 10% duty cycle (~50ms) + params.filter_duplicates = filter_mode_; // Use constructor-specified filter mode + start(params); } - - HCIScanner::~HCIScanner() + void BLEScanner::stop() { - try - { - stop(); - } - catch(IOError&) - { + ENTER(); + if (!running_) { + return; } - } - - HCIScanner::FilterEntry::FilterEntry(const AdvertisingResponse& a) - :mac_address(a.address),type(static_cast(a.type)) - {} - - bool HCIScanner::FilterEntry::operator<(const FilterEntry& f) const - { - //Simple lexi-sorting - if(mac_address < f.mac_address) - return true; - else if(mac_address == f.mac_address) - return type < f.type; - else - return false; - } - std::vector HCIScanner::read_with_retry() - { - int len; - std::vector buf(HCI_MAX_EVENT_SIZE); - - - while((len = read(hci_fd, buf.data(), buf.size())) < 0) - { - if(errno == EAGAIN) - continue; - else if(errno == EINTR) - throw Interrupted("interrupted reading HCI packet"); - else - throw IOError("reading HCI packet", errno); + int result = transport_->stop_scan(); + if (result < 0) { + throw HCIScannerError("Failed to stop scan"); } - buf.resize(len); - return buf; + running_ = false; + LOG(Info, "BLE scanner stopped"); } - std::vector HCIScanner::get_advertisements() + std::vector BLEScanner::get_advertisements(int timeout_ms) { - std::vector adverts = parse_packet(read_with_retry()); - - if(software_filtering) - { - std::vector filtered; + if (!running_) { + throw HCIScannerError("Scanner not running"); + } - for(const auto& a: adverts) - { - auto r = scanned_devices.insert(FilterEntry(a)); + // Get advertisements from transport + std::vector ads; + int result = transport_->get_advertisements(ads, timeout_ms); + if (result < 0) { + throw HCIScannerError("Failed to get advertisements"); + } - if(r.second) - filtered.emplace_back(std::move(a)); - else - LOG(Debug, "Entry " << a.address << " " << static_cast(a.type) << " found already"); + // Convert AdvertisementData to AdvertisingResponse + std::vector responses; + for (const auto& ad : ads) { + AdvertisingResponse resp; + resp.address = ad.address; + resp.type = static_cast(ad.event_type); + resp.rssi = ad.rssi; + + // Store raw packet data for later parsing if needed + // The transport's AdvertisementData.data contains the raw advertising payload + // Applications can parse this using parse_advertisement_packet() if needed + resp.raw_packet.push_back(ad.data); + + // Software filtering if enabled + if (filter_mode_ == FilterDuplicates::Software) { + FilterEntry entry(resp); + if (scanned_devices_.count(entry)) { + continue; // Skip duplicate + } + scanned_devices_.insert(entry); } - return filtered; + responses.push_back(std::move(resp)); } - else - return adverts; + + return responses; } - /* - Hello comment-reader! - - This is a class for dealing with device scanning. The scans are done - using the HCI (Host Controller Interface). Interstingly, the HCI is - well specified even down to the transport layer (RS232 syle, USB, - SDIO and some sort of SLIP based serial). The HCI sometimes unpacks - and aggregates PDUs (the data part of packets) before sending them - for some reason, so you need to look in the HCI part of the spec, - not the PDU part of the spec. - - The HCI is also semi-autonomous, and can be instructed to do things like - sctive scanning (where it queries and reports additional information, - typically the device name) and filtering of duplicated advertising - events. This is great for low power draw because the host CPU doesn't need - to wake up to do menial or unimportant things. - - Also, the HCI of course gives you everything! The kernel contains no - permissioned control over filtering which means anything with permissions - to open the HCI can also get all data packets in both directions and - therefore sniff everything. As a result, scanning normally has to be - activated by root only. - - - The general HCI event format is (Bluetooth 4.0 Vol 2, Part E, 5.4.4) - - - Note that the HCI unpacks some of the bitfields in the advertising - PDU and presents them as whole bytes. - - Packet type is not part of the HCI command. In USB the type is - determined by the endpoint address. In all the serial protocols the - packet type is 1 byte at the beginning where - - Command = 0x01 Event = 0x04 - - See, e.g. 4.0/4/A.2 - - Linux seems to use these. - - And the LE events are all = 0x3E (HCI_LE_META_EVT) as per - 4.0/2/E.7.7.65 - And the format of is: - - - - And we're interested in advertising events, where - = 0x02 (as per 4.0/2/E.7.7.65.2) - - And the format of in this case is - - - - Where is - -
-
- - - - - is an advertising packet and that's specified - all over the place. 4.0/3/C.8 for example. The advertising packets - are of the format: - - - ... - - - Now, the meaning of type, and the valid data are not defined in the main - spec, but in "Supplement to the Bluetooth Core Specification" version 4, - referred to here as S4. Except that defines only the name of the types - not their numeric code for some reason. Those are on the web. - https://www.bluetooth.org/en-us/specification/assigned-numbers/generic-access-profile - - - Some datatypes are not valid in advertising packets and some are also not - valid in LE applications. Apart from a small selection, many of the types - are rare to the point of nonexistence. I ignore them because I've got nothing - that generates tham and I wouldn't know what to do with them anyway. Feel free - to fix or ping me if you need them :) - - The ones generally of interest are: - <> - <> - <> - <> - <> - <> - <> - <> - <> - - Personal experience suggest UUIDs and flags are almost always present. - Names are often present. iBeacons use manufacturer specific data whereas - Eddystone beacons use service data. I've used neither. I've not personally - seen or heard of any of the other types, including 32 bit UUIDs. - - There are also some moderately sensible restrictions. A device SHALL NOT - report bot incomplete and complete lists of the same things in the same - packet S4/1.1.1. Furthermore an ommitted UUID specification is equivalent - to a incomplete list with no elements. - - - - Returns 1 on success, 0 on failure - such as an inability - to handle the packet, not an error. - - Return code can probably be ignored because - it will call lambdas on specific packets anyway. - - TODO: replace some errors with throw. - such as the HCI device spewing crap. - - */ + // Advertisement packet parsing - available for all transports + // These functions parse HCI advertisement packets and are used by + // BLEScanner with any transport backend (BlueZ, Nimble, etc.) + + // Forward declarations for internal parsing functions std::vector parse_event_packet(Span packet); std::vector parse_le_meta_event(Span packet); std::vector parse_le_meta_event_advertisement(Span packet); - std::vector HCIScanner::parse_packet(const std::vector& p) + // Standalone function + std::vector parse_advertisement_packet(const std::vector& p) { Span packet(p); LOG(Debug, to_hex(p)); @@ -478,22 +279,22 @@ namespace BLEPP else { LOG(LogLevels::Error, "Unknown HCI packet received"); - throw HCIError("Unknown HCI packet received"); + throw HCIParseError("Unknown HCI packet received"); } } std::vector parse_event_packet(Span packet) { if(packet.size() < 2) - throw HCIScanner::HCIError("Truncated event packet"); - + throw HCIParseError("Truncated event packet"); + uint8_t event_code = packet.pop_front(); uint8_t length = packet.pop_front(); - + if(packet.size() != length) - throw HCIScanner::HCIError("Bad packet length"); - + throw HCIParseError("Bad packet length"); + if(event_code == EVT_LE_META_EVENT) { LOG(Info, "event_code = 0x" << std::hex << (int)event_code << ": Meta event" << std::dec); @@ -505,7 +306,7 @@ namespace BLEPP { LOG(Info, "event_code = 0x" << std::hex << (int)event_code << std::dec); LOGVAR(Info, length); - throw HCIScanner::HCIError("Unexpected HCI event packet"); + throw HCIParseError("Unexpected HCI event packet"); } } @@ -614,7 +415,7 @@ namespace BLEPP if(type == GAP::flags) { - rsp.flags = AdvertisingResponse::Flags({chunk.begin(), chunk.end()}); + rsp.flags = new AdvertisingResponse::Flags({chunk.begin(), chunk.end()}); LOG(Info, "Flags = " << to_hex(rsp.flags->flag_data)); @@ -655,12 +456,12 @@ namespace BLEPP else if(type == GAP::shortened_local_name || type == GAP::complete_local_name) { chunk.pop_front(); - AdvertisingResponse::Name n; - n.complete = type==GAP::complete_local_name; - n.name = std::string(chunk.begin(), chunk.end()); + AdvertisingResponse::Name* n = new AdvertisingResponse::Name(); + n->complete = type==GAP::complete_local_name; + n->name = std::string(chunk.begin(), chunk.end()); rsp.local_name = n; - LOG(Info, "Name (" << (n.complete?"complete":"incomplete") << "): " << n.name); + LOG(Info, "Name (" << (n->complete?"complete":"incomplete") << "): " << n->name); } else if(type == GAP::manufacturer_data) { @@ -698,5 +499,4 @@ namespace BLEPP return ret; } - -} +} // namespace BLEPP diff --git a/src/lescan.cc.bak b/src/lescan.cc.bak new file mode 100644 index 0000000..48710f1 --- /dev/null +++ b/src/lescan.cc.bak @@ -0,0 +1,873 @@ +#include "blepp/lescan.h" +#include "blepp/pretty_printers.h" +#include "blepp/gap.h" + +#include +#include +#include +#include + +#ifdef BLEPP_BLUEZ_SUPPORT +#include +#endif + +// HCI packet types (standard Bluetooth HCI constants) +#ifndef HCI_EVENT_PKT +#define HCI_EVENT_PKT 0x04 +#endif + +// HCI event codes (standard Bluetooth HCI constants) +#ifndef EVT_LE_META_EVENT +#define EVT_LE_META_EVENT 0x3E +#endif + +namespace BLEPP +{ + class Span + { + private: + const uint8_t* begin_; + const uint8_t* end_; + + public: + Span(const std::vector& d) + :begin_(d.data()),end_(begin_ + d.size()) + { + } + + Span(const Span&) = default; + + Span pop_front(size_t length) + { + if(length > size()) + throw std::out_of_range(""); + + Span s = *this; + s.end_ = begin_ + length; + + begin_ += length; + return s; + } + const uint8_t* begin() const + { + return begin_; + } + const uint8_t* end() const + { + return end_; + } + + const uint8_t& operator[](const size_t i) const + { + if(i >= size()) + throw std::out_of_range(""); + return begin_[i]; + } + + bool empty() const + { + return size()==0; + } + + size_t size() const + { + return end_ - begin_; + } + + const uint8_t* data() const + { + return begin_; + } + + const uint8_t& pop_front() + { + if(begin_ == end_) + throw std::out_of_range(""); + + begin_++; + return *(begin_-1); + } + }; + + AdvertisingResponse::Flags::Flags(std::vector&& s) + :flag_data(s) + { + //Remove the type field + flag_data.erase(flag_data.begin()); + if(!flag_data.empty()) + { + //See 4.0/4.C.18.1 + LE_limited_discoverable = flag_data[0] & (1<<0); + LE_general_discoverable = flag_data[0] & (1<<1); + BR_EDR_unsupported = flag_data[0] & (1<<2); + simultaneous_LE_BR_controller = flag_data[0] & (1<<3); + simultaneous_LE_BR_host = flag_data[0] & (1<<4); + } + } + + std::string to_hex(const Span& s) + { + return to_hex(s.data(), s.size()); + } + + // HCI Scanner error implementation - always available + HCIScannerError::HCIScannerError(const std::string& why) + :std::runtime_error(why) + { + LOG(LogLevels::Error, why); + } + + // =================================================================== + // BLEScanner - Transport-agnostic scanner implementation + // =================================================================== + + #include "blepp/bleclienttransport.h" + + BLEScanner::FilterEntry::FilterEntry(const AdvertisingResponse& ad) + :mac_address(ad.address), type(static_cast(ad.type)) + { + } + + bool BLEScanner::FilterEntry::operator<(const FilterEntry& e) const + { + if(mac_address < e.mac_address) + return true; + else if(mac_address > e.mac_address) + return false; + else + return type < e.type; + } + + BLEScanner::BLEScanner(BLEClientTransport* transport, FilterDuplicates filter) + : transport_(transport) + , running_(false) + , software_filtering_(filter == FilterDuplicates::Software) + { + if (!transport_) { + throw std::invalid_argument("BLEScanner: transport cannot be null"); + } + } + + BLEScanner::~BLEScanner() + { + if (running_) { + try { + stop(); + } catch (...) { + // Suppress exceptions in destructor + } + } + } + + void BLEScanner::start(bool passive) + { + ENTER(); + if (running_) { + LOG(Trace, "Scanner is already running"); + return; + } + + // Configure scan parameters + ScanParams params; + params.type = passive ? ScanParams::ScanType::Passive : ScanParams::ScanType::Active; + params.interval_ms = 16; // 10ms units, so 16 * 10ms = 160ms + params.window_ms = 16; + params.filter_duplicates = !software_filtering_; // Hardware filtering if not software filtering + + int result = transport_->start_scan(params); + if (result < 0) { + throw HCIScannerError("Failed to start scan"); + } + + scanned_devices_.clear(); + running_ = true; + LOG(Info, "BLE scanner started"); + } + + void BLEScanner::stop() + { + ENTER(); + if (!running_) { + return; + } + + int result = transport_->stop_scan(); + if (result < 0) { + throw HCIScannerError("Failed to stop scan"); + } + + running_ = false; + LOG(Info, "BLE scanner stopped"); + } + + std::vector BLEScanner::get_advertisements(int timeout_ms) + { + if (!running_) { + throw HCIScannerError("Scanner not running"); + } + + // Get advertisements from transport + std::vector ads; + int result = transport_->get_advertisements(ads, timeout_ms); + if (result < 0) { + throw HCIScannerError("Failed to get advertisements"); + } + + // Convert AdvertisementData to AdvertisingResponse + std::vector responses; + for (const auto& ad : ads) { + AdvertisingResponse resp; + resp.address = ad.address; + resp.type = static_cast(ad.event_type); + resp.rssi = ad.rssi; + + // Copy UUIDs + resp.UUIDs = ad.UUIDs; + + // Set local name if present + if (!ad.local_name.empty()) { + resp.local_name = new AdvertisingResponse::Name(); + resp.local_name->name = ad.local_name; + resp.local_name->complete = ad.local_name_complete; + } + + // Copy manufacturer data + if (!ad.manufacturer_data.empty()) { + resp.manufacturer_specific_data.push_back(ad.manufacturer_data); + } + + // Software filtering if enabled + if (software_filtering_) { + FilterEntry entry(resp); + if (scanned_devices_.count(entry)) { + continue; // Skip duplicate + } + scanned_devices_.insert(entry); + } + + responses.push_back(std::move(resp)); + } + + return responses; + } + +#ifdef BLEPP_BLUEZ_SUPPORT + HCIScanner::Error::Error(const std::string& why) + :std::runtime_error(why) + { + LOG(LogLevels::Error, why); + } + + HCIScanner::IOError::IOError(const std::string& why, int errno_val) + :Error(why + ": " + strerror(errno_val)) + { + } + + HCIScanner::HCIScanner(bool start_scan) + :HCIScanner(start_scan, FilterDuplicates::Both, ScanType::Active) + { + } + + + HCIScanner::HCIScanner(bool start_scan, FilterDuplicates filtering, ScanType st, std::string device) + { + if(filtering == FilterDuplicates::Hardware || filtering == FilterDuplicates::Both) + hardware_filtering = true; + else + hardware_filtering = false; + + if(filtering == FilterDuplicates::Software || filtering == FilterDuplicates::Both) + software_filtering = true; + else + software_filtering = false; + + scan_type=st; + + int dev_id = 0; + if (device == "") { + //Get a route to any(?) BTLE adapter (?) + dev_id = hci_get_route(NULL); + } + else { + dev_id = hci_devid(device.c_str()); + } + if (dev_id < 0) { + throw HCIError("Error obtaining HCI device ID"); + } + + //Open the device + //FIXME check errors + hci_fd.set(hci_open_dev(dev_id)); + + if(start_scan) + start(); + } + + HCIScanner::HCIScanner() + :HCIScanner(true) + { + + } + + void HCIScanner::start() + { + ENTER(); + if(running) + { + LOG(Trace, "Scanner is already running"); + return; + } + + //Cadged from the hcitool sources. No idea what + //these mean + uint16_t interval = htobs(0x0010); + uint16_t window = htobs(0x0010); + + //Address for the adapter (I think). Use a public address. + //other option is random. Works either way it seems. + uint8_t own_type = LE_PUBLIC_ADDRESS; + + //Don't use a whitelist (?) + uint8_t filter_policy = 0x00; + + + //The 10,000 thing seems to be some sort of retry logic timeout + //thing. Number of miliseconds, but there are multiple tries + //where it gets reduced by 10ms each time. It's a bit odd. + int err = hci_le_set_scan_parameters(hci_fd, static_cast(scan_type), interval, window, + own_type, filter_policy, 10000); + if(err < 0) + { + if(errno != EIO) + throw IOError("Setting scan parameters", errno); + else + { + //If the BLE device is already set to scanning, then we get an IO error. So + //try turning it off and trying again. This bad state would happen, if, to pick + //like a *totally* hypothetical example, the program segged-out during scanning + //and so never cleaned up properly. + LOG(LogLevels::Warning, "Received I/O error while setting scan parameters."); + LOG(LogLevels::Warning, "Switching off HCI scanner"); + err = hci_le_set_scan_enable(hci_fd, 0x00, 0x00, 10000); + if(err < 0) + throw IOError("Error disabling scan:", errno); + + + err = hci_le_set_scan_parameters(hci_fd, static_cast(scan_type), interval, window, own_type, filter_policy, 10000); + if(err < 0) + throw IOError("Error disabling scan:", errno); + else + LOG(LogLevels::Warning, "Setting scan parameters worked this time."); + + + } + } + + LOG(LogLevels::Info, "Starting scanner"); + scanned_devices.clear(); + + //Removal of duplicates done on the adapter itself + uint8_t filter_dup = hardware_filtering?0x01:0x00; + + + //Set up the filters. + socklen_t olen = sizeof(old_filter); + if (getsockopt(hci_fd, SOL_HCI, HCI_FILTER, &old_filter, &olen) < 0) + throw IOError("Getting HCI filter socket options", errno); + + + //Magic incantations to get scan events + struct hci_filter nf; + hci_filter_clear(&nf); + hci_filter_set_ptype(HCI_EVENT_PKT, &nf); + hci_filter_set_event(EVT_LE_META_EVENT, &nf); + if (setsockopt(hci_fd, SOL_HCI, HCI_FILTER, &nf, sizeof(nf)) < 0) + throw IOError("Setting HCI filter socket options", errno); + + + //device disable/enable duplictes ???? + err = hci_le_set_scan_enable(hci_fd, 0x01, filter_dup, 10000); + if(err < 0) + throw IOError("Enabling scan", errno); + + running=true; + } + + void HCIScanner::stop() + { + ENTER(); + if(!running) + { + return; + } + + LOG(LogLevels::Info, "Cleaning up HCI scanner"); + int err = hci_le_set_scan_enable(hci_fd, 0x00, 0x00, 10000); + + if(err < 0) + throw IOError("Error disabling scan:", errno); + + err = setsockopt(hci_fd, SOL_HCI, HCI_FILTER, &old_filter, sizeof(old_filter)); + + if(err < 0) + throw IOError("Error resetting HCI socket:", errno); + + running = false; + } + + int HCIScanner::get_fd() const + { + return hci_fd; + } + + + HCIScanner::~HCIScanner() + { + try + { + stop(); + } + catch(IOError&) + { + } + } + + HCIScanner::FilterEntry::FilterEntry(const AdvertisingResponse& a) + :mac_address(a.address),type(static_cast(a.type)) + {} + + bool HCIScanner::FilterEntry::operator<(const FilterEntry& f) const + { + //Simple lexi-sorting + if(mac_address < f.mac_address) + return true; + else if(mac_address == f.mac_address) + return type < f.type; + else + return false; + } + + std::vector HCIScanner::read_with_retry() + { + int len; + std::vector buf(HCI_MAX_EVENT_SIZE); + + + while((len = read(hci_fd, buf.data(), buf.size())) < 0) + { + if(errno == EAGAIN) + continue; + else if(errno == EINTR) + throw Interrupted("interrupted reading HCI packet"); + else + throw IOError("reading HCI packet", errno); + } + + buf.resize(len); + return buf; + } + + std::vector HCIScanner::get_advertisements() + { + std::vector adverts = parse_packet(read_with_retry()); + + if(software_filtering) + { + std::vector filtered; + + for(const auto& a: adverts) + { + auto r = scanned_devices.insert(FilterEntry(a)); + + if(r.second) + filtered.emplace_back(std::move(a)); + else + LOG(Debug, "Entry " << a.address << " " << static_cast(a.type) << " found already"); + } + + return filtered; + } + else + return adverts; + } + + /* + Hello comment-reader! + + This is a class for dealing with device scanning. The scans are done + using the HCI (Host Controller Interface). Interstingly, the HCI is + well specified even down to the transport layer (RS232 syle, USB, + SDIO and some sort of SLIP based serial). The HCI sometimes unpacks + and aggregates PDUs (the data part of packets) before sending them + for some reason, so you need to look in the HCI part of the spec, + not the PDU part of the spec. + + The HCI is also semi-autonomous, and can be instructed to do things like + sctive scanning (where it queries and reports additional information, + typically the device name) and filtering of duplicated advertising + events. This is great for low power draw because the host CPU doesn't need + to wake up to do menial or unimportant things. + + Also, the HCI of course gives you everything! The kernel contains no + permissioned control over filtering which means anything with permissions + to open the HCI can also get all data packets in both directions and + therefore sniff everything. As a result, scanning normally has to be + activated by root only. + + + The general HCI event format is (Bluetooth 4.0 Vol 2, Part E, 5.4.4) + + + Note that the HCI unpacks some of the bitfields in the advertising + PDU and presents them as whole bytes. + + Packet type is not part of the HCI command. In USB the type is + determined by the endpoint address. In all the serial protocols the + packet type is 1 byte at the beginning where + + Command = 0x01 Event = 0x04 + + See, e.g. 4.0/4/A.2 + + Linux seems to use these. + + And the LE events are all = 0x3E (HCI_LE_META_EVT) as per + 4.0/2/E.7.7.65 + And the format of is: + + + + And we're interested in advertising events, where + = 0x02 (as per 4.0/2/E.7.7.65.2) + + And the format of in this case is + + + + Where is + +
+
+ + + + + is an advertising packet and that's specified + all over the place. 4.0/3/C.8 for example. The advertising packets + are of the format: + + + ... + + + Now, the meaning of type, and the valid data are not defined in the main + spec, but in "Supplement to the Bluetooth Core Specification" version 4, + referred to here as S4. Except that defines only the name of the types + not their numeric code for some reason. Those are on the web. + https://www.bluetooth.org/en-us/specification/assigned-numbers/generic-access-profile + + + Some datatypes are not valid in advertising packets and some are also not + valid in LE applications. Apart from a small selection, many of the types + are rare to the point of nonexistence. I ignore them because I've got nothing + that generates tham and I wouldn't know what to do with them anyway. Feel free + to fix or ping me if you need them :) + + The ones generally of interest are: + <> + <> + <> + <> + <> + <> + <> + <> + <> + + Personal experience suggest UUIDs and flags are almost always present. + Names are often present. iBeacons use manufacturer specific data whereas + Eddystone beacons use service data. I've used neither. I've not personally + seen or heard of any of the other types, including 32 bit UUIDs. + + There are also some moderately sensible restrictions. A device SHALL NOT + report bot incomplete and complete lists of the same things in the same + packet S4/1.1.1. Furthermore an ommitted UUID specification is equivalent + to a incomplete list with no elements. + + + + Returns 1 on success, 0 on failure - such as an inability + to handle the packet, not an error. + + Return code can probably be ignored because + it will call lambdas on specific packets anyway. + + TODO: replace some errors with throw. + such as the HCI device spewing crap. + + */ + + +#endif // BLEPP_BLUEZ_SUPPORT + + // Advertisement packet parsing - available for all transports + // These functions parse HCI advertisement packets and are used by both + // BlueZ (HCIScanner) and Nimble transports + + // Forward declarations for internal parsing functions + std::vector parse_event_packet(Span packet); + std::vector parse_le_meta_event(Span packet); + std::vector parse_le_meta_event_advertisement(Span packet); + + // Standalone function + std::vector parse_advertisement_packet(const std::vector& p) + { + Span packet(p); + LOG(Debug, to_hex(p)); + + if(packet.size() < 1) + { + LOG(LogLevels::Error, "Empty packet received"); + return {}; + } + + uint8_t packet_id = packet.pop_front(); + + + if(packet_id == HCI_EVENT_PKT) + { + LOG(Debug, "Event packet received"); + return parse_event_packet(packet); + } + else + { + LOG(LogLevels::Error, "Unknown HCI packet received"); + throw HCIParseError("Unknown HCI packet received"); + } + } + + std::vector parse_event_packet(Span packet) + { + if(packet.size() < 2) + throw HCIParseError("Truncated event packet"); + + uint8_t event_code = packet.pop_front(); + uint8_t length = packet.pop_front(); + + + if(packet.size() != length) + throw HCIParseError("Bad packet length"); + + if(event_code == EVT_LE_META_EVENT) + { + LOG(Info, "event_code = 0x" << std::hex << (int)event_code << ": Meta event" << std::dec); + LOGVAR(Info, length); + + return parse_le_meta_event(packet); + } + else + { + LOG(Info, "event_code = 0x" << std::hex << (int)event_code << std::dec); + LOGVAR(Info, length); + throw HCIParseError("Unexpected HCI event packet"); + } + } + + + std::vector parse_le_meta_event(Span packet) + { + uint8_t subevent_code = packet.pop_front(); + + if(subevent_code == 0x02) // see big blob of comments above + { + LOG(Info, "subevent_code = 0x02: LE Advertising Report Event"); + return parse_le_meta_event_advertisement(packet); + } + else + { + LOGVAR(Info, subevent_code); + return {}; + } + } + + std::vector parse_le_meta_event_advertisement(Span packet) + { + std::vector ret; + + uint8_t num_reports = packet.pop_front(); + LOGVAR(Info, num_reports); + + for(int i=0; i < num_reports; i++) + { + LeAdvertisingEventType event_type = static_cast(packet.pop_front()); + + if(event_type == LeAdvertisingEventType::ADV_IND) + LOG(Info, "event_type = 0x00 ADV_IND, Connectable undirected advertising"); + else if(event_type == LeAdvertisingEventType::ADV_DIRECT_IND) + LOG(Info, "event_type = 0x01 ADV_DIRECT_IND, Connectable directed advertising"); + else if(event_type == LeAdvertisingEventType::ADV_SCAN_IND) + LOG(Info, "event_type = 0x02 ADV_SCAN_IND, Scannable undirected advertising"); + else if(event_type == LeAdvertisingEventType::ADV_NONCONN_IND) + LOG(Info, "event_type = 0x03 ADV_NONCONN_IND, Non connectable undirected advertising"); + else if(event_type == LeAdvertisingEventType::SCAN_RSP) + LOG(Info, "event_type = 0x04 SCAN_RSP, Scan response"); + else + LOG(Warning, "event_type = 0x" << std::hex << (int)event_type << std::dec << ", unknown"); + + uint8_t address_type = packet.pop_front(); + + if(address_type == 0) + LOG(Info, "Address type = 0: Public device address"); + else if(address_type == 1) + LOG(Info, "Address type = 0: Random device address"); + else + LOG(Info, "Address type = 0x" << to_hex(address_type) << ": unknown"); + + + std::string address; + for(int j=0; j < 6; j++) + { + std::ostringstream s; + s << std::hex << std::setw(2) << std::setfill('0') << (int) packet.pop_front(); + if(j != 0) + s << ":"; + + address = s.str() + address; + } + + + LOGVAR(Info, address); + + uint8_t length = packet.pop_front(); + LOGVAR(Info, length); + + + Span data = packet.pop_front(length); + + LOG(Debug, "Data = " << to_hex(data)); + + int8_t rssi = packet.pop_front(); + + if(rssi == 127) + LOG(Info, "RSSI = 127: unavailable"); + else if(rssi <= 20) + LOG(Info, "RSSI = " << (int) rssi << " dBm"); + else + LOG(Info, "RSSI = " << to_hex((uint8_t)rssi) << " unknown"); + + try{ + AdvertisingResponse rsp; + rsp.address = address; + rsp.type = event_type; + rsp.rssi = rssi; + rsp.raw_packet.push_back({data.begin(), data.end()}); + + while(data.size() > 0) + { + LOGVAR(Debug, data.size()); + LOG(Debug, "Packet = " << to_hex(data)); + //Format is length, type, crap + int length = data.pop_front(); + + LOGVAR(Debug, length); + + Span chunk = data.pop_front(length); + + uint8_t type = chunk[0]; + LOGVAR(Debug, type); + + if(type == GAP::flags) + { + rsp.flags = new AdvertisingResponse::Flags({chunk.begin(), chunk.end()}); + + LOG(Info, "Flags = " << to_hex(rsp.flags->flag_data)); + + if(rsp.flags->LE_limited_discoverable) + LOG(Info, " LE limited discoverable"); + + if(rsp.flags->LE_general_discoverable) + LOG(Info, " LE general discoverable"); + + if(rsp.flags->BR_EDR_unsupported) + LOG(Info, " BR/EDR unsupported"); + + if(rsp.flags->simultaneous_LE_BR_host) + LOG(Info, " simultaneous LE BR host"); + + if(rsp.flags->simultaneous_LE_BR_controller) + LOG(Info, " simultaneous LE BR controller"); + } + else if(type == GAP::incomplete_list_of_16_bit_UUIDs || type == GAP::complete_list_of_16_bit_UUIDs) + { + rsp.uuid_16_bit_complete = (type == GAP::complete_list_of_16_bit_UUIDs); + chunk.pop_front(); //remove the type field + + while(!chunk.empty()) + { + uint16_t u = chunk.pop_front() + chunk.pop_front()*256; + rsp.UUIDs.push_back(UUID(u)); + } + } + else if(type == GAP::incomplete_list_of_128_bit_UUIDs || type == GAP::complete_list_of_128_bit_UUIDs) + { + rsp.uuid_128_bit_complete = (type == GAP::complete_list_of_128_bit_UUIDs); + chunk.pop_front(); //remove the type field + + while(!chunk.empty()) + rsp.UUIDs.push_back(UUID::from(att_get_uuid128(chunk.pop_front(16).data()))); + } + else if(type == GAP::shortened_local_name || type == GAP::complete_local_name) + { + chunk.pop_front(); + AdvertisingResponse::Name* n = new AdvertisingResponse::Name(); + n->complete = type==GAP::complete_local_name; + n->name = std::string(chunk.begin(), chunk.end()); + rsp.local_name = n; + + LOG(Info, "Name (" << (n->complete?"complete":"incomplete") << "): " << n->name); + } + else if(type == GAP::manufacturer_data) + { + chunk.pop_front(); + rsp.manufacturer_specific_data.push_back({chunk.begin(), chunk.end()}); + LOG(Info, "Manufacturer data: " << to_hex(chunk)); + } + else + { + rsp.unparsed_data_with_types.push_back({chunk.begin(), chunk.end()}); + + LOG(Info, "Unparsed chunk " << to_hex(chunk)); + } + } + + if(rsp.UUIDs.size() > 0) + { + LOG(Info, "UUIDs (128 bit " << (rsp.uuid_128_bit_complete?"complete":"incomplete") + << ", 16 bit " << (rsp.uuid_16_bit_complete?"complete":"incomplete") << " ):"); + + for(const auto& uuid: rsp.UUIDs) + LOG(Info, " " << to_str(uuid)); + } + + ret.push_back(rsp); + + + } + catch(std::out_of_range& r) + { + LOG(LogLevels::Error, "Corrupted data sent by device " << address); + } + } + + return ret; + } + +#ifdef BLEPP_BLUEZ_SUPPORT + // Compatibility wrapper - HCIScanner::parse_packet calls the standalone function + std::vector HCIScanner::parse_packet(const std::vector& p) + { + return parse_advertisement_packet(p); + } +#endif + +} // namespace BLEPP diff --git a/src/nimble_client_transport.cc b/src/nimble_client_transport.cc new file mode 100644 index 0000000..e44a20b --- /dev/null +++ b/src/nimble_client_transport.cc @@ -0,0 +1,914 @@ +/* + * + * blepp - Implementation of the Generic ATTribute Protocol + * + * Copyright (C) 2024 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +#ifdef BLEPP_NIMBLE_SUPPORT + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +// Nimble headers +extern "C" { +#include "host/ble_hs.h" +#include "host/ble_gap.h" +#include "host/ble_gatt.h" +#include "host/util/util.h" +#include "host/ble_hs_mbuf.h" +#include "nimble/ble.h" +#include "nimble/nimble_port.h" +#include "nimble/nimble_npl.h" + +// Internal API for sending raw ATT PDUs +int ble_att_tx(uint16_t conn_handle, struct os_mbuf *txom); + +// Global variable used by nimble_port_run() to exit event loop +extern int nimble_th_exit; + +// HCI ioctl transport functions (ATBM-specific) +int hif_ioctl_init(); +void hif_ioctl_loop(); + +// Nimble port ATBM OS integration +void nimble_port_atbmos_init(void(* host_task_fn)(void*)); +void nimble_port_atbmos_free(void); +} + +#include +#include +#include +#include + +namespace BLEPP +{ + +// Static pointer to transport instance for callbacks +static NimbleClientTransport* g_nimble_transport_instance = nullptr; + +// Static Nimble host task function +static void nimble_host_task(void* param) +{ + LOG(Info, "Nimble host task thread starting"); + + // Run the Nimble event loop - this processes all Nimble stack events + nimble_port_run(); + + LOG(Info, "Nimble host task thread exiting"); +} + +NimbleClientTransport::NimbleClientTransport() + : initialized_(false) + , synchronized_(false) + , scanning_(false) + , next_fd_(1000) // Start with 1000 to avoid conflicts with real FDs +{ + LOG(Info, "NimbleClientTransport: Initializing Nimble transport"); + + g_nimble_transport_instance = this; + + if (initialize_nimble() == 0) { + initialized_ = true; + + // Wait for Nimble host to synchronize (like ATBM lib_ble_main_init does) + LOG(Info, "Waiting for Nimble host to synchronize..."); + LOG(Info, "Initial synchronized_ value: " << synchronized_.load()); + LOG(Info, "g_nimble_transport_instance: " << (void*)g_nimble_transport_instance); + int timeout_ms = 5000; // 5 second timeout + int waited_ms = 0; + while (!synchronized_.load() && waited_ms < timeout_ms) { + if (waited_ms % 500 == 0) { // Log every 500ms + LOG(Info, "Still waiting for sync... (" << waited_ms << "ms elapsed, synchronized_=" << synchronized_.load() << ")"); + } + usleep(10000); // Sleep for 10ms + waited_ms += 10; + } + + if (synchronized_.load()) { + LOG(Info, "Nimble host synchronized after " << waited_ms << "ms"); + } else { + LOG(Error, "Nimble host failed to synchronize after " << timeout_ms << "ms"); + LOG(Error, "Final synchronized_ value: " << synchronized_.load()); + } + } +} + +NimbleClientTransport::~NimbleClientTransport() +{ + LOG(Info, "NimbleClientTransport: Shutting down"); + + if (scanning_) { + stop_scan(); + } + + // Disconnect all connections + std::lock_guard lock(conn_mutex_); + for (auto& pair : connections_) { + if (pair.second.connected) { + ble_gap_terminate(pair.second.conn_handle, BLE_ERR_REM_USER_CONN_TERM); + } + } + connections_.clear(); + handle_to_fd_.clear(); + + if (initialized_) { + shutdown_nimble(); + } +} + +bool NimbleClientTransport::is_available() const +{ + return initialized_; +} + +// ============================================================================ +// Nimble Initialization +// ============================================================================ + +// Static sync callback that sets the BLE address +static void nimble_sync_callback() +{ + LOG(Info, ">>> Nimble sync callback called - host synchronized <<<"); + + // Mark as synchronized + if (g_nimble_transport_instance) { + LOG(Info, "Setting synchronized flag to true"); + g_nimble_transport_instance->synchronized_ = true; + } else { + LOG(Error, "g_nimble_transport_instance is NULL in sync callback!"); + } + + // ATBM hardware doesn't have a factory BLE address, so we need to set one + // Try to ensure an address exists first + int rc = ble_hs_util_ensure_addr(0); + if (rc == BLE_HS_ENOADDR) { + // No address available - derive from WiFi MAC address + LOG(Warning, "No BLE address found, deriving from WiFi MAC address"); + + uint8_t ble_addr[6] = {0}; + bool mac_found = false; + + // Try to get WiFi MAC address from network interface + int sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock >= 0) { + struct ifreq ifr; + const char* wifi_interfaces[] = {"wlan0", "wlan1", "ath0", "ra0", NULL}; + + for (int i = 0; wifi_interfaces[i] != NULL && !mac_found; i++) { + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, wifi_interfaces[i], IFNAMSIZ - 1); + + if (ioctl(sock, SIOCGIFHWADDR, &ifr) == 0) { + // Successfully got MAC address + memcpy(ble_addr, ifr.ifr_hwaddr.sa_data, 6); + mac_found = true; + LOG(Info, "Using WiFi MAC from " << wifi_interfaces[i]); + } + } + close(sock); + } + + if (!mac_found) { + // Fallback to random address if WiFi MAC not available + LOG(Warning, "WiFi MAC not found, using random address"); + srand(time(NULL)); + for (int i = 0; i < 6; i++) { + ble_addr[i] = rand() & 0xFF; + } + } + + // Set top 2 bits to '11' to make it a random static address + ble_addr[5] |= 0xC0; + + // Set the BLE address (now in sync callback, controller is ready) + rc = ble_hs_id_set_rnd(ble_addr); + if (rc != 0) { + LOG(Error, "Failed to set BLE address: " << rc); + return; + } + + LOG(Info, "Set BLE address: " << + std::hex << std::setfill('0') << + std::setw(2) << (int)ble_addr[5] << ":" << + std::setw(2) << (int)ble_addr[4] << ":" << + std::setw(2) << (int)ble_addr[3] << ":" << + std::setw(2) << (int)ble_addr[2] << ":" << + std::setw(2) << (int)ble_addr[1] << ":" << + std::setw(2) << (int)ble_addr[0] << std::dec); + } else if (rc != 0) { + LOG(Error, "Failed to ensure address: " << rc); + } else { + LOG(Info, "BLE address already configured"); + } +} + +int NimbleClientTransport::initialize_nimble() +{ + LOG(Info, "=== NimbleClientTransport: Initializing Nimble BLE stack ==="); + + // Initialize HCI ioctl transport (ATBM-specific) + LOG(Info, "Calling hif_ioctl_init()..."); + int rc = hif_ioctl_init(); + if (rc != 0) { + LOG(Error, "hif_ioctl_init() failed with rc=" << rc); + return -1; + } + LOG(Info, "hif_ioctl_init() completed successfully"); + + // Initialize Nimble port + LOG(Info, "Calling nimble_port_init()..."); + nimble_port_init(); + LOG(Info, "nimble_port_init() completed"); + + // Set callbacks - address will be set in sync callback + LOG(Info, "Setting sync and reset callbacks"); + ble_hs_cfg.sync_cb = nimble_sync_callback; + + ble_hs_cfg.reset_cb = [](int reason) { + LOG(Error, "Nimble host reset, reason=" << reason); + }; + + // Create the Nimble host thread using ATBM's thread management + LOG(Info, "Creating Nimble host thread via nimble_port_atbmos_init()..."); + nimble_port_atbmos_init(nimble_host_task); + LOG(Info, "Nimble host thread created"); + + // Schedule the BLE host to start - this triggers synchronization + LOG(Info, "Calling ble_hs_sched_start()..."); + ble_hs_sched_start(); + LOG(Info, "ble_hs_sched_start() completed"); + + LOG(Info, "=== Nimble BLE stack initialization complete ==="); + return 0; +} + +void NimbleClientTransport::shutdown_nimble() +{ + LOG(Info, "NimbleClientTransport: Shutting down Nimble"); + + // Stop scanning if active + if (ble_gap_disc_active()) { + LOG(Info, "Cancelling active scan..."); + ble_gap_disc_cancel(); + } + + // Stop the Nimble host thread using ATBM's cleanup function + // This will signal nimble_th_exit=1, post a dummy event, and stop the thread + LOG(Info, "Stopping Nimble host thread via nimble_port_atbmos_free()..."); + nimble_port_atbmos_free(); + LOG(Info, "Nimble host thread stopped"); + + // Release nimble port resources + LOG(Info, "Releasing nimble port resources..."); + nimble_port_release(); + LOG(Info, "Nimble shutdown complete"); +} + +// ============================================================================ +// Nimble Callbacks +// ============================================================================ + +int NimbleClientTransport::gap_event_callback(struct ble_gap_event* event, void* arg) +{ + NimbleClientTransport* self = static_cast(arg); + return self->handle_gap_event(event); +} + +int NimbleClientTransport::gatt_event_callback(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt* ctxt, void* arg) +{ + NimbleClientTransport* self = static_cast(arg); + + // Note: This callback is for GATT server events (when acting as a peripheral). + // For client transport, we don't register any GATT services, so this won't + // be called. Server-initiated messages (notifications/indications) come through + // BLE_GAP_EVENT_NOTIFY_RX in the GAP event callback instead. + + LOG(Warning, "Unexpected GATT server event on client transport"); + return BLE_ATT_ERR_UNLIKELY; +} + +int NimbleClientTransport::handle_gap_event(struct ble_gap_event* event) +{ + switch (event->type) { + case BLE_GAP_EVENT_DISC: + handle_disc_event(&event->disc); + break; + + case BLE_GAP_EVENT_CONNECT: + handle_connect_event(event); + break; + + case BLE_GAP_EVENT_DISCONNECT: + handle_disconnect_event(event); + break; + + case BLE_GAP_EVENT_MTU: + handle_mtu_event(event); + break; + + case BLE_GAP_EVENT_NOTIFY_RX: + handle_notify_rx_event(event); + break; + + case BLE_GAP_EVENT_DISC_COMPLETE: + LOG(Info, "Discovery complete"); + scanning_ = false; + break; + + default: + LOG(Debug, "Unhandled GAP event: " << (int)event->type); + break; + } + + return 0; +} + +void NimbleClientTransport::handle_disc_event(const struct ble_gap_disc_desc* disc) +{ + std::lock_guard lock(scan_mutex_); + + // Convert address to string + std::string addr_str = addr_to_string(disc->addr.val); + + // Check for duplicates if software filtering is enabled + if (scan_params_.filter_duplicates == ScanParams::FilterDuplicates::Software) { + if (seen_devices_.find(addr_str) != seen_devices_.end()) { + return; // Duplicate + } + seen_devices_.insert(addr_str); + } + + // Create advertisement data structure + AdvertisementData ad; + ad.address = addr_str; + ad.address_type = disc->addr.type; + ad.rssi = disc->rssi; + ad.event_type = disc->event_type; + + // Copy raw advertisement data + if (disc->length_data > 0 && disc->data != nullptr) { + ad.data.assign(disc->data, disc->data + disc->length_data); + } + + scan_results_.push(ad); + + LOG(Debug, "Received advertisement from " << addr_str << " RSSI=" << (int)disc->rssi); + + // Call on_advertisement callback if registered + if (on_advertisement) { + on_advertisement(ad); + } +} + +void NimbleClientTransport::handle_connect_event(struct ble_gap_event* event) +{ + std::lock_guard lock(conn_mutex_); + + uint16_t conn_handle = event->connect.conn_handle; + + if (event->connect.status != 0) { + LOG(Error, "Connection failed: status=" << event->connect.status); + + // Clean up failed connection attempt + auto it = handle_to_fd_.find(conn_handle); + if (it != handle_to_fd_.end()) { + int fd = it->second; + connections_.erase(fd); + handle_to_fd_.erase(conn_handle); + LOG(Debug, "Cleaned up failed connection fd=" << fd); + } + + // Call on_disconnected callback if registered (connection failed) + if (on_disconnected) { + on_disconnected(-1); // -1 indicates connection never succeeded + } + + return; + } + + // Find the fd for this connection + auto it = handle_to_fd_.find(conn_handle); + if (it == handle_to_fd_.end()) { + LOG(Error, "Connection complete for unknown handle: " << conn_handle); + return; + } + + int fd = it->second; + auto& conn = connections_[fd]; + conn.connected = true; + conn.conn_handle = conn_handle; + + LOG(Info, "Connected: handle=" << conn_handle << " fd=" << fd); + + // Call on_connected callback if registered + if (on_connected) { + on_connected(fd); + } +} + +void NimbleClientTransport::handle_disconnect_event(struct ble_gap_event* event) +{ + std::lock_guard lock(conn_mutex_); + + uint16_t conn_handle = event->disconnect.conn.conn_handle; + + auto it = handle_to_fd_.find(conn_handle); + if (it == handle_to_fd_.end()) { + LOG(Warning, "Disconnect for unknown handle: " << conn_handle); + return; + } + + int fd = it->second; + + LOG(Info, "Disconnected: handle=" << conn_handle << " fd=" << fd << " reason=" << event->disconnect.reason); + + // Call on_disconnected callback before cleanup + if (on_disconnected) { + on_disconnected(fd); + } + + // Clean up connection state + connections_.erase(fd); + handle_to_fd_.erase(conn_handle); +} + +void NimbleClientTransport::handle_mtu_event(struct ble_gap_event* event) +{ + std::lock_guard lock(conn_mutex_); + + uint16_t conn_handle = event->mtu.conn_handle; + uint16_t mtu = event->mtu.value; + + auto it = handle_to_fd_.find(conn_handle); + if (it != handle_to_fd_.end()) { + int fd = it->second; + connections_[fd].mtu = mtu; + LOG(Info, "MTU updated: handle=" << conn_handle << " mtu=" << mtu); + } +} + +void NimbleClientTransport::handle_notify_rx_event(struct ble_gap_event* event) +{ + std::lock_guard lock(conn_mutex_); + + uint16_t conn_handle = event->notify_rx.conn_handle; + struct os_mbuf* om = event->notify_rx.om; + + auto it = handle_to_fd_.find(conn_handle); + if (it == handle_to_fd_.end()) { + LOG(Warning, "Notification for unknown handle: " << conn_handle); + return; + } + + int fd = it->second; + auto& conn = connections_[fd]; + + // Copy data from mbuf to vector + // Note: This includes the ATT opcode (0x1b for notification, 0x1d for indication) + uint16_t len = OS_MBUF_PKTLEN(om); + std::vector data(len); + int rc = ble_hs_mbuf_to_flat(om, data.data(), len, NULL); + if (rc == 0) { + conn.rx_queue.push(data); + LOG(Debug, "Received notification/indication: " << len << " bytes"); + + // Call on_data_received callback if registered + if (on_data_received) { + on_data_received(fd, data.data(), len); + } + } +} + +// ============================================================================ +// ATT PDU Reception Notes +// ============================================================================ +// +// Transport Layer Behavior: +// - send() transmits raw ATT PDUs via ble_att_tx() ✓ +// - receive() returns notifications/indications from the rx_queue ✓ +// +// Architectural Difference from BlueZ: +// BlueZ uses raw L2CAP sockets where all ATT PDUs (requests, responses, +// notifications) are accessible as raw bytes. +// +// Nimble uses an integrated stack where ATT request/response pairs are +// handled internally and matched synchronously. Only server-initiated +// messages (notifications/indications) are exposed via GAP events. +// +// Implications: +// 1. Notifications/indications work perfectly - they're queued in rx_queue +// 2. ATT requests sent via send() will transmit correctly +// 3. ATT responses to those requests won't appear in receive() - they're +// consumed internally by Nimble's ATT client layer +// +// For applications using libblepp's higher-level APIs (BLEDevice), this +// works correctly since they handle ATT protocol state machines. +// +// For raw ATT PDU bidirectional access, consider using Nimble's native +// GATT client APIs (ble_gattc_*) instead of the transport layer. + +// ============================================================================ +// Scanning Operations +// ============================================================================ + +int NimbleClientTransport::start_scan(const ScanParams& params) +{ + if (!initialized_) { + LOG(Error, "Transport not initialized"); + return -1; + } + + if (!synchronized_) { + LOG(Error, "Nimble host not synchronized yet"); + return -BLE_HS_EDISABLED; + } + + if (scanning_) { + LOG(Warning, "Scan already in progress"); + return -1; + } + + scan_params_ = params; + + // Clear previous results + { + std::lock_guard lock(scan_mutex_); + while (!scan_results_.empty()) { + scan_results_.pop(); + } + seen_devices_.clear(); + } + + // Setup scan parameters + struct ble_gap_disc_params disc_params; + memset(&disc_params, 0, sizeof(disc_params)); + + disc_params.passive = params.scan_type == ScanParams::ScanType::Passive ? 1 : 0; + // Hardware filtering only when Hardware mode is selected + disc_params.filter_duplicates = (params.filter_duplicates == ScanParams::FilterDuplicates::Hardware) ? 1 : 0; + disc_params.filter_policy = BLE_HCI_SCAN_FILT_NO_WL; + + // Convert interval and window from ms to BLE units (0.625ms units) + disc_params.itvl = (params.interval_ms * 1000) / 625; + disc_params.window = (params.window_ms * 1000) / 625; + + // Infer the address type to use automatically + uint8_t own_addr_type; + int rc = ble_hs_id_infer_auto(0, &own_addr_type); + if (rc != 0) { + LOG(Error, "Failed to infer address type: " << rc); + return rc; + } + LOG(Debug, "Using address type: " << (int)own_addr_type); + + // Start scan + rc = ble_gap_disc(own_addr_type, BLE_HS_FOREVER, &disc_params, + gap_event_callback, this); + + if (rc != 0) { + LOG(Error, "Failed to start scan: " << rc); + return -1; + } + + scanning_ = true; + LOG(Info, "Scan started"); + + return 0; +} + +int NimbleClientTransport::stop_scan() +{ + if (!initialized_) { + return -1; + } + + if (!scanning_) { + return 0; + } + + int rc = ble_gap_disc_cancel(); + if (rc != 0 && rc != BLE_HS_EALREADY) { + LOG(Error, "Failed to stop scan: " << rc); + return -1; + } + + scanning_ = false; + LOG(Info, "Scan stopped"); + + return 0; +} + +int NimbleClientTransport::get_advertisements(std::vector& ads, int timeout_ms) +{ + std::lock_guard lock(scan_mutex_); + + while (!scan_results_.empty()) { + ads.push_back(scan_results_.front()); + scan_results_.pop(); + } + + return ads.size(); +} + +// ============================================================================ +// Connection Operations +// ============================================================================ + +int NimbleClientTransport::connect(const ClientConnectionParams& params) +{ + if (!initialized_) { + return -1; + } + + // Allocate a new fd + int fd = allocate_fd(); + + // Parse address + uint8_t addr[6]; + string_to_addr(params.peer_address, addr); + + // Create connection info + ConnectionInfo conn_info; + conn_info.conn_handle = 0; // Will be set when connection completes + conn_info.mtu = 23; // Default ATT MTU + conn_info.address = params.peer_address; + conn_info.connected = false; + + { + std::lock_guard lock(conn_mutex_); + connections_[fd] = conn_info; + } + + // Setup connection parameters + struct ble_gap_conn_params conn_params; + memset(&conn_params, 0, sizeof(conn_params)); + + // Convert from BLE units (params are already in BLE units) + conn_params.scan_itvl = 0x0010; // 10ms + conn_params.scan_window = 0x0010; // 10ms + conn_params.itvl_min = params.min_interval; // Already in 1.25ms units + conn_params.itvl_max = params.max_interval; // Already in 1.25ms units + conn_params.latency = params.latency; + conn_params.supervision_timeout = params.timeout; // Already in 10ms units + conn_params.min_ce_len = 0; + conn_params.max_ce_len = 0; + + // Start connection + ble_addr_t peer_addr; + peer_addr.type = params.peer_address_type; // 0=public, 1=random + memcpy(peer_addr.val, addr, 6); + + int rc = ble_gap_connect(BLE_OWN_ADDR_PUBLIC, &peer_addr, 30000, // 30 second timeout + &conn_params, gap_event_callback, this); + + if (rc != 0) { + LOG(Error, "Failed to initiate connection: " << rc); + release_fd(fd); + return -1; + } + + // Store the handle mapping (will be updated when connection completes) + { + std::lock_guard lock(conn_mutex_); + // Get the connection handle that was just created + struct ble_gap_conn_desc desc; + if (ble_gap_conn_find_by_addr(&peer_addr, &desc) == 0) { + handle_to_fd_[desc.conn_handle] = fd; + connections_[fd].conn_handle = desc.conn_handle; + } + } + + LOG(Info, "Connection initiated to " << params.peer_address << " fd=" << fd); + + return fd; +} + +int NimbleClientTransport::disconnect(int fd) +{ + std::lock_guard lock(conn_mutex_); + + auto it = connections_.find(fd); + if (it == connections_.end()) { + return -1; + } + + uint16_t conn_handle = it->second.conn_handle; + + int rc = ble_gap_terminate(conn_handle, BLE_ERR_REM_USER_CONN_TERM); + if (rc != 0) { + LOG(Error, "Failed to disconnect: " << rc); + return -1; + } + + LOG(Info, "Disconnect initiated for fd=" << fd); + + return 0; +} + +int NimbleClientTransport::get_fd(int fd) const +{ + // For Nimble transport, we don't have a real file descriptor + // Return the fake fd itself for use with select/poll emulation + return fd; +} + +// ============================================================================ +// Data Transfer +// ============================================================================ + +int NimbleClientTransport::send(int fd, const uint8_t* data, size_t len) +{ + std::lock_guard lock(conn_mutex_); + + auto it = connections_.find(fd); + if (it == connections_.end() || !it->second.connected) { + return -1; + } + + uint16_t conn_handle = it->second.conn_handle; + + // Allocate mbuf for the ATT PDU + struct os_mbuf* om = ble_hs_mbuf_from_flat(data, len); + if (om == NULL) { + LOG(Error, "Failed to allocate mbuf for send"); + return -1; + } + + // Send raw ATT PDU through Nimble stack + int rc = ble_att_tx(conn_handle, om); + if (rc != 0) { + LOG(Error, "Failed to send ATT PDU: " << rc); + // mbuf is freed on error by ble_att_tx + return -1; + } + + LOG(Debug, "Sent " << len << " bytes on fd=" << fd); + + return len; +} + +int NimbleClientTransport::receive(int fd, uint8_t* data, size_t max_len) +{ + std::lock_guard lock(conn_mutex_); + + auto it = connections_.find(fd); + if (it == connections_.end()) { + return -1; + } + + auto& conn = it->second; + + if (conn.rx_queue.empty()) { + return 0; // No data available + } + + const std::vector& pkt = conn.rx_queue.front(); + size_t copy_len = (max_len < pkt.size()) ? max_len : pkt.size(); + + memcpy(data, pkt.data(), copy_len); + conn.rx_queue.pop(); + + return copy_len; +} + +// ============================================================================ +// MTU Operations +// ============================================================================ + +uint16_t NimbleClientTransport::get_mtu(int fd) const +{ + std::lock_guard lock(conn_mutex_); + + auto it = connections_.find(fd); + if (it == connections_.end()) { + return 0; + } + + return it->second.mtu; +} + +int NimbleClientTransport::set_mtu(int fd, uint16_t mtu) +{ + std::lock_guard lock(conn_mutex_); + + auto it = connections_.find(fd); + if (it == connections_.end() || !it->second.connected) { + return -1; + } + + uint16_t conn_handle = it->second.conn_handle; + + // Initiate MTU exchange + int rc = ble_gattc_exchange_mtu(conn_handle, NULL, NULL); + if (rc != 0) { + LOG(Error, "Failed to exchange MTU: " << rc); + return -1; + } + + // The actual MTU will be updated in handle_mtu_event + return 0; +} + +// ============================================================================ +// Helper Functions +// ============================================================================ + +int NimbleClientTransport::allocate_fd() +{ + return next_fd_++; +} + +void NimbleClientTransport::release_fd(int fd) +{ + std::lock_guard lock(conn_mutex_); + connections_.erase(fd); +} + +std::string NimbleClientTransport::addr_to_string(const uint8_t addr[6]) +{ + std::ostringstream oss; + oss << std::hex << std::setfill('0'); + for (int i = 5; i >= 0; --i) { + oss << std::setw(2) << (int)addr[i]; + if (i > 0) oss << ":"; + } + return oss.str(); +} + +void NimbleClientTransport::string_to_addr(const std::string& str, uint8_t addr[6]) +{ + int values[6]; + if (sscanf(str.c_str(), "%x:%x:%x:%x:%x:%x", + &values[5], &values[4], &values[3], + &values[2], &values[1], &values[0]) == 6) { + for (int i = 0; i < 6; ++i) { + addr[i] = (uint8_t)values[i]; + } + } +} + +// ============================================================================ +// MAC Address Operations +// ============================================================================ + +std::string NimbleClientTransport::get_mac_address() const +{ + // If cached, return it + if (!mac_address_.empty()) { + return mac_address_; + } + + // Read from Nimble + int is_nrpa; + uint8_t ble_addr[6]; + int rc = ble_hs_id_copy_addr(BLE_ADDR_RANDOM, ble_addr, &is_nrpa); + if (rc != 0) { + LOG(Warning, "Failed to read BLE address: " << rc); + return ""; + } + + // Cache and return + std::ostringstream addr_stream; + addr_stream << std::hex << std::setfill('0') << + std::setw(2) << (int)ble_addr[5] << ":" << + std::setw(2) << (int)ble_addr[4] << ":" << + std::setw(2) << (int)ble_addr[3] << ":" << + std::setw(2) << (int)ble_addr[2] << ":" << + std::setw(2) << (int)ble_addr[1] << ":" << + std::setw(2) << (int)ble_addr[0]; + mac_address_ = addr_stream.str(); + + return mac_address_; +} + +} // namespace BLEPP + +#endif // BLEPP_NIMBLE_SUPPORT diff --git a/src/nimble_transport.cc b/src/nimble_transport.cc new file mode 100644 index 0000000..1042d5e --- /dev/null +++ b/src/nimble_transport.cc @@ -0,0 +1,1096 @@ +/* + * + * blepp - Implementation of the Generic ATTribute Protocol + * + * Copyright (C) 2024 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +#ifdef BLEPP_NIMBLE_SUPPORT + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// NimBLE stack headers +extern "C" { +#include "nimble/nimble_port.h" +#include "host/ble_hs.h" +#include "host/ble_gap.h" +#include "host/ble_gatt.h" +#include "host/util/util.h" +#include "os/os_mbuf.h" + +// ATBM-specific NimBLE port functions +void nimble_port_atbmos_init(void(* host_task_fn)(void*)); +void nimble_port_atbmos_free(void); +int hif_ioctl_init(void); +void ble_hs_sched_start(void); +} + +// ATBM ioctl base number +#define ATBM_IOCTL (121) + +// ATBM ioctl command definitions (from ble_host/os/linux/atbm_os_api.c) +#define ATBM_AT_CMD_DIRECT _IOW(ATBM_IOCTL, 0, unsigned int) +#define ATBM_BLE_SMART _IOW(ATBM_IOCTL, 1, unsigned int) + +// ATBM BLE ioctl commands (from ble_host/nimble_v42/nimble/transport/ioctl/ble_hci_hif.c) +#define ATBM_BLE_COEXIST_START _IOW(ATBM_IOCTL, 0, unsigned int) +#define ATBM_BLE_COEXIST_STOP _IOW(ATBM_IOCTL, 1, unsigned int) +#define ATBM_BLE_SET_ADV_DATA _IOW(ATBM_IOCTL, 2, unsigned int) +#define ATBM_BLE_ADV_RESP_MODE_START _IOW(ATBM_IOCTL, 3, unsigned int) +#define ATBM_BLE_SET_RESP_DATA _IOW(ATBM_IOCTL, 4, unsigned int) +#define ATBM_BLE_HIF_TXDATA _IOW(ATBM_IOCTL, 5, unsigned int) + +// HCI packet types +#define BLE_HCI_HIF_NONE 0x00 +#define BLE_HCI_HIF_CMD 0x01 +#define BLE_HCI_HIF_ACL 0x02 +#define BLE_HCI_HIF_SCO 0x03 +#define BLE_HCI_HIF_EVT 0x04 +#define BLE_HCI_HIF_ISO 0x05 + +// WSM message types +#define HI_MSG_ID_BLE_BASE 0xC00 +#define HI_MSG_ID_BLE_EVENT (HI_MSG_ID_BLE_BASE + 0x01) +#define HI_MSG_ID_BLE_ACK (HI_MSG_ID_BLE_BASE + 0x02) + +// Buffer sizes +#define MAX_SYNC_EVENT_BUFFER_LEN 512 +#define HCI_ACL_SHARE_SIZE 1538 + +namespace BLEPP +{ + +// Global pointer to NimbleTransport instance for signal handler +static NimbleTransport* g_nimble_transport_instance = nullptr; +static std::mutex g_signal_mutex; + +// Global variables for NimBLE stack synchronization +static uint8_t own_addr_type = 0; +static bool nimble_synced = false; +static bool gatts_started = false; +static std::mutex nimble_sync_mutex; +static sem_t nimble_sync_sem; + +// NimBLE host sync callback - called when BLE stack is ready +static void nimble_sync_callback(void) +{ + int rc; + + LOG(Info, "NimBLE stack sync callback"); + + // Ensure we have a valid address + rc = ble_hs_util_ensure_addr(0); + if (rc != 0) { + LOG(Error, "Failed to ensure address: " << rc); + return; + } + + // Infer own address type + rc = ble_hs_id_infer_auto(0, &own_addr_type); + if (rc != 0) { + LOG(Error, "Failed to infer address type: " << rc); + return; + } + + { + std::lock_guard lock(nimble_sync_mutex); + nimble_synced = true; + } + + // Services are already registered before the host task starts + // No need to register them here + + // Signal that sync is complete + sem_post(&nimble_sync_sem); + + LOG(Info, "NimBLE stack synchronized, address type: " << (int)own_addr_type); +} + +// NimBLE GAP event callback - handles connection/disconnection events +static int nimble_gap_event_callback(struct ble_gap_event *event, void *arg) +{ + switch (event->type) { + case BLE_GAP_EVENT_CONNECT: + LOG(Info, "GAP Connect event: status=" << event->connect.status); + if (event->connect.status == 0) { + LOG(Info, "Connected: handle=" << event->connect.conn_handle); + } else { + // Connection failed, restart advertising + LOG(Info, "Connection failed, restarting advertising"); + std::lock_guard lock(g_signal_mutex); + if (g_nimble_transport_instance) { + g_nimble_transport_instance->restart_advertising(); + } + } + break; + + case BLE_GAP_EVENT_DISCONNECT: + LOG(Info, "GAP Disconnect event: reason=" << event->disconnect.reason); + // Resume advertising after disconnect + { + std::lock_guard lock(g_signal_mutex); + if (g_nimble_transport_instance) { + g_nimble_transport_instance->restart_advertising(); + } + } + break; + + case BLE_GAP_EVENT_ADV_COMPLETE: + LOG(Info, "GAP Advertising complete"); + break; + + case BLE_GAP_EVENT_SUBSCRIBE: + LOG(Info, "GAP Subscribe event: handle=" << event->subscribe.attr_handle); + break; + + case BLE_GAP_EVENT_CONN_UPDATE: + LOG(Info, "GAP Connection update: status=" << event->conn_update.status); + break; + + case BLE_GAP_EVENT_MTU: + LOG(Info, "GAP MTU update: conn_handle=" << event->mtu.conn_handle + << " channel_id=" << event->mtu.channel_id + << " mtu=" << event->mtu.value); + break; + + case BLE_GAP_EVENT_NOTIFY_TX: + LOG(Debug, "GAP Notify TX: conn_handle=" << event->notify_tx.conn_handle + << " attr_handle=" << event->notify_tx.attr_handle + << " status=" << event->notify_tx.status); + break; + + case BLE_GAP_EVENT_NOTIFY_RX: + LOG(Debug, "GAP Notify RX: conn_handle=" << event->notify_rx.conn_handle + << " attr_handle=" << event->notify_rx.attr_handle); + break; + + default: + LOG(Info, "GAP event: type=" << event->type << " (unknown)"); + break; + } + + return 0; +} + +// NimBLE host task function +static void nimble_host_task(void *param) +{ + // Set sync callback + ble_hs_cfg.sync_cb = nimble_sync_callback; + + LOG(Info, "NimBLE host task starting"); + + // Run NimBLE host stack event loop + nimble_port_run(); + + LOG(Info, "NimBLE host task stopped"); +} + +// Signal handler for SIGIO +static void nimble_signal_handler(int sig_num) +{ + if (sig_num == SIGIO) { + std::lock_guard lock(g_signal_mutex); + if (g_nimble_transport_instance) { + g_nimble_transport_instance->signal_event(); + } + } +} + +NimbleTransport::NimbleTransport(const char* device_path) + : device_path_(device_path) + , ioctl_fd_(-1) + , advertising_(false) + , next_conn_handle_(1) + , host_task_started_(false) + , adv_params_valid_(false) + , running_(false) +{ + ENTER(); + + // Open Nimble device + ioctl_fd_ = open(device_path_.c_str(), O_RDWR); + if (ioctl_fd_ < 0) { + throw std::runtime_error(std::string("Failed to open ") + device_path_ + ": " + strerror(errno)); + } + + // Set up async I/O + fcntl(ioctl_fd_, F_SETOWN, getpid()); + unsigned long flags = fcntl(ioctl_fd_, F_GETFL); + fcntl(ioctl_fd_, F_SETFL, flags | FASYNC); + + // Set close-on-exec flag + flags = fcntl(ioctl_fd_, F_GETFD, 0); + fcntl(ioctl_fd_, F_SETFD, flags | FD_CLOEXEC); + + // Register signal handler + { + std::lock_guard lock(g_signal_mutex); + g_nimble_transport_instance = this; + signal(SIGIO, nimble_signal_handler); + } + + // Initialize semaphores + sem_init(&event_sem_, 0, 0); + sem_init(&ioctl_sem_, 0, 1); + sem_init(&nimble_sync_sem, 0, 0); + + // Initialize NimBLE stack + LOG(Info, "Initializing NimBLE stack"); + nimble_port_init(); + + // Clear GATT database before registering services + // This must be called AFTER nimble_port_init but BEFORE starting host task + ble_gatts_reset(); + LOG(Info, "GATT database reset (before host task start)"); + + // Initialize HCI ioctl interface + if (hif_ioctl_init() < 0) { + close(ioctl_fd_); + throw std::runtime_error("Failed to initialize HCI ioctl interface"); + } + + // NOTE: We do NOT start the host task here! + // Services must be registered before starting the host task. + // The host task will be started in convert_and_register_services() after services are added. + + LOG(Info, "NimbleTransport initialized on " << device_path_ << " (host task will start after service registration)"); +} + +NimbleTransport::~NimbleTransport() +{ + ENTER(); + cleanup(); +} + +void NimbleTransport::cleanup() +{ + if (running_) { + // Stop event thread + running_ = false; + sem_post(&event_sem_); // Wake up event thread + + if (event_thread_.joinable()) { + event_thread_.join(); + } + + // Stop advertising if active + if (advertising_) { + stop_advertising(); + } + + // Shutdown NimBLE stack + LOG(Info, "Shutting down NimBLE stack"); + nimble_port_atbmos_free(); + + // Cleanup signal handler + { + std::lock_guard lock(g_signal_mutex); + if (g_nimble_transport_instance == this) { + g_nimble_transport_instance = nullptr; + signal(SIGIO, SIG_DFL); + } + } + + // Close device + if (ioctl_fd_ >= 0) { + close(ioctl_fd_); + ioctl_fd_ = -1; + } + + // Destroy semaphores + sem_destroy(&event_sem_); + sem_destroy(&ioctl_sem_); + sem_destroy(&nimble_sync_sem); + + LOG(Info, "NimbleTransport cleaned up"); + } +} + +void NimbleTransport::signal_event() +{ + // Called from signal handler - post to semaphore to wake event thread + sem_post(&event_sem_); +} + +void NimbleTransport::event_loop_thread() +{ + LOG(Info, "Nimble event loop thread started"); + + while (running_) { + // Wait for SIGIO signal + sem_wait(&event_sem_); + + if (!running_) { + break; + } + + // Read all pending events + NimbleTransport::status_async event; + do { + ssize_t len = read(ioctl_fd_, &event, sizeof(event)); + + if (len != sizeof(event)) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + LOG(Error, "Failed to read Nimble event: " << strerror(errno)); + } + break; + } + + // Process the event + process_nimble_event(&event); + + } while (event.list_empty == 0); // Continue if more events pending + } + + LOG(Info, "Nimble event loop thread stopped"); +} + +void NimbleTransport::process_nimble_event(const struct status_async* event) +{ + // Extract WSM header + const struct wsm_hdr* wsm = reinterpret_cast(event->event_buffer); + const uint8_t* hci_data = event->event_buffer + sizeof(struct wsm_hdr); + + LOG(Debug, "Nimble event: type=" << (int)event->type + << " driver_mode=" << (int)event->driver_mode + << " wsm_id=0x" << std::hex << wsm->id + << " wsm_len=" << std::dec << wsm->len); + + if (wsm->id == HI_MSG_ID_BLE_ACK || wsm->id == HI_MSG_ID_BLE_EVENT) { + // Process HCI packet + process_hci_event(hci_data, wsm->len); + } else { + LOG(Warning, "Unknown Nimble event ID: 0x" << std::hex << wsm->id); + } +} + +void NimbleTransport::process_hci_event(const uint8_t* data, size_t len) +{ + if (len < 2) { + LOG(Error, "HCI event too short: " << len); + return; + } + + uint8_t hci_type = data[0]; + + if (hci_type == BLE_HCI_HIF_EVT) { + // HCI Event packet + if (len < 3) { + LOG(Error, "HCI event packet too short"); + return; + } + + uint8_t event_code = data[1]; + uint8_t param_len = data[2]; + + LOG(Debug, "HCI Event: code=0x" << std::hex << (int)event_code + << " param_len=" << std::dec << (int)param_len); + + // Parse specific HCI events + switch (event_code) { + case 0x03: // HCI_Connection_Complete + if (param_len >= 11) { + uint8_t status = data[3]; + uint16_t conn_handle = (data[5] << 8) | data[4]; + + if (status == 0 && on_connected) { + // Extract peer address (6 bytes in reverse order for BLE) + ConnectionParams conn_params; + char addr_str[18]; + snprintf(addr_str, sizeof(addr_str), "%02X:%02X:%02X:%02X:%02X:%02X", + data[11], data[10], data[9], data[8], data[7], data[6]); + conn_params.peer_address = addr_str; + conn_params.conn_handle = conn_handle; + on_connected(conn_params); + LOG(Info, "Connection complete: handle=" << conn_handle); + } else { + LOG(Error, "Connection failed: status=" << (int)status); + } + } + break; + + case 0x05: // HCI_Disconnection_Complete + if (param_len >= 4) { + uint8_t status = data[3]; + uint16_t conn_handle = (data[5] << 8) | data[4]; + uint8_t reason = data[6]; + + if (status == 0 && on_disconnected) { + on_disconnected(conn_handle); + LOG(Info, "Disconnection complete: handle=" << conn_handle << " reason=" << (int)reason); + } + } + break; + + case 0x0E: // HCI_Command_Complete + LOG(Debug, "Command complete event"); + break; + + case 0x0F: // HCI_Command_Status + if (param_len >= 3) { + uint8_t status = data[3]; + if (status != 0) { + LOG(Warning, "Command status error: " << (int)status); + } + } + break; + + default: + // For other events, pass to on_data_received if registered + if (on_data_received) { + // Try to extract connection handle if present + uint16_t conn_handle = 0xFFFF; // Invalid handle + if (param_len >= 2) { + conn_handle = (data[4] << 8) | data[3]; + } + on_data_received(conn_handle, data + 3, param_len); + } + break; + } + + } else if (hci_type == BLE_HCI_HIF_ACL) { + // HCI ACL Data packet + if (len < 5) { + LOG(Error, "HCI ACL packet too short"); + return; + } + + uint16_t handle_flags = (data[2] << 8) | data[1]; + uint16_t conn_handle = handle_flags & 0x0FFF; + uint16_t data_len = (data[4] << 8) | data[3]; + + LOG(Debug, "HCI ACL Data: conn_handle=" << conn_handle + << " data_len=" << data_len); + + if (on_data_received && len >= 5 + data_len) { + on_data_received(conn_handle, data + 5, data_len); + } + } +} + +int NimbleTransport::start_advertising(const AdvertisingParams& params) +{ + ENTER(); + + if (advertising_) { + LOG(Warning, "Already advertising"); + return 0; + } + + // Store advertising parameters for potential restart after disconnect + adv_params_ = params; + adv_params_valid_ = true; + + // Set up advertising data using NimBLE GAP API + struct ble_hs_adv_fields fields; + memset(&fields, 0, sizeof(fields)); + + // Add service UUIDs if provided + static ble_uuid128_t service_uuids[8]; // Static storage for UUIDs + if (!params.service_uuids.empty()) { + size_t num_uuids = (params.service_uuids.size() < 8) ? params.service_uuids.size() : 8; + LOG(Info, "Adding " << num_uuids << " service UUIDs to advertising data"); + for (size_t i = 0; i < num_uuids; ++i) { + // Convert UUID to NimBLE format (little-endian) + memcpy(service_uuids[i].value, params.service_uuids[i].value.u128.data, 16); + service_uuids[i].u.type = BLE_UUID_TYPE_128; + + // Log UUID bytes for debugging + LOG(Debug, "UUID[" << i << "] bytes: " + << std::hex << std::setfill('0') + << std::setw(2) << (int)service_uuids[i].value[0] << " " + << std::setw(2) << (int)service_uuids[i].value[1] << " " + << std::setw(2) << (int)service_uuids[i].value[2] << " " + << std::setw(2) << (int)service_uuids[i].value[3] + << std::dec); + } + fields.uuids128 = service_uuids; + fields.num_uuids128 = num_uuids; + fields.uuids128_is_complete = 1; + } + + // Add service data if provided (for Improv WiFi v2.0 compliance) + static uint8_t svc_data_buf[31]; // Static storage for service data + if (params.service_data_uuid16 != 0 && !params.service_data.empty()) { + LOG(Info, "Adding service data for UUID16 0x" << std::hex << params.service_data_uuid16 + << std::dec << " (" << params.service_data.size() << " bytes)"); + + // Build service data: [UUID16 little-endian][data bytes] + svc_data_buf[0] = params.service_data_uuid16 & 0xFF; + svc_data_buf[1] = (params.service_data_uuid16 >> 8) & 0xFF; + size_t data_len = params.service_data.size(); + if (data_len > sizeof(svc_data_buf) - 2) { + data_len = sizeof(svc_data_buf) - 2; + LOG(Warning, "Service data truncated to " << data_len << " bytes"); + } + memcpy(svc_data_buf + 2, params.service_data.data(), data_len); + + fields.svc_data_uuid16 = svc_data_buf; + fields.svc_data_uuid16_len = data_len + 2; // UUID16 (2 bytes) + data + } + + int rc = ble_gap_adv_set_fields(&fields); + if (rc != 0) { + LOG(Error, "Failed to set advertising fields: " << rc); + return -1; + } + LOG(Info, "Advertising fields set successfully"); + + // Set scan response data with device name + struct ble_hs_adv_fields rsp_fields; + memset(&rsp_fields, 0, sizeof(rsp_fields)); + + if (!params.device_name.empty()) { + rsp_fields.name = (uint8_t*)params.device_name.c_str(); + rsp_fields.name_len = params.device_name.length(); + rsp_fields.name_is_complete = 1; + } + + rc = ble_gap_adv_rsp_set_fields(&rsp_fields); + if (rc != 0) { + LOG(Error, "Failed to set scan response fields: " << rc); + return -1; + } + LOG(Info, "Scan response fields set successfully"); + + // Set advertising parameters + struct ble_gap_adv_params advp; + memset(&advp, 0, sizeof(advp)); + advp.conn_mode = BLE_GAP_CONN_MODE_UND; // Undirected connectable + advp.disc_mode = BLE_GAP_DISC_MODE_GEN; // General discoverable + // Convert milliseconds to BLE advertising interval units (0.625ms) + advp.itvl_min = (params.min_interval_ms * 1000) / 625; // ms to 0.625ms units + advp.itvl_max = (params.max_interval_ms * 1000) / 625; + + LOG(Info, "Starting advertising with interval " << advp.itvl_min + << "-" << advp.itvl_max << " (0.625ms units), own_addr_type=" << (int)own_addr_type); + + // Start advertising using NimBLE GAP API with event callback + rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER, &advp, nimble_gap_event_callback, NULL); + if (rc != 0) { + LOG(Error, "Failed to start Nimble advertising: " << rc); + return -1; + } + + advertising_ = true; + LOG(Info, "Nimble advertising started: " << params.device_name); + return 0; +} + +void NimbleTransport::restart_advertising() +{ + ENTER(); + + if (!adv_params_valid_) { + LOG(Warning, "Cannot restart advertising: no previous advertising parameters"); + return; + } + + LOG(Info, "Restarting advertising after disconnect"); + + // Reset advertising flag so start_advertising doesn't bail early + advertising_ = false; + + // Restart advertising with stored parameters + int rc = start_advertising(adv_params_); + if (rc != 0) { + LOG(Error, "Failed to restart advertising: " << rc); + } +} + +int NimbleTransport::stop_advertising() +{ + ENTER(); + + if (!advertising_) { + return 0; + } + + // Stop advertising using NimBLE GAP API + int rc = ble_gap_adv_stop(); + if (rc != 0) { + LOG(Error, "Failed to stop Nimble advertising: " << rc); + return -1; + } + + advertising_ = false; + LOG(Info, "Nimble advertising stopped"); + return 0; +} + +// NimBLE GATT callback bridge structures +struct NimbleGATTCallbackContext +{ + GATTAccessCallback callback; + void* user_arg; +}; + +// Static storage for NimBLE GATT structures (must persist) +static std::vector nimble_service_uuids; +static std::vector nimble_char_uuids; +static std::vector nimble_characteristics; +static std::vector nimble_services; +static std::vector nimble_callbacks; + +// NimBLE GATT access callback - bridges to our GATTAccessCallback +static int nimble_gatt_access_cb(uint16_t conn_handle, uint16_t attr_handle, + struct ble_gatt_access_ctxt *ctxt, void *arg) +{ + LOG(Info, ">>> NimBLE GATT ACCESS CALLBACK <<<"); + LOG(Info, "conn_handle=" << conn_handle << " attr_handle=" << attr_handle << " op=" << (int)ctxt->op); + + NimbleGATTCallbackContext* ctx = static_cast(arg); + if (!ctx || !ctx->callback) { + LOG(Error, "NimBLE GATT callback context is null"); + return BLE_ATT_ERR_UNLIKELY; + } + + // Convert NimBLE operation to our ATTAccessOp + ATTAccessOp op; + switch (ctxt->op) { + case BLE_GATT_ACCESS_OP_READ_CHR: + op = ATTAccessOp::READ_CHR; + break; + case BLE_GATT_ACCESS_OP_WRITE_CHR: + op = ATTAccessOp::WRITE_CHR; + break; + case BLE_GATT_ACCESS_OP_READ_DSC: + op = ATTAccessOp::READ_DSC; + break; + case BLE_GATT_ACCESS_OP_WRITE_DSC: + op = ATTAccessOp::WRITE_DSC; + break; + default: + LOG(Warning, "Unknown NimBLE GATT operation: " << ctxt->op); + return BLE_ATT_ERR_UNLIKELY; + } + + // Prepare data buffer + std::vector data; + uint16_t offset = 0; // NimBLE doesn't use offset for characteristic access + + if (op == ATTAccessOp::WRITE_CHR || op == ATTAccessOp::WRITE_DSC) { + // For writes, extract data from mbuf chain + size_t len = OS_MBUF_PKTLEN(ctxt->om); + LOG(Debug, "Write operation: len=" << len); + data.resize(len); + os_mbuf_copydata(ctxt->om, 0, data.size(), data.data()); + } + + // Call our callback + LOG(Debug, "Calling application callback: op=" << (int)op << " data_len=" << data.size()); + int result = ctx->callback(conn_handle, op, offset, data); + LOG(Debug, "Application callback returned: " << result); + + if (op == ATTAccessOp::READ_CHR || op == ATTAccessOp::READ_DSC) { + // For reads, copy data to mbuf + LOG(Debug, "Read operation: result=" << result << " data_len=" << data.size()); + if (result == 0 && !data.empty()) { + int rc = os_mbuf_append(ctxt->om, data.data(), data.size()); + if (rc != 0) { + LOG(Error, "Failed to append data to mbuf: " << rc); + return BLE_ATT_ERR_INSUFFICIENT_RES; + } + LOG(Debug, "Successfully appended " << data.size() << " bytes to mbuf"); + } + } + + return result; +} + +int NimbleTransport::convert_and_register_services() +{ + ENTER(); + + if (service_defs_.empty()) { + LOG(Info, "No services to register with NimBLE"); + return 0; + } + + LOG(Info, "Converting " << service_defs_.size() << " services to NimBLE format"); + + // Clear previous registrations + nimble_service_uuids.clear(); + nimble_char_uuids.clear(); + nimble_characteristics.clear(); + nimble_services.clear(); + nimble_callbacks.clear(); + + // Reserve space to avoid reallocation (which would invalidate pointers) + size_t total_chars = 0; + for (const auto& svc : service_defs_) { + total_chars += svc.characteristics.size(); + } + + nimble_service_uuids.reserve(service_defs_.size()); + nimble_char_uuids.reserve(total_chars); + nimble_callbacks.reserve(total_chars); + nimble_services.reserve(service_defs_.size() + 1); // +1 for terminator + + // Convert each service + for (const auto& svc_def : service_defs_) { + // Convert service UUID + ble_uuid128_t svc_uuid; + memcpy(svc_uuid.value, svc_def.uuid.value.u128.data, 16); + svc_uuid.u.type = BLE_UUID_TYPE_128; + nimble_service_uuids.push_back(svc_uuid); + + LOG(Info, "Converting service with " << svc_def.characteristics.size() << " characteristics"); + + // Start characteristics array for this service + size_t char_start_idx = nimble_characteristics.size(); + + // Convert each characteristic + for (const auto& char_def : svc_def.characteristics) { + LOG(Debug, "Converting characteristic with flags=0x" << std::hex << char_def.flags << std::dec + << " has_callback=" << (char_def.access_cb ? "yes" : "no")); + // Convert characteristic UUID + ble_uuid128_t char_uuid; + memcpy(char_uuid.value, char_def.uuid.value.u128.data, 16); + char_uuid.u.type = BLE_UUID_TYPE_128; + nimble_char_uuids.push_back(char_uuid); + + // Create callback context if callback exists + ble_gatt_access_fn* access_cb_ptr = nullptr; + void* cb_arg = nullptr; + + if (char_def.access_cb) { + NimbleGATTCallbackContext ctx; + ctx.callback = char_def.access_cb; + ctx.user_arg = char_def.arg; + nimble_callbacks.push_back(ctx); + + access_cb_ptr = nimble_gatt_access_cb; + cb_arg = &nimble_callbacks.back(); + LOG(Debug, "Callback registered for characteristic"); + } else { + LOG(Warning, "No callback registered for characteristic with flags=0x" << std::hex << char_def.flags << std::dec); + } + + // Convert flags + uint16_t nimble_flags = 0; + if (char_def.flags & GATT_CHR_F_READ) nimble_flags |= BLE_GATT_CHR_F_READ; + if (char_def.flags & GATT_CHR_F_WRITE) nimble_flags |= BLE_GATT_CHR_F_WRITE; + if (char_def.flags & GATT_CHR_F_WRITE_NO_RSP) nimble_flags |= BLE_GATT_CHR_F_WRITE_NO_RSP; + if (char_def.flags & GATT_CHR_F_NOTIFY) nimble_flags |= BLE_GATT_CHR_F_NOTIFY; + if (char_def.flags & GATT_CHR_F_INDICATE) nimble_flags |= BLE_GATT_CHR_F_INDICATE; + + // Create NimBLE characteristic definition + ble_gatt_chr_def chr = { + .uuid = &nimble_char_uuids.back().u, + .access_cb = access_cb_ptr, + .arg = cb_arg, + .descriptors = nullptr, // TODO: Support descriptors + .flags = nimble_flags, + .min_key_size = char_def.min_key_size, + .val_handle = char_def.val_handle_ptr + }; + + nimble_characteristics.push_back(chr); + } + + // Add terminator for characteristics array + ble_gatt_chr_def chr_term = {0}; + nimble_characteristics.push_back(chr_term); + + // Create NimBLE service definition + ble_gatt_svc_def svc = { + .type = static_cast((svc_def.type == GATTServiceType::PRIMARY) ? + BLE_GATT_SVC_TYPE_PRIMARY : BLE_GATT_SVC_TYPE_SECONDARY), + .uuid = &nimble_service_uuids.back().u, + .includes = nullptr, // TODO: Support included services + .characteristics = &nimble_characteristics[char_start_idx] + }; + + nimble_services.push_back(svc); + } + + // Add terminator for services array + ble_gatt_svc_def svc_term = {0}; + nimble_services.push_back(svc_term); + + // Register services with NimBLE GATTS + LOG(Info, "Registering " << nimble_services.size() - 1 << " services (" + << nimble_characteristics.size() << " total characteristic entries) with NimBLE GATTS"); + + int rc = ble_gatts_count_cfg(nimble_services.data()); + if (rc != 0) { + LOG(Error, "Failed to count NimBLE GATT services: " << rc); + return -1; + } + LOG(Debug, "ble_gatts_count_cfg returned: " << rc); + + rc = ble_gatts_add_svcs(nimble_services.data()); + if (rc != 0) { + LOG(Error, "Failed to add NimBLE GATT services: " << rc); + return -1; + } + LOG(Debug, "ble_gatts_add_svcs returned: " << rc); + + LOG(Info, "Successfully registered " << service_defs_.size() << " services with NimBLE"); + return 0; +} + +int NimbleTransport::register_services(const std::vector& services) +{ + ENTER(); + + // Store service definitions for later use + service_defs_ = services; + + LOG(Info, "Storing " << services.size() << " GATT service definitions"); + + // Register services with NimBLE IMMEDIATELY (before host task starts) + int rc = convert_and_register_services(); + if (rc != 0) { + LOG(Error, "Failed to register services: " << rc); + return rc; + } + + // Now that services are registered, start the NimBLE host task (if not already started) + if (!host_task_started_) { + LOG(Info, "Starting NimBLE host task after service registration"); + + // Start NimBLE host task (this will trigger the sync callback) + nimble_port_atbmos_init(nimble_host_task); + + // Start BLE host scheduler (this triggers the sync callback) + LOG(Info, "Starting BLE host scheduler"); + ble_hs_sched_start(); + + //Wait for NimBLE stack to synchronize (with timeout) + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += 5; // 5 second timeout + + LOG(Info, "Waiting for NimBLE stack to synchronize..."); + if (sem_timedwait(&nimble_sync_sem, &ts) < 0) { + LOG(Error, "Timeout waiting for NimBLE stack synchronization"); + return -1; + } + + LOG(Info, "NimBLE stack synchronized successfully"); + + // Start event loop thread + running_ = true; + event_thread_ = std::thread(&NimbleTransport::event_loop_thread, this); + + host_task_started_ = true; + LOG(Info, "NimBLE host task and event loop started"); + } + + return 0; +} + +bool NimbleTransport::is_advertising() const +{ + return advertising_; +} + +int NimbleTransport::accept_connection() +{ + // For Nimble, connections are handled asynchronously via events + // The event loop thread will call on_connected when a connection occurs + return 0; +} + +int NimbleTransport::disconnect(uint16_t conn_handle) +{ + ENTER(); + + std::lock_guard lock(connections_mutex_); + + auto it = connections_.find(conn_handle); + if (it == connections_.end()) { + LOG(Warning, "Connection " << conn_handle << " not found"); + return -1; + } + + // Send HCI Disconnect command + uint8_t hci_disconnect[] = { + 0x06, 0x04, // HCI_Disconnect opcode (0x0406) + 0x03, // Parameter length + (uint8_t)(conn_handle & 0xFF), + (uint8_t)(conn_handle >> 8), + 0x13 // Reason: Remote User Terminated Connection + }; + + int ret = send_hci_command(hci_disconnect, sizeof(hci_disconnect)); + if (ret < 0) { + LOG(Error, "Failed to send HCI disconnect command"); + return -1; + } + + // Remove from connections map (actual disconnect event will come later) + connections_.erase(it); + + LOG(Info, "Disconnecting connection " << conn_handle); + return 0; +} + +int NimbleTransport::send_pdu(uint16_t conn_handle, const uint8_t* data, size_t len) +{ + if (len > HCI_ACL_SHARE_SIZE) { + LOG(Error, "PDU too large: " << len); + return -1; + } + + // Build HCI ACL packet with L2CAP header + // Format: + // [0-1]: Length (total packet length for ioctl) + // [2]: Packet type (BLE_HCI_HIF_ACL = 0x02) + // [3-4]: HCI ACL handle + flags + // [5-6]: HCI ACL data length (L2CAP length + 4 for L2CAP header) + // [7-8]: L2CAP length (ATT PDU length) + // [9-10]: L2CAP CID (0x0004 for ATT) + // [11+]: ATT PDU data + + uint8_t hci_packet[HCI_ACL_SHARE_SIZE + 20]; + size_t offset = 0; + + // [0-1]: Total length for ioctl = 1 (type) + 4 (HCI ACL hdr) + 4 (L2CAP hdr) + len (ATT data) + uint16_t total_len = 1 + 4 + 4 + len; + hci_packet[offset++] = total_len & 0xFF; + hci_packet[offset++] = (total_len >> 8) & 0xFF; + + // [2]: Packet type + hci_packet[offset++] = BLE_HCI_HIF_ACL; + + // [3-4]: HCI ACL handle + flags (PB=00, BC=00 for start of L2CAP PDU) + hci_packet[offset++] = conn_handle & 0xFF; + hci_packet[offset++] = (conn_handle >> 8) & 0x0F; + + // [5-6]: HCI ACL data length (L2CAP header + ATT data) + uint16_t acl_len = 4 + len; // L2CAP header (4) + ATT PDU + hci_packet[offset++] = acl_len & 0xFF; + hci_packet[offset++] = (acl_len >> 8) & 0xFF; + + // [7-8]: L2CAP length (just the ATT PDU) + hci_packet[offset++] = len & 0xFF; + hci_packet[offset++] = (len >> 8) & 0xFF; + + // [9-10]: L2CAP CID (0x0004 for ATT) + hci_packet[offset++] = 0x04; + hci_packet[offset++] = 0x00; + + // [11+]: ATT PDU data + memcpy(&hci_packet[offset], data, len); + offset += len; + + sem_wait(&ioctl_sem_); + int ret = ioctl(ioctl_fd_, ATBM_BLE_HIF_TXDATA, (unsigned long)hci_packet); + sem_post(&ioctl_sem_); + + if (ret < 0) { + LOG(Error, "Failed to send HCI ACL data: " << strerror(errno)); + return -1; + } + + LOG(Debug, "Sent " << len << " bytes ATT data on connection " << conn_handle + << " (total HCI packet: " << offset << " bytes)"); + return len; +} + +int NimbleTransport::recv_pdu(uint16_t conn_handle, uint8_t* buf, size_t len) +{ + // For Nimble, data is received asynchronously via events + // This function is not used in the async model + return -1; +} + +int NimbleTransport::send_hci_command(const uint8_t* cmd, size_t len) +{ + if (len > 258) { // Max HCI command size + LOG(Error, "HCI command too large: " << len); + return -1; + } + + // Build HCI command packet + uint8_t hci_packet[260]; + uint16_t* plen = reinterpret_cast(hci_packet); + + *plen = len + 1; // Type byte + command data + hci_packet[2] = BLE_HCI_HIF_CMD; + memcpy(&hci_packet[3], cmd, len); + + sem_wait(&ioctl_sem_); + int ret = ioctl(ioctl_fd_, ATBM_BLE_HIF_TXDATA, (unsigned long)hci_packet); + sem_post(&ioctl_sem_); + + if (ret < 0) { + LOG(Error, "Failed to send HCI command: " << strerror(errno)); + return -1; + } + + LOG(Debug, "Sent HCI command: " << len << " bytes"); + return 0; +} + +int NimbleTransport::set_mtu(uint16_t conn_handle, uint16_t mtu) +{ + std::lock_guard lock(connections_mutex_); + + auto it = connections_.find(conn_handle); + if (it == connections_.end()) { + return -1; + } + + it->second.mtu = mtu; + LOG(Info, "Set MTU for connection " << conn_handle << " to " << mtu); + return 0; +} + +uint16_t NimbleTransport::get_mtu(uint16_t conn_handle) const +{ + std::lock_guard lock(connections_mutex_); + + auto it = connections_.find(conn_handle); + if (it == connections_.end()) { + return 23; // Default ATT MTU + } + + return it->second.mtu; +} + +int NimbleTransport::process_events() +{ + // Events are processed asynchronously by the event thread + // This function is for compatibility with the transport interface + return 0; +} + +} // namespace BLEPP + +#endif // BLEPP_NIMBLE_SUPPORT diff --git a/src/uuid.cc b/src/uuid.cc index 3d3fd43..173d0b2 100644 --- a/src/uuid.cc +++ b/src/uuid.cc @@ -28,6 +28,7 @@ #include #include +#include #include #include diff --git a/tests/test_scan.cc b/tests/test_scan.cc index ca86d29..67c8bdf 100644 --- a/tests/test_scan.cc +++ b/tests/test_scan.cc @@ -6,7 +6,6 @@ #include #include - using namespace BLEPP; #define check(X) do{\ @@ -57,7 +56,7 @@ device: hci0 snap_len: 1500 filter: 0xffffffffffffffff > 04 0E 04 01 0C 20 00 */ - AdvertisingResponse r = HCIScanner::parse_packet(to_data("> 04 3E 21 02 01 00 00 1B EE B5 80 07 00 15 02 01 06 11 06 64 97 81 D1 ED BA 6B AC 11 4C 9D 34 3E 20 09 73 BC")).back(); + AdvertisingResponse r = parse_advertisement_packet(to_data("> 04 3E 21 02 01 00 00 1B EE B5 80 07 00 15 02 01 06 11 06 64 97 81 D1 ED BA 6B AC 11 4C 9D 34 3E 20 09 73 BC")).back(); check(r.UUIDs[0] == UUID("7309203e-349d-4c11-ac6b-baedd1819764")); check(r.UUIDs.size() == 1); check(r.service_data.empty()); @@ -76,7 +75,7 @@ device: hci0 snap_len: 1500 filter: 0xffffffffffffffff check(!r.flags->simultaneous_LE_BR_host); - r = HCIScanner::parse_packet(to_data("> 04 3E 24 02 01 04 00 1B EE B5 80 07 00 18 17 09 44 79 6E 6F 66 69 74 20 49 6E 63 20 44 4F 54 53 20 78 78 78 78 31 BE")).back(); + r = parse_advertisement_packet(to_data("> 04 3E 24 02 01 04 00 1B EE B5 80 07 00 18 17 09 44 79 6E 6F 66 69 74 20 49 6E 63 20 44 4F 54 53 20 78 78 78 78 31 BE")).back(); check(r.UUIDs.size() == 0); check(r.service_data.empty()); check(r.manufacturer_specific_data.empty()); @@ -89,7 +88,7 @@ device: hci0 snap_len: 1500 filter: 0xffffffffffffffff check(!r.uuid_128_bit_complete); check(!r.flags); - r = HCIScanner::parse_packet(to_data("> 04 3E 17 02 01 00 01 0B 57 16 21 76 7C 0B 02 01 1A 07 FF 4C 00 10 02 0A 00 BC")).back(); + r = parse_advertisement_packet(to_data("> 04 3E 17 02 01 00 01 0B 57 16 21 76 7C 0B 02 01 1A 07 FF 4C 00 10 02 0A 00 BC")).back(); std::vector vendor_data_1 = {0x4c, 0x00, 0x10, 0x02, 0x0a, 0x00}; check(r.UUIDs.size() == 0); check(r.service_data.empty()); @@ -105,4 +104,6 @@ device: hci0 snap_len: 1500 filter: 0xffffffffffffffff check(r.flags->simultaneous_LE_BR_controller); check(r.flags->simultaneous_LE_BR_host); + std::cout << "OK" << std::endl; + return 0; } diff --git a/tests/test_transport.cc b/tests/test_transport.cc new file mode 100644 index 0000000..0f4a7ee --- /dev/null +++ b/tests/test_transport.cc @@ -0,0 +1,64 @@ +#include +#include +#include +#include + +using namespace BLEPP; + +#define check(X) do{\ +if(!(X))\ +{\ + std::cerr << "Test failed on line " << __LINE__ << ": " << #X << std::endl;\ + exit(1);\ +}}while(0) + +int main() +{ + log_level = LogLevels::Warning; + + // Test 1: Factory function may return nullptr if no hardware is available + // This is OK in build environments - we test what we can + BLEClientTransport* transport = create_client_transport(); + + if (transport == nullptr) { + // No transport available (no hardware/permissions) + // This is acceptable in build/test environments + std::cout << "No transport available (no hardware/permissions) - skipping runtime tests" << std::endl; + std::cout << "OK" << std::endl; + return 0; + } + + // Test 2: Transport should have a name + const char* name = transport->get_transport_name(); + check(name != nullptr); + check(name[0] != '\0'); + std::cout << "Using transport: " << name << std::endl; + + // Test 3: Transport should report as available (if factory returned it) + check(transport->is_available()); + + // Test 4: Default MTU should be valid (23 is BLE minimum) + // Note: Can't test with invalid fd=-1 on all transports, so skip this test + // uint16_t mtu = transport->get_mtu(-1); + // check(mtu >= 23); + + // Test 5: ScanParams should have sensible defaults + ScanParams params; + check(params.scan_type == ScanParams::ScanType::Active); + check(params.interval_ms == 10); + check(params.window_ms == 10); + check(params.filter_policy == ScanParams::FilterPolicy::All); + check(params.filter_duplicates == true); + + // Test 6: Can configure scan parameters + params.scan_type = ScanParams::ScanType::Passive; + params.filter_duplicates = false; + check(params.scan_type == ScanParams::ScanType::Passive); + check(params.filter_duplicates == false); + + // Cleanup + delete transport; + + std::cout << "OK" << std::endl; + return 0; +}