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  }