Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
node_pack.hpp
Go to the documentation of this file.
1// pulp/host/node_pack.hpp — load signed, precompiled custom-node packs.
2//
3// A "node pack" is a dynamic library (.dylib / .so / .dll) exporting the public
4// `pulp_node_v1_entry` symbol, plus a JSON manifest that declares the pack's
5// identity, ABI major, the binary's SHA-256, node metadata, resource/runtime
6// requirements, and an Ed25519 signature by a trusted publisher key. The loader
7// verifies trust and host policy BEFORE it ever dlopen()s the binary: untrusted
8// signer, bad signature, unsupported requirements, hash mismatch, or ABI
9// mismatch all reject the pack without loading code.
10//
11// Desktop + Android only. iOS / AUv3 / sandboxed targets do NOT do dynamic
12// loading (App Store dlopen policy): native components there are source-built,
13// statically linked, and signed as part of the app — this whole translation
14// unit is compiled out with `core/host` on iOS.
15#pragma once
16
18
19#include <cstdint>
20#include <memory>
21#include <string>
22#include <vector>
23
24#if defined(__APPLE__)
25#include <TargetConditionals.h>
26#endif
27
28namespace pulp::host {
29
30enum class GeneratedDevicePlatform : std::uint8_t {
35};
36
46
47enum class GeneratedDeviceOrigin : std::uint8_t {
50};
51
53 bool allowed = false;
56};
57
58[[nodiscard]] constexpr GeneratedDevicePlatform build_generated_device_platform() noexcept {
59#if defined(__ANDROID__)
61#elif defined(__EMSCRIPTEN__)
63#elif defined(__APPLE__) && TARGET_OS_IPHONE
65#else
67#endif
68}
69
72[[nodiscard]] constexpr GeneratedDeviceAdmission
75 GeneratedDeviceOrigin origin) noexcept {
82 return {};
83 }
86 return {true, false, true};
87 }
89 return {true, false, false};
90 }
92 const bool supported = platform == GeneratedDevicePlatform::Desktop ||
95 return {supported, false, false};
96 }
98 const bool supported = platform == GeneratedDevicePlatform::Desktop ||
100 return {supported, supported, false};
101 }
102 if (artifact == GeneratedDeviceArtifact::WebAssembly) {
103 const bool supported = platform == GeneratedDevicePlatform::Web;
104 return {supported, false, false};
105 }
106 if (artifact == GeneratedDeviceArtifact::NativePack) {
107 const bool supported = platform == GeneratedDevicePlatform::Desktop ||
110 return {supported, false, false};
111 }
112 return {};
113}
114
115// One node declared by a pack manifest (for host-side discovery before load).
117 std::string type_id;
118 std::uint32_t capabilities = 0;
119};
120
122 std::string id;
123 std::string kind;
124 std::string sha256_hex;
125 bool required = true;
126};
127
129 bool realtime_safe = true;
131 std::uint32_t max_block_size = 0; // 0 means unspecified by the pack
132 std::uint64_t persistent_bytes = 0;
133 std::uint64_t scratch_bytes = 0;
134};
135
136// Parsed, not-yet-verified manifest.
138 std::string pack_id;
139 std::uint32_t abi_major = 0;
140 std::string binary; // filename relative to the manifest dir
141 std::string sha256_hex; // expected SHA-256 of the binary
142 std::vector<std::uint8_t> signer_public_key; // 32 bytes (Ed25519)
143 std::vector<std::uint8_t> signature; // 64 bytes (Ed25519, detached)
144 std::vector<NodePackEntry> nodes;
145 std::vector<NodePackResource> resources;
147};
148
149// Trust policy: the set of publisher public keys the host accepts. A pack signed
150// by a key not in this set is rejected (this is where revocation lives — drop a
151// key to revoke it).
153 std::vector<std::vector<std::uint8_t>> trusted_public_keys; // each 32 bytes
154};
155
168
169enum class NodePackError {
170 Ok = 0,
171 ManifestInvalid = 1, // unparseable / missing fields / wrong key sizes
172 UntrustedSigner = 2, // signer_public_key not in the trust set
173 BadSignature = 3, // Ed25519 verification failed
174 HashMismatch = 4, // binary's SHA-256 != manifest sha256_hex
175 AbiMismatch = 5, // manifest or entry abi_major != PULP_NODE_V1_ABI_MAJOR
176 LoadFailed = 6, // dlopen/LoadLibrary failed
177 SymbolMissing = 7, // no pulp_node_v1_entry export
178 UnsupportedRequirements = 8, // capabilities / RT / resource needs unsupported
179 NodeMetadataMismatch = 9, // manifest node declarations != loaded descriptor
180 PlatformPolicyDenied = 10, // native artifact is forbidden on this platform/origin
181 BinaryChanged = 11, // the mapped module is not the file whose bytes were verified
182};
183
186 const pulp_node_entry_v1* entry = nullptr; // valid only when error == Ok
187 void* handle = nullptr; // opaque dl handle; pass to unload
188 bool ok() const { return error == NodePackError::Ok; }
189};
190
191// Parse a manifest JSON string. Returns false (and leaves `out` unspecified) on
192// malformed input or wrong key/signature sizes.
193bool parse_node_pack_manifest(const std::string& json, NodePackManifest& out);
194
195// The canonical byte string the manifest signature covers: binds pack identity,
196// ABI major, binary hash, declared node type-ids/capabilities, resources, and
197// runtime requirements. Publishers sign exactly these bytes.
198std::vector<std::uint8_t> node_pack_signed_message(const NodePackManifest& m);
199
200// The pack binary, opened once and hashed from that open file. The load stage
201// maps exactly these bytes, so a file swapped at the path after verification
202// is never the code that runs:
203// - macOS / Linux: the dynamic loader is handed the descriptor path
204// (/dev/fd/N, /proc/self/fd/N), which the kernel resolves to this open file
205// instead of walking the pack directory again. dyld maps the inode behind
206// the descriptor even when the directory entry already points elsewhere.
207// - Windows: the file is held open without FILE_SHARE_WRITE / FILE_SHARE_DELETE
208// so the path cannot be rewritten or replaced while it is held, and the
209// mapped module's volume + file index are compared with this handle after
210// LoadLibrary; a mismatch unloads it and reports BinaryChanged.
211// The file stays open for as long as the pack is loaded; unload_node_pack
212// releases it.
214
216 public:
223
224 bool is_open() const;
225 // Path that was opened (manifest_dir + "/" + manifest.binary).
226 const std::string& path() const;
227 // SHA-256 of the bytes actually read from the open file.
228 const std::string& sha256_hex() const;
229
230 // Opaque on-disk handle; the loader's platform helpers operate on it.
231 struct Impl;
232
233 private:
234 std::unique_ptr<Impl> impl_;
235
237 const NodePackManifest&);
239 const NodePackManifest&, const NodePackTrust&,
240 const NodePackHostPolicy&);
241};
242
244 // Ok, LoadFailed (cannot open, or not a regular file) or HashMismatch.
246 VerifiedNodePackBinary binary; // open only when error == Ok
247};
248
249// Stage one of loading: open the pack binary once and compare the hash of the
250// bytes read from that open file with the manifest's signed hash. Does not
251// check trust or policy; load_verified_node_pack re-checks both.
252NodePackVerifyResult verify_node_pack_binary(const std::string& manifest_dir,
253 const NodePackManifest& manifest);
254
255// Stage two: re-run the manifest trust and policy checks, then map the bytes
256// behind `binary` and resolve the node entry. Takes ownership of the open
257// file; it is released on any failure and otherwise by unload_node_pack.
259 const NodePackManifest& manifest,
260 const NodePackTrust& trust,
261 const NodePackHostPolicy& policy);
262
263// Verify trust + integrity, then load the binary and resolve the node entry.
264// `manifest_dir` is the directory containing the manifest and the binary. No
265// code is loaded unless the signer is trusted, the signature is authentic, and
266// the binary matches the signed hash; the two stages above are composed so the
267// bytes that were hashed are the bytes that are mapped.
268NodePackLoadResult load_node_pack(const std::string& manifest_dir,
269 const NodePackManifest& manifest,
270 const NodePackTrust& trust);
271NodePackLoadResult load_node_pack(const std::string& manifest_dir,
272 const NodePackManifest& manifest,
273 const NodePackTrust& trust,
274 const NodePackHostPolicy& policy);
275
276// Unload a previously loaded pack (dlclose/FreeLibrary) and release the open
277// file it was mapped from. Safe on nullptr.
278void unload_node_pack(void* handle);
279
280} // namespace pulp::host
Definition node_pack.hpp:215
friend NodePackLoadResult load_verified_node_pack(VerifiedNodePackBinary, const NodePackManifest &, const NodePackTrust &, const NodePackHostPolicy &)
friend NodePackVerifyResult verify_node_pack_binary(const std::string &, const NodePackManifest &)
const std::string & path() const
VerifiedNodePackBinary(VerifiedNodePackBinary &&) noexcept
const std::string & sha256_hex() const
Bounded event contracts shared by host ingress and graph-authored nodes.
Definition anticipation_eligibility.hpp:9
constexpr GeneratedDeviceAdmission generated_device_admission(GeneratedDevicePlatform platform, GeneratedDeviceArtifact artifact, GeneratedDeviceOrigin origin) noexcept
Returns the platform policy before any executable artifact is opened.
Definition node_pack.hpp:73
constexpr GeneratedDevicePlatform build_generated_device_platform() noexcept
Definition node_pack.hpp:58
bool parse_node_pack_manifest(const std::string &json, NodePackManifest &out)
GeneratedDeviceArtifact
Definition node_pack.hpp:37
@ NativePack
Definition node_pack.hpp:40
@ FrozenAudio
Definition node_pack.hpp:42
@ DeclarativeGraph
Definition node_pack.hpp:38
@ GeneratedUi
Definition node_pack.hpp:44
@ WebAssembly
Definition node_pack.hpp:41
@ Recipe
Definition node_pack.hpp:43
@ InterpretedProgram
Definition node_pack.hpp:39
GeneratedDevicePlatform
Definition node_pack.hpp:30
@ Desktop
Definition node_pack.hpp:31
@ Ios
Definition node_pack.hpp:33
@ Web
Definition node_pack.hpp:34
@ Android
Definition node_pack.hpp:32
NodePackError
Definition node_pack.hpp:169
@ HashMismatch
Definition node_pack.hpp:174
@ ManifestInvalid
Definition node_pack.hpp:171
@ NodeMetadataMismatch
Definition node_pack.hpp:179
@ LoadFailed
Definition node_pack.hpp:176
@ BinaryChanged
Definition node_pack.hpp:181
@ Ok
Definition node_pack.hpp:170
@ UnsupportedRequirements
Definition node_pack.hpp:178
@ UntrustedSigner
Definition node_pack.hpp:172
@ BadSignature
Definition node_pack.hpp:173
@ PlatformPolicyDenied
Definition node_pack.hpp:180
@ AbiMismatch
Definition node_pack.hpp:175
@ SymbolMissing
Definition node_pack.hpp:177
void unload_node_pack(void *handle)
NodePackLoadResult load_node_pack(const std::string &manifest_dir, const NodePackManifest &manifest, const NodePackTrust &trust)
std::vector< std::uint8_t > node_pack_signed_message(const NodePackManifest &m)
GeneratedDeviceOrigin
Definition node_pack.hpp:47
@ AppBundled
Definition node_pack.hpp:48
@ RuntimeDownloaded
Definition node_pack.hpp:49
@ Ok
Definition baked_graph_processor.hpp:156
Device capability vocabulary: one tier ladder, one thermal ladder, and the projection from observed i...
Definition child_process.hpp:15
STL namespace.
@ PULP_NODE_CAP_LATENCY_V1
Definition pulp_node_v1.h:78
@ PULP_NODE_CAP_STATE_V1
Definition pulp_node_v1.h:75
@ PULP_NODE_CAP_EVENTS_V1
Definition pulp_node_v1.h:77
@ PULP_NODE_CAP_RESET_V1
Definition pulp_node_v1.h:76
Definition node_pack.hpp:52
bool requires_separate_origin
Definition node_pack.hpp:54
bool universal_fallback_part
Definition node_pack.hpp:55
bool allowed
Definition node_pack.hpp:53
Definition node_pack.hpp:116
std::string type_id
Definition node_pack.hpp:117
std::uint32_t capabilities
Definition node_pack.hpp:118
Definition node_pack.hpp:156
std::uint64_t max_persistent_bytes
Definition node_pack.hpp:163
bool allow_audio_thread_allocations
Definition node_pack.hpp:161
std::uint32_t max_block_size
Definition node_pack.hpp:162
std::uint64_t max_scratch_bytes
Definition node_pack.hpp:164
GeneratedDeviceOrigin origin
Definition node_pack.hpp:166
GeneratedDevicePlatform platform
Definition node_pack.hpp:165
bool require_realtime_safe
Definition node_pack.hpp:160
std::uint32_t supported_capabilities
Definition node_pack.hpp:157
Definition node_pack.hpp:184
const pulp_node_entry_v1 * entry
Definition node_pack.hpp:186
void * handle
Definition node_pack.hpp:187
bool ok() const
Definition node_pack.hpp:188
NodePackError error
Definition node_pack.hpp:185
Definition node_pack.hpp:137
std::vector< NodePackResource > resources
Definition node_pack.hpp:145
std::uint32_t abi_major
Definition node_pack.hpp:139
std::vector< NodePackEntry > nodes
Definition node_pack.hpp:144
std::vector< std::uint8_t > signer_public_key
Definition node_pack.hpp:142
std::string binary
Definition node_pack.hpp:140
std::string sha256_hex
Definition node_pack.hpp:141
std::string pack_id
Definition node_pack.hpp:138
std::vector< std::uint8_t > signature
Definition node_pack.hpp:143
NodePackRuntimeRequirements requirements
Definition node_pack.hpp:146
Definition node_pack.hpp:121
bool required
Definition node_pack.hpp:125
std::string kind
Definition node_pack.hpp:123
std::string sha256_hex
Definition node_pack.hpp:124
std::string id
Definition node_pack.hpp:122
Definition node_pack.hpp:128
std::uint64_t persistent_bytes
Definition node_pack.hpp:132
bool realtime_safe
Definition node_pack.hpp:129
std::uint64_t scratch_bytes
Definition node_pack.hpp:133
std::uint32_t max_block_size
Definition node_pack.hpp:131
bool audio_thread_allocations
Definition node_pack.hpp:130
Definition node_pack.hpp:152
std::vector< std::vector< std::uint8_t > > trusted_public_keys
Definition node_pack.hpp:153
Definition node_pack.hpp:243
NodePackError error
Definition node_pack.hpp:245
VerifiedNodePackBinary binary
Definition node_pack.hpp:246
Definition pulp_node_v1.h:161