Compare commits

...

2 Commits

Author SHA1 Message Date
Muhammad Azeez
51a6aa313f feat: add support for manifest 2023-09-13 17:35:32 +03:00
Muhammad Azeez
c676cf1924 try to fix the crash 2023-09-12 17:39:12 +03:00
9 changed files with 320 additions and 27 deletions

View File

@@ -18,7 +18,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Extism.runtime.win-x64" Version="0.4.0" />
<!-- <PackageReference Include="Extism.runtime.win-x64" Version="0.7.0" /> -->
</ItemGroup>
<ItemGroup>

View File

@@ -4,6 +4,8 @@ using Extism.Sdk.Native;
using System.Runtime.InteropServices;
using System.Text;
Console.WriteLine($"Version: {Plugin.ExtismVersion()}");
var userData = Marshal.StringToHGlobalAnsi("Hello again!");
using var helloWorld = new HostFunction(
@@ -27,8 +29,17 @@ void HelloWorld(CurrentPlugin plugin, Span<ExtismVal> inputs, Span<ExtismVal> ou
outputs[0].v.i64 = plugin.WriteString(input);
}
var wasm = File.ReadAllBytes("./code-functions.wasm");
using var plugin = new Plugin(wasm, new[] { helloWorld }, withWasi: true);
var manifest = new Manifest(new PathWasmSource("./code-functions.wasm"))
{
Config = new Dictionary<string, string>
{
{ "my-key", "some cool value" }
},
};
using var plugin = new Plugin(manifest, new[] { helloWorld }, withWasi: true);
Console.WriteLine("Plugin creatd!!!");
var output = Encoding.UTF8.GetString(
plugin.CallFunction("count_vowels", Encoding.UTF8.GetBytes("Hello World!"))

Binary file not shown.

View File

@@ -5,7 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<LangVersion>10</LangVersion>
<LangVersion>11</LangVersion>
</PropertyGroup>
<PropertyGroup>
@@ -24,5 +24,6 @@
<ItemGroup>
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
<PackageReference Include="System.Text.Json" Version="7.0.3" />
</ItemGroup>
</Project>

View File

@@ -188,9 +188,17 @@ internal static class LibExtism
/// <param name="functions">Array of host function pointers.</param>
/// <param name="nFunctions">Number of host functions.</param>
/// <param name="withWasi">Enables/disables WASI.</param>
/// <param name="errmsg"></param>
/// <returns></returns>
[DllImport("extism")]
unsafe internal static extern ExtismPlugin* extism_plugin_new(byte* wasm, int wasmSize, IntPtr* functions, int nFunctions, bool withWasi, IntPtr* errmsg);
unsafe internal static extern ExtismPlugin* extism_plugin_new(byte* wasm, ulong wasmSize, IntPtr* functions, ulong nFunctions, [MarshalAs(UnmanagedType.I1)] bool withWasi, out char** errmsg);
/// <summary>
/// Frees a plugin error message.
/// </summary>
/// <param name="errorMessage"></param>
[DllImport("extism")]
unsafe internal static extern void extism_plugin_new_error_free(IntPtr errorMessage);
/// <summary>
/// Remove a plugin from the registry and free associated memory.
@@ -263,11 +271,11 @@ internal static class LibExtism
internal static extern bool extism_log_file(string filename, string logLevel);
/// <summary>
/// Get the Extism Plugin ID, a 16-bit UUID in host order
/// Get Extism Runtime version.
/// </summary>
/// <returns></returns>
// [DllImport("extism")]
// unsafe internal static extern IntPtr extism_plugin_id(ExtismPlugin* plugin);
[DllImport("extism")]
internal static extern IntPtr extism_version();
/// <summary>
/// Extism Log Levels

View File

@@ -0,0 +1,221 @@
using System.Security.Cryptography;
using System.Text.Json.Serialization;
using System.Text.Json;
using System.Text;
using System.Xml.Linq;
namespace Extism.Sdk
{
/// <summary>
/// The manifest is a description of your plugin and some of the runtime constraints to apply to it.
/// You can think of it as a blueprint to build your plugin.
/// </summary>
public class Manifest
{
/// <summary>
/// Create an empty manifest.
/// </summary>
public Manifest()
{
AllowedPaths = new Dictionary<string, string>
{
{ "/usr/plugins/1/data", "/data" }, // src, dest
{ "d:/plugins/1/data", "/data" } // src, dest
};
}
/// <summary>
/// Create a manifest from one or more Wasm sources.
/// </summary>
/// <param name="sources"></param>
public Manifest(params WasmSource[] sources)
{
Sources.AddRange(sources);
}
/// <summary>
/// List of Wasm sources. See <see cref="PathWasmSource"/> and <see cref="ByteArrayWasmSource"/>.
/// </summary>
[JsonPropertyName("wasm")]
public List<WasmSource> Sources { get; set; } = new();
/// <summary>
/// Configures memory for the Wasm runtime.
/// Memory is described in units of pages (64KB) and represent contiguous chunks of addressable memory.
/// </summary>
[JsonPropertyName("memory")]
public MemoryOptions? MemoryOptions { get; set; }
/// <summary>
/// List of host names the plugins can access. Example:
/// <code>
/// AllowedHosts = new List&lt;string&gt; {
/// "www.example.com",
/// "api.*.com",
/// "example.*",
/// }
/// </code>
/// </summary>
[JsonPropertyName("allowed_hosts")]
public List<string> AllowedHosts { get; set; } = new();
/// <summary>
/// List of directories that can be accessed by the plugins. Examples:
/// <code>
/// AllowedPaths = new Dictionary&lt;string, string&gt;
/// {
/// { "/usr/plugins/1/data", "/data" }, // src, dest
/// { "d:/plugins/1/data", "/data" } // src, dest
/// };
/// </code>
/// </summary>
[JsonPropertyName("allowed_paths")]
public Dictionary<string, string> AllowedPaths { get; set; } = new();
/// <summary>
/// Configurations available to the plugins. Examples:
/// <code>
/// Config = new Dictionary&lt;string, string&gt;
/// {
/// { "userId", "55" }, // key, value
/// { "mySecret", "super-secret-key" } // key, value
/// };
/// </code>
/// </summary>
[JsonPropertyName("config")]
public Dictionary<string, string> Config { get; set; } = new();
}
/// <summary>
/// Configures memory for the Wasm runtime.
/// Memory is described in units of pages (64KB) and represent contiguous chunks of addressable memory.
/// </summary>
public class MemoryOptions
{
/// <summary>
/// Max number of pages. Each page is 64KB.
/// </summary>
[JsonPropertyName("max")]
public int MaxPages { get; set; }
}
/// <summary>
/// A named Wasm source.
/// </summary>
public abstract class WasmSource
{
/// <summary>
/// Logical name of the Wasm source
/// </summary>
[JsonPropertyName("name")]
public string? Name { get; set; }
/// <summary>
/// Hash of the WASM source
/// </summary>
[JsonPropertyName("hash")]
public string? Hash { get; set; }
}
/// <summary>
/// Wasm Source represented by a file referenced by a path.
/// </summary>
public class PathWasmSource : WasmSource
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="path">path to wasm plugin.</param>
/// <param name="name"></param>
/// <param name="hash"></param>
public PathWasmSource(string path, string? name = null, string? hash = null)
{
Path = System.IO.Path.GetFullPath(path);
Name = name ?? System.IO.Path.GetFileNameWithoutExtension(path);
Hash = hash;
if (Hash is null)
{
using var file = File.OpenRead(Path);
Hash = Helpers.ComputeSha256Hash(file);
}
}
/// <summary>
/// Path to wasm plugin.
/// </summary>
[JsonPropertyName("path")]
public string Path { get; }
}
/// <summary>
/// Wasm Source represented by raw bytes.
/// </summary>
public class ByteArrayWasmSource : WasmSource
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="data">the byte array representing the Wasm code</param>
/// <param name="name"></param>
/// <param name="hash"></param>
public ByteArrayWasmSource(byte[] data, string? name, string? hash = null)
{
Data = data;
Name = name;
Hash = hash;
if (Hash is null)
{
using var memory = new MemoryStream(data);
Hash = Helpers.ComputeSha256Hash(memory);
}
}
/// <summary>
/// The byte array representing the Wasm code
/// </summary>
[JsonPropertyName("data")]
[JsonConverter(typeof(Base64EncodedStringConverter))]
public byte[] Data { get; }
}
static class Helpers
{
public static string ComputeSha256Hash(Stream stream)
{
using (SHA256 sha256 = SHA256.Create())
{
byte[] hashBytes = sha256.ComputeHash(stream);
return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
}
}
}
class Base64EncodedStringConverter : JsonConverter<string>
{
public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
Encoding.UTF8.GetString(reader.GetBytesFromBase64());
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) =>
writer.WriteBase64StringValue(Encoding.UTF8.GetBytes(value));
}
class WasmSourceConverter : JsonConverter<WasmSource>
{
public override WasmSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
throw new NotImplementedException();
}
public override void Write(Utf8JsonWriter writer, WasmSource value, JsonSerializerOptions options)
{
if (value is PathWasmSource path)
JsonSerializer.Serialize(writer, path, typeof(PathWasmSource), options);
else if (value is ByteArrayWasmSource bytes)
JsonSerializer.Serialize(writer, bytes, typeof(ByteArrayWasmSource), options);
else
throw new ArgumentOutOfRangeException(nameof(value), "Unknown Wasm Source");
}
}
}

View File

@@ -1,5 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json;
namespace Extism.Sdk.Native;
@@ -19,33 +21,72 @@ public unsafe class Plugin : IDisposable
internal LibExtism.ExtismPlugin* NativeHandle { get; }
/// <summary>
/// Create a and load a plug-in
/// Create a plugin from a Manifest.
/// </summary>
/// <param name="manifest"></param>
/// <param name="functions"></param>
/// <param name="withWasi"></param>
public Plugin(Manifest manifest, HostFunction[] functions, bool withWasi)
{
_functions = functions;
var options = new JsonSerializerOptions
{
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
options.Converters.Add(new WasmSourceConverter());
var json = JsonSerializer.Serialize(manifest, options);
var bytes = Encoding.UTF8.GetBytes(json);
var functionHandles = functions.Select(f => f.NativeHandle).ToArray();
fixed (byte* wasmPtr = bytes)
fixed (IntPtr* functionsPtr = functionHandles)
{
NativeHandle = Initialize(wasmPtr, bytes.Length, functions, withWasi, functionsPtr);
}
}
/// <summary>
/// Create and load a plugin from a byte array.
/// </summary>
/// <param name="wasm">A WASM module (wat or wasm) or a JSON encoded manifest.</param>
/// <param name="functions">List of host functions expected by the plugin.</param>
/// <param name="withWasi">Enable/Disable WASI.</param>
public Plugin(ReadOnlySpan<byte> wasm, HostFunction[] functions, bool withWasi) {
public Plugin(ReadOnlySpan<byte> wasm, HostFunction[] functions, bool withWasi)
{
_functions = functions;
var functionHandles = functions.Select(f => f.NativeHandle).ToArray();
unsafe
var functionHandles = functions.Select(f => f.NativeHandle).ToArray();
fixed (byte* wasmPtr = wasm)
fixed (IntPtr* functionsPtr = functionHandles)
{
fixed (byte* wasmPtr = wasm)
fixed (IntPtr* functionsPtr = functionHandles)
{
NativeHandle = LibExtism.extism_plugin_new(wasmPtr, wasm.Length, functionsPtr, functions.Length, withWasi, null);
if (NativeHandle == null)
{
throw new ExtismException("Unable to create plugin");
// TODO: handle error
// var s = Marshal.PtrToStringUTF8(result);
// LibExtism.extism_plugin_new_error_free(errmsg);
// throw new ExtismException(s);
}
}
NativeHandle = Initialize(wasmPtr, wasm.Length, functions, withWasi, functionsPtr);
}
}
private unsafe LibExtism.ExtismPlugin* Initialize(byte* wasmPtr, int wasmLength, HostFunction[] functions, bool withWasi, IntPtr* functionsPtr)
{
char** errorMsgPtr;
var handle = LibExtism.extism_plugin_new(wasmPtr, (ulong)wasmLength, functionsPtr, (ulong)functions.Length, withWasi, out errorMsgPtr);
if (handle == null)
{
var msg = "Unable to create plugin";
if (errorMsgPtr is not null)
{
msg = Marshal.PtrToStringAnsi(new IntPtr(errorMsgPtr));
}
throw new ExtismException(msg);
}
return handle;
}
/// <summary>
/// Update plugin config values, this will merge with the existing values.
/// </summary>
@@ -199,4 +240,15 @@ public unsafe class Plugin : IDisposable
{
Dispose(false);
}
/// <summary>
/// Get Extism Runtime version.
/// </summary>
/// <returns></returns>
public static string ExtismVersion()
{
var version = LibExtism.extism_version();
return Marshal.PtrToStringAnsi(version);
}
}

View File

@@ -36,7 +36,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Extism.runtime.win-x64" Version="0.4.0" />
<PackageReference Include="Extism.runtime.win-x64" Version="0.7.0" />
</ItemGroup>
</Project>

View File

@@ -8,7 +8,7 @@
/**
* A list of all possible value types in WebAssembly.
* An enumeration of all possible value types in WebAssembly.
*/
typedef enum {
/**