wa-lang.org/wazero@v1.0.2/imports/proxywasm/_proxytest/proxytest.go (about) 1 // Copyright 2021 Tetrate 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package proxytest 16 17 import ( 18 "fmt" 19 "log" 20 "strings" 21 22 "wa-lang.org/wazero/imports/proxywasm" 23 "wa-lang.org/wazero/imports/proxywasm/internal" 24 "wa-lang.org/wazero/imports/proxywasm/types" 25 ) 26 27 // HostEmulator implements the host side of proxy-wasm. Methods on HostEmulator will either invoke 28 // methods in the plugin or return state about the host itself, reflecting any changes from previous 29 // plugin invocations. 30 type HostEmulator interface { 31 // StartVM executes types.VMContext.OnVMStart in the plugin. 32 StartVM() types.OnVMStartStatus 33 // StartPlugin executes types.PluginContext.OnPluginStart in the plugin. 34 StartPlugin() types.OnPluginStartStatus 35 // FinishVM executes types.PluginContext.OnPluginDone in the plugin. 36 FinishVM() bool 37 // GetCalloutAttributesFromContext returns the current HTTP callout attributes for the given HTTP context in the 38 // host. 39 GetCalloutAttributesFromContext(contextID uint32) []HttpCalloutAttribute 40 // CallOnHttpCallResponse executes the callback for the HTTP call with ID calloutID in the plugin. 41 CallOnHttpCallResponse(calloutID uint32, headers [][2]string, trailers [][2]string, body []byte) 42 // GetCounterMetric returns the value for the counter in the host. 43 GetCounterMetric(name string) (uint64, error) 44 // GetGaugeMetric returns the value for the gauge in the host. 45 GetGaugeMetric(name string) (uint64, error) 46 // GetHistogramMetric returns the value for the histogram in the host. 47 GetHistogramMetric(name string) (uint64, error) 48 // GetTraceLogs returns the trace logs that have been collected in the host. 49 GetTraceLogs() []string 50 // GetDebugLogs returns the debug logs that have been collected in the host. 51 GetDebugLogs() []string 52 // GetInfoLogs returns the info logs that have been collected in the host. 53 GetInfoLogs() []string 54 // GetWarnLogs returns the warn logs that have been collected in the host. 55 GetWarnLogs() []string 56 // GetErrorLogs returns the error logs that have been collected in the host. 57 GetErrorLogs() []string 58 // GetCriticalLogs returns the critical logs that have been collected in the host. 59 GetCriticalLogs() []string 60 // GetTickPeriod returns the current tick period in the host. 61 GetTickPeriod() uint32 62 // Tick executes types.PluginContext.OnTick in the plugin. 63 Tick() 64 // GetQueueSize gets the current size of the queue in the host. 65 GetQueueSize(queueID uint32) int 66 // RegisterForeignFunction registers the foreign function in the host. 67 RegisterForeignFunction(name string, f func([]byte) []byte) 68 69 // InitializeConnection executes types.TcpContext.OnNewConnection in the plugin. 70 InitializeConnection() (contextID uint32, action types.Action) 71 // CallOnUpstreamData executes types.TcpContext.OnUpstreamData in the plugin. 72 CallOnUpstreamData(contextID uint32, data []byte) types.Action 73 // CallOnDownstreamData executes types.TcpContext.OnDownstreamData in the plugin. 74 CallOnDownstreamData(contextID uint32, data []byte) types.Action 75 // CloseUpstreamConnection executes types.TcpContext.OnUpstreamClose in the plugin. 76 CloseUpstreamConnection(contextID uint32) 77 // CloseDownstreamConnection executes types.TcpContext.OnDownstreamClose in the plugin. 78 CloseDownstreamConnection(contextID uint32) 79 // CompleteConnection executes types.TcpContext.OnStreamDone in the plugin. 80 CompleteConnection(contextID uint32) 81 82 // InitializeHttpContext executes types.PluginContext.NewHttpContext in the plugin. 83 InitializeHttpContext() (contextID uint32) 84 // CallOnResponseHeaders executes types.HttpContext.OnHttpResponseHeaders in the plugin. 85 // The number of headers and endOfStream are passed to the plugin and the content of headers are visible in 86 // the plugin for methods like proxywasm.GetHttpResponseHeaders. 87 CallOnResponseHeaders(contextID uint32, headers [][2]string, endOfStream bool) types.Action 88 // CallOnResponseBody executes types.HttpContext.OnHttpResponseBody in the plugin. 89 // The number of bytes and endOfStream are passed to the plugin and the content of bytes are visible in the plugin 90 // for methods like proxywasm.GetHttpResponseBody. 91 CallOnResponseBody(contextID uint32, body []byte, endOfStream bool) types.Action 92 // CallOnResponseTrailers executes types.HttpContext.OnHttpResponseTrailers in the plugin. 93 // The number of trailers and endOfStream are passed to the plugin and the content of trailers are visible in the 94 // plugin for methods like proxywasm.GetHttpResponseTrailers. 95 CallOnResponseTrailers(contextID uint32, trailers [][2]string) types.Action 96 // CallOnRequestHeaders executes types.HttpContext.OnHttpRequestHeaders in the plugin. 97 // The number of headers and endOfStream are passed to the plugin and the content of headers are visible in the 98 // plugin for methods like proxywasm.GetHttpRequestHeaders. 99 CallOnRequestHeaders(contextID uint32, headers [][2]string, endOfStream bool) types.Action 100 // CallOnRequestTrailers executes types.HttpContext.OnHttpRequestTrailers in the plugin. 101 // The number of trailers and endOfStream are passed to the plugin and the content of trailers are visible in the 102 // plugin for methods like proxywasm.GetHttpRequestTrailers. 103 CallOnRequestTrailers(contextID uint32, trailers [][2]string) types.Action 104 // CallOnRequestBody executes types.HttpContext.OnHttpRequestBody in the plugin. 105 // The number of bytes and endOfStream are passed to the plugin and the content of bytes are visible in the plugin 106 // for methods like proxywasm.GetHttpRequestBody. 107 CallOnRequestBody(contextID uint32, body []byte, endOfStream bool) types.Action 108 // CompleteHttpContext executes types.HttpContext.OnHttpStreamDone in the plugin. 109 CompleteHttpContext(contextID uint32) 110 // GetCurrentHttpStreamAction returns the current action for the HTTP stream with ID contextID in the host. 111 // This is the return value of a previous lifecycle call in the plugin. 112 GetCurrentHttpStreamAction(contextID uint32) types.Action 113 // GetCurrentRequestHeaders returns the current request headers for the HTTP stream with ID contextID in the host. 114 // This will reflect any mutations made by the plugin such as with proxywasm.AddHttpRequestHeader. 115 GetCurrentRequestHeaders(contextID uint32) [][2]string 116 // GetCurrentResponseHeaders returns the current response headers for the HTTP stream with ID contextID in the host. 117 // This will reflect any mutations made by the plugin such as with proxywasm.AddHttpResponseHeader. 118 GetCurrentResponseHeaders(contextID uint32) [][2]string 119 // GetCurrentRequestBody returns the current request body for the HTTP stream with ID contextID in the host. 120 // This will reflect any mutations made by th eplugin such as with proxywasm.AppendHttpRequestBody. 121 GetCurrentRequestBody(contextID uint32) []byte 122 // GetCurrentResponseBody returns the current response body for the HTTP stream with ID contextID in the host. 123 // This will reflect any mutations made by the plugin such as with proxywasm.AppendHttpResponseBody. 124 GetCurrentResponseBody(contextID uint32) []byte 125 // GetSentLocalResponse returns the local response that has been sent for the HTTP stream with ID contextID in the 126 // host. This contains the arguments passed to proxywasm.SendHttpResponse in the plugin. If 127 // proxywasm.SendHttpResponse hasn't been invoked by the plugin, this will return nil. 128 GetSentLocalResponse(contextID uint32) *LocalHttpResponse 129 // GetProperty returns property data from the host, for a given path. 130 GetProperty(path []string) ([]byte, error) 131 // SetProperty sets property data on the host, for a given path. 132 SetProperty(path []string, data []byte) error 133 } 134 135 const ( 136 PluginContextID uint32 = 1 // TODO: support multiple pluginContext 137 ) 138 139 var nextContextID = PluginContextID + 1 140 141 type hostEmulator struct { 142 *rootHostEmulator 143 *networkHostEmulator 144 *httpHostEmulator 145 146 effectiveContextID uint32 147 properties map[string][]byte 148 } 149 150 // NewHostEmulator returns a new HostEmulator that can be used to test a plugin. Plugin tests will 151 // often involve calling methods on HostEmulator to invoke methods in the plugin while checking 152 // the state within the host after plugin execution. 153 func NewHostEmulator(opt *EmulatorOption) (host HostEmulator, reset func()) { 154 root := newRootHostEmulator(opt.pluginConfiguration, opt.vmConfiguration) 155 network := newNetworkHostEmulator() 156 http := newHttpHostEmulator() 157 emulator := &hostEmulator{ 158 root, 159 network, 160 http, 161 0, 162 make(map[string][]byte), 163 } 164 165 for key, value := range opt.properties { 166 emulator.properties[key] = value 167 } 168 169 release := internal.RegisterMockWasmHost(emulator) 170 171 // set up state 172 proxywasm.SetVMContext(opt.vmContext) 173 174 // create plugin context: TODO: support multiple plugin contexts 175 internal.ProxyOnContextCreate(PluginContextID, 0) 176 177 return emulator, func() { 178 defer release() 179 defer internal.VMStateReset() 180 } 181 } 182 183 func cloneWithLowerCaseMapKeys(m [][2]string) [][2]string { 184 r := make([][2]string, len(m)) 185 for i, entry := range m { 186 r[i] = [2]string{strings.ToLower(entry[0]), entry[1]} 187 } 188 return r 189 } 190 191 func deserializeRawBytePtrToMap(aw *byte, size int) [][2]string { 192 m := internal.DeserializeMap(internal.RawBytePtrToByteSlice(aw, size)) 193 for _, entry := range m { 194 entry[0] = strings.ToLower(entry[0]) 195 } 196 return m 197 } 198 199 func getNextContextID() (ret uint32) { 200 ret = nextContextID 201 nextContextID++ 202 return 203 } 204 205 // impl internal.ProxyWasmHost 206 func (h *hostEmulator) ProxyGetBufferBytes(bt internal.BufferType, start int, maxSize int, 207 returnBufferData **byte, returnBufferSize *int) internal.Status { 208 switch bt { 209 case internal.BufferTypePluginConfiguration, internal.BufferTypeVMConfiguration, internal.BufferTypeHttpCallResponseBody: 210 return h.rootHostEmulatorProxyGetBufferBytes(bt, start, maxSize, returnBufferData, returnBufferSize) 211 case internal.BufferTypeDownstreamData, internal.BufferTypeUpstreamData: 212 return h.networkHostEmulatorProxyGetBufferBytes(bt, start, maxSize, returnBufferData, returnBufferSize) 213 case internal.BufferTypeHttpRequestBody, internal.BufferTypeHttpResponseBody: 214 return h.httpHostEmulatorProxyGetBufferBytes(bt, start, maxSize, returnBufferData, returnBufferSize) 215 default: 216 panic("unreachable: maybe a bug in this host emulation or SDK") 217 } 218 } 219 220 func (h *hostEmulator) ProxySetBufferBytes(bt internal.BufferType, start int, maxSize int, bufferData *byte, bufferSize int) (ret internal.Status) { 221 switch bt { 222 case internal.BufferTypeHttpRequestBody, internal.BufferTypeHttpResponseBody: 223 ret = h.httpHostEmulatorProxySetBufferBytes(bt, start, maxSize, bufferData, bufferSize) 224 default: 225 panic(fmt.Sprintf("buffer type %d is not supported by proxytest frame work yet", bt)) 226 } 227 return 228 } 229 230 // impl internal.ProxyWasmHost 231 func (h *hostEmulator) ProxyGetHeaderMapValue(mapType internal.MapType, keyData *byte, 232 keySize int, returnValueData **byte, returnValueSize *int) internal.Status { 233 switch mapType { 234 case internal.MapTypeHttpRequestHeaders, internal.MapTypeHttpResponseHeaders, 235 internal.MapTypeHttpRequestTrailers, internal.MapTypeHttpResponseTrailers: 236 return h.httpHostEmulatorProxyGetHeaderMapValue(mapType, keyData, 237 keySize, returnValueData, returnValueSize) 238 case internal.MapTypeHttpCallResponseHeaders, internal.MapTypeHttpCallResponseTrailers: 239 return h.rootHostEmulatorProxyGetMapValue(mapType, keyData, 240 keySize, returnValueData, returnValueSize) 241 default: 242 panic("unreachable: maybe a bug in this host emulation or SDK") 243 } 244 } 245 246 // impl internal.ProxyWasmHost 247 func (h *hostEmulator) ProxyGetHeaderMapPairs(mapType internal.MapType, returnValueData **byte, 248 returnValueSize *int) internal.Status { 249 switch mapType { 250 case internal.MapTypeHttpRequestHeaders, internal.MapTypeHttpResponseHeaders, 251 internal.MapTypeHttpRequestTrailers, internal.MapTypeHttpResponseTrailers: 252 return h.httpHostEmulatorProxyGetHeaderMapPairs(mapType, returnValueData, returnValueSize) 253 case internal.MapTypeHttpCallResponseHeaders, internal.MapTypeHttpCallResponseTrailers: 254 return h.rootHostEmulatorProxyGetHeaderMapPairs(mapType, returnValueData, returnValueSize) 255 default: 256 panic("unreachable: maybe a bug in this host emulation or SDK") 257 } 258 } 259 260 // impl internal.ProxyWasmHost 261 func (h *hostEmulator) ProxySetEffectiveContext(contextID uint32) internal.Status { 262 h.effectiveContextID = contextID 263 // TODO(ikeeip): This is a workaround. Originally host uses both true context and 264 // effective context every time. We should implement this behavior hostEmulator too. 265 // see: https://github.com/proxy-wasm/proxy-wasm-cpp-host/blob/f38347360feaaf5b2a733f219c4d8c9660d626f0/src/exports.cc#L23 266 internal.VMStateSetActiveContextID(contextID) 267 return internal.StatusOK 268 } 269 270 // impl internal.ProxyWasmHost 271 func (h *hostEmulator) ProxySetProperty(pathPtr *byte, pathSize int, dataPtr *byte, dataSize int) internal.Status { 272 path := internal.RawBytePtrToString(pathPtr, pathSize) 273 data := internal.RawBytePtrToByteSlice(dataPtr, dataSize) 274 h.properties[path] = data 275 return internal.StatusOK 276 } 277 278 // impl internal.ProxyWasmHost 279 func (h *hostEmulator) ProxyGetProperty(pathPtr *byte, pathSize int, dataPtrPtr **byte, dataSizePtr *int) internal.Status { 280 path := internal.RawBytePtrToString(pathPtr, pathSize) 281 if _, ok := h.properties[path]; !ok { 282 return internal.StatusNotFound 283 } 284 data := h.properties[path] 285 *dataPtrPtr = &data[0] 286 dataSize := len(data) 287 *dataSizePtr = dataSize 288 return internal.StatusOK 289 } 290 291 // impl internal.ProxyWasmHost 292 func (h *hostEmulator) ProxyResolveSharedQueue(vmIDData *byte, vmIDSize int, nameData *byte, nameSize int, returnID *uint32) internal.Status { 293 log.Printf("ProxyResolveSharedQueue not implemented in the host emulator yet") 294 return 0 295 } 296 297 // impl internal.ProxyWasmHost 298 func (h *hostEmulator) ProxyCloseStream(streamType internal.StreamType) internal.Status { 299 log.Printf("ProxyCloseStream not implemented in the host emulator yet") 300 return 0 301 } 302 303 // impl internal.ProxyWasmHost 304 func (h *hostEmulator) ProxyDone() internal.Status { 305 log.Printf("ProxyDone not implemented in the host emulator yet") 306 return 0 307 }