//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 }