Some checks failed
Pipeline: Test, Lint, Build / Get version info (push) Has been cancelled
Pipeline: Test, Lint, Build / Lint Go code (push) Has been cancelled
Pipeline: Test, Lint, Build / Test Go code (push) Has been cancelled
Pipeline: Test, Lint, Build / Test JS code (push) Has been cancelled
Pipeline: Test, Lint, Build / Lint i18n files (push) Has been cancelled
Pipeline: Test, Lint, Build / Check Docker configuration (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (darwin/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (darwin/arm64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/386) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v5) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v6) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm/v7) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (linux/arm64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (windows/386) (push) Has been cancelled
Pipeline: Test, Lint, Build / Build (windows/amd64) (push) Has been cancelled
Pipeline: Test, Lint, Build / Push to GHCR (push) Has been cancelled
Pipeline: Test, Lint, Build / Push to Docker Hub (push) Has been cancelled
Pipeline: Test, Lint, Build / Cleanup digest artifacts (push) Has been cancelled
Pipeline: Test, Lint, Build / Build Windows installers (push) Has been cancelled
Pipeline: Test, Lint, Build / Package/Release (push) Has been cancelled
Pipeline: Test, Lint, Build / Upload Linux PKG (push) Has been cancelled
Close stale issues and PRs / stale (push) Has been cancelled
POEditor import / update-translations (push) Has been cancelled
1689 lines
48 KiB
Go
1689 lines
48 KiB
Go
//go:build !wasip1
|
|
|
|
// Code generated by protoc-gen-go-plugin. DO NOT EDIT.
|
|
// versions:
|
|
// protoc-gen-go-plugin v0.1.0
|
|
// protoc v5.29.3
|
|
// source: api/api.proto
|
|
|
|
package api
|
|
|
|
import (
|
|
context "context"
|
|
errors "errors"
|
|
fmt "fmt"
|
|
wazero "github.com/tetratelabs/wazero"
|
|
api "github.com/tetratelabs/wazero/api"
|
|
sys "github.com/tetratelabs/wazero/sys"
|
|
os "os"
|
|
)
|
|
|
|
const MetadataAgentPluginAPIVersion = 1
|
|
|
|
type MetadataAgentPlugin struct {
|
|
newRuntime func(context.Context) (wazero.Runtime, error)
|
|
moduleConfig wazero.ModuleConfig
|
|
}
|
|
|
|
func NewMetadataAgentPlugin(ctx context.Context, opts ...wazeroConfigOption) (*MetadataAgentPlugin, error) {
|
|
o := &WazeroConfig{
|
|
newRuntime: DefaultWazeroRuntime(),
|
|
moduleConfig: wazero.NewModuleConfig().WithStartFunctions("_initialize"),
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
opt(o)
|
|
}
|
|
|
|
return &MetadataAgentPlugin{
|
|
newRuntime: o.newRuntime,
|
|
moduleConfig: o.moduleConfig,
|
|
}, nil
|
|
}
|
|
|
|
type metadataAgent interface {
|
|
Close(ctx context.Context) error
|
|
MetadataAgent
|
|
}
|
|
|
|
func (p *MetadataAgentPlugin) Load(ctx context.Context, pluginPath string) (metadataAgent, error) {
|
|
b, err := os.ReadFile(pluginPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Create a new runtime so that multiple modules will not conflict
|
|
r, err := p.newRuntime(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Compile the WebAssembly module using the default configuration.
|
|
code, err := r.CompileModule(ctx, b)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// InstantiateModule runs the "_start" function, WASI's "main".
|
|
module, err := r.InstantiateModule(ctx, code, p.moduleConfig)
|
|
if err != nil {
|
|
// Note: Most compilers do not exit the module after running "_start",
|
|
// unless there was an Error. This allows you to call exported functions.
|
|
if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() != 0 {
|
|
return nil, fmt.Errorf("unexpected exit_code: %d", exitErr.ExitCode())
|
|
} else if !ok {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Compare API versions with the loading plugin
|
|
apiVersion := module.ExportedFunction("metadata_agent_api_version")
|
|
if apiVersion == nil {
|
|
return nil, errors.New("metadata_agent_api_version is not exported")
|
|
}
|
|
results, err := apiVersion.Call(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
} else if len(results) != 1 {
|
|
return nil, errors.New("invalid metadata_agent_api_version signature")
|
|
}
|
|
if results[0] != MetadataAgentPluginAPIVersion {
|
|
return nil, fmt.Errorf("API version mismatch, host: %d, plugin: %d", MetadataAgentPluginAPIVersion, results[0])
|
|
}
|
|
|
|
getartistmbid := module.ExportedFunction("metadata_agent_get_artist_mbid")
|
|
if getartistmbid == nil {
|
|
return nil, errors.New("metadata_agent_get_artist_mbid is not exported")
|
|
}
|
|
getartisturl := module.ExportedFunction("metadata_agent_get_artist_url")
|
|
if getartisturl == nil {
|
|
return nil, errors.New("metadata_agent_get_artist_url is not exported")
|
|
}
|
|
getartistbiography := module.ExportedFunction("metadata_agent_get_artist_biography")
|
|
if getartistbiography == nil {
|
|
return nil, errors.New("metadata_agent_get_artist_biography is not exported")
|
|
}
|
|
getsimilarartists := module.ExportedFunction("metadata_agent_get_similar_artists")
|
|
if getsimilarartists == nil {
|
|
return nil, errors.New("metadata_agent_get_similar_artists is not exported")
|
|
}
|
|
getartistimages := module.ExportedFunction("metadata_agent_get_artist_images")
|
|
if getartistimages == nil {
|
|
return nil, errors.New("metadata_agent_get_artist_images is not exported")
|
|
}
|
|
getartisttopsongs := module.ExportedFunction("metadata_agent_get_artist_top_songs")
|
|
if getartisttopsongs == nil {
|
|
return nil, errors.New("metadata_agent_get_artist_top_songs is not exported")
|
|
}
|
|
getalbuminfo := module.ExportedFunction("metadata_agent_get_album_info")
|
|
if getalbuminfo == nil {
|
|
return nil, errors.New("metadata_agent_get_album_info is not exported")
|
|
}
|
|
getalbumimages := module.ExportedFunction("metadata_agent_get_album_images")
|
|
if getalbumimages == nil {
|
|
return nil, errors.New("metadata_agent_get_album_images is not exported")
|
|
}
|
|
|
|
malloc := module.ExportedFunction("malloc")
|
|
if malloc == nil {
|
|
return nil, errors.New("malloc is not exported")
|
|
}
|
|
|
|
free := module.ExportedFunction("free")
|
|
if free == nil {
|
|
return nil, errors.New("free is not exported")
|
|
}
|
|
return &metadataAgentPlugin{
|
|
runtime: r,
|
|
module: module,
|
|
malloc: malloc,
|
|
free: free,
|
|
getartistmbid: getartistmbid,
|
|
getartisturl: getartisturl,
|
|
getartistbiography: getartistbiography,
|
|
getsimilarartists: getsimilarartists,
|
|
getartistimages: getartistimages,
|
|
getartisttopsongs: getartisttopsongs,
|
|
getalbuminfo: getalbuminfo,
|
|
getalbumimages: getalbumimages,
|
|
}, nil
|
|
}
|
|
|
|
func (p *metadataAgentPlugin) Close(ctx context.Context) (err error) {
|
|
if r := p.runtime; r != nil {
|
|
r.Close(ctx)
|
|
}
|
|
return
|
|
}
|
|
|
|
type metadataAgentPlugin struct {
|
|
runtime wazero.Runtime
|
|
module api.Module
|
|
malloc api.Function
|
|
free api.Function
|
|
getartistmbid api.Function
|
|
getartisturl api.Function
|
|
getartistbiography api.Function
|
|
getsimilarartists api.Function
|
|
getartistimages api.Function
|
|
getartisttopsongs api.Function
|
|
getalbuminfo api.Function
|
|
getalbumimages api.Function
|
|
}
|
|
|
|
func (p *metadataAgentPlugin) GetArtistMBID(ctx context.Context, request *ArtistMBIDRequest) (*ArtistMBIDResponse, error) {
|
|
data, err := request.MarshalVT()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataSize := uint64(len(data))
|
|
|
|
var dataPtr uint64
|
|
// If the input data is not empty, we must allocate the in-Wasm memory to store it, and pass to the plugin.
|
|
if dataSize != 0 {
|
|
results, err := p.malloc.Call(ctx, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataPtr = results[0]
|
|
// This pointer is managed by the Wasm module, which is unaware of external usage.
|
|
// So, we have to free it when finished
|
|
defer p.free.Call(ctx, dataPtr)
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
if !p.module.Memory().Write(uint32(dataPtr), data) {
|
|
return nil, fmt.Errorf("Memory.Write(%d, %d) out of range of memory size %d", dataPtr, dataSize, p.module.Memory().Size())
|
|
}
|
|
}
|
|
|
|
ptrSize, err := p.getartistmbid.Call(ctx, dataPtr, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resPtr := uint32(ptrSize[0] >> 32)
|
|
resSize := uint32(ptrSize[0])
|
|
var isErrResponse bool
|
|
if (resSize & (1 << 31)) > 0 {
|
|
isErrResponse = true
|
|
resSize &^= (1 << 31)
|
|
}
|
|
|
|
// We don't need the memory after deserialization: make sure it is freed.
|
|
if resPtr != 0 {
|
|
defer p.free.Call(ctx, uint64(resPtr))
|
|
}
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
bytes, ok := p.module.Memory().Read(resPtr, resSize)
|
|
if !ok {
|
|
return nil, fmt.Errorf("Memory.Read(%d, %d) out of range of memory size %d",
|
|
resPtr, resSize, p.module.Memory().Size())
|
|
}
|
|
|
|
if isErrResponse {
|
|
return nil, errors.New(string(bytes))
|
|
}
|
|
|
|
response := new(ArtistMBIDResponse)
|
|
if err = response.UnmarshalVT(bytes); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
func (p *metadataAgentPlugin) GetArtistURL(ctx context.Context, request *ArtistURLRequest) (*ArtistURLResponse, error) {
|
|
data, err := request.MarshalVT()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataSize := uint64(len(data))
|
|
|
|
var dataPtr uint64
|
|
// If the input data is not empty, we must allocate the in-Wasm memory to store it, and pass to the plugin.
|
|
if dataSize != 0 {
|
|
results, err := p.malloc.Call(ctx, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataPtr = results[0]
|
|
// This pointer is managed by the Wasm module, which is unaware of external usage.
|
|
// So, we have to free it when finished
|
|
defer p.free.Call(ctx, dataPtr)
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
if !p.module.Memory().Write(uint32(dataPtr), data) {
|
|
return nil, fmt.Errorf("Memory.Write(%d, %d) out of range of memory size %d", dataPtr, dataSize, p.module.Memory().Size())
|
|
}
|
|
}
|
|
|
|
ptrSize, err := p.getartisturl.Call(ctx, dataPtr, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resPtr := uint32(ptrSize[0] >> 32)
|
|
resSize := uint32(ptrSize[0])
|
|
var isErrResponse bool
|
|
if (resSize & (1 << 31)) > 0 {
|
|
isErrResponse = true
|
|
resSize &^= (1 << 31)
|
|
}
|
|
|
|
// We don't need the memory after deserialization: make sure it is freed.
|
|
if resPtr != 0 {
|
|
defer p.free.Call(ctx, uint64(resPtr))
|
|
}
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
bytes, ok := p.module.Memory().Read(resPtr, resSize)
|
|
if !ok {
|
|
return nil, fmt.Errorf("Memory.Read(%d, %d) out of range of memory size %d",
|
|
resPtr, resSize, p.module.Memory().Size())
|
|
}
|
|
|
|
if isErrResponse {
|
|
return nil, errors.New(string(bytes))
|
|
}
|
|
|
|
response := new(ArtistURLResponse)
|
|
if err = response.UnmarshalVT(bytes); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
func (p *metadataAgentPlugin) GetArtistBiography(ctx context.Context, request *ArtistBiographyRequest) (*ArtistBiographyResponse, error) {
|
|
data, err := request.MarshalVT()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataSize := uint64(len(data))
|
|
|
|
var dataPtr uint64
|
|
// If the input data is not empty, we must allocate the in-Wasm memory to store it, and pass to the plugin.
|
|
if dataSize != 0 {
|
|
results, err := p.malloc.Call(ctx, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataPtr = results[0]
|
|
// This pointer is managed by the Wasm module, which is unaware of external usage.
|
|
// So, we have to free it when finished
|
|
defer p.free.Call(ctx, dataPtr)
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
if !p.module.Memory().Write(uint32(dataPtr), data) {
|
|
return nil, fmt.Errorf("Memory.Write(%d, %d) out of range of memory size %d", dataPtr, dataSize, p.module.Memory().Size())
|
|
}
|
|
}
|
|
|
|
ptrSize, err := p.getartistbiography.Call(ctx, dataPtr, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resPtr := uint32(ptrSize[0] >> 32)
|
|
resSize := uint32(ptrSize[0])
|
|
var isErrResponse bool
|
|
if (resSize & (1 << 31)) > 0 {
|
|
isErrResponse = true
|
|
resSize &^= (1 << 31)
|
|
}
|
|
|
|
// We don't need the memory after deserialization: make sure it is freed.
|
|
if resPtr != 0 {
|
|
defer p.free.Call(ctx, uint64(resPtr))
|
|
}
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
bytes, ok := p.module.Memory().Read(resPtr, resSize)
|
|
if !ok {
|
|
return nil, fmt.Errorf("Memory.Read(%d, %d) out of range of memory size %d",
|
|
resPtr, resSize, p.module.Memory().Size())
|
|
}
|
|
|
|
if isErrResponse {
|
|
return nil, errors.New(string(bytes))
|
|
}
|
|
|
|
response := new(ArtistBiographyResponse)
|
|
if err = response.UnmarshalVT(bytes); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
func (p *metadataAgentPlugin) GetSimilarArtists(ctx context.Context, request *ArtistSimilarRequest) (*ArtistSimilarResponse, error) {
|
|
data, err := request.MarshalVT()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataSize := uint64(len(data))
|
|
|
|
var dataPtr uint64
|
|
// If the input data is not empty, we must allocate the in-Wasm memory to store it, and pass to the plugin.
|
|
if dataSize != 0 {
|
|
results, err := p.malloc.Call(ctx, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataPtr = results[0]
|
|
// This pointer is managed by the Wasm module, which is unaware of external usage.
|
|
// So, we have to free it when finished
|
|
defer p.free.Call(ctx, dataPtr)
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
if !p.module.Memory().Write(uint32(dataPtr), data) {
|
|
return nil, fmt.Errorf("Memory.Write(%d, %d) out of range of memory size %d", dataPtr, dataSize, p.module.Memory().Size())
|
|
}
|
|
}
|
|
|
|
ptrSize, err := p.getsimilarartists.Call(ctx, dataPtr, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resPtr := uint32(ptrSize[0] >> 32)
|
|
resSize := uint32(ptrSize[0])
|
|
var isErrResponse bool
|
|
if (resSize & (1 << 31)) > 0 {
|
|
isErrResponse = true
|
|
resSize &^= (1 << 31)
|
|
}
|
|
|
|
// We don't need the memory after deserialization: make sure it is freed.
|
|
if resPtr != 0 {
|
|
defer p.free.Call(ctx, uint64(resPtr))
|
|
}
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
bytes, ok := p.module.Memory().Read(resPtr, resSize)
|
|
if !ok {
|
|
return nil, fmt.Errorf("Memory.Read(%d, %d) out of range of memory size %d",
|
|
resPtr, resSize, p.module.Memory().Size())
|
|
}
|
|
|
|
if isErrResponse {
|
|
return nil, errors.New(string(bytes))
|
|
}
|
|
|
|
response := new(ArtistSimilarResponse)
|
|
if err = response.UnmarshalVT(bytes); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
func (p *metadataAgentPlugin) GetArtistImages(ctx context.Context, request *ArtistImageRequest) (*ArtistImageResponse, error) {
|
|
data, err := request.MarshalVT()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataSize := uint64(len(data))
|
|
|
|
var dataPtr uint64
|
|
// If the input data is not empty, we must allocate the in-Wasm memory to store it, and pass to the plugin.
|
|
if dataSize != 0 {
|
|
results, err := p.malloc.Call(ctx, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataPtr = results[0]
|
|
// This pointer is managed by the Wasm module, which is unaware of external usage.
|
|
// So, we have to free it when finished
|
|
defer p.free.Call(ctx, dataPtr)
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
if !p.module.Memory().Write(uint32(dataPtr), data) {
|
|
return nil, fmt.Errorf("Memory.Write(%d, %d) out of range of memory size %d", dataPtr, dataSize, p.module.Memory().Size())
|
|
}
|
|
}
|
|
|
|
ptrSize, err := p.getartistimages.Call(ctx, dataPtr, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resPtr := uint32(ptrSize[0] >> 32)
|
|
resSize := uint32(ptrSize[0])
|
|
var isErrResponse bool
|
|
if (resSize & (1 << 31)) > 0 {
|
|
isErrResponse = true
|
|
resSize &^= (1 << 31)
|
|
}
|
|
|
|
// We don't need the memory after deserialization: make sure it is freed.
|
|
if resPtr != 0 {
|
|
defer p.free.Call(ctx, uint64(resPtr))
|
|
}
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
bytes, ok := p.module.Memory().Read(resPtr, resSize)
|
|
if !ok {
|
|
return nil, fmt.Errorf("Memory.Read(%d, %d) out of range of memory size %d",
|
|
resPtr, resSize, p.module.Memory().Size())
|
|
}
|
|
|
|
if isErrResponse {
|
|
return nil, errors.New(string(bytes))
|
|
}
|
|
|
|
response := new(ArtistImageResponse)
|
|
if err = response.UnmarshalVT(bytes); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
func (p *metadataAgentPlugin) GetArtistTopSongs(ctx context.Context, request *ArtistTopSongsRequest) (*ArtistTopSongsResponse, error) {
|
|
data, err := request.MarshalVT()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataSize := uint64(len(data))
|
|
|
|
var dataPtr uint64
|
|
// If the input data is not empty, we must allocate the in-Wasm memory to store it, and pass to the plugin.
|
|
if dataSize != 0 {
|
|
results, err := p.malloc.Call(ctx, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataPtr = results[0]
|
|
// This pointer is managed by the Wasm module, which is unaware of external usage.
|
|
// So, we have to free it when finished
|
|
defer p.free.Call(ctx, dataPtr)
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
if !p.module.Memory().Write(uint32(dataPtr), data) {
|
|
return nil, fmt.Errorf("Memory.Write(%d, %d) out of range of memory size %d", dataPtr, dataSize, p.module.Memory().Size())
|
|
}
|
|
}
|
|
|
|
ptrSize, err := p.getartisttopsongs.Call(ctx, dataPtr, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resPtr := uint32(ptrSize[0] >> 32)
|
|
resSize := uint32(ptrSize[0])
|
|
var isErrResponse bool
|
|
if (resSize & (1 << 31)) > 0 {
|
|
isErrResponse = true
|
|
resSize &^= (1 << 31)
|
|
}
|
|
|
|
// We don't need the memory after deserialization: make sure it is freed.
|
|
if resPtr != 0 {
|
|
defer p.free.Call(ctx, uint64(resPtr))
|
|
}
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
bytes, ok := p.module.Memory().Read(resPtr, resSize)
|
|
if !ok {
|
|
return nil, fmt.Errorf("Memory.Read(%d, %d) out of range of memory size %d",
|
|
resPtr, resSize, p.module.Memory().Size())
|
|
}
|
|
|
|
if isErrResponse {
|
|
return nil, errors.New(string(bytes))
|
|
}
|
|
|
|
response := new(ArtistTopSongsResponse)
|
|
if err = response.UnmarshalVT(bytes); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
func (p *metadataAgentPlugin) GetAlbumInfo(ctx context.Context, request *AlbumInfoRequest) (*AlbumInfoResponse, error) {
|
|
data, err := request.MarshalVT()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataSize := uint64(len(data))
|
|
|
|
var dataPtr uint64
|
|
// If the input data is not empty, we must allocate the in-Wasm memory to store it, and pass to the plugin.
|
|
if dataSize != 0 {
|
|
results, err := p.malloc.Call(ctx, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataPtr = results[0]
|
|
// This pointer is managed by the Wasm module, which is unaware of external usage.
|
|
// So, we have to free it when finished
|
|
defer p.free.Call(ctx, dataPtr)
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
if !p.module.Memory().Write(uint32(dataPtr), data) {
|
|
return nil, fmt.Errorf("Memory.Write(%d, %d) out of range of memory size %d", dataPtr, dataSize, p.module.Memory().Size())
|
|
}
|
|
}
|
|
|
|
ptrSize, err := p.getalbuminfo.Call(ctx, dataPtr, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resPtr := uint32(ptrSize[0] >> 32)
|
|
resSize := uint32(ptrSize[0])
|
|
var isErrResponse bool
|
|
if (resSize & (1 << 31)) > 0 {
|
|
isErrResponse = true
|
|
resSize &^= (1 << 31)
|
|
}
|
|
|
|
// We don't need the memory after deserialization: make sure it is freed.
|
|
if resPtr != 0 {
|
|
defer p.free.Call(ctx, uint64(resPtr))
|
|
}
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
bytes, ok := p.module.Memory().Read(resPtr, resSize)
|
|
if !ok {
|
|
return nil, fmt.Errorf("Memory.Read(%d, %d) out of range of memory size %d",
|
|
resPtr, resSize, p.module.Memory().Size())
|
|
}
|
|
|
|
if isErrResponse {
|
|
return nil, errors.New(string(bytes))
|
|
}
|
|
|
|
response := new(AlbumInfoResponse)
|
|
if err = response.UnmarshalVT(bytes); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
func (p *metadataAgentPlugin) GetAlbumImages(ctx context.Context, request *AlbumImagesRequest) (*AlbumImagesResponse, error) {
|
|
data, err := request.MarshalVT()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataSize := uint64(len(data))
|
|
|
|
var dataPtr uint64
|
|
// If the input data is not empty, we must allocate the in-Wasm memory to store it, and pass to the plugin.
|
|
if dataSize != 0 {
|
|
results, err := p.malloc.Call(ctx, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataPtr = results[0]
|
|
// This pointer is managed by the Wasm module, which is unaware of external usage.
|
|
// So, we have to free it when finished
|
|
defer p.free.Call(ctx, dataPtr)
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
if !p.module.Memory().Write(uint32(dataPtr), data) {
|
|
return nil, fmt.Errorf("Memory.Write(%d, %d) out of range of memory size %d", dataPtr, dataSize, p.module.Memory().Size())
|
|
}
|
|
}
|
|
|
|
ptrSize, err := p.getalbumimages.Call(ctx, dataPtr, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resPtr := uint32(ptrSize[0] >> 32)
|
|
resSize := uint32(ptrSize[0])
|
|
var isErrResponse bool
|
|
if (resSize & (1 << 31)) > 0 {
|
|
isErrResponse = true
|
|
resSize &^= (1 << 31)
|
|
}
|
|
|
|
// We don't need the memory after deserialization: make sure it is freed.
|
|
if resPtr != 0 {
|
|
defer p.free.Call(ctx, uint64(resPtr))
|
|
}
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
bytes, ok := p.module.Memory().Read(resPtr, resSize)
|
|
if !ok {
|
|
return nil, fmt.Errorf("Memory.Read(%d, %d) out of range of memory size %d",
|
|
resPtr, resSize, p.module.Memory().Size())
|
|
}
|
|
|
|
if isErrResponse {
|
|
return nil, errors.New(string(bytes))
|
|
}
|
|
|
|
response := new(AlbumImagesResponse)
|
|
if err = response.UnmarshalVT(bytes); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
|
|
const ScrobblerPluginAPIVersion = 1
|
|
|
|
type ScrobblerPlugin struct {
|
|
newRuntime func(context.Context) (wazero.Runtime, error)
|
|
moduleConfig wazero.ModuleConfig
|
|
}
|
|
|
|
func NewScrobblerPlugin(ctx context.Context, opts ...wazeroConfigOption) (*ScrobblerPlugin, error) {
|
|
o := &WazeroConfig{
|
|
newRuntime: DefaultWazeroRuntime(),
|
|
moduleConfig: wazero.NewModuleConfig().WithStartFunctions("_initialize"),
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
opt(o)
|
|
}
|
|
|
|
return &ScrobblerPlugin{
|
|
newRuntime: o.newRuntime,
|
|
moduleConfig: o.moduleConfig,
|
|
}, nil
|
|
}
|
|
|
|
type scrobbler interface {
|
|
Close(ctx context.Context) error
|
|
Scrobbler
|
|
}
|
|
|
|
func (p *ScrobblerPlugin) Load(ctx context.Context, pluginPath string) (scrobbler, error) {
|
|
b, err := os.ReadFile(pluginPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Create a new runtime so that multiple modules will not conflict
|
|
r, err := p.newRuntime(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Compile the WebAssembly module using the default configuration.
|
|
code, err := r.CompileModule(ctx, b)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// InstantiateModule runs the "_start" function, WASI's "main".
|
|
module, err := r.InstantiateModule(ctx, code, p.moduleConfig)
|
|
if err != nil {
|
|
// Note: Most compilers do not exit the module after running "_start",
|
|
// unless there was an Error. This allows you to call exported functions.
|
|
if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() != 0 {
|
|
return nil, fmt.Errorf("unexpected exit_code: %d", exitErr.ExitCode())
|
|
} else if !ok {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Compare API versions with the loading plugin
|
|
apiVersion := module.ExportedFunction("scrobbler_api_version")
|
|
if apiVersion == nil {
|
|
return nil, errors.New("scrobbler_api_version is not exported")
|
|
}
|
|
results, err := apiVersion.Call(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
} else if len(results) != 1 {
|
|
return nil, errors.New("invalid scrobbler_api_version signature")
|
|
}
|
|
if results[0] != ScrobblerPluginAPIVersion {
|
|
return nil, fmt.Errorf("API version mismatch, host: %d, plugin: %d", ScrobblerPluginAPIVersion, results[0])
|
|
}
|
|
|
|
isauthorized := module.ExportedFunction("scrobbler_is_authorized")
|
|
if isauthorized == nil {
|
|
return nil, errors.New("scrobbler_is_authorized is not exported")
|
|
}
|
|
nowplaying := module.ExportedFunction("scrobbler_now_playing")
|
|
if nowplaying == nil {
|
|
return nil, errors.New("scrobbler_now_playing is not exported")
|
|
}
|
|
scrobble := module.ExportedFunction("scrobbler_scrobble")
|
|
if scrobble == nil {
|
|
return nil, errors.New("scrobbler_scrobble is not exported")
|
|
}
|
|
|
|
malloc := module.ExportedFunction("malloc")
|
|
if malloc == nil {
|
|
return nil, errors.New("malloc is not exported")
|
|
}
|
|
|
|
free := module.ExportedFunction("free")
|
|
if free == nil {
|
|
return nil, errors.New("free is not exported")
|
|
}
|
|
return &scrobblerPlugin{
|
|
runtime: r,
|
|
module: module,
|
|
malloc: malloc,
|
|
free: free,
|
|
isauthorized: isauthorized,
|
|
nowplaying: nowplaying,
|
|
scrobble: scrobble,
|
|
}, nil
|
|
}
|
|
|
|
func (p *scrobblerPlugin) Close(ctx context.Context) (err error) {
|
|
if r := p.runtime; r != nil {
|
|
r.Close(ctx)
|
|
}
|
|
return
|
|
}
|
|
|
|
type scrobblerPlugin struct {
|
|
runtime wazero.Runtime
|
|
module api.Module
|
|
malloc api.Function
|
|
free api.Function
|
|
isauthorized api.Function
|
|
nowplaying api.Function
|
|
scrobble api.Function
|
|
}
|
|
|
|
func (p *scrobblerPlugin) IsAuthorized(ctx context.Context, request *ScrobblerIsAuthorizedRequest) (*ScrobblerIsAuthorizedResponse, error) {
|
|
data, err := request.MarshalVT()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataSize := uint64(len(data))
|
|
|
|
var dataPtr uint64
|
|
// If the input data is not empty, we must allocate the in-Wasm memory to store it, and pass to the plugin.
|
|
if dataSize != 0 {
|
|
results, err := p.malloc.Call(ctx, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataPtr = results[0]
|
|
// This pointer is managed by the Wasm module, which is unaware of external usage.
|
|
// So, we have to free it when finished
|
|
defer p.free.Call(ctx, dataPtr)
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
if !p.module.Memory().Write(uint32(dataPtr), data) {
|
|
return nil, fmt.Errorf("Memory.Write(%d, %d) out of range of memory size %d", dataPtr, dataSize, p.module.Memory().Size())
|
|
}
|
|
}
|
|
|
|
ptrSize, err := p.isauthorized.Call(ctx, dataPtr, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resPtr := uint32(ptrSize[0] >> 32)
|
|
resSize := uint32(ptrSize[0])
|
|
var isErrResponse bool
|
|
if (resSize & (1 << 31)) > 0 {
|
|
isErrResponse = true
|
|
resSize &^= (1 << 31)
|
|
}
|
|
|
|
// We don't need the memory after deserialization: make sure it is freed.
|
|
if resPtr != 0 {
|
|
defer p.free.Call(ctx, uint64(resPtr))
|
|
}
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
bytes, ok := p.module.Memory().Read(resPtr, resSize)
|
|
if !ok {
|
|
return nil, fmt.Errorf("Memory.Read(%d, %d) out of range of memory size %d",
|
|
resPtr, resSize, p.module.Memory().Size())
|
|
}
|
|
|
|
if isErrResponse {
|
|
return nil, errors.New(string(bytes))
|
|
}
|
|
|
|
response := new(ScrobblerIsAuthorizedResponse)
|
|
if err = response.UnmarshalVT(bytes); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
func (p *scrobblerPlugin) NowPlaying(ctx context.Context, request *ScrobblerNowPlayingRequest) (*ScrobblerNowPlayingResponse, error) {
|
|
data, err := request.MarshalVT()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataSize := uint64(len(data))
|
|
|
|
var dataPtr uint64
|
|
// If the input data is not empty, we must allocate the in-Wasm memory to store it, and pass to the plugin.
|
|
if dataSize != 0 {
|
|
results, err := p.malloc.Call(ctx, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataPtr = results[0]
|
|
// This pointer is managed by the Wasm module, which is unaware of external usage.
|
|
// So, we have to free it when finished
|
|
defer p.free.Call(ctx, dataPtr)
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
if !p.module.Memory().Write(uint32(dataPtr), data) {
|
|
return nil, fmt.Errorf("Memory.Write(%d, %d) out of range of memory size %d", dataPtr, dataSize, p.module.Memory().Size())
|
|
}
|
|
}
|
|
|
|
ptrSize, err := p.nowplaying.Call(ctx, dataPtr, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resPtr := uint32(ptrSize[0] >> 32)
|
|
resSize := uint32(ptrSize[0])
|
|
var isErrResponse bool
|
|
if (resSize & (1 << 31)) > 0 {
|
|
isErrResponse = true
|
|
resSize &^= (1 << 31)
|
|
}
|
|
|
|
// We don't need the memory after deserialization: make sure it is freed.
|
|
if resPtr != 0 {
|
|
defer p.free.Call(ctx, uint64(resPtr))
|
|
}
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
bytes, ok := p.module.Memory().Read(resPtr, resSize)
|
|
if !ok {
|
|
return nil, fmt.Errorf("Memory.Read(%d, %d) out of range of memory size %d",
|
|
resPtr, resSize, p.module.Memory().Size())
|
|
}
|
|
|
|
if isErrResponse {
|
|
return nil, errors.New(string(bytes))
|
|
}
|
|
|
|
response := new(ScrobblerNowPlayingResponse)
|
|
if err = response.UnmarshalVT(bytes); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
func (p *scrobblerPlugin) Scrobble(ctx context.Context, request *ScrobblerScrobbleRequest) (*ScrobblerScrobbleResponse, error) {
|
|
data, err := request.MarshalVT()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataSize := uint64(len(data))
|
|
|
|
var dataPtr uint64
|
|
// If the input data is not empty, we must allocate the in-Wasm memory to store it, and pass to the plugin.
|
|
if dataSize != 0 {
|
|
results, err := p.malloc.Call(ctx, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataPtr = results[0]
|
|
// This pointer is managed by the Wasm module, which is unaware of external usage.
|
|
// So, we have to free it when finished
|
|
defer p.free.Call(ctx, dataPtr)
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
if !p.module.Memory().Write(uint32(dataPtr), data) {
|
|
return nil, fmt.Errorf("Memory.Write(%d, %d) out of range of memory size %d", dataPtr, dataSize, p.module.Memory().Size())
|
|
}
|
|
}
|
|
|
|
ptrSize, err := p.scrobble.Call(ctx, dataPtr, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resPtr := uint32(ptrSize[0] >> 32)
|
|
resSize := uint32(ptrSize[0])
|
|
var isErrResponse bool
|
|
if (resSize & (1 << 31)) > 0 {
|
|
isErrResponse = true
|
|
resSize &^= (1 << 31)
|
|
}
|
|
|
|
// We don't need the memory after deserialization: make sure it is freed.
|
|
if resPtr != 0 {
|
|
defer p.free.Call(ctx, uint64(resPtr))
|
|
}
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
bytes, ok := p.module.Memory().Read(resPtr, resSize)
|
|
if !ok {
|
|
return nil, fmt.Errorf("Memory.Read(%d, %d) out of range of memory size %d",
|
|
resPtr, resSize, p.module.Memory().Size())
|
|
}
|
|
|
|
if isErrResponse {
|
|
return nil, errors.New(string(bytes))
|
|
}
|
|
|
|
response := new(ScrobblerScrobbleResponse)
|
|
if err = response.UnmarshalVT(bytes); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
|
|
const SchedulerCallbackPluginAPIVersion = 1
|
|
|
|
type SchedulerCallbackPlugin struct {
|
|
newRuntime func(context.Context) (wazero.Runtime, error)
|
|
moduleConfig wazero.ModuleConfig
|
|
}
|
|
|
|
func NewSchedulerCallbackPlugin(ctx context.Context, opts ...wazeroConfigOption) (*SchedulerCallbackPlugin, error) {
|
|
o := &WazeroConfig{
|
|
newRuntime: DefaultWazeroRuntime(),
|
|
moduleConfig: wazero.NewModuleConfig().WithStartFunctions("_initialize"),
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
opt(o)
|
|
}
|
|
|
|
return &SchedulerCallbackPlugin{
|
|
newRuntime: o.newRuntime,
|
|
moduleConfig: o.moduleConfig,
|
|
}, nil
|
|
}
|
|
|
|
type schedulerCallback interface {
|
|
Close(ctx context.Context) error
|
|
SchedulerCallback
|
|
}
|
|
|
|
func (p *SchedulerCallbackPlugin) Load(ctx context.Context, pluginPath string) (schedulerCallback, error) {
|
|
b, err := os.ReadFile(pluginPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Create a new runtime so that multiple modules will not conflict
|
|
r, err := p.newRuntime(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Compile the WebAssembly module using the default configuration.
|
|
code, err := r.CompileModule(ctx, b)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// InstantiateModule runs the "_start" function, WASI's "main".
|
|
module, err := r.InstantiateModule(ctx, code, p.moduleConfig)
|
|
if err != nil {
|
|
// Note: Most compilers do not exit the module after running "_start",
|
|
// unless there was an Error. This allows you to call exported functions.
|
|
if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() != 0 {
|
|
return nil, fmt.Errorf("unexpected exit_code: %d", exitErr.ExitCode())
|
|
} else if !ok {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Compare API versions with the loading plugin
|
|
apiVersion := module.ExportedFunction("scheduler_callback_api_version")
|
|
if apiVersion == nil {
|
|
return nil, errors.New("scheduler_callback_api_version is not exported")
|
|
}
|
|
results, err := apiVersion.Call(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
} else if len(results) != 1 {
|
|
return nil, errors.New("invalid scheduler_callback_api_version signature")
|
|
}
|
|
if results[0] != SchedulerCallbackPluginAPIVersion {
|
|
return nil, fmt.Errorf("API version mismatch, host: %d, plugin: %d", SchedulerCallbackPluginAPIVersion, results[0])
|
|
}
|
|
|
|
onschedulercallback := module.ExportedFunction("scheduler_callback_on_scheduler_callback")
|
|
if onschedulercallback == nil {
|
|
return nil, errors.New("scheduler_callback_on_scheduler_callback is not exported")
|
|
}
|
|
|
|
malloc := module.ExportedFunction("malloc")
|
|
if malloc == nil {
|
|
return nil, errors.New("malloc is not exported")
|
|
}
|
|
|
|
free := module.ExportedFunction("free")
|
|
if free == nil {
|
|
return nil, errors.New("free is not exported")
|
|
}
|
|
return &schedulerCallbackPlugin{
|
|
runtime: r,
|
|
module: module,
|
|
malloc: malloc,
|
|
free: free,
|
|
onschedulercallback: onschedulercallback,
|
|
}, nil
|
|
}
|
|
|
|
func (p *schedulerCallbackPlugin) Close(ctx context.Context) (err error) {
|
|
if r := p.runtime; r != nil {
|
|
r.Close(ctx)
|
|
}
|
|
return
|
|
}
|
|
|
|
type schedulerCallbackPlugin struct {
|
|
runtime wazero.Runtime
|
|
module api.Module
|
|
malloc api.Function
|
|
free api.Function
|
|
onschedulercallback api.Function
|
|
}
|
|
|
|
func (p *schedulerCallbackPlugin) OnSchedulerCallback(ctx context.Context, request *SchedulerCallbackRequest) (*SchedulerCallbackResponse, error) {
|
|
data, err := request.MarshalVT()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataSize := uint64(len(data))
|
|
|
|
var dataPtr uint64
|
|
// If the input data is not empty, we must allocate the in-Wasm memory to store it, and pass to the plugin.
|
|
if dataSize != 0 {
|
|
results, err := p.malloc.Call(ctx, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataPtr = results[0]
|
|
// This pointer is managed by the Wasm module, which is unaware of external usage.
|
|
// So, we have to free it when finished
|
|
defer p.free.Call(ctx, dataPtr)
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
if !p.module.Memory().Write(uint32(dataPtr), data) {
|
|
return nil, fmt.Errorf("Memory.Write(%d, %d) out of range of memory size %d", dataPtr, dataSize, p.module.Memory().Size())
|
|
}
|
|
}
|
|
|
|
ptrSize, err := p.onschedulercallback.Call(ctx, dataPtr, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resPtr := uint32(ptrSize[0] >> 32)
|
|
resSize := uint32(ptrSize[0])
|
|
var isErrResponse bool
|
|
if (resSize & (1 << 31)) > 0 {
|
|
isErrResponse = true
|
|
resSize &^= (1 << 31)
|
|
}
|
|
|
|
// We don't need the memory after deserialization: make sure it is freed.
|
|
if resPtr != 0 {
|
|
defer p.free.Call(ctx, uint64(resPtr))
|
|
}
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
bytes, ok := p.module.Memory().Read(resPtr, resSize)
|
|
if !ok {
|
|
return nil, fmt.Errorf("Memory.Read(%d, %d) out of range of memory size %d",
|
|
resPtr, resSize, p.module.Memory().Size())
|
|
}
|
|
|
|
if isErrResponse {
|
|
return nil, errors.New(string(bytes))
|
|
}
|
|
|
|
response := new(SchedulerCallbackResponse)
|
|
if err = response.UnmarshalVT(bytes); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
|
|
const LifecycleManagementPluginAPIVersion = 1
|
|
|
|
type LifecycleManagementPlugin struct {
|
|
newRuntime func(context.Context) (wazero.Runtime, error)
|
|
moduleConfig wazero.ModuleConfig
|
|
}
|
|
|
|
func NewLifecycleManagementPlugin(ctx context.Context, opts ...wazeroConfigOption) (*LifecycleManagementPlugin, error) {
|
|
o := &WazeroConfig{
|
|
newRuntime: DefaultWazeroRuntime(),
|
|
moduleConfig: wazero.NewModuleConfig().WithStartFunctions("_initialize"),
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
opt(o)
|
|
}
|
|
|
|
return &LifecycleManagementPlugin{
|
|
newRuntime: o.newRuntime,
|
|
moduleConfig: o.moduleConfig,
|
|
}, nil
|
|
}
|
|
|
|
type lifecycleManagement interface {
|
|
Close(ctx context.Context) error
|
|
LifecycleManagement
|
|
}
|
|
|
|
func (p *LifecycleManagementPlugin) Load(ctx context.Context, pluginPath string) (lifecycleManagement, error) {
|
|
b, err := os.ReadFile(pluginPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Create a new runtime so that multiple modules will not conflict
|
|
r, err := p.newRuntime(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Compile the WebAssembly module using the default configuration.
|
|
code, err := r.CompileModule(ctx, b)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// InstantiateModule runs the "_start" function, WASI's "main".
|
|
module, err := r.InstantiateModule(ctx, code, p.moduleConfig)
|
|
if err != nil {
|
|
// Note: Most compilers do not exit the module after running "_start",
|
|
// unless there was an Error. This allows you to call exported functions.
|
|
if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() != 0 {
|
|
return nil, fmt.Errorf("unexpected exit_code: %d", exitErr.ExitCode())
|
|
} else if !ok {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Compare API versions with the loading plugin
|
|
apiVersion := module.ExportedFunction("lifecycle_management_api_version")
|
|
if apiVersion == nil {
|
|
return nil, errors.New("lifecycle_management_api_version is not exported")
|
|
}
|
|
results, err := apiVersion.Call(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
} else if len(results) != 1 {
|
|
return nil, errors.New("invalid lifecycle_management_api_version signature")
|
|
}
|
|
if results[0] != LifecycleManagementPluginAPIVersion {
|
|
return nil, fmt.Errorf("API version mismatch, host: %d, plugin: %d", LifecycleManagementPluginAPIVersion, results[0])
|
|
}
|
|
|
|
oninit := module.ExportedFunction("lifecycle_management_on_init")
|
|
if oninit == nil {
|
|
return nil, errors.New("lifecycle_management_on_init is not exported")
|
|
}
|
|
|
|
malloc := module.ExportedFunction("malloc")
|
|
if malloc == nil {
|
|
return nil, errors.New("malloc is not exported")
|
|
}
|
|
|
|
free := module.ExportedFunction("free")
|
|
if free == nil {
|
|
return nil, errors.New("free is not exported")
|
|
}
|
|
return &lifecycleManagementPlugin{
|
|
runtime: r,
|
|
module: module,
|
|
malloc: malloc,
|
|
free: free,
|
|
oninit: oninit,
|
|
}, nil
|
|
}
|
|
|
|
func (p *lifecycleManagementPlugin) Close(ctx context.Context) (err error) {
|
|
if r := p.runtime; r != nil {
|
|
r.Close(ctx)
|
|
}
|
|
return
|
|
}
|
|
|
|
type lifecycleManagementPlugin struct {
|
|
runtime wazero.Runtime
|
|
module api.Module
|
|
malloc api.Function
|
|
free api.Function
|
|
oninit api.Function
|
|
}
|
|
|
|
func (p *lifecycleManagementPlugin) OnInit(ctx context.Context, request *InitRequest) (*InitResponse, error) {
|
|
data, err := request.MarshalVT()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataSize := uint64(len(data))
|
|
|
|
var dataPtr uint64
|
|
// If the input data is not empty, we must allocate the in-Wasm memory to store it, and pass to the plugin.
|
|
if dataSize != 0 {
|
|
results, err := p.malloc.Call(ctx, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataPtr = results[0]
|
|
// This pointer is managed by the Wasm module, which is unaware of external usage.
|
|
// So, we have to free it when finished
|
|
defer p.free.Call(ctx, dataPtr)
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
if !p.module.Memory().Write(uint32(dataPtr), data) {
|
|
return nil, fmt.Errorf("Memory.Write(%d, %d) out of range of memory size %d", dataPtr, dataSize, p.module.Memory().Size())
|
|
}
|
|
}
|
|
|
|
ptrSize, err := p.oninit.Call(ctx, dataPtr, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resPtr := uint32(ptrSize[0] >> 32)
|
|
resSize := uint32(ptrSize[0])
|
|
var isErrResponse bool
|
|
if (resSize & (1 << 31)) > 0 {
|
|
isErrResponse = true
|
|
resSize &^= (1 << 31)
|
|
}
|
|
|
|
// We don't need the memory after deserialization: make sure it is freed.
|
|
if resPtr != 0 {
|
|
defer p.free.Call(ctx, uint64(resPtr))
|
|
}
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
bytes, ok := p.module.Memory().Read(resPtr, resSize)
|
|
if !ok {
|
|
return nil, fmt.Errorf("Memory.Read(%d, %d) out of range of memory size %d",
|
|
resPtr, resSize, p.module.Memory().Size())
|
|
}
|
|
|
|
if isErrResponse {
|
|
return nil, errors.New(string(bytes))
|
|
}
|
|
|
|
response := new(InitResponse)
|
|
if err = response.UnmarshalVT(bytes); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
|
|
const WebSocketCallbackPluginAPIVersion = 1
|
|
|
|
type WebSocketCallbackPlugin struct {
|
|
newRuntime func(context.Context) (wazero.Runtime, error)
|
|
moduleConfig wazero.ModuleConfig
|
|
}
|
|
|
|
func NewWebSocketCallbackPlugin(ctx context.Context, opts ...wazeroConfigOption) (*WebSocketCallbackPlugin, error) {
|
|
o := &WazeroConfig{
|
|
newRuntime: DefaultWazeroRuntime(),
|
|
moduleConfig: wazero.NewModuleConfig().WithStartFunctions("_initialize"),
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
opt(o)
|
|
}
|
|
|
|
return &WebSocketCallbackPlugin{
|
|
newRuntime: o.newRuntime,
|
|
moduleConfig: o.moduleConfig,
|
|
}, nil
|
|
}
|
|
|
|
type webSocketCallback interface {
|
|
Close(ctx context.Context) error
|
|
WebSocketCallback
|
|
}
|
|
|
|
func (p *WebSocketCallbackPlugin) Load(ctx context.Context, pluginPath string) (webSocketCallback, error) {
|
|
b, err := os.ReadFile(pluginPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Create a new runtime so that multiple modules will not conflict
|
|
r, err := p.newRuntime(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Compile the WebAssembly module using the default configuration.
|
|
code, err := r.CompileModule(ctx, b)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// InstantiateModule runs the "_start" function, WASI's "main".
|
|
module, err := r.InstantiateModule(ctx, code, p.moduleConfig)
|
|
if err != nil {
|
|
// Note: Most compilers do not exit the module after running "_start",
|
|
// unless there was an Error. This allows you to call exported functions.
|
|
if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() != 0 {
|
|
return nil, fmt.Errorf("unexpected exit_code: %d", exitErr.ExitCode())
|
|
} else if !ok {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Compare API versions with the loading plugin
|
|
apiVersion := module.ExportedFunction("web_socket_callback_api_version")
|
|
if apiVersion == nil {
|
|
return nil, errors.New("web_socket_callback_api_version is not exported")
|
|
}
|
|
results, err := apiVersion.Call(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
} else if len(results) != 1 {
|
|
return nil, errors.New("invalid web_socket_callback_api_version signature")
|
|
}
|
|
if results[0] != WebSocketCallbackPluginAPIVersion {
|
|
return nil, fmt.Errorf("API version mismatch, host: %d, plugin: %d", WebSocketCallbackPluginAPIVersion, results[0])
|
|
}
|
|
|
|
ontextmessage := module.ExportedFunction("web_socket_callback_on_text_message")
|
|
if ontextmessage == nil {
|
|
return nil, errors.New("web_socket_callback_on_text_message is not exported")
|
|
}
|
|
onbinarymessage := module.ExportedFunction("web_socket_callback_on_binary_message")
|
|
if onbinarymessage == nil {
|
|
return nil, errors.New("web_socket_callback_on_binary_message is not exported")
|
|
}
|
|
onerror := module.ExportedFunction("web_socket_callback_on_error")
|
|
if onerror == nil {
|
|
return nil, errors.New("web_socket_callback_on_error is not exported")
|
|
}
|
|
onclose := module.ExportedFunction("web_socket_callback_on_close")
|
|
if onclose == nil {
|
|
return nil, errors.New("web_socket_callback_on_close is not exported")
|
|
}
|
|
|
|
malloc := module.ExportedFunction("malloc")
|
|
if malloc == nil {
|
|
return nil, errors.New("malloc is not exported")
|
|
}
|
|
|
|
free := module.ExportedFunction("free")
|
|
if free == nil {
|
|
return nil, errors.New("free is not exported")
|
|
}
|
|
return &webSocketCallbackPlugin{
|
|
runtime: r,
|
|
module: module,
|
|
malloc: malloc,
|
|
free: free,
|
|
ontextmessage: ontextmessage,
|
|
onbinarymessage: onbinarymessage,
|
|
onerror: onerror,
|
|
onclose: onclose,
|
|
}, nil
|
|
}
|
|
|
|
func (p *webSocketCallbackPlugin) Close(ctx context.Context) (err error) {
|
|
if r := p.runtime; r != nil {
|
|
r.Close(ctx)
|
|
}
|
|
return
|
|
}
|
|
|
|
type webSocketCallbackPlugin struct {
|
|
runtime wazero.Runtime
|
|
module api.Module
|
|
malloc api.Function
|
|
free api.Function
|
|
ontextmessage api.Function
|
|
onbinarymessage api.Function
|
|
onerror api.Function
|
|
onclose api.Function
|
|
}
|
|
|
|
func (p *webSocketCallbackPlugin) OnTextMessage(ctx context.Context, request *OnTextMessageRequest) (*OnTextMessageResponse, error) {
|
|
data, err := request.MarshalVT()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataSize := uint64(len(data))
|
|
|
|
var dataPtr uint64
|
|
// If the input data is not empty, we must allocate the in-Wasm memory to store it, and pass to the plugin.
|
|
if dataSize != 0 {
|
|
results, err := p.malloc.Call(ctx, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataPtr = results[0]
|
|
// This pointer is managed by the Wasm module, which is unaware of external usage.
|
|
// So, we have to free it when finished
|
|
defer p.free.Call(ctx, dataPtr)
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
if !p.module.Memory().Write(uint32(dataPtr), data) {
|
|
return nil, fmt.Errorf("Memory.Write(%d, %d) out of range of memory size %d", dataPtr, dataSize, p.module.Memory().Size())
|
|
}
|
|
}
|
|
|
|
ptrSize, err := p.ontextmessage.Call(ctx, dataPtr, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resPtr := uint32(ptrSize[0] >> 32)
|
|
resSize := uint32(ptrSize[0])
|
|
var isErrResponse bool
|
|
if (resSize & (1 << 31)) > 0 {
|
|
isErrResponse = true
|
|
resSize &^= (1 << 31)
|
|
}
|
|
|
|
// We don't need the memory after deserialization: make sure it is freed.
|
|
if resPtr != 0 {
|
|
defer p.free.Call(ctx, uint64(resPtr))
|
|
}
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
bytes, ok := p.module.Memory().Read(resPtr, resSize)
|
|
if !ok {
|
|
return nil, fmt.Errorf("Memory.Read(%d, %d) out of range of memory size %d",
|
|
resPtr, resSize, p.module.Memory().Size())
|
|
}
|
|
|
|
if isErrResponse {
|
|
return nil, errors.New(string(bytes))
|
|
}
|
|
|
|
response := new(OnTextMessageResponse)
|
|
if err = response.UnmarshalVT(bytes); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
func (p *webSocketCallbackPlugin) OnBinaryMessage(ctx context.Context, request *OnBinaryMessageRequest) (*OnBinaryMessageResponse, error) {
|
|
data, err := request.MarshalVT()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataSize := uint64(len(data))
|
|
|
|
var dataPtr uint64
|
|
// If the input data is not empty, we must allocate the in-Wasm memory to store it, and pass to the plugin.
|
|
if dataSize != 0 {
|
|
results, err := p.malloc.Call(ctx, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataPtr = results[0]
|
|
// This pointer is managed by the Wasm module, which is unaware of external usage.
|
|
// So, we have to free it when finished
|
|
defer p.free.Call(ctx, dataPtr)
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
if !p.module.Memory().Write(uint32(dataPtr), data) {
|
|
return nil, fmt.Errorf("Memory.Write(%d, %d) out of range of memory size %d", dataPtr, dataSize, p.module.Memory().Size())
|
|
}
|
|
}
|
|
|
|
ptrSize, err := p.onbinarymessage.Call(ctx, dataPtr, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resPtr := uint32(ptrSize[0] >> 32)
|
|
resSize := uint32(ptrSize[0])
|
|
var isErrResponse bool
|
|
if (resSize & (1 << 31)) > 0 {
|
|
isErrResponse = true
|
|
resSize &^= (1 << 31)
|
|
}
|
|
|
|
// We don't need the memory after deserialization: make sure it is freed.
|
|
if resPtr != 0 {
|
|
defer p.free.Call(ctx, uint64(resPtr))
|
|
}
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
bytes, ok := p.module.Memory().Read(resPtr, resSize)
|
|
if !ok {
|
|
return nil, fmt.Errorf("Memory.Read(%d, %d) out of range of memory size %d",
|
|
resPtr, resSize, p.module.Memory().Size())
|
|
}
|
|
|
|
if isErrResponse {
|
|
return nil, errors.New(string(bytes))
|
|
}
|
|
|
|
response := new(OnBinaryMessageResponse)
|
|
if err = response.UnmarshalVT(bytes); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
func (p *webSocketCallbackPlugin) OnError(ctx context.Context, request *OnErrorRequest) (*OnErrorResponse, error) {
|
|
data, err := request.MarshalVT()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataSize := uint64(len(data))
|
|
|
|
var dataPtr uint64
|
|
// If the input data is not empty, we must allocate the in-Wasm memory to store it, and pass to the plugin.
|
|
if dataSize != 0 {
|
|
results, err := p.malloc.Call(ctx, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataPtr = results[0]
|
|
// This pointer is managed by the Wasm module, which is unaware of external usage.
|
|
// So, we have to free it when finished
|
|
defer p.free.Call(ctx, dataPtr)
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
if !p.module.Memory().Write(uint32(dataPtr), data) {
|
|
return nil, fmt.Errorf("Memory.Write(%d, %d) out of range of memory size %d", dataPtr, dataSize, p.module.Memory().Size())
|
|
}
|
|
}
|
|
|
|
ptrSize, err := p.onerror.Call(ctx, dataPtr, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resPtr := uint32(ptrSize[0] >> 32)
|
|
resSize := uint32(ptrSize[0])
|
|
var isErrResponse bool
|
|
if (resSize & (1 << 31)) > 0 {
|
|
isErrResponse = true
|
|
resSize &^= (1 << 31)
|
|
}
|
|
|
|
// We don't need the memory after deserialization: make sure it is freed.
|
|
if resPtr != 0 {
|
|
defer p.free.Call(ctx, uint64(resPtr))
|
|
}
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
bytes, ok := p.module.Memory().Read(resPtr, resSize)
|
|
if !ok {
|
|
return nil, fmt.Errorf("Memory.Read(%d, %d) out of range of memory size %d",
|
|
resPtr, resSize, p.module.Memory().Size())
|
|
}
|
|
|
|
if isErrResponse {
|
|
return nil, errors.New(string(bytes))
|
|
}
|
|
|
|
response := new(OnErrorResponse)
|
|
if err = response.UnmarshalVT(bytes); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
func (p *webSocketCallbackPlugin) OnClose(ctx context.Context, request *OnCloseRequest) (*OnCloseResponse, error) {
|
|
data, err := request.MarshalVT()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataSize := uint64(len(data))
|
|
|
|
var dataPtr uint64
|
|
// If the input data is not empty, we must allocate the in-Wasm memory to store it, and pass to the plugin.
|
|
if dataSize != 0 {
|
|
results, err := p.malloc.Call(ctx, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dataPtr = results[0]
|
|
// This pointer is managed by the Wasm module, which is unaware of external usage.
|
|
// So, we have to free it when finished
|
|
defer p.free.Call(ctx, dataPtr)
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
if !p.module.Memory().Write(uint32(dataPtr), data) {
|
|
return nil, fmt.Errorf("Memory.Write(%d, %d) out of range of memory size %d", dataPtr, dataSize, p.module.Memory().Size())
|
|
}
|
|
}
|
|
|
|
ptrSize, err := p.onclose.Call(ctx, dataPtr, dataSize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resPtr := uint32(ptrSize[0] >> 32)
|
|
resSize := uint32(ptrSize[0])
|
|
var isErrResponse bool
|
|
if (resSize & (1 << 31)) > 0 {
|
|
isErrResponse = true
|
|
resSize &^= (1 << 31)
|
|
}
|
|
|
|
// We don't need the memory after deserialization: make sure it is freed.
|
|
if resPtr != 0 {
|
|
defer p.free.Call(ctx, uint64(resPtr))
|
|
}
|
|
|
|
// The pointer is a linear memory offset, which is where we write the name.
|
|
bytes, ok := p.module.Memory().Read(resPtr, resSize)
|
|
if !ok {
|
|
return nil, fmt.Errorf("Memory.Read(%d, %d) out of range of memory size %d",
|
|
resPtr, resSize, p.module.Memory().Size())
|
|
}
|
|
|
|
if isErrResponse {
|
|
return nil, errors.New(string(bytes))
|
|
}
|
|
|
|
response := new(OnCloseResponse)
|
|
if err = response.UnmarshalVT(bytes); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return response, nil
|
|
}
|