Documentation
¶
Overview ¶
Package ffibridge is the host side of f4's foreign function interface.
It turns a textual C prototype plus a list of plain values into a real native call, so that a sandboxed plugin can reach host APIs without f4 shipping a hand-written wrapper for every function in every system library. Everything crossing the sandbox boundary is an integer, a float or a string: library handles, function pointers, memory blocks and callbacks are all plain addresses, which maps equally well onto Lua and onto wasm imports.
The bridge is built on pureffi (a purego-compatible, cgo-free FFI). Because purego's RegisterFunc is fully reflective, the Go function type matching a prototype is constructed at run time, which is what makes dynamic calls with floats, structs and C-variadic arguments possible at all.
Security: FFI inside a sandbox is, by construction, an escape hatch from that sandbox. Options.Allow is the single choke point where the permission model will be enforced; until it is wired up, leaving it nil allows everything, which is appropriate only for local development.
Index ¶
- Constants
- Variables
- func LibCNames() []string
- type Bridge
- func (b *Bridge) Alloc(size int) (uintptr, error)
- func (b *Bridge) Bytes(addr uintptr) ([]byte, error)
- func (b *Bridge) CString(s string) (uintptr, error)
- func (b *Bridge) Call(fn uintptr, sig string, args ...any) (any, error)
- func (b *Bridge) CallSig(fn uintptr, sig *Signature, args []any) (result any, err error)
- func (b *Bridge) CallSym(lib uintptr, name, sig string, args ...any) (any, error)
- func (b *Bridge) Close() error
- func (b *Bridge) CloseLib(lib uintptr) error
- func (b *Bridge) Free(addr uintptr) error
- func (b *Bridge) GoStringAt(addr uintptr) (string, error)
- func (b *Bridge) NewCallback(sig string, fn Callback) (uintptr, error)
- func (b *Bridge) Open(name string) (uintptr, error)
- func (b *Bridge) OpenLibC() (uintptr, error)
- func (b *Bridge) Peek(addr uintptr, n int) ([]byte, error)
- func (b *Bridge) Poke(addr uintptr, data []byte) error
- func (b *Bridge) Read(addr uintptr, off, n int) ([]byte, error)
- func (b *Bridge) Sym(lib uintptr, name string) (uintptr, error)
- func (b *Bridge) Write(addr uintptr, off int, data []byte) error
- type Callback
- type Kind
- type Op
- type Options
- type Signature
Constants ¶
const DefaultMaxAlloc = 64 << 20
DefaultMaxAlloc caps bridge-owned memory when Options.MaxAlloc is zero.
const Supported = true
Supported reports whether this build can make native calls. Building with the noffi tag, or on a platform pureffi does not cover, leaves the rest of the plugin machinery intact and only disables the escape hatch.
Variables ¶
var ( // ErrUnsupported is returned when the platform or the build has no FFI. ErrUnsupported = errors.New("ffibridge: FFI is not available in this build") // ErrClosed is returned once the owning plugin has been torn down. ErrClosed = errors.New("ffibridge: bridge is closed") )
Functions ¶
Types ¶
type Bridge ¶
type Bridge struct {
// contains filtered or unexported fields
}
Bridge is one sandbox's view of the host FFI. Each plugin gets its own, so that tearing the plugin down releases its libraries and memory. A Bridge is safe for concurrent use.
func (*Bridge) Bytes ¶
Bytes exposes a bridge-owned block for direct host-side access. Writes through the returned slice are visible to native code.
func (*Bridge) CallSig ¶
CallSig is Call with an already parsed signature, which avoids re-parsing on hot paths.
func (*Bridge) Close ¶
Close releases every library and memory block the bridge owns. Callbacks are intentionally kept alive: native code may still hold their addresses, and there is no portable way to revoke a trampoline.
func (*Bridge) GoStringAt ¶
GoStringAt reads a NUL terminated string from an arbitrary address.
func (*Bridge) NewCallback ¶
NewCallback builds a native function pointer that dispatches into fn. The address stays valid for the lifetime of the bridge; there is no portable way to revoke a trampoline, so callbacks are never reclaimed early.
func (*Bridge) Peek ¶
Peek reads raw memory at an arbitrary address. It is unchecked by nature: a bad address crashes the process, exactly as it would in C.
type Callback ¶
Callback is the sandbox-facing shape of a native callback body. Arguments arrive normalised the same way call results are, and the returned value is converted back according to the signature's return type.
type Kind ¶
type Kind uint8
Kind is one primitive type of the ffibridge signature mini-language.
The language deliberately has no C type names, no typedefs and no declaration parser: a sandboxed plugin describes the ABI it wants, not the C source it came from. A cdef-style parser may be layered on top later.
type Op ¶
type Op string
Op names an operation the bridge can be asked to perform. It is the unit of granularity the permission model works with.
type Options ¶
type Options struct {
// Allow, when not nil, is consulted before every operation. A non-nil
// error aborts the operation and is returned to the caller verbatim.
Allow func(op Op, detail string) error
// MaxAlloc caps the total size of live blocks allocated through the
// bridge. Zero means DefaultMaxAlloc.
MaxAlloc int64
}
Options configures one bridge instance.
type Signature ¶
type Signature struct {
Text string
Ret Kind
Args []Kind
Variadic bool
// contains filtered or unexported fields
}
Signature is one parsed C prototype written in the mini-language:
<ret>(<arg>, <arg>, ...)
Examples:
i64(str) size_t strlen(const char *) ptr(ptr,ptr,i64) void *memcpy(void *, const void *, size_t) void(ptr,i64,i64,ptr) void qsort(void *, size_t, size_t, cmp *) i32(ptr,str,...) int sprintf(char *, const char *, ...)
An empty or "void" argument list means no arguments. A trailing "..." marks a true C-variadic function; the types of the variadic arguments are then derived from the Go values passed at call time.
func ParseSignature ¶
ParseSignature parses a signature and caches the result. Parsed signatures are immutable and safe for concurrent use.