@obinexusltd/curl-polycall 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/Makefile +122 -0
- package/README.md +206 -0
- package/build/bin/.gitkeep +1 -0
- package/build/obj/.gitkeep +1 -0
- package/config/curl-polycall.env +7 -0
- package/debian/changelog +6 -0
- package/debian/control +27 -0
- package/debian/copyright +26 -0
- package/debian/docs +2 -0
- package/debian/rules +13 -0
- package/debian/source/format +1 -0
- package/docs/FAULT_TOLERANT_FFI_PROOF.md +70 -0
- package/docs/UNIX_PACKAGING.md +98 -0
- package/examples/curl.ps1 +33 -0
- package/examples/curl.sh +33 -0
- package/ffi.py +75 -0
- package/package.json +68 -0
- package/scripts/build-deb.sh +28 -0
- package/scripts/build-windows.ps1 +69 -0
- package/scripts/curl-polycall-server.in +14 -0
- package/scripts/curl-polycall.in +60 -0
- package/server.py +78 -0
- package/src/polycall_ffi.c +79 -0
- package/src/polycall_ffi.h +39 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OBINexus
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/Makefile
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
ifneq ($(OS),Windows_NT)
|
|
2
|
+
ifeq ($(origin CC),default)
|
|
3
|
+
CC := cc
|
|
4
|
+
endif
|
|
5
|
+
endif
|
|
6
|
+
CFLAGS ?= -O2 -Wall -Wextra
|
|
7
|
+
PIC_FLAGS ?= -fPIC
|
|
8
|
+
|
|
9
|
+
SRC := src/polycall_ffi.c
|
|
10
|
+
OBJ_DIR := build/obj
|
|
11
|
+
BIN_DIR := build/bin
|
|
12
|
+
OBJ := $(OBJ_DIR)/polycall_ffi.o
|
|
13
|
+
|
|
14
|
+
DESTDIR ?=
|
|
15
|
+
PREFIX ?= /usr/local
|
|
16
|
+
SYSCONFDIR ?= /etc
|
|
17
|
+
BINDIR ?= $(PREFIX)/bin
|
|
18
|
+
LIBEXECDIR ?= $(PREFIX)/lib/curl-polycall
|
|
19
|
+
DOCDIR ?= $(PREFIX)/share/doc/curl-polycall
|
|
20
|
+
EXAMPLESDIR ?= $(PREFIX)/share/curl-polycall/examples
|
|
21
|
+
|
|
22
|
+
ifeq ($(OS),Windows_NT)
|
|
23
|
+
PIC_FLAGS :=
|
|
24
|
+
PYTHON ?= python
|
|
25
|
+
LIB := $(BIN_DIR)/polycall_ffi.dll
|
|
26
|
+
TMP_LIB := $(BIN_DIR)/polycall_ffi.tmp.dll
|
|
27
|
+
SHARED_FLAGS := -shared
|
|
28
|
+
else
|
|
29
|
+
PYTHON ?= python3
|
|
30
|
+
UNAME_S := $(shell uname -s 2>/dev/null || echo Unknown)
|
|
31
|
+
ifeq ($(UNAME_S),Darwin)
|
|
32
|
+
LIB := $(BIN_DIR)/libpolycall_ffi.dylib
|
|
33
|
+
SHARED_FLAGS := -dynamiclib
|
|
34
|
+
else
|
|
35
|
+
LIB := $(BIN_DIR)/libpolycall_ffi.so
|
|
36
|
+
SHARED_FLAGS := -shared
|
|
37
|
+
endif
|
|
38
|
+
endif
|
|
39
|
+
|
|
40
|
+
.PHONY: all clean dirs install uninstall run windows-build FORCE
|
|
41
|
+
|
|
42
|
+
ifeq ($(OS),Windows_NT)
|
|
43
|
+
all: windows-build
|
|
44
|
+
else
|
|
45
|
+
all: dirs $(LIB)
|
|
46
|
+
endif
|
|
47
|
+
|
|
48
|
+
FORCE:
|
|
49
|
+
|
|
50
|
+
ifeq ($(OS),Windows_NT)
|
|
51
|
+
dirs:
|
|
52
|
+
@if not exist "build" mkdir "build"
|
|
53
|
+
@if not exist "build\bin" mkdir "build\bin"
|
|
54
|
+
@if not exist "build\obj" mkdir "build\obj"
|
|
55
|
+
else
|
|
56
|
+
dirs:
|
|
57
|
+
mkdir -p $(BIN_DIR) $(OBJ_DIR)
|
|
58
|
+
endif
|
|
59
|
+
|
|
60
|
+
$(OBJ): FORCE $(SRC) src/polycall_ffi.h | dirs
|
|
61
|
+
$(CC) $(CFLAGS) $(PIC_FLAGS) -c $(SRC) -o $(OBJ)
|
|
62
|
+
|
|
63
|
+
ifeq ($(OS),Windows_NT)
|
|
64
|
+
windows-build: dirs
|
|
65
|
+
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/build-windows.ps1
|
|
66
|
+
else
|
|
67
|
+
$(LIB): $(OBJ) | dirs
|
|
68
|
+
$(CC) $(SHARED_FLAGS) $(OBJ) -o $(LIB)
|
|
69
|
+
endif
|
|
70
|
+
|
|
71
|
+
run: all
|
|
72
|
+
$(PYTHON) server.py
|
|
73
|
+
|
|
74
|
+
ifeq ($(OS),Windows_NT)
|
|
75
|
+
install:
|
|
76
|
+
@echo "make install is only supported on Unix-like systems"
|
|
77
|
+
@exit 1
|
|
78
|
+
|
|
79
|
+
uninstall:
|
|
80
|
+
@echo "make uninstall is only supported on Unix-like systems"
|
|
81
|
+
@exit 1
|
|
82
|
+
else
|
|
83
|
+
install: all
|
|
84
|
+
install -d "$(DESTDIR)$(BINDIR)"
|
|
85
|
+
install -d "$(DESTDIR)$(LIBEXECDIR)"
|
|
86
|
+
install -d "$(DESTDIR)$(LIBEXECDIR)/build/bin"
|
|
87
|
+
install -d "$(DESTDIR)$(SYSCONFDIR)/curl-polycall"
|
|
88
|
+
install -d "$(DESTDIR)$(DOCDIR)"
|
|
89
|
+
install -d "$(DESTDIR)$(EXAMPLESDIR)"
|
|
90
|
+
install -m 0644 ffi.py server.py "$(DESTDIR)$(LIBEXECDIR)/"
|
|
91
|
+
install -m 0644 "$(LIB)" "$(DESTDIR)$(LIBEXECDIR)/build/bin/"
|
|
92
|
+
install -m 0644 config/curl-polycall.env "$(DESTDIR)$(SYSCONFDIR)/curl-polycall/curl-polycall.env"
|
|
93
|
+
install -m 0644 README.md LICENSE "$(DESTDIR)$(DOCDIR)/"
|
|
94
|
+
install -m 0644 docs/FAULT_TOLERANT_FFI_PROOF.md "$(DESTDIR)$(DOCDIR)/"
|
|
95
|
+
install -m 0755 examples/curl.sh "$(DESTDIR)$(EXAMPLESDIR)/curl.sh"
|
|
96
|
+
sed -e 's|@LIBEXECDIR@|$(LIBEXECDIR)|g' \
|
|
97
|
+
-e 's|@SYSCONFDIR@|$(SYSCONFDIR)|g' \
|
|
98
|
+
scripts/curl-polycall-server.in > "$(DESTDIR)$(BINDIR)/curl-polycall-server"
|
|
99
|
+
chmod 0755 "$(DESTDIR)$(BINDIR)/curl-polycall-server"
|
|
100
|
+
sed -e 's|@BINDIR@|$(BINDIR)|g' \
|
|
101
|
+
-e 's|@EXAMPLESDIR@|$(EXAMPLESDIR)|g' \
|
|
102
|
+
scripts/curl-polycall.in > "$(DESTDIR)$(BINDIR)/curl-polycall"
|
|
103
|
+
chmod 0755 "$(DESTDIR)$(BINDIR)/curl-polycall"
|
|
104
|
+
|
|
105
|
+
uninstall:
|
|
106
|
+
rm -f "$(DESTDIR)$(BINDIR)/curl-polycall" "$(DESTDIR)$(BINDIR)/curl-polycall-server"
|
|
107
|
+
rm -rf "$(DESTDIR)$(LIBEXECDIR)"
|
|
108
|
+
rm -rf "$(DESTDIR)$(DOCDIR)"
|
|
109
|
+
rm -rf "$(DESTDIR)$(EXAMPLESDIR)"
|
|
110
|
+
rm -f "$(DESTDIR)$(SYSCONFDIR)/curl-polycall/curl-polycall.env"
|
|
111
|
+
rmdir "$(DESTDIR)$(SYSCONFDIR)/curl-polycall" 2>/dev/null || true
|
|
112
|
+
endif
|
|
113
|
+
|
|
114
|
+
ifeq ($(OS),Windows_NT)
|
|
115
|
+
clean:
|
|
116
|
+
@if exist "build\bin\polycall_ffi.dll" del /q "build\bin\polycall_ffi.dll"
|
|
117
|
+
@if exist "build\bin\polycall_ffi.tmp.dll" del /q "build\bin\polycall_ffi.tmp.dll"
|
|
118
|
+
@if exist "build\obj\polycall_ffi.o" del /q "build\obj\polycall_ffi.o"
|
|
119
|
+
else
|
|
120
|
+
clean:
|
|
121
|
+
rm -rf $(BIN_DIR)/* $(OBJ_DIR)/*
|
|
122
|
+
endif
|
package/README.md
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# curl-polycall
|
|
2
|
+
|
|
3
|
+
Minimal direct FFI demonstration for libpolycall-style command interpolation through `curl` or `wget`.
|
|
4
|
+
|
|
5
|
+
The point of this example is narrow:
|
|
6
|
+
|
|
7
|
+
- expose a tiny native C ABI;
|
|
8
|
+
- load it from Python 3 with `import ffi`;
|
|
9
|
+
- serve HTTP endpoints that call the native ABI directly;
|
|
10
|
+
- keep NSIGII as an external attachable artifact, not C code inside libpolycall;
|
|
11
|
+
- keep `micro attach` and `micro detach` as runtime dependency registration only.
|
|
12
|
+
|
|
13
|
+
## Layout
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
curl-polycall/
|
|
17
|
+
|-- build/
|
|
18
|
+
| |-- bin/
|
|
19
|
+
| `-- obj/
|
|
20
|
+
|-- config/
|
|
21
|
+
| `-- curl-polycall.env
|
|
22
|
+
|-- debian/
|
|
23
|
+
| |-- control
|
|
24
|
+
| |-- rules
|
|
25
|
+
| `-- source/
|
|
26
|
+
|-- docs/
|
|
27
|
+
| |-- FAULT_TOLERANT_FFI_PROOF.md
|
|
28
|
+
| `-- UNIX_PACKAGING.md
|
|
29
|
+
|-- examples/
|
|
30
|
+
| |-- curl.ps1
|
|
31
|
+
| `-- curl.sh
|
|
32
|
+
|-- scripts/
|
|
33
|
+
| `-- build-windows.ps1
|
|
34
|
+
|-- src/
|
|
35
|
+
| |-- polycall_ffi.c
|
|
36
|
+
| `-- polycall_ffi.h
|
|
37
|
+
|-- ffi.py
|
|
38
|
+
|-- server.py
|
|
39
|
+
|-- Makefile
|
|
40
|
+
|-- package.json
|
|
41
|
+
`-- README.md
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## npm package metadata
|
|
45
|
+
|
|
46
|
+
The project root includes `package.json` for the npm package
|
|
47
|
+
`@obinexusltd/curl-polycall`. The package metadata lists every source folder in
|
|
48
|
+
`directories` and keeps generated native build outputs out of the source package
|
|
49
|
+
while preserving `build/bin` and `build/obj` with `.gitkeep` placeholders.
|
|
50
|
+
|
|
51
|
+
The package also exposes two CLI entry points via npm:
|
|
52
|
+
|
|
53
|
+
- `curl-polycall` → `scripts/curl-polycall.in`
|
|
54
|
+
- `curl-polycall-server` → `scripts/curl-polycall-server.in`
|
|
55
|
+
|
|
56
|
+
Install globally or run through `npx`:
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
npm install -g @obinexusltd/curl-polycall
|
|
60
|
+
curl-polycall server
|
|
61
|
+
curl-polycall health
|
|
62
|
+
curl-polycall command ping
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Or run directly with `npx`:
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
npx @obinexusltd/curl-polycall server
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Useful npm scripts:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
npm run build
|
|
75
|
+
npm run build:deb
|
|
76
|
+
npm run build:windows
|
|
77
|
+
npm start
|
|
78
|
+
npm run demo
|
|
79
|
+
npm run demo:windows
|
|
80
|
+
npm run test:ffi
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Native ABI
|
|
84
|
+
|
|
85
|
+
```c
|
|
86
|
+
int polycall_verify_command(const char *command, char *out_buffer, int out_buffer_len);
|
|
87
|
+
int polycall_runtime_micro_attach(const char *dependency_path, char *out_buffer, int out_buffer_len);
|
|
88
|
+
int polycall_runtime_micro_detach(const char *dependency_path, char *out_buffer, int out_buffer_len);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Platform outputs:
|
|
92
|
+
|
|
93
|
+
- Windows: `build/bin/polycall_ffi.dll`
|
|
94
|
+
- Linux: `build/bin/libpolycall_ffi.so`
|
|
95
|
+
- macOS: `build/bin/libpolycall_ffi.dylib`
|
|
96
|
+
- Objects: `build/obj`
|
|
97
|
+
|
|
98
|
+
## Build
|
|
99
|
+
|
|
100
|
+
Linux/macOS:
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
make
|
|
104
|
+
python3 server.py
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Windows PowerShell:
|
|
108
|
+
|
|
109
|
+
```powershell
|
|
110
|
+
.\scripts\build-windows.ps1
|
|
111
|
+
python server.py
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
If Windows reports `cannot open file 'build\bin\polycall_ffi.dll'` or
|
|
115
|
+
`Permission denied`, stop the running server with `Ctrl+C` before rebuilding.
|
|
116
|
+
Python keeps the DLL loaded while `server.py` is running.
|
|
117
|
+
|
|
118
|
+
## Unix and apt install
|
|
119
|
+
|
|
120
|
+
For generic Unix installs:
|
|
121
|
+
|
|
122
|
+
```sh
|
|
123
|
+
make
|
|
124
|
+
sudo make install PREFIX=/usr SYSCONFDIR=/etc
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
For Debian/Ubuntu local apt installs:
|
|
128
|
+
|
|
129
|
+
```sh
|
|
130
|
+
sudo apt update
|
|
131
|
+
sudo apt install build-essential debhelper devscripts
|
|
132
|
+
chmod +x debian/rules scripts/build-deb.sh
|
|
133
|
+
sh scripts/build-deb.sh
|
|
134
|
+
sudo apt install ../curl-polycall_0.1.0_$(dpkg --print-architecture).deb
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Then run:
|
|
138
|
+
|
|
139
|
+
```sh
|
|
140
|
+
curl-polycall server
|
|
141
|
+
curl-polycall health
|
|
142
|
+
curl-polycall command ping
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Plain `sudo apt install curl-polycall` works only after the `.deb` is published
|
|
146
|
+
to an apt repository configured on the machine. See
|
|
147
|
+
[docs/UNIX_PACKAGING.md](docs/UNIX_PACKAGING.md).
|
|
148
|
+
|
|
149
|
+
## Direct Python FFI
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
import ffi
|
|
153
|
+
|
|
154
|
+
runtime = ffi.load()
|
|
155
|
+
runtime.command("ping")
|
|
156
|
+
runtime.attach("build/bin/example.nsigii")
|
|
157
|
+
runtime.detach("build/bin/example.nsigii")
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Curl endpoints
|
|
161
|
+
|
|
162
|
+
Start the server first:
|
|
163
|
+
|
|
164
|
+
```sh
|
|
165
|
+
python server.py
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Then call individual endpoints:
|
|
169
|
+
|
|
170
|
+
```sh
|
|
171
|
+
curl "http://127.0.0.1:8084/"
|
|
172
|
+
curl "http://127.0.0.1:8084/command?cmd=ping"
|
|
173
|
+
curl "http://127.0.0.1:8084/command?cmd=health"
|
|
174
|
+
curl "http://127.0.0.1:8084/command?cmd=unknown"
|
|
175
|
+
curl "http://127.0.0.1:8084/micro/attach?path=build/bin/example.nsigii"
|
|
176
|
+
curl "http://127.0.0.1:8084/micro/detach?path=build/bin/example.nsigii"
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Or run the example script:
|
|
180
|
+
|
|
181
|
+
```sh
|
|
182
|
+
bash examples/curl.sh
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
From PowerShell:
|
|
186
|
+
|
|
187
|
+
```powershell
|
|
188
|
+
.\examples\curl.ps1
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Both example scripts wait briefly for `http://127.0.0.1:8084/` before sending
|
|
192
|
+
the endpoint requests, so they can be launched while `server.py` is still
|
|
193
|
+
starting.
|
|
194
|
+
|
|
195
|
+
Do not run `curl.exe examples/curl.sh`; that asks curl to fetch a URL named
|
|
196
|
+
`examples/curl.sh`. Use `bash examples/curl.sh` or the PowerShell script above.
|
|
197
|
+
|
|
198
|
+
Each response is trinary JSON:
|
|
199
|
+
|
|
200
|
+
```json
|
|
201
|
+
{"status":"YES","message":"..."}
|
|
202
|
+
{"status":"NO","message":"..."}
|
|
203
|
+
{"status":"MAYBE","message":"..."}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Known commands return `YES`, invalid input returns `NO`, and unknown but syntactically valid commands return `MAYBE`.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
package/debian/changelog
ADDED
package/debian/control
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Source: curl-polycall
|
|
2
|
+
Section: net
|
|
3
|
+
Priority: optional
|
|
4
|
+
Maintainer: OBINexus LTD <support@obinexus.com>
|
|
5
|
+
Build-Depends:
|
|
6
|
+
debhelper-compat (= 13),
|
|
7
|
+
gcc,
|
|
8
|
+
make,
|
|
9
|
+
python3
|
|
10
|
+
Standards-Version: 4.6.2
|
|
11
|
+
Rules-Requires-Root: no
|
|
12
|
+
Homepage: https://github.com/obinexusltd/curl-polycall
|
|
13
|
+
|
|
14
|
+
Package: curl-polycall
|
|
15
|
+
Architecture: any
|
|
16
|
+
Depends:
|
|
17
|
+
${misc:Depends},
|
|
18
|
+
${shlibs:Depends},
|
|
19
|
+
curl,
|
|
20
|
+
python3
|
|
21
|
+
Description: Direct FFI curl endpoint demonstration for libpolycall
|
|
22
|
+
curl-polycall is a minimal verification-first Foreign Function Interface
|
|
23
|
+
demonstration. It exposes a native C ABI, loads it from Python with ctypes,
|
|
24
|
+
and serves curl/wget-friendly HTTP endpoints that return YES, NO, or MAYBE.
|
|
25
|
+
.
|
|
26
|
+
NSIGII artifacts remain external runtime dependencies; they are not embedded
|
|
27
|
+
into the native libpolycall demonstration.
|
package/debian/copyright
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
|
2
|
+
Upstream-Name: curl-polycall
|
|
3
|
+
Source: https://github.com/obinexusltd/curl-polycall
|
|
4
|
+
|
|
5
|
+
Files: *
|
|
6
|
+
Copyright: 2026 OBINexus
|
|
7
|
+
License: MIT
|
|
8
|
+
|
|
9
|
+
License: MIT
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
.
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
.
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
package/debian/docs
ADDED
package/debian/rules
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.0 (native)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Fault-Tolerant Direct FFI Proof
|
|
2
|
+
|
|
3
|
+
## Scope
|
|
4
|
+
|
|
5
|
+
`curl-polycall` proves only the direct Foreign Function Interface boundary.
|
|
6
|
+
|
|
7
|
+
It does not implement NSIGII as `nsigii.h` or `nsigii.c` inside libpolycall. It does not implement a DOP adapter. It does not copy microvm example code. NSIGII remains an external proof, artifact, or dependency that can be attached, detached, inspected, or loaded later by a runtime.
|
|
8
|
+
|
|
9
|
+
## Direct FFI Call Chain
|
|
10
|
+
|
|
11
|
+
The call chain is:
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
curl/wget
|
|
15
|
+
-> Python 3 http.server endpoint
|
|
16
|
+
-> import ffi
|
|
17
|
+
-> ctypes.CDLL(...)
|
|
18
|
+
-> native C ABI function
|
|
19
|
+
-> bounded JSON response buffer
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The Python host performs a real foreign function call into the native shared library. The HTTP server is only a transport surface for command-line interoperability.
|
|
23
|
+
|
|
24
|
+
## Native ABI Boundary
|
|
25
|
+
|
|
26
|
+
The exported ABI is exactly:
|
|
27
|
+
|
|
28
|
+
```c
|
|
29
|
+
int polycall_verify_command(const char *command, char *out_buffer, int out_buffer_len);
|
|
30
|
+
int polycall_runtime_micro_attach(const char *dependency_path, char *out_buffer, int out_buffer_len);
|
|
31
|
+
int polycall_runtime_micro_detach(const char *dependency_path, char *out_buffer, int out_buffer_len);
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Each function accepts caller-owned input plus a bounded output buffer. The C implementation writes responses with `snprintf`, checks truncation, and returns an error code if the buffer is invalid or too small.
|
|
35
|
+
|
|
36
|
+
## Why NSIGII Is Not Embedded
|
|
37
|
+
|
|
38
|
+
NSIGII is treated as an external artifact because libpolycall's responsibility here is native ABI interoperability, not NSIGII protocol logic. Embedding NSIGII directly as C headers or C source would collapse two separate concerns:
|
|
39
|
+
|
|
40
|
+
- libpolycall: command interpolation, ABI boundary, language interop, runtime dependency attachment;
|
|
41
|
+
- NSIGII: external proof/artifact/dependency semantics.
|
|
42
|
+
|
|
43
|
+
Keeping NSIGII outside the native demo preserves the ability to attach, detach, inspect, or replace artifacts without rebuilding libpolycall.
|
|
44
|
+
|
|
45
|
+
## Attach/Detach Is Separate From Execution
|
|
46
|
+
|
|
47
|
+
`polycall_runtime_micro_attach` and `polycall_runtime_micro_detach` register dependency paths only. They do not load, invoke, fork, evaluate, or execute the dependency.
|
|
48
|
+
|
|
49
|
+
This preserves a linkable-then-executable model: attachment establishes that a runtime dependency may be known to the runtime; execution remains a later, explicit phase outside this minimal proof.
|
|
50
|
+
|
|
51
|
+
## Trinary Verification Model
|
|
52
|
+
|
|
53
|
+
The response model is trinary:
|
|
54
|
+
|
|
55
|
+
- `YES`: the command or dependency operation is accepted.
|
|
56
|
+
- `NO`: the input is invalid, empty, or unsafe for registration.
|
|
57
|
+
- `MAYBE`: the command is syntactically valid but not registered.
|
|
58
|
+
|
|
59
|
+
This maps to verification-first command processing. The server never executes command strings. It asks the native ABI to verify the command, then returns the verification result.
|
|
60
|
+
|
|
61
|
+
## Distributed Command-Line Interoperability
|
|
62
|
+
|
|
63
|
+
`curl` and `wget` provide distributed command-line entry points:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
curl "http://127.0.0.1:8084/command?cmd=ping"
|
|
67
|
+
wget -qO- "http://127.0.0.1:8084/command?cmd=health"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The HTTP layer can be reached by ordinary command-line tools, while the actual decision boundary remains the native FFI call. That gives the demo a minimal decentralized shape: remote text request in, verification-first native ABI decision out.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Unix Packaging And Apt Install
|
|
2
|
+
|
|
3
|
+
## What apt can and cannot do
|
|
4
|
+
|
|
5
|
+
`sudo apt install curl-polycall` only works after `curl-polycall` has been
|
|
6
|
+
published into an apt repository that the machine knows about.
|
|
7
|
+
|
|
8
|
+
For local development, build a `.deb` first and install it with:
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
sudo apt install ./curl-polycall_0.1.0_$(dpkg --print-architecture).deb
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Standard Unix Layout
|
|
15
|
+
|
|
16
|
+
The package follows the Filesystem Hierarchy Standard:
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
/usr/bin/curl-polycall
|
|
20
|
+
/usr/bin/curl-polycall-server
|
|
21
|
+
/usr/lib/curl-polycall/
|
|
22
|
+
/usr/lib/curl-polycall/build/bin/libpolycall_ffi.so
|
|
23
|
+
/etc/curl-polycall/curl-polycall.env
|
|
24
|
+
/usr/share/doc/curl-polycall/
|
|
25
|
+
/usr/share/curl-polycall/examples/
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Runtime config is read from `/etc/curl-polycall/curl-polycall.env`:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
CURL_POLYCALL_HOST=127.0.0.1
|
|
32
|
+
CURL_POLYCALL_PORT=8084
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Debian/Ubuntu Local Package
|
|
36
|
+
|
|
37
|
+
Install build tools:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
sudo apt update
|
|
41
|
+
sudo apt install build-essential debhelper devscripts
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Build the Debian package:
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
chmod +x debian/rules scripts/build-deb.sh
|
|
48
|
+
sh scripts/build-deb.sh
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Install the local package:
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
sudo apt install ../curl-polycall_0.1.0_$(dpkg --print-architecture).deb
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Use the installed commands:
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
curl-polycall server
|
|
61
|
+
curl-polycall health
|
|
62
|
+
curl-polycall command ping
|
|
63
|
+
curl-polycall command unknown
|
|
64
|
+
curl-polycall attach build/bin/example.nsigii
|
|
65
|
+
curl-polycall detach build/bin/example.nsigii
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Generic Unix Install
|
|
69
|
+
|
|
70
|
+
For Unix-like systems without apt:
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
make
|
|
74
|
+
sudo make install PREFIX=/usr SYSCONFDIR=/etc
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Remove:
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
sudo make uninstall PREFIX=/usr SYSCONFDIR=/etc
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Local Apt Repository
|
|
84
|
+
|
|
85
|
+
After building the `.deb`, create a tiny local apt repository:
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
mkdir -p ~/apt-repo
|
|
89
|
+
cp ../curl-polycall_0.1.0_$(dpkg --print-architecture).deb ~/apt-repo/
|
|
90
|
+
cd ~/apt-repo
|
|
91
|
+
dpkg-scanpackages . /dev/null | gzip -9c > Packages.gz
|
|
92
|
+
echo "deb [trusted=yes] file:$HOME/apt-repo ./" | sudo tee /etc/apt/sources.list.d/curl-polycall-local.list
|
|
93
|
+
sudo apt update
|
|
94
|
+
sudo apt install curl-polycall
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
That is the step that makes plain `sudo apt install curl-polycall` work on a
|
|
98
|
+
machine.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env pwsh
|
|
2
|
+
$ErrorActionPreference = "Stop"
|
|
3
|
+
|
|
4
|
+
$base = "http://127.0.0.1:8084"
|
|
5
|
+
|
|
6
|
+
Write-Host "Waiting for curl-polycall at $base ..."
|
|
7
|
+
$ready = $false
|
|
8
|
+
for ($i = 0; $i -lt 30; $i++) {
|
|
9
|
+
curl.exe --silent --fail "$base/" > $null 2>$null
|
|
10
|
+
if ($LASTEXITCODE -eq 0) {
|
|
11
|
+
$ready = $true
|
|
12
|
+
break
|
|
13
|
+
}
|
|
14
|
+
Start-Sleep -Milliseconds 500
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (-not $ready) {
|
|
18
|
+
throw "curl-polycall is not reachable at $base. Start it with: python .\server.py"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
$paths = @(
|
|
22
|
+
"/",
|
|
23
|
+
"/command?cmd=ping",
|
|
24
|
+
"/command?cmd=health",
|
|
25
|
+
"/command?cmd=unknown",
|
|
26
|
+
"/micro/attach?path=build/bin/example.nsigii",
|
|
27
|
+
"/micro/detach?path=build/bin/example.nsigii"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
foreach ($path in $paths) {
|
|
31
|
+
curl.exe --silent --show-error "$base$path"
|
|
32
|
+
Write-Host ""
|
|
33
|
+
}
|
package/examples/curl.sh
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
set -eu
|
|
3
|
+
base="http://127.0.0.1:8084"
|
|
4
|
+
|
|
5
|
+
printf 'Waiting for curl-polycall at %s ...\n' "$base"
|
|
6
|
+
ready=0
|
|
7
|
+
i=0
|
|
8
|
+
while [ "$i" -lt 30 ]; do
|
|
9
|
+
if curl --silent --fail --output /dev/null "$base/"; then
|
|
10
|
+
ready=1
|
|
11
|
+
break
|
|
12
|
+
fi
|
|
13
|
+
i=$((i + 1))
|
|
14
|
+
sleep 0.5
|
|
15
|
+
done
|
|
16
|
+
|
|
17
|
+
if [ "$ready" -ne 1 ]; then
|
|
18
|
+
printf 'curl-polycall is not reachable at %s. Start it with: python server.py\n' "$base" >&2
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
curl --silent --show-error "$base/"
|
|
23
|
+
printf '\n'
|
|
24
|
+
curl --silent --show-error "$base/command?cmd=ping"
|
|
25
|
+
printf '\n'
|
|
26
|
+
curl --silent --show-error "$base/command?cmd=health"
|
|
27
|
+
printf '\n'
|
|
28
|
+
curl --silent --show-error "$base/command?cmd=unknown"
|
|
29
|
+
printf '\n'
|
|
30
|
+
curl --silent --show-error "$base/micro/attach?path=build/bin/example.nsigii"
|
|
31
|
+
printf '\n'
|
|
32
|
+
curl --silent --show-error "$base/micro/detach?path=build/bin/example.nsigii"
|
|
33
|
+
printf '\n'
|
package/ffi.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Direct FFI loader for curl-polycall.
|
|
2
|
+
|
|
3
|
+
This module intentionally gives the Python side an `import ffi` surface.
|
|
4
|
+
It uses ctypes from the Python standard library so no extra dependency is needed.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import ctypes
|
|
9
|
+
import os
|
|
10
|
+
import platform
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
ROOT = Path(__file__).resolve().parent
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _default_library_name() -> str:
|
|
17
|
+
system = platform.system().lower()
|
|
18
|
+
if system == "windows":
|
|
19
|
+
return "polycall_ffi.dll"
|
|
20
|
+
if system == "darwin":
|
|
21
|
+
return "libpolycall_ffi.dylib"
|
|
22
|
+
return "libpolycall_ffi.so"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _default_library_path() -> Path:
|
|
26
|
+
return ROOT / "build" / "bin" / _default_library_name()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class PolycallFFI:
|
|
30
|
+
def __init__(self, library_path: str | os.PathLike[str] | None = None) -> None:
|
|
31
|
+
self.library_path = Path(library_path) if library_path else _default_library_path()
|
|
32
|
+
if not self.library_path.exists():
|
|
33
|
+
raise FileNotFoundError(
|
|
34
|
+
f"FFI library not found: {self.library_path}. Build it first."
|
|
35
|
+
)
|
|
36
|
+
try:
|
|
37
|
+
self.lib = ctypes.CDLL(str(self.library_path))
|
|
38
|
+
except OSError as exc:
|
|
39
|
+
raise OSError(
|
|
40
|
+
f"Unable to load FFI library {self.library_path}: {exc}. "
|
|
41
|
+
"Rebuild it with a compiler target that matches this Python process."
|
|
42
|
+
) from exc
|
|
43
|
+
self._bind()
|
|
44
|
+
|
|
45
|
+
def _bind(self) -> None:
|
|
46
|
+
for name in (
|
|
47
|
+
"polycall_verify_command",
|
|
48
|
+
"polycall_runtime_micro_attach",
|
|
49
|
+
"polycall_runtime_micro_detach",
|
|
50
|
+
):
|
|
51
|
+
fn = getattr(self.lib, name)
|
|
52
|
+
fn.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int]
|
|
53
|
+
fn.restype = ctypes.c_int
|
|
54
|
+
|
|
55
|
+
def _call(self, fn_name: str, value: str) -> str:
|
|
56
|
+
output = ctypes.create_string_buffer(1024)
|
|
57
|
+
fn = getattr(self.lib, fn_name)
|
|
58
|
+
code = fn(value.encode("utf-8"), output, len(output))
|
|
59
|
+
text = output.value.decode("utf-8", errors="replace")
|
|
60
|
+
if code != 0:
|
|
61
|
+
raise RuntimeError(f"{fn_name} failed with code {code}: {text}")
|
|
62
|
+
return text
|
|
63
|
+
|
|
64
|
+
def command(self, command: str) -> str:
|
|
65
|
+
return self._call("polycall_verify_command", command)
|
|
66
|
+
|
|
67
|
+
def attach(self, dependency_path: str) -> str:
|
|
68
|
+
return self._call("polycall_runtime_micro_attach", dependency_path)
|
|
69
|
+
|
|
70
|
+
def detach(self, dependency_path: str) -> str:
|
|
71
|
+
return self._call("polycall_runtime_micro_detach", dependency_path)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def load(library_path: str | os.PathLike[str] | None = None) -> PolycallFFI:
|
|
75
|
+
return PolycallFFI(library_path)
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@obinexusltd/curl-polycall",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Minimal curl-to-Python-to-native direct FFI demonstration for libpolycall.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://github.com/obinexus/curl-polycall#readme",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/obinexusltd/curl-polycall.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/obinexusltd/curl-polycall/issues"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"ffi",
|
|
16
|
+
"ctypes",
|
|
17
|
+
"curl",
|
|
18
|
+
"polycall",
|
|
19
|
+
"libpolycall",
|
|
20
|
+
"native-abi"
|
|
21
|
+
],
|
|
22
|
+
"type": "commonjs",
|
|
23
|
+
"files": [
|
|
24
|
+
"build/bin/.gitkeep",
|
|
25
|
+
"build/obj/.gitkeep",
|
|
26
|
+
"config/",
|
|
27
|
+
"debian/",
|
|
28
|
+
"docs/",
|
|
29
|
+
"examples/",
|
|
30
|
+
"scripts/",
|
|
31
|
+
"src/",
|
|
32
|
+
"ffi.py",
|
|
33
|
+
"server.py",
|
|
34
|
+
"Makefile",
|
|
35
|
+
"README.md",
|
|
36
|
+
"LICENSE"
|
|
37
|
+
],
|
|
38
|
+
"bin": {
|
|
39
|
+
"curl-polycall": "scripts/curl-polycall.in",
|
|
40
|
+
"curl-polycall-server": "scripts/curl-polycall-server.in"
|
|
41
|
+
},
|
|
42
|
+
"directories": {
|
|
43
|
+
"build": "build",
|
|
44
|
+
"buildBin": "build/bin",
|
|
45
|
+
"buildObj": "build/obj",
|
|
46
|
+
"config": "config",
|
|
47
|
+
"debian": "debian",
|
|
48
|
+
"doc": "docs",
|
|
49
|
+
"example": "examples",
|
|
50
|
+
"lib": "src",
|
|
51
|
+
"scripts": "scripts",
|
|
52
|
+
"src": "src"
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "make",
|
|
56
|
+
"build:deb": "sh scripts/build-deb.sh",
|
|
57
|
+
"build:windows": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/build-windows.ps1",
|
|
58
|
+
"clean": "make clean",
|
|
59
|
+
"demo": "sh examples/curl.sh",
|
|
60
|
+
"demo:windows": "powershell -NoProfile -ExecutionPolicy Bypass -File examples/curl.ps1",
|
|
61
|
+
"start": "python server.py",
|
|
62
|
+
"start:python3": "python3 server.py",
|
|
63
|
+
"test:ffi": "python -c \"import ffi; runtime = ffi.load(); print(runtime.command('ping'))\""
|
|
64
|
+
},
|
|
65
|
+
"publishConfig": {
|
|
66
|
+
"access": "public"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
if ! command -v dpkg-buildpackage >/dev/null 2>&1; then
|
|
5
|
+
cat >&2 <<'MSG'
|
|
6
|
+
dpkg-buildpackage was not found.
|
|
7
|
+
|
|
8
|
+
Install the Debian packaging toolchain first:
|
|
9
|
+
sudo apt update
|
|
10
|
+
sudo apt install build-essential debhelper devscripts
|
|
11
|
+
MSG
|
|
12
|
+
exit 1
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
dpkg-buildpackage -us -uc -b
|
|
16
|
+
|
|
17
|
+
cat <<'MSG'
|
|
18
|
+
|
|
19
|
+
Debian package build complete.
|
|
20
|
+
|
|
21
|
+
Install locally with:
|
|
22
|
+
sudo apt install ../curl-polycall_0.1.0_$(dpkg --print-architecture).deb
|
|
23
|
+
|
|
24
|
+
After install:
|
|
25
|
+
curl-polycall server
|
|
26
|
+
curl-polycall health
|
|
27
|
+
curl-polycall command ping
|
|
28
|
+
MSG
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
$ErrorActionPreference = "Stop"
|
|
2
|
+
|
|
3
|
+
New-Item -ItemType Directory -Force -Path build/bin | Out-Null
|
|
4
|
+
New-Item -ItemType Directory -Force -Path build/obj | Out-Null
|
|
5
|
+
Remove-Item -Force -ErrorAction SilentlyContinue `
|
|
6
|
+
build/bin/polycall_ffi.tmp.dll, `
|
|
7
|
+
build/obj/polycall_ffi.tmp.o
|
|
8
|
+
|
|
9
|
+
function Test-LastExitCode {
|
|
10
|
+
param([string]$Label)
|
|
11
|
+
if ($LASTEXITCODE -ne 0) {
|
|
12
|
+
throw "$Label failed with exit code $LASTEXITCODE"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function Move-BuiltDll {
|
|
17
|
+
try {
|
|
18
|
+
if (Test-Path build/bin/polycall_ffi.dll) {
|
|
19
|
+
Remove-Item -Force build/bin/polycall_ffi.dll
|
|
20
|
+
}
|
|
21
|
+
Move-Item -Force build/bin/polycall_ffi.tmp.dll build/bin/polycall_ffi.dll
|
|
22
|
+
} catch {
|
|
23
|
+
Remove-Item -Force -ErrorAction SilentlyContinue build/bin/polycall_ffi.tmp.dll
|
|
24
|
+
throw "Cannot replace build/bin/polycall_ffi.dll. Stop the running Python server first because Windows keeps loaded DLLs locked. Original error: $($_.Exception.Message)"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
$clCommand = "cl /nologo /W4 /O2 /LD /Fe:build\bin\polycall_ffi.tmp.dll /Fo:build\obj\polycall_ffi.obj src\polycall_ffi.c /link /IMPLIB:build\obj\polycall_ffi.lib /PDB:build\obj\polycall_ffi.pdb"
|
|
29
|
+
$vcvarsCandidates = @(
|
|
30
|
+
"C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat",
|
|
31
|
+
"C:\Program Files\Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build\vcvars64.bat",
|
|
32
|
+
"C:\Program Files (x86)\Microsoft Visual Studio\18\BuildTools\VC\Auxiliary\Build\vcvars64.bat"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
foreach ($vcvars in $vcvarsCandidates) {
|
|
36
|
+
if (Test-Path $vcvars) {
|
|
37
|
+
$cmd = "`"$vcvars`" >nul && $clCommand"
|
|
38
|
+
cmd.exe /d /s /c $cmd
|
|
39
|
+
Test-LastExitCode "MSVC x64 build"
|
|
40
|
+
Move-BuiltDll
|
|
41
|
+
Write-Host "Built build/bin/polycall_ffi.dll with MSVC x64"
|
|
42
|
+
exit 0
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if ($env:INCLUDE -and $env:LIB -and (Get-Command cl.exe -ErrorAction SilentlyContinue)) {
|
|
47
|
+
cl /nologo /W4 /O2 /LD /Fe:build\bin\polycall_ffi.tmp.dll /Fo:build\obj\polycall_ffi.obj src\polycall_ffi.c /link /IMPLIB:build\obj\polycall_ffi.lib /PDB:build\obj\polycall_ffi.pdb
|
|
48
|
+
Test-LastExitCode "MSVC build"
|
|
49
|
+
Move-BuiltDll
|
|
50
|
+
Write-Host "Built build/bin/polycall_ffi.dll with MSVC"
|
|
51
|
+
exit 0
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (Get-Command gcc.exe -ErrorAction SilentlyContinue) {
|
|
55
|
+
$target = (gcc -dumpmachine).Trim()
|
|
56
|
+
if ($target -match "mingw32") {
|
|
57
|
+
Write-Warning "GCC target '$target' is commonly 32-bit; the DLL may not load into 64-bit Python."
|
|
58
|
+
}
|
|
59
|
+
gcc -O2 -Wall -Wextra -c src/polycall_ffi.c -o build/obj/polycall_ffi.tmp.o
|
|
60
|
+
Test-LastExitCode "GCC compile"
|
|
61
|
+
gcc -shared build/obj/polycall_ffi.tmp.o -o build/bin/polycall_ffi.tmp.dll
|
|
62
|
+
Test-LastExitCode "GCC link"
|
|
63
|
+
Move-Item -Force build/obj/polycall_ffi.tmp.o build/obj/polycall_ffi.o
|
|
64
|
+
Move-BuiltDll
|
|
65
|
+
Write-Host "Built build/bin/polycall_ffi.dll with GCC"
|
|
66
|
+
exit 0
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
throw "No Windows C compiler found. Install Visual Studio Build Tools or MinGW GCC."
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
libexecdir="@LIBEXECDIR@"
|
|
5
|
+
envfile="${CURL_POLYCALL_ENV:-@SYSCONFDIR@/curl-polycall/curl-polycall.env}"
|
|
6
|
+
|
|
7
|
+
if [ -r "$envfile" ]; then
|
|
8
|
+
set -a
|
|
9
|
+
# shellcheck disable=SC1090
|
|
10
|
+
. "$envfile"
|
|
11
|
+
set +a
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
exec "${PYTHON:-python3}" "$libexecdir/server.py" "$@"
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
base="${CURL_POLYCALL_URL:-http://127.0.0.1:8084}"
|
|
5
|
+
|
|
6
|
+
usage() {
|
|
7
|
+
cat <<'USAGE'
|
|
8
|
+
Usage:
|
|
9
|
+
curl-polycall server
|
|
10
|
+
curl-polycall health
|
|
11
|
+
curl-polycall command [ping|health|unknown]
|
|
12
|
+
curl-polycall attach PATH
|
|
13
|
+
curl-polycall detach PATH
|
|
14
|
+
curl-polycall demo
|
|
15
|
+
|
|
16
|
+
Environment:
|
|
17
|
+
CURL_POLYCALL_URL Base URL for client commands, default http://127.0.0.1:8084
|
|
18
|
+
USAGE
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
case "${1:-help}" in
|
|
22
|
+
server)
|
|
23
|
+
exec "@BINDIR@/curl-polycall-server"
|
|
24
|
+
;;
|
|
25
|
+
health)
|
|
26
|
+
curl --silent --show-error --fail "$base/"
|
|
27
|
+
printf '\n'
|
|
28
|
+
;;
|
|
29
|
+
command)
|
|
30
|
+
command_name="${2:-ping}"
|
|
31
|
+
curl --silent --show-error --fail "$base/command?cmd=$command_name"
|
|
32
|
+
printf '\n'
|
|
33
|
+
;;
|
|
34
|
+
attach)
|
|
35
|
+
if [ "${2:-}" = "" ]; then
|
|
36
|
+
printf 'curl-polycall attach requires a dependency path\n' >&2
|
|
37
|
+
exit 2
|
|
38
|
+
fi
|
|
39
|
+
curl --silent --show-error --fail "$base/micro/attach?path=$2"
|
|
40
|
+
printf '\n'
|
|
41
|
+
;;
|
|
42
|
+
detach)
|
|
43
|
+
if [ "${2:-}" = "" ]; then
|
|
44
|
+
printf 'curl-polycall detach requires a dependency path\n' >&2
|
|
45
|
+
exit 2
|
|
46
|
+
fi
|
|
47
|
+
curl --silent --show-error --fail "$base/micro/detach?path=$2"
|
|
48
|
+
printf '\n'
|
|
49
|
+
;;
|
|
50
|
+
demo)
|
|
51
|
+
exec sh "@EXAMPLESDIR@/curl.sh"
|
|
52
|
+
;;
|
|
53
|
+
help|--help|-h)
|
|
54
|
+
usage
|
|
55
|
+
;;
|
|
56
|
+
*)
|
|
57
|
+
usage >&2
|
|
58
|
+
exit 2
|
|
59
|
+
;;
|
|
60
|
+
esac
|
package/server.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
7
|
+
from urllib.parse import parse_qs, urlparse
|
|
8
|
+
|
|
9
|
+
import ffi
|
|
10
|
+
|
|
11
|
+
runtime: ffi.PolycallFFI | None = None
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Handler(BaseHTTPRequestHandler):
|
|
15
|
+
def _send_json(self, payload: str, status: int = 200) -> None:
|
|
16
|
+
self.send_response(status)
|
|
17
|
+
self.send_header("Content-Type", "application/json")
|
|
18
|
+
self.end_headers()
|
|
19
|
+
self.wfile.write(payload.encode("utf-8"))
|
|
20
|
+
|
|
21
|
+
def do_GET(self) -> None:
|
|
22
|
+
if runtime is None:
|
|
23
|
+
self._send_json(json.dumps({"status": "NO", "message": "ffi runtime not loaded"}), 500)
|
|
24
|
+
return
|
|
25
|
+
|
|
26
|
+
parsed = urlparse(self.path)
|
|
27
|
+
query = parse_qs(parsed.query)
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
if parsed.path == "/":
|
|
31
|
+
return self._send_json(json.dumps({
|
|
32
|
+
"status": "YES",
|
|
33
|
+
"message": "curl-polycall direct ffi server",
|
|
34
|
+
"endpoints": [
|
|
35
|
+
"/command?cmd=ping",
|
|
36
|
+
"/command?cmd=health",
|
|
37
|
+
"/command?cmd=unknown",
|
|
38
|
+
"/micro/attach?path=build/bin/example.nsigii",
|
|
39
|
+
"/micro/detach?path=build/bin/example.nsigii"
|
|
40
|
+
]
|
|
41
|
+
}))
|
|
42
|
+
|
|
43
|
+
if parsed.path == "/command":
|
|
44
|
+
command = query.get("cmd", [""])[0]
|
|
45
|
+
return self._send_json(runtime.command(command))
|
|
46
|
+
|
|
47
|
+
if parsed.path == "/micro/attach":
|
|
48
|
+
dep = query.get("path", [""])[0]
|
|
49
|
+
return self._send_json(runtime.attach(dep))
|
|
50
|
+
|
|
51
|
+
if parsed.path == "/micro/detach":
|
|
52
|
+
dep = query.get("path", [""])[0]
|
|
53
|
+
return self._send_json(runtime.detach(dep))
|
|
54
|
+
|
|
55
|
+
self._send_json(json.dumps({"status": "NO", "message": "unknown endpoint"}), 404)
|
|
56
|
+
except Exception as exc:
|
|
57
|
+
self._send_json(json.dumps({"status": "NO", "message": str(exc)}), 500)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def main() -> None:
|
|
61
|
+
global runtime
|
|
62
|
+
|
|
63
|
+
host = os.environ.get("CURL_POLYCALL_HOST", "127.0.0.1")
|
|
64
|
+
port = int(os.environ.get("CURL_POLYCALL_PORT", "8084"))
|
|
65
|
+
try:
|
|
66
|
+
runtime = ffi.load()
|
|
67
|
+
except Exception as exc:
|
|
68
|
+
raise SystemExit(f"curl-polycall FFI load failed before serving: {exc}") from exc
|
|
69
|
+
|
|
70
|
+
print(f"curl-polycall serving http://{host}:{port}")
|
|
71
|
+
try:
|
|
72
|
+
HTTPServer((host, port), Handler).serve_forever()
|
|
73
|
+
except KeyboardInterrupt:
|
|
74
|
+
print("\ncurl-polycall stopped", file=sys.stderr)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
if __name__ == "__main__":
|
|
78
|
+
main()
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#define POLYCALL_FFI_EXPORTS
|
|
2
|
+
#include "polycall_ffi.h"
|
|
3
|
+
|
|
4
|
+
#include <ctype.h>
|
|
5
|
+
#include <stdio.h>
|
|
6
|
+
#include <string.h>
|
|
7
|
+
|
|
8
|
+
static int has_non_space(const char *value) {
|
|
9
|
+
if (value == NULL) {
|
|
10
|
+
return 0;
|
|
11
|
+
}
|
|
12
|
+
while (*value != '\0') {
|
|
13
|
+
if (!isspace((unsigned char)*value)) {
|
|
14
|
+
return 1;
|
|
15
|
+
}
|
|
16
|
+
value++;
|
|
17
|
+
}
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static int dependency_path_is_valid(const char *dependency_path) {
|
|
22
|
+
if (!has_non_space(dependency_path)) {
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
for (const unsigned char *cursor = (const unsigned char *)dependency_path;
|
|
26
|
+
*cursor != '\0';
|
|
27
|
+
cursor++) {
|
|
28
|
+
if (*cursor < 32U || *cursor == 127U) {
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return 1;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static int write_result(char *out_buffer, int out_buffer_len, const char *status, const char *message) {
|
|
36
|
+
if (out_buffer == NULL || out_buffer_len <= 0) {
|
|
37
|
+
return -1;
|
|
38
|
+
}
|
|
39
|
+
int written = snprintf(out_buffer, (size_t)out_buffer_len,
|
|
40
|
+
"{\"status\":\"%s\",\"message\":\"%s\"}",
|
|
41
|
+
status, message);
|
|
42
|
+
if (written < 0 || written >= out_buffer_len) {
|
|
43
|
+
return -2;
|
|
44
|
+
}
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
int polycall_verify_command(const char *command, char *out_buffer, int out_buffer_len) {
|
|
49
|
+
if (!has_non_space(command)) {
|
|
50
|
+
return write_result(out_buffer, out_buffer_len, "NO", "empty command");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Verification-first example: accept only a small command vocabulary. */
|
|
54
|
+
if (strcmp(command, "ping") == 0) {
|
|
55
|
+
return write_result(out_buffer, out_buffer_len, "YES", "polycall pong via direct ffi");
|
|
56
|
+
}
|
|
57
|
+
if (strcmp(command, "health") == 0) {
|
|
58
|
+
return write_result(out_buffer, out_buffer_len, "YES", "ffi runtime healthy");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return write_result(out_buffer, out_buffer_len, "MAYBE", "command not registered");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
int polycall_runtime_micro_attach(const char *dependency_path, char *out_buffer, int out_buffer_len) {
|
|
65
|
+
if (!dependency_path_is_valid(dependency_path)) {
|
|
66
|
+
return write_result(out_buffer, out_buffer_len, "NO", "invalid dependency path");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Attach means register/link dependency metadata at runtime; no execution here. */
|
|
70
|
+
return write_result(out_buffer, out_buffer_len, "YES", "micro dependency attached");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
int polycall_runtime_micro_detach(const char *dependency_path, char *out_buffer, int out_buffer_len) {
|
|
74
|
+
if (!dependency_path_is_valid(dependency_path)) {
|
|
75
|
+
return write_result(out_buffer, out_buffer_len, "NO", "invalid dependency path");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return write_result(out_buffer, out_buffer_len, "YES", "micro dependency detached");
|
|
79
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#ifndef POLYCALL_FFI_H
|
|
2
|
+
#define POLYCALL_FFI_H
|
|
3
|
+
|
|
4
|
+
#ifdef _WIN32
|
|
5
|
+
#ifdef POLYCALL_FFI_EXPORTS
|
|
6
|
+
#define POLYCALL_API __declspec(dllexport)
|
|
7
|
+
#else
|
|
8
|
+
#define POLYCALL_API __declspec(dllimport)
|
|
9
|
+
#endif
|
|
10
|
+
#else
|
|
11
|
+
#define POLYCALL_API __attribute__((visibility("default")))
|
|
12
|
+
#endif
|
|
13
|
+
|
|
14
|
+
#ifdef __cplusplus
|
|
15
|
+
extern "C" {
|
|
16
|
+
#endif
|
|
17
|
+
|
|
18
|
+
/*
|
|
19
|
+
* Direct FFI boundary for libpolycall-style calls.
|
|
20
|
+
* This is intentionally NOT NSIGII logic and NOT a DOP adapter.
|
|
21
|
+
* It exposes stable C ABI functions that Python/Node/etc can load.
|
|
22
|
+
*/
|
|
23
|
+
POLYCALL_API int polycall_verify_command(const char *command,
|
|
24
|
+
char *out_buffer,
|
|
25
|
+
int out_buffer_len);
|
|
26
|
+
|
|
27
|
+
POLYCALL_API int polycall_runtime_micro_attach(const char *dependency_path,
|
|
28
|
+
char *out_buffer,
|
|
29
|
+
int out_buffer_len);
|
|
30
|
+
|
|
31
|
+
POLYCALL_API int polycall_runtime_micro_detach(const char *dependency_path,
|
|
32
|
+
char *out_buffer,
|
|
33
|
+
int out_buffer_len);
|
|
34
|
+
|
|
35
|
+
#ifdef __cplusplus
|
|
36
|
+
}
|
|
37
|
+
#endif
|
|
38
|
+
|
|
39
|
+
#endif /* POLYCALL_FFI_H */
|