| # Axis2/C Android Support | |
| This document describes how Axis2/C supports Android deployment with HTTP/2 and JSON-RPC. | |
| ## Quick Reference for Claude | |
| When working with Axis2/C Android support, remember: | |
| 1. **All builds use `-std=gnu99`** - Required for json-c header compatibility | |
| 2. **Android uses static linking** - No dlopen(), services use weak symbol registry | |
| 3. **Service code lives in applications** - Axis2/C core only has weak symbol stubs | |
| 4. **`--whole-archive` is required** - For weak/strong symbol resolution during linking | |
| 5. **IPC uses fork()/execvp()** - NEVER use system() for security (command injection) | |
| 6. **Java validates all input** - SecurityValidator class provides defense-in-depth | |
| ## Build System | |
| ### C Standard: Always gnu99 | |
| All Axis2/C builds use `-std=gnu99` (GNU C99 mode). This is required because: | |
| 1. **json-c headers** use C99 `inline` keyword | |
| 2. **Android NDK headers** require GNU extensions | |
| 3. **POSIX functions** are hidden by strict ANSI mode | |
| **Implementation in all configure.ac files:** | |
| ```m4 | |
| if test "$GCC" = "yes"; then | |
| dnl Use gnu99 for json-c compatibility | |
| CFLAGS="$CFLAGS -std=gnu99 -Wall -Wno-implicit-function-declaration -D_GNU_SOURCE" | |
| fi | |
| ``` | |
| The following configure.ac files all use this pattern: | |
| - `configure.ac` (root) | |
| - `util/configure.ac` | |
| - `axiom/configure.ac` | |
| - `neethi/configure.ac` | |
| - `guththila/configure.ac` | |
| - `samples/configure.ac` | |
| - `tools/md5/configure.ac` | |
| - `tools/tcpmon/configure.ac` | |
| ### Android Detection | |
| Android builds are detected by the `--host` parameter: | |
| ```m4 | |
| AC_MSG_CHECKING([for Android target]) | |
| case "$host" in | |
| *-linux-android*|*-android*) | |
| android_build=yes | |
| AC_DEFINE([AXIS2_ANDROID_BUILD], [1], [Building for Android]) | |
| ;; | |
| *) | |
| android_build=no | |
| ;; | |
| esac | |
| ``` | |
| When `android_build=yes`: | |
| - `AXIS2_ANDROID_BUILD` is defined | |
| - `__ANDROID__` is typically already defined by the NDK compiler | |
| - Static linking is expected | |
| ### Android NDK Cross-Compilation | |
| **Environment setup:** | |
| ```bash | |
| export ANDROID_NDK_HOME=$HOME/Android/Sdk/ndk/28.0.12916984 | |
| export TOOLCHAIN=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64 | |
| export CC=$TOOLCHAIN/bin/aarch64-linux-android21-clang | |
| export AR=$TOOLCHAIN/bin/llvm-ar | |
| export RANLIB=$TOOLCHAIN/bin/llvm-ranlib | |
| ``` | |
| **Configure command:** | |
| ```bash | |
| ./configure \ | |
| --host=aarch64-linux-android \ | |
| --prefix=$DEPS \ | |
| --enable-static \ | |
| --disable-shared \ | |
| --enable-http2 \ | |
| --enable-json \ | |
| --with-apr=$DEPS \ | |
| --with-openssl=$DEPS | |
| ``` | |
| **Convenience script:** Use `build_android.sh` in the repository root for a complete build: | |
| ```bash | |
| ./build_android.sh | |
| ``` | |
| This script sets up the toolchain, configures with all required flags, and builds. Edit the script to adjust paths for your NDK installation and dependency locations. | |
| ## Static Service Registry (Android) | |
| ### The Problem | |
| Traditional Axis2/C loads services dynamically: | |
| ```c | |
| void *lib = dlopen("libmyservice.so", RTLD_NOW); | |
| func = dlsym(lib, "service_invoke_json"); | |
| ``` | |
| Android with static linking has no `.so` files to load. | |
| ### Solution: Weak Symbol Architecture | |
| Axis2/C uses weak symbols so applications can provide service implementations at link time. | |
| **In `src/core/receivers/axis2_json_rpc_msg_recv.c`:** | |
| ```c | |
| #ifdef __ANDROID__ | |
| typedef json_object* (*json_service_invoke_func_t)( | |
| const axutil_env_t *env, json_object *json_request); | |
| /* Weak symbol - returns NULL if no implementation linked */ | |
| __attribute__((weak)) | |
| json_object* camera_control_service_invoke_json( | |
| const axutil_env_t *env, json_object *json_request) | |
| { | |
| (void)env; | |
| (void)json_request; | |
| return NULL; | |
| } | |
| /* Lookup function - returns function pointer for known services */ | |
| json_service_invoke_func_t | |
| android_static_service_lookup(const char *service_name) | |
| { | |
| if (!service_name) return NULL; | |
| if (strcmp(service_name, "CameraControlService") == 0) { | |
| return camera_control_service_invoke_json; | |
| } | |
| /* Add more services here */ | |
| return NULL; | |
| } | |
| #endif | |
| ``` | |
| ### How It Works | |
| ``` | |
| [Axis2/C Framework] [Application] | |
| | | | |
| weak symbol: strong symbol: | |
| camera_control_service_ camera_control_service_ | |
| invoke_json() -> NULL invoke_json() -> response | |
| | | | |
| +----------- Link Time ---------+ | |
| | | |
| Strong symbol wins | |
| ``` | |
| 1. **Framework compiles** with weak symbol stubs (return NULL) | |
| 2. **Application provides** strong symbol implementations | |
| 3. **At link time**, strong symbols override weak symbols | |
| 4. **Result**: Application's service code handles requests | |
| ### Application Service Implementation | |
| Applications provide two files: | |
| **Adapter (bridges Axis2/C to app):** | |
| ```c | |
| // axis2_static_service_adapter.c | |
| #include <axutil_env.h> | |
| #include <json-c/json.h> | |
| extern int camera_control_service_impl(const char* req, char* resp, size_t size); | |
| /* Strong symbol overrides weak in Axis2/C */ | |
| json_object* camera_control_service_invoke_json( | |
| const axutil_env_t *env, json_object *json_request) | |
| { | |
| const char *req_str = json_object_to_json_string(json_request); | |
| char response[65536] = {0}; | |
| camera_control_service_impl(req_str, response, sizeof(response)); | |
| return json_tokener_parse(response); | |
| } | |
| ``` | |
| **Implementation (app-specific logic):** | |
| ```c | |
| // camera_control_service.c | |
| int camera_control_service_impl(const char* req, char* resp, size_t size) | |
| { | |
| /* Parse request, handle action, build response */ | |
| snprintf(resp, size, "{\"result\":\"ok\"}"); | |
| return 0; | |
| } | |
| ``` | |
| ### Critical: Functions Must Be Non-Static | |
| Registry functions MUST NOT use `static`: | |
| ```c | |
| /* WRONG - may be optimized away */ | |
| static json_service_invoke_func_t android_static_service_lookup(...) { } | |
| /* CORRECT - global symbol preserved */ | |
| json_service_invoke_func_t android_static_service_lookup(...) { } | |
| ``` | |
| Static functions have internal linkage and may be excluded from static archives. | |
| ## Android vs Server-Side: Two `_invoke_json` Signatures | |
| The `axis2_json_rpc_msg_recv` framework dispatches to service functions | |
| using **different signatures** depending on the platform: | |
| ### Android (2-param, `json_object*`) | |
| ```c | |
| json_object* camera_control_service_invoke_json( | |
| const axutil_env_t *env, | |
| json_object *json_request); | |
| ``` | |
| - Resolved via the **static service registry** (`android_static_service_lookup`) | |
| - Uses weak/strong symbol linking — no `dlopen`/`dlsym` at runtime | |
| - Works with `json_object*` (json-c object pointers) | |
| - The Kanaha app's `axis2_static_service_adapter.c` bridges this to the | |
| app's `_impl()` functions which use plain `char*` strings | |
| ### Server-side / Linux (4-param, `char*`) | |
| ```c | |
| axis2_char_t* financial_benchmark_service_invoke_json( | |
| axis2_svc_t *svc, | |
| const axutil_env_t *env, | |
| const axis2_char_t *json_request, | |
| axis2_msg_ctx_t *msg_ctx); | |
| ``` | |
| - Resolved via `dlsym(RTLD_DEFAULT, "<serviceclass>_invoke_json")` after | |
| the service `.so` is loaded by `axutil_class_loader_create_dll()` | |
| - Has access to the `axis2_svc_t` and `axis2_msg_ctx_t` for operation | |
| routing, endpoint info, and MCP catalog generation | |
| - Works with `axis2_char_t*` (JSON strings), not `json_object*` | |
| ### Why Two Signatures? | |
| Server-side services need `svc` and `msg_ctx` to: | |
| - Extract the operation name from the message context (URL path dispatch) | |
| - Access endpoint reference for MCP catalog intercepts | |
| - Get configuration context for multi-service deployments | |
| Android services don't need these because: | |
| - The static registry already routes by service name | |
| - Operations are identified by the `"action"` field in the JSON body | |
| - There is no multi-service deployment — one app, one service | |
| Services that want to work in both environments can export both: | |
| - `axis2_char_t* <serviceclass>_invoke_json(svc, env, json_str, msg_ctx)` for server-side | |
| - `axis2_char_t* <serviceclass>_process_json_only(env, json_str)` for simpler contexts | |
| ## Linking for Android | |
| ### Direct Linking Required | |
| Libtool strips `--whole-archive` flags, so direct linking is necessary: | |
| ```bash | |
| $CC -fPIC -o httpd modules.o buildmark.o \ | |
| -Wl,--export-dynamic \ | |
| -L$DEPS/lib \ | |
| # Apache modules... \ | |
| $DEPS/lib/libmod_axis2.a \ | |
| -Wl,--whole-archive $DEPS/lib/libaxis2_engine.a -Wl,--no-whole-archive \ | |
| -Wl,--whole-archive libapp_services.a -Wl,--no-whole-archive \ | |
| # Other Axis2 libraries... \ | |
| -ljson-c -lnghttp2 -laprutil-1 -lapr-1 \ | |
| -lm -llog -pthread | |
| ``` | |
| **Key flags:** | |
| | Flag | Purpose | | |
| |------|---------| | |
| | `-Wl,--whole-archive` | Include ALL objects from archive | | |
| | `-Wl,--no-whole-archive` | Resume normal archive linking | | |
| | `-Wl,--export-dynamic` | Export all symbols | | |
| | `-llog` | Android logging | | |
| ### Verify Symbol Inclusion | |
| ```bash | |
| nm httpd | grep -E "android_static|camera_control" | |
| # Expected (T = global text): | |
| # 00000000007b4998 T android_static_service_lookup | |
| # 00000000007b3ae0 T camera_control_service_invoke_json | |
| ``` | |
| ## services.xml Configuration | |
| ### REST Dispatcher Parameters | |
| Use `RESTLocation` and `RESTMethod`, NOT `httpPath` and `httpMethod`: | |
| ```xml | |
| <service name="CameraControlService"> | |
| <operation name="getStatus"> | |
| <messageReceiver class="axis2_json_rpc_msg_recv"/> | |
| <parameter name="RESTMethod">POST</parameter> | |
| <parameter name="RESTLocation">/getStatus</parameter> | |
| <parameter name="contentType">application/json</parameter> | |
| </operation> | |
| </service> | |
| ``` | |
| ## Adding New Services | |
| ### Step 1: Add Weak Symbol to Axis2/C | |
| In `src/core/receivers/axis2_json_rpc_msg_recv.c`: | |
| ```c | |
| #ifdef __ANDROID__ | |
| __attribute__((weak)) | |
| json_object* my_new_service_invoke_json( | |
| const axutil_env_t *env, json_object *json_request) | |
| { | |
| (void)env; (void)json_request; | |
| return NULL; | |
| } | |
| json_service_invoke_func_t android_static_service_lookup(const char *svc) | |
| { | |
| /* Existing services... */ | |
| if (strcmp(svc, "MyNewService") == 0) { | |
| return my_new_service_invoke_json; | |
| } | |
| return NULL; | |
| } | |
| #endif | |
| ``` | |
| ### Step 2: Implement in Application | |
| Create adapter and implementation files in your application, then link with `--whole-archive`. | |
| ### Step 3: Create services.xml | |
| Deploy to `services/MyNewService/services.xml` in the Axis2 repository. | |
| ## mod_axis2 Configuration | |
| ### Android-Specific Initialization | |
| In `mod_axis2.c`, Android builds: | |
| - Skip shared memory (SELinux restrictions) | |
| - Use pool-based allocator | |
| - Run in single-process mode (`-X` flag) | |
| ```c | |
| #ifdef __ANDROID__ | |
| /* Skip shared memory, use pool allocation */ | |
| allocator = apr_palloc(pconf, sizeof(axutil_allocator_t)); | |
| goto engine_init; | |
| #endif | |
| ``` | |
| ### Debug Logging | |
| Android logging uses INFO level: | |
| ```c | |
| #ifdef __ANDROID__ | |
| #include <android/log.h> | |
| #define AXIS2_ANDROID_LOG(fmt, ...) \ | |
| __android_log_print(ANDROID_LOG_INFO, "Axis2-mod", fmt, ##__VA_ARGS__) | |
| #else | |
| #define AXIS2_ANDROID_LOG(fmt, ...) ((void)0) | |
| #endif | |
| ``` | |
| ## HTTP/2 on Android | |
| ### Single-Process Mode Considerations | |
| Apache runs with `-X` (single-process) on Android. For older devices, configure HTTP/2 conservatively: | |
| ```apache | |
| # In http2-performance.conf | |
| H2MaxSessionStreams 1 # Single stream for stability | |
| H2StreamTimeout 30 # Prevent hanging streams | |
| H2WindowSize 65535 # Reduced memory | |
| H2Push off # Not needed for JSON-RPC | |
| H2MinWorkers 1 | |
| H2MaxWorkers 2 | |
| ``` | |
| ## Android IPC Security | |
| ### Native to Java Communication | |
| Axis2/C services on Android often need to communicate with Java application components. This is done via Android Intent broadcasts using the `am` (Activity Manager) command. | |
| ### CRITICAL: Never Use system() | |
| **INSECURE (vulnerable to command injection):** | |
| ```c | |
| /* NEVER DO THIS - user input is parsed by shell */ | |
| char cmd[1024]; | |
| snprintf(cmd, sizeof(cmd), | |
| "am broadcast --es pattern '%s'", user_pattern); | |
| system(cmd); /* Shell injection if pattern contains: '; rm -rf / ; ' */ | |
| ``` | |
| **SECURE (use fork/execvp):** | |
| ```c | |
| /* Arguments passed directly to process, no shell parsing */ | |
| typedef struct { | |
| const char* type; /* "--es" for string, "--ei" for int */ | |
| const char* key; | |
| const char* value; | |
| } intent_extra_t; | |
| static int send_intent_broadcast_secure( | |
| const char* component, | |
| const char* action, | |
| const intent_extra_t* extras, | |
| int num_extras) | |
| { | |
| /* Build argument array */ | |
| int total_args = 8 + (num_extras * 3) + 1; /* base args + extras + NULL */ | |
| char** argv = (char**)malloc(total_args * sizeof(char*)); | |
| int i = 0; | |
| argv[i++] = "am"; | |
| argv[i++] = "broadcast"; | |
| argv[i++] = "--user"; | |
| argv[i++] = "0"; | |
| argv[i++] = "-n"; | |
| argv[i++] = (char*)component; | |
| argv[i++] = "-a"; | |
| argv[i++] = (char*)action; | |
| for (int j = 0; j < num_extras; j++) { | |
| argv[i++] = (char*)extras[j].type; | |
| argv[i++] = (char*)extras[j].key; | |
| argv[i++] = (char*)extras[j].value; /* User data - safe, not parsed */ | |
| } | |
| argv[i] = NULL; | |
| pid_t pid = fork(); | |
| if (pid == 0) { | |
| /* Child process */ | |
| execvp("/system/bin/am", argv); | |
| _exit(127); /* exec failed */ | |
| } | |
| /* Parent waits for child */ | |
| int status; | |
| waitpid(pid, &status, 0); | |
| free(argv); | |
| return WIFEXITED(status) && WEXITSTATUS(status) == 0 ? 0 : -1; | |
| } | |
| ``` | |
| ### Why fork/execvp Is Secure | |
| | Approach | Shell Invoked | User Data Parsed | Injection Risk | | |
| |----------|---------------|------------------|----------------| | |
| | `system("cmd " + input)` | Yes | Yes | **HIGH** | | |
| | `popen("cmd " + input, "r")` | Yes | Yes | **HIGH** | | |
| | `fork()` + `execvp(argv[])` | No | No | **None** | | |
| With `execvp()`, arguments are passed as an array directly to the new process. Shell metacharacters (`'`, `;`, `|`, `$()`, etc.) are treated as literal characters, not interpreted. | |
| ### Usage Example | |
| ```c | |
| /* Safe: user-provided pattern passed directly to Java */ | |
| int camera_device_delete_files_impl(const char* pattern, char* op_id) | |
| { | |
| intent_extra_t extras[] = { | |
| {"--es", "action", "delete_files"}, | |
| {"--es", "pattern", pattern}, /* User input - safe */ | |
| {"--es", "operation_id", op_id} | |
| }; | |
| return send_intent_broadcast_secure( | |
| "org.kanaha.camera/org.kanaha.camera.CameraControlReceiver", | |
| "org.kanaha.CAMERA_CONTROL", | |
| extras, 3); | |
| } | |
| ``` | |
| ### Java-Side Validation | |
| The Java `BroadcastReceiver` provides defense-in-depth validation: | |
| ```java | |
| // In CameraControlReceiver.java | |
| class SecurityValidator { | |
| // Length limits | |
| static final int MAX_FILENAME_LENGTH = 255; | |
| static final int MAX_PATH_LENGTH = 1024; | |
| // Blocked patterns | |
| private static final String[] PATH_TRAVERSAL = {"..", "%2e%2e", "%00"}; | |
| private static final String[] INJECTION = {"<script", "${", "`", "$("}; | |
| static String validateFilenameOrPattern(String input, String paramName) { | |
| if (input == null || input.isEmpty()) return "empty " + paramName; | |
| if (input.length() > MAX_FILENAME_LENGTH) return paramName + " too long"; | |
| // Check for path traversal | |
| String lower = input.toLowerCase(); | |
| for (String pattern : PATH_TRAVERSAL) { | |
| if (lower.contains(pattern)) return "path traversal in " + paramName; | |
| } | |
| // Check for injection attempts | |
| for (String pattern : INJECTION) { | |
| if (lower.contains(pattern)) return "injection attempt in " + paramName; | |
| } | |
| // Whitelist allowed characters | |
| if (!input.matches("^[a-zA-Z0-9_\\-\\.\\*]+$")) { | |
| return "invalid characters in " + paramName; | |
| } | |
| return null; // Valid | |
| } | |
| } | |
| ``` | |
| ### Security Headers Reference | |
| See the [Kanaha](https://github.com/robertlazarski/kanaha) project's `docs/SECURITY.md` for complete security documentation including: | |
| - mTLS configuration | |
| - Certificate revocation (CRL) | |
| - Audit logging | |
| - Apache httpd security headers | |
| **Note:** [Kanaha](https://github.com/robertlazarski/kanaha) is an independent project (GPLv3 licensed due to OpenCamera | |
| dependency) and is not affiliated with Apache Software Foundation. It demonstrates | |
| production deployment patterns for the CameraControlService sample in | |
| `samples/user_guide/camera-control-service/`. | |
| ## Summary Table | |
| | Aspect | Desktop/Server | Android | | |
| |--------|----------------|---------| | |
| | C standard | `-std=gnu99` | `-std=gnu99` | | |
| | Library type | Shared (.so) | Static (.a) | | |
| | Service loading | dlopen() | Weak symbol registry | | |
| | Service location | External .so | Application provides strong symbols | | |
| | Linker | Libtool | Direct clang with `--whole-archive` | | |
| | Apache mode | Multi-process | Single-process (`-X`) | | |
| | IPC method | N/A | fork()/execvp() (NOT system()) | | |
| | Input validation | Application | Java SecurityValidator + C checks | | |
| ## Files Reference | |
| Key files for Android support: | |
| **Axis2/C Core:** | |
| - `configure.ac` - Android detection, C flags | |
| - `src/core/receivers/axis2_json_rpc_msg_recv.c` - Static service registry | |
| - `src/core/transport/http/server/apache2/mod_axis2.c` - Android init, logging | |
| - `src/core/transport/http/server/apache2/apache2_worker.c` - Request processing | |
| **Application ([Kanaha](https://github.com/robertlazarski/kanaha) example):** | |
| - `app/src/main/cpp/axis2c/camera_control_service.c` - Service impl with secure IPC | |
| - `app/src/main/java/org/kanaha/camera/CameraControlReceiver.java` - Java receiver with SecurityValidator | |
| - `app/src/main/assets/apache/httpd.conf` - Apache security config | |
| - `app/src/main/assets/apache/ssl.conf` - mTLS and CRL config | |
| - `docs/SECURITY.md` - Comprehensive security documentation | |
| --- | |
| *Last updated: 2026-01-14* |