-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
-
48
-
49
-
50
-
51
-
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
-
60
-
61
-
62
-
63
-
64
-
65
-
66
-
67
-
68
-
69
-
70
-
71
-
72
-
73
-
74
-
75
-
76
-
77
-
78
-
79
-
80
-
81
-
82
-
83
-
84
-
85
-
86
-
87
-
88
-
89
-
90
-
91
-
92
-
93
-
94
-
95
-
96
-
97
-
98
-
99
-
100
-
101
-
102
-
103
-
104
-
105
-
106
-
107
-
108
-
109
-
110
-
111
-
112
-
113
-
114
-
115
-
116
-
117
-
118
-
119
-
120
-
121
-
122
-
123
-
124
-
125
-
126
-
127
-
128
-
129
-
130
-
131
-
132
-
133
-
134
-
135
-
136
-
137
-
138
-
139
-
140
-
141
-
142
-
143
-
144
-
145
-
146
-
147
-
148
-
149
-
150
-
151
-
152
-
153
-
154
-
155
-
156
-
157
-
158
// SPDX-FileCopyrightText: 2025 Shota FUJI <pockawoooh@gmail.com>
// SPDX-License-Identifier: AGPL-3.0-only
package src
import (
"bytes"
"context"
"fmt"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
)
// Core は WASM で実装されており、特にメモリアクセスでかなり煩雑になる。
// この構造体はそれらの操作を抽象化する。
// 内部実装は基本的に JavaScript 実装 (worker.ts) のポートとなっている。
type Core struct {
wasmRuntime wazero.Runtime
mod api.Module
corePtr uint64
}
func Init(ctx context.Context, wasm []byte) (*Core, error) {
runtime := wazero.NewRuntime(ctx)
mod, err := runtime.Instantiate(ctx, wasm)
if err != nil {
return nil, err
}
initCore := mod.ExportedFunction("init_core")
initCoreResults, err := initCore.Call(ctx)
if err != nil {
return nil, err
}
return &Core{
wasmRuntime: runtime,
mod: mod,
corePtr: initCoreResults[0],
}, nil
}
// allocate は WASM の "allocate_bytes" を呼び、確保されたアドレスを返す。
func (core Core) allocate(ctx context.Context, length uint32) (uint32, error) {
fn := core.mod.ExportedFunction("allocate_bytes")
results, err := fn.Call(ctx, uint64(length))
if err != nil {
return 0, err
}
return uint32(results[0]), nil
}
// copy は受け取ったバイト列を WASM のメモリ上にコピーして WASM から読めるようにする。
// 確保したアドレスとバイト長を返す。
func (core Core) copy(ctx context.Context, bytes []byte) (uint32, uint32, error) {
length := uint32(len(bytes))
ptr, err := core.allocate(ctx, length)
if err != nil {
return 0, 0, err
}
if length == 0 {
return ptr, length, nil
}
if !core.mod.Memory().Write(ptr, bytes) {
return 0, 0, fmt.Errorf("Failed to write to memory (%d bytes)", length)
}
return ptr, length, nil
}
// Handle は RPC のリクエストを受けてレスポンスを返す関数。
// サービス名もメソッド名も変更しないため、レスポンスのバイナリデータのみを返す。
func (core Core) Handle(ctx context.Context, service string, method string, data []byte) ([]byte, error) {
freeBytes := core.mod.ExportedFunction("free_bytes")
servicePtr, serviceLen, err := core.copy(ctx, []byte(service))
if err != nil {
return nil, err
}
defer freeBytes.Call(ctx, uint64(servicePtr), uint64(serviceLen))
methodPtr, methodLen, err := core.copy(ctx, []byte(method))
if err != nil {
return nil, err
}
defer freeBytes.Call(ctx, uint64(methodPtr), uint64(methodLen))
dataPtr, dataLen, err := core.copy(ctx, data)
if err != nil {
return nil, err
}
defer freeBytes.Call(ctx, uint64(dataPtr), uint64(dataLen))
destroyResponse := core.mod.ExportedFunction("destroy_response")
createResponse := core.mod.ExportedFunction("create_response")
createResponseResult, err := createResponse.Call(ctx)
if err != nil {
return nil, err
}
defer destroyResponse.Call(ctx, createResponseResult[0])
handleFn := core.mod.ExportedFunction("handle")
handleResults, err := handleFn.Call(
ctx, core.corePtr, uint64(servicePtr), uint64(serviceLen),
uint64(methodPtr), uint64(methodLen), uint64(dataPtr), uint64(dataLen),
createResponseResult[0],
)
if err != nil {
return nil, err
}
if handleResults[0] != 0 {
return nil, fmt.Errorf("handler returned non-zero result: %d", handleResults[0])
}
getResponsePtr := core.mod.ExportedFunction("get_response_ptr")
getResponseLen := core.mod.ExportedFunction("get_response_len")
responsePtr, err := getResponsePtr.Call(ctx, createResponseResult[0])
if err != nil {
return nil, err
}
responseLen, err := getResponseLen.Call(ctx, createResponseResult[0])
if err != nil {
return nil, err
}
if responseLen[0] == 0 {
return []byte{}, nil
}
responseData, isRangeOK := core.mod.Memory().Read(uint32(responsePtr[0]), uint32(responseLen[0]))
if !isRangeOK {
return nil, fmt.Errorf("get_response_ptr/len returned out-of-range address or length")
}
// destroy_response によってメモリが解放されるため、返す前にクローンする
// 必要がある。しないと read after free になってしまう。
return bytes.Clone(responseData), nil
}
// Close を呼ぶと以降は Core を使った操作はできなくなる。
// WASM ランタイムのリソースが解放される。
func (core *Core) Close(ctx context.Context) {
core.mod.Close(ctx)
core.wasmRuntime.Close(ctx)
core = nil
}