go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/llx/llx.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package llx
     5  
     6  //go:generate protoc --proto_path=. --go_out=. --go_opt=paths=source_relative --rangerrpc_out=. llx.proto
     7  
     8  import (
     9  	"errors"
    10  	"sort"
    11  	"strconv"
    12  	"sync"
    13  
    14  	uuid "github.com/gofrs/uuid"
    15  	"github.com/rs/zerolog/log"
    16  	"go.mondoo.com/cnquery/providers-sdk/v1/resources"
    17  	"go.mondoo.com/cnquery/types"
    18  	"go.mondoo.com/cnquery/utils/multierr"
    19  )
    20  
    21  // ResultCallback function type
    22  type ResultCallback func(*RawResult)
    23  
    24  var emptyFunction = Function{}
    25  
    26  // RawResult wraps RawData to code and refs
    27  type RawResult struct {
    28  	Data   *RawData
    29  	CodeID string
    30  	Ref    uint64
    31  }
    32  
    33  type stepCache struct {
    34  	Result   *RawData
    35  	IsStatic bool
    36  }
    37  
    38  // Calls is a map connecting call-refs with each other
    39  type Calls struct {
    40  	locker sync.Mutex
    41  	calls  map[uint64][]uint64
    42  }
    43  
    44  // Store a new call connection.
    45  // Returns true if this connection already exists.
    46  // Returns false if this is a new connection.
    47  func (c *Calls) Store(k uint64, v uint64) bool {
    48  	c.locker.Lock()
    49  	defer c.locker.Unlock()
    50  
    51  	calls, ok := c.calls[k]
    52  	if !ok {
    53  		calls = []uint64{}
    54  	} else {
    55  		for k := range calls {
    56  			if calls[k] == v {
    57  				return true
    58  			}
    59  		}
    60  	}
    61  
    62  	calls = append(calls, v)
    63  	c.calls[k] = calls
    64  	return false
    65  }
    66  
    67  // Load a call connection
    68  func (c *Calls) Load(k uint64) ([]uint64, bool) {
    69  	c.locker.Lock()
    70  	v, ok := c.calls[k]
    71  	c.locker.Unlock()
    72  	return v, ok
    73  }
    74  
    75  // cache is a map containing stepCache values
    76  type cache struct {
    77  	data map[uint64]*stepCache
    78  	lock sync.Mutex
    79  }
    80  
    81  func newCache() *cache {
    82  	return &cache{
    83  		data: map[uint64]*stepCache{},
    84  		lock: sync.Mutex{},
    85  	}
    86  }
    87  
    88  // Store a new call connection
    89  func (c *cache) Store(k uint64, v *stepCache) {
    90  	c.lock.Lock()
    91  	c.data[k] = v
    92  	c.lock.Unlock()
    93  }
    94  
    95  // Load a call connection
    96  func (c *cache) Load(k uint64) (*stepCache, bool) {
    97  	c.lock.Lock()
    98  	res, ok := c.data[k]
    99  	c.lock.Unlock()
   100  
   101  	return res, ok
   102  }
   103  
   104  type blockExecutor struct {
   105  	id             string
   106  	blockRef       uint64
   107  	entrypoints    map[uint64]struct{}
   108  	callback       ResultCallback
   109  	callbackPoints map[uint64]string
   110  	cache          *cache
   111  	stepTracker    *cache
   112  	calls          *Calls
   113  	block          *Block
   114  	parent         *blockExecutor
   115  	ctx            *MQLExecutorV2
   116  	watcherIds     *types.StringSet
   117  }
   118  
   119  // MQLExecutorV2 is the runtime of MQL codestructure
   120  type MQLExecutorV2 struct {
   121  	id      string
   122  	runtime Runtime
   123  	code    *CodeV2
   124  	starts  []uint64
   125  	props   map[string]*Primitive
   126  
   127  	lock           sync.Mutex
   128  	blockExecutors []*blockExecutor
   129  	unregistered   bool
   130  }
   131  
   132  func (c *blockExecutor) watcherUID(ref uint64) string {
   133  	return c.id + "\x00" + strconv.FormatInt(int64(ref), 10)
   134  }
   135  
   136  func errorResult(err error, codeID string) *RawResult {
   137  	return &RawResult{
   138  		Data:   &RawData{Error: err},
   139  		CodeID: codeID,
   140  	}
   141  }
   142  
   143  func errorResultMsg(msg string, codeID string) *RawResult {
   144  	return &RawResult{
   145  		Data:   &RawData{Error: errors.New(msg)},
   146  		CodeID: codeID,
   147  	}
   148  }
   149  
   150  // NewExecutor will create a code runner from code, running in a runtime, calling
   151  // callback whenever we get a result
   152  func NewExecutorV2(code *CodeV2, runtime Runtime, props map[string]*Primitive, callback ResultCallback) (*MQLExecutorV2, error) {
   153  	if runtime == nil {
   154  		return nil, errors.New("cannot exec MQL without a runtime")
   155  	}
   156  
   157  	if code == nil {
   158  		return nil, errors.New("cannot run executor without code")
   159  	}
   160  
   161  	res := &MQLExecutorV2{
   162  		id:             uuid.Must(uuid.NewV4()).String(),
   163  		runtime:        runtime,
   164  		code:           code,
   165  		props:          props,
   166  		blockExecutors: []*blockExecutor{},
   167  	}
   168  
   169  	exec, err := res._newBlockExecutor(1<<32, callback, nil)
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  
   174  	res.blockExecutors = append(res.blockExecutors, exec)
   175  
   176  	return res, nil
   177  }
   178  
   179  func (c *MQLExecutorV2) _newBlockExecutor(blockRef uint64, callback ResultCallback, parent *blockExecutor) (*blockExecutor, error) {
   180  	block := c.code.Block(blockRef)
   181  
   182  	if block == nil {
   183  		return nil, errors.New("cannot find block " + strconv.FormatUint(blockRef, 10))
   184  	}
   185  
   186  	callbackPoints := map[uint64]string{}
   187  
   188  	res := &blockExecutor{
   189  		id:             uuid.Must(uuid.NewV4()).String() + "/" + strconv.FormatUint(blockRef>>32, 10),
   190  		blockRef:       blockRef,
   191  		callback:       callback,
   192  		callbackPoints: callbackPoints,
   193  		cache:          newCache(),
   194  		stepTracker:    newCache(),
   195  		calls: &Calls{
   196  			locker: sync.Mutex{},
   197  			calls:  map[uint64][]uint64{},
   198  		},
   199  		block:       block,
   200  		ctx:         c,
   201  		parent:      parent,
   202  		watcherIds:  &types.StringSet{},
   203  		entrypoints: map[uint64]struct{}{},
   204  	}
   205  
   206  	for _, ref := range block.Entrypoints {
   207  		id := c.code.Checksums[ref]
   208  		if id == "" {
   209  			return nil, errors.New("llx.executor> cannot execute with invalid ref ID in entrypoint")
   210  		}
   211  		if ref < 1 {
   212  			return nil, errors.New("llx.executor> cannot execute with invalid ref number in entrypoint")
   213  		}
   214  		res.entrypoints[ref] = struct{}{}
   215  		res.callbackPoints[ref] = id
   216  	}
   217  
   218  	for _, ref := range block.Datapoints {
   219  		id := c.code.Checksums[ref]
   220  		if id == "" {
   221  			return nil, errors.New("llx.executor> cannot execute with invalid ref ID in datapoint")
   222  		}
   223  		if ref < 1 {
   224  			return nil, errors.New("llx.executor> cannot execute with invalid ref number in datapoint")
   225  		}
   226  		res.callbackPoints[ref] = id
   227  	}
   228  
   229  	if len(res.callbackPoints) == 0 {
   230  		panic("no callback points")
   231  	}
   232  
   233  	return res, nil
   234  }
   235  
   236  // NoRun returns error for all callbacks and don't run code
   237  func (c *MQLExecutorV2) NoRun(err error) {
   238  	callback := c.blockExecutors[0].callback
   239  
   240  	for ref := range c.blockExecutors[0].callbackPoints {
   241  		if codeID, ok := c.blockExecutors[0].callbackPoints[ref]; ok {
   242  			callback(errorResult(err, codeID))
   243  		}
   244  	}
   245  }
   246  
   247  func (c *MQLExecutorV2) addBlockExecutor(be *blockExecutor) bool {
   248  	c.lock.Lock()
   249  	defer c.lock.Unlock()
   250  	if c.unregistered {
   251  		return false
   252  	}
   253  	c.blockExecutors = append(c.blockExecutors, be)
   254  	return true
   255  }
   256  
   257  func (c *MQLExecutorV2) Unregister() error {
   258  	log.Trace().Str("id", c.id).Msg("exec> unregister")
   259  	c.lock.Lock()
   260  	defer c.lock.Unlock()
   261  	if c.unregistered {
   262  		return nil
   263  	}
   264  	c.unregistered = true
   265  
   266  	var errs []error
   267  	for i := range c.blockExecutors {
   268  		be := c.blockExecutors[i]
   269  		errs = append(errs, be.unregister()...)
   270  	}
   271  
   272  	if len(errs) > 0 {
   273  		return errors.New("multiple errors unregistering")
   274  	}
   275  
   276  	return nil
   277  }
   278  
   279  // Run a given set of code. Only returns an error if the couldn't be started.
   280  func (c *MQLExecutorV2) Run() error {
   281  	if len(c.blockExecutors) == 0 {
   282  		return errors.New("cannot find initial block executor for running this code")
   283  	}
   284  
   285  	core := c.blockExecutors[0]
   286  	core.run()
   287  	return nil
   288  }
   289  
   290  func (b *blockExecutor) newBlockExecutor(blockRef uint64, callback ResultCallback) (*blockExecutor, error) {
   291  	return b.ctx._newBlockExecutor(blockRef, callback, b)
   292  }
   293  
   294  func (e *blockExecutor) unregister() []error {
   295  	var errs []error
   296  
   297  	e.watcherIds.Range(func(key string) bool {
   298  		if err := e.ctx.runtime.Unregister(key); err != nil {
   299  			log.Error().Err(err).Msg("exec> unregister error")
   300  			errs = append(errs, err)
   301  		}
   302  		return true
   303  	})
   304  
   305  	return errs
   306  }
   307  
   308  func (b *blockExecutor) isInMyBlock(ref uint64) bool {
   309  	return (ref >> 32) == (b.blockRef >> 32)
   310  }
   311  
   312  func (b *blockExecutor) mustLookup(ref uint64) *RawData {
   313  	d, _, err := b.parent.lookupValue(ref)
   314  	if err != nil {
   315  		panic(err)
   316  	}
   317  	if d == nil {
   318  		panic("did not lookup datapoint")
   319  	}
   320  	return d
   321  }
   322  
   323  // run code with a runtime and return results
   324  func (b *blockExecutor) run() {
   325  	for ref, codeID := range b.callbackPoints {
   326  		if !b.isInMyBlock(ref) {
   327  			v := b.mustLookup(ref)
   328  			b.callback(&RawResult{
   329  				CodeID: codeID,
   330  				Data:   v,
   331  			})
   332  		}
   333  	}
   334  	// work down all entrypoints
   335  	refs := make([]uint64, len(b.block.Entrypoints)+len(b.block.Datapoints))
   336  	i := 0
   337  	for _, ref := range b.block.Entrypoints {
   338  		refs[i] = ref
   339  		i++
   340  	}
   341  	for _, ref := range b.block.Datapoints {
   342  		refs[i] = ref
   343  		i++
   344  	}
   345  	sort.Slice(refs, func(i, j int) bool { return refs[i] > refs[j] })
   346  
   347  	for _, ref := range refs {
   348  		// if this entrypoint is already connected, don't add it again
   349  		if _, ok := b.stepTracker.Load(ref); ok {
   350  			continue
   351  		}
   352  
   353  		log.Trace().Uint64("entrypoint", ref).Str("exec-ID", b.ctx.id).Msg("exec.Run>")
   354  		b.runChain(ref)
   355  	}
   356  }
   357  
   358  func (b *blockExecutor) ensureArgsResolved(args []*Primitive, ref uint64) (uint64, error) {
   359  	for _, arg := range args {
   360  		_, dref, err := b.resolveValue(arg, ref)
   361  		if dref != 0 || err != nil {
   362  			return dref, err
   363  		}
   364  	}
   365  	return 0, nil
   366  }
   367  
   368  func reportSync(cb ResultCallback) ResultCallback {
   369  	lock := sync.Mutex{}
   370  	return func(rr *RawResult) {
   371  		lock.Lock()
   372  		defer lock.Unlock()
   373  		cb(rr)
   374  	}
   375  }
   376  
   377  type arrayBlockCallResults struct {
   378  	lock                 sync.Mutex
   379  	results              []arrayBlockCallResult
   380  	errors               []error
   381  	waiting              []int
   382  	unfinishedBlockCalls int
   383  	onComplete           func([]arrayBlockCallResult, []error)
   384  	entrypoints          map[string]struct{}
   385  	datapoints           map[string]struct{}
   386  }
   387  
   388  type arrayBlockCallResult struct {
   389  	entrypoints map[string]interface{}
   390  	datapoints  map[string]interface{}
   391  }
   392  
   393  func (a arrayBlockCallResult) toRawData() *RawData {
   394  	v := make(map[string]interface{}, len(a.entrypoints)+len(a.datapoints))
   395  
   396  	for checksum, res := range a.entrypoints {
   397  		v[checksum] = res
   398  	}
   399  
   400  	for checksum, res := range a.datapoints {
   401  		v[checksum] = res
   402  	}
   403  
   404  	v["__t"] = &RawData{
   405  		Type:  types.Bool,
   406  		Value: a.isTruthy(),
   407  	}
   408  
   409  	success, ok := a.isSuccess()
   410  	if ok {
   411  		v["__s"] = &RawData{
   412  			Type:  types.Bool,
   413  			Value: success,
   414  		}
   415  	} else {
   416  		v["__s"] = &RawData{
   417  			Type: types.Nil,
   418  		}
   419  	}
   420  
   421  	return &RawData{
   422  		Type:  types.Block,
   423  		Value: v,
   424  	}
   425  }
   426  
   427  func (a arrayBlockCallResult) isTruthy() bool {
   428  	for _, res := range a.entrypoints {
   429  		rd := &RawData{
   430  			Type:  types.Any,
   431  			Value: res,
   432  		}
   433  		isT, isValid := rd.IsTruthy()
   434  		if isValid && !isT {
   435  			return false
   436  		}
   437  	}
   438  	return true
   439  }
   440  
   441  func (a arrayBlockCallResult) isSuccess() (bool, bool) {
   442  	valid := false
   443  	for _, res := range a.entrypoints {
   444  		rd := &RawData{
   445  			Type:  types.Any,
   446  			Value: res,
   447  		}
   448  		isS, isValid := rd.IsSuccess()
   449  		if isValid && !isS {
   450  			return false, true
   451  		}
   452  		valid = valid || isValid
   453  	}
   454  	return true, valid
   455  }
   456  
   457  func (a *arrayBlockCallResults) update(i int, res *RawResult) {
   458  	a.lock.Lock()
   459  	defer a.lock.Unlock()
   460  
   461  	_, isEntrypoint := a.entrypoints[res.CodeID]
   462  	_, isDatapoint := a.datapoints[res.CodeID]
   463  
   464  	if !(isEntrypoint || isDatapoint) {
   465  		return
   466  	}
   467  
   468  	_, hasEntrypointResult := a.results[i].entrypoints[res.CodeID]
   469  	_, hasDatapointResult := a.results[i].datapoints[res.CodeID]
   470  
   471  	if !(hasEntrypointResult || hasDatapointResult) {
   472  		a.waiting[i]--
   473  		if a.waiting[i] == 0 {
   474  			a.unfinishedBlockCalls--
   475  		}
   476  	}
   477  
   478  	if isEntrypoint {
   479  		a.results[i].entrypoints[res.CodeID] = res.Data
   480  	}
   481  
   482  	if isDatapoint {
   483  		a.results[i].datapoints[res.CodeID] = res.Data
   484  	}
   485  
   486  	if res.Data.Error != nil {
   487  		a.errors = append(a.errors, res.Data.Error)
   488  	}
   489  
   490  	if a.unfinishedBlockCalls == 0 {
   491  		a.onComplete(a.results, a.errors)
   492  	}
   493  }
   494  
   495  func newArrayBlockCallResultsV2(expectedBlockCalls int, code *CodeV2, blockRef uint64, onComplete func([]arrayBlockCallResult, []error)) (*arrayBlockCallResults, bool) {
   496  	results := make([]arrayBlockCallResult, expectedBlockCalls)
   497  	waiting := make([]int, expectedBlockCalls)
   498  
   499  	codepoints := map[string]struct{}{}
   500  	entrypoints := map[string]struct{}{}
   501  
   502  	b := code.Block(blockRef)
   503  
   504  	for _, ep := range b.Entrypoints {
   505  		checksum := code.Checksums[ep]
   506  		codepoints[checksum] = struct{}{}
   507  		entrypoints[checksum] = struct{}{}
   508  	}
   509  
   510  	datapoints := map[string]struct{}{}
   511  	for _, dp := range b.Datapoints {
   512  		checksum := code.Checksums[dp]
   513  		codepoints[checksum] = struct{}{}
   514  		datapoints[checksum] = struct{}{}
   515  	}
   516  
   517  	expectedCodepoints := len(codepoints)
   518  	if expectedCodepoints == 0 {
   519  		results := make([]arrayBlockCallResult, expectedBlockCalls)
   520  		onComplete(results, nil)
   521  		return nil, false
   522  	} else {
   523  		for i := range waiting {
   524  			waiting[i] = expectedCodepoints
   525  		}
   526  	}
   527  
   528  	for i := range results {
   529  		results[i] = arrayBlockCallResult{
   530  			entrypoints: map[string]interface{}{},
   531  			datapoints:  map[string]interface{}{},
   532  		}
   533  	}
   534  
   535  	return &arrayBlockCallResults{
   536  		lock:                 sync.Mutex{},
   537  		results:              results,
   538  		waiting:              waiting,
   539  		unfinishedBlockCalls: expectedBlockCalls,
   540  		onComplete:           onComplete,
   541  		entrypoints:          entrypoints,
   542  		datapoints:           datapoints,
   543  	}, true
   544  }
   545  
   546  func (c *blockExecutor) runFunctionBlocks(argList [][]*RawData, blockRef uint64,
   547  	onComplete func([]arrayBlockCallResult, []error),
   548  ) error {
   549  	callResults, shouldRun := newArrayBlockCallResultsV2(len(argList), c.ctx.code, blockRef, onComplete)
   550  	if !shouldRun {
   551  		return nil
   552  	}
   553  	for idx := range argList {
   554  		i := idx
   555  		args := argList[i]
   556  		err := c.runFunctionBlock(args, blockRef, func(rr *RawResult) {
   557  			callResults.update(i, rr)
   558  		})
   559  		if err != nil {
   560  			return err
   561  		}
   562  	}
   563  	return nil
   564  }
   565  
   566  func (b *blockExecutor) runFunctionBlock(args []*RawData, blockRef uint64, cb ResultCallback) error {
   567  	executor, err := b.newBlockExecutor(blockRef, reportSync(cb))
   568  	if err != nil {
   569  		return err
   570  	}
   571  
   572  	b.ctx.addBlockExecutor(executor)
   573  
   574  	if len(args) < int(executor.block.Parameters) {
   575  		panic("not enough arguments")
   576  	}
   577  
   578  	for i := int32(0); i < executor.block.Parameters; i++ {
   579  		executor.cache.Store(blockRef|uint64(i+1), &stepCache{
   580  			Result:   args[i],
   581  			IsStatic: true,
   582  		})
   583  	}
   584  
   585  	executor.run()
   586  	return nil
   587  }
   588  
   589  func (b *blockExecutor) runBlock(bind *RawData, functionRef *Primitive, args []*Primitive, ref uint64) (*RawData, uint64, error) {
   590  	if bind != nil && bind.Value == nil && bind.Type != types.Nil {
   591  		return &RawData{Type: bind.Type, Value: nil}, 0, nil
   592  	}
   593  
   594  	typ := types.Type(functionRef.Type)
   595  	if !typ.IsFunction() {
   596  		return nil, 0, errors.New("called block with wrong function type")
   597  	}
   598  	fref, ok := functionRef.RefV2()
   599  	if !ok {
   600  		return nil, 0, errors.New("cannot retrieve function reference on block call")
   601  	}
   602  
   603  	block := b.ctx.code.Block(fref)
   604  	if block == nil {
   605  		return nil, 0, errors.New("block function is nil")
   606  	}
   607  
   608  	fargs := []*RawData{}
   609  	if bind != nil {
   610  		fargs = append(fargs, bind)
   611  	}
   612  	for i := range args {
   613  		a, b, c := b.resolveValue(args[i], ref)
   614  		if c != nil || b != 0 {
   615  			return a, b, c
   616  		}
   617  		fargs = append(fargs, a)
   618  	}
   619  
   620  	err := b.runFunctionBlocks([][]*RawData{fargs}, fref, func(results []arrayBlockCallResult, errs []error) {
   621  		var err multierr.Errors
   622  		err.Add(errs...)
   623  
   624  		if len(results) > 0 {
   625  			fun := b.ctx.code.Block(fref)
   626  			if fun.SingleValue {
   627  				res := results[0].entrypoints[b.ctx.code.Checksums[fun.Entrypoints[0]]].(*RawData)
   628  				b.cache.Store(ref, &stepCache{
   629  					Result: res,
   630  				})
   631  				b.triggerChain(ref, res)
   632  				return
   633  			}
   634  		}
   635  
   636  		data := results[0].toRawData()
   637  		data.Error = err.Deduplicate()
   638  		blockResult := data.Value.(map[string]interface{})
   639  
   640  		if bind != nil && bind.Type.IsResource() {
   641  			rr, ok := bind.Value.(Resource)
   642  			if !ok {
   643  				log.Warn().Msg("cannot cast resource to resource type")
   644  			} else {
   645  				blockResult["_"] = &RawData{
   646  					Type:  bind.Type,
   647  					Value: rr,
   648  				}
   649  			}
   650  		}
   651  
   652  		b.cache.Store(ref, &stepCache{
   653  			Result:   data,
   654  			IsStatic: true,
   655  		})
   656  		b.triggerChain(ref, data)
   657  	})
   658  
   659  	return nil, 0, err
   660  }
   661  
   662  type resourceInterface interface {
   663  	MqlResource() Resource
   664  }
   665  
   666  func pargs2argmap(b *blockExecutor, ref uint64, args []*Primitive) (map[string]*Primitive, uint64, error) {
   667  	if len(args) == 0 {
   668  		return nil, 0, nil
   669  	}
   670  
   671  	res := make(map[string]*Primitive, len(args))
   672  	var x *RawData
   673  	var rref uint64
   674  	var err error
   675  	for i := 0; i+1 < len(args); i += 2 {
   676  		k := args[i]
   677  		if types.Type(k.Type) != types.String {
   678  			return nil, 0, errors.New("incorrect argument type (caller keys should always be strings)")
   679  		}
   680  
   681  		key := k.RawData().Value.(string)
   682  
   683  		// TODO: this is a tedious and slow approach, speed it up...
   684  		x, rref, err = b.resolveValue(args[i+1], ref)
   685  		if rref != 0 || err != nil {
   686  			return nil, rref, err
   687  		}
   688  		res[key] = x.Result().Data
   689  	}
   690  
   691  	return res, 0, nil
   692  }
   693  
   694  func (b *blockExecutor) createResource(name string, binding uint64, f *Function, ref uint64) (*RawData, uint64, error) {
   695  	runtime := b.ctx.runtime
   696  	if binding != 0 {
   697  		panic("NOT SURE HOW TO RESOLVE THIS")
   698  		// res, dref, err := b.resolveRef(binding, ref)
   699  		// if dref != 0 || err != nil {
   700  		// 	return res, dref, err
   701  		// }
   702  		// mqlResource := res.Value.(resourceInterface).MqlResource()
   703  		// runtime = mqlResource.MqlRuntime
   704  	}
   705  
   706  	args, rref, err := pargs2argmap(b, ref, f.Args)
   707  	if err != nil || rref != 0 {
   708  		return nil, rref, err
   709  	}
   710  
   711  	resource, err := runtime.CreateResource(name, args)
   712  	if err != nil {
   713  		// in case it's not something that requires later loading, store the error
   714  		// so that consecutive steps can retrieve it cached
   715  		if _, ok := err.(resources.NotReadyError); !ok {
   716  			res := stepCache{
   717  				Result: &RawData{
   718  					Type:  types.Resource(name),
   719  					Value: nil,
   720  					Error: err,
   721  				},
   722  				IsStatic: true,
   723  			}
   724  			b.cache.Store(ref, &res)
   725  		}
   726  
   727  		return nil, 0, err
   728  	}
   729  
   730  	res := stepCache{
   731  		Result: &RawData{
   732  			Type:  types.Resource(name),
   733  			Value: resource,
   734  		},
   735  		IsStatic: true,
   736  	}
   737  	b.cache.Store(ref, &res)
   738  	return res.Result, 0, nil
   739  }
   740  
   741  func (b *blockExecutor) runGlobalFunction(chunk *Chunk, f *Function, ref uint64) (*RawData, uint64, error) {
   742  	h, ok := handleGlobalV2(chunk.Id)
   743  	if ok {
   744  		if h == nil {
   745  			return nil, 0, errors.New("found function " + chunk.Id + " but no handler. this should not happen and points to an implementation error")
   746  		}
   747  
   748  		res, dref, err := h(b, f, ref)
   749  		log.Trace().Msgf("exec> global: %s %+v = %#v", chunk.Id, f.Args, res)
   750  		if res != nil {
   751  			b.cache.Store(ref, &stepCache{Result: res})
   752  		}
   753  		return res, dref, err
   754  	}
   755  
   756  	return b.createResource(chunk.Id, 0, f, ref)
   757  }
   758  
   759  // connect references, calling `dst` if `src` is updated
   760  func (b *blockExecutor) connectRef(src uint64, dst uint64) (*RawData, uint64, error) {
   761  	if !b.isInMyBlock(src) || !b.isInMyBlock(dst) {
   762  		panic("cannot connect refs across block boundaries")
   763  	}
   764  	// connect the ref. If it is already connected, someone else already made this
   765  	// call, so we don't have to follow up anymore
   766  	if exists := b.calls.Store(src, dst); exists {
   767  		return nil, 0, nil
   768  	}
   769  
   770  	// if the ref was not yet connected, we must run the src ref after we connected it
   771  	return nil, src, nil
   772  }
   773  
   774  func (e *blockExecutor) runFunction(chunk *Chunk, ref uint64) (*RawData, uint64, error) {
   775  	f := chunk.Function
   776  	if f == nil {
   777  		f = &emptyFunction
   778  	}
   779  
   780  	// global functions, for now only resources
   781  	if f.Binding == 0 {
   782  		return e.runGlobalFunction(chunk, f, ref)
   783  	}
   784  
   785  	// check if the bound value exists, otherwise connect it
   786  	res, dref, err := e.resolveRef(f.Binding, ref)
   787  	if res == nil {
   788  		return res, dref, err
   789  	}
   790  
   791  	if res.Error != nil {
   792  		e.cache.Store(ref, &stepCache{Result: res})
   793  		return nil, 0, res.Error
   794  	}
   795  
   796  	return e.runBoundFunction(res, chunk, ref)
   797  }
   798  
   799  func (e *blockExecutor) runChunk(chunk *Chunk, ref uint64) (*RawData, uint64, error) {
   800  	switch chunk.Call {
   801  	case Chunk_PRIMITIVE:
   802  		res, dref, err := e.resolveValue(chunk.Primitive, ref)
   803  		if res != nil {
   804  			e.cache.Store(ref, &stepCache{Result: res})
   805  		} else if err != nil {
   806  			e.cache.Store(ref, &stepCache{Result: &RawData{
   807  				Error: err,
   808  			}})
   809  		}
   810  
   811  		return res, dref, err
   812  	case Chunk_FUNCTION:
   813  		return e.runFunction(chunk, ref)
   814  
   815  	case Chunk_PROPERTY:
   816  		property, ok := e.ctx.props[chunk.Id]
   817  		if !ok {
   818  			return nil, 0, errors.New("cannot find property '" + chunk.Id + "'")
   819  		}
   820  
   821  		res, dref, err := e.resolveValue(property, ref)
   822  		if dref != 0 || err != nil {
   823  			return res, dref, err
   824  		}
   825  		e.cache.Store(ref, &stepCache{Result: res})
   826  		return res, dref, err
   827  
   828  	default:
   829  		return nil, 0, errors.New("Tried to run a chunk which has an unknown type: " + chunk.Call.String())
   830  	}
   831  }
   832  
   833  func (e *blockExecutor) runRef(ref uint64) (*RawData, uint64, error) {
   834  	chunk := e.ctx.code.Chunk(ref)
   835  	if chunk == nil {
   836  		return nil, 0, errors.New("Called a chunk that doesn't exist, ref = " + strconv.FormatInt(int64(ref), 10))
   837  	}
   838  	return e.runChunk(chunk, ref)
   839  }
   840  
   841  // runChain starting at a ref of the code, follow it down and report
   842  // jever result it has at the end of its execution. this will register
   843  // async callbacks against referenced chunks too
   844  func (e *blockExecutor) runChain(start uint64) {
   845  	var res *RawData
   846  	var err error
   847  	nextRef := start
   848  	var curRef uint64
   849  	var remaining []uint64
   850  
   851  	for nextRef != 0 {
   852  		curRef = nextRef
   853  		e.stepTracker.Store(curRef, nil)
   854  		// log.Trace().Uint64("ref", curRef).Msg("exec> run chain")
   855  
   856  		// Try to load the result from cache if it already exists. This was added
   857  		// so that blocks that are called on top of a binding, where the results
   858  		// for the binding are pre-loaded, are actually read from cache. Typically
   859  		// follow-up calls would try to load from cache and would get the correct
   860  		// value, however if there are no follow-up calls we still want to return
   861  		// the correct value.
   862  		// This may be optimized in a way that we don't have to check loading it
   863  		// on every call.
   864  		cached, ok := e.cache.Load(curRef)
   865  		if ok {
   866  			res = cached.Result
   867  			nextRef = 0
   868  			err = nil
   869  		} else {
   870  			res, nextRef, err = e.runRef(curRef)
   871  		}
   872  
   873  		// stop this chain of execution, if it didn't return anything and
   874  		// there is nothing else left to process
   875  		// we need more data ie an event to provide info
   876  		if res == nil && nextRef == 0 && err == nil && len(remaining) == 0 {
   877  			log.Trace().Uint64("ref", curRef).Msg("exec> stop chain")
   878  			return
   879  		}
   880  
   881  		// if this is a result for a callback (entry- or datapoint) send it
   882  		if res != nil {
   883  			if codeID, ok := e.callbackPoints[curRef]; ok {
   884  				e.callback(&RawResult{Data: res, CodeID: codeID})
   885  			}
   886  		} else if err != nil {
   887  			if codeID, ok := e.callbackPoints[curRef]; ok {
   888  				e.callback(errorResult(err, codeID))
   889  			}
   890  			if _, isNotReadyError := err.(resources.NotReadyError); !isNotReadyError {
   891  				if sc, _ := e.cache.Load(curRef); sc == nil {
   892  					e.cache.Store(curRef, &stepCache{
   893  						Result: &RawData{
   894  							Type:  types.Unset,
   895  							Value: nil,
   896  							Error: err,
   897  						},
   898  					})
   899  				}
   900  			}
   901  		}
   902  
   903  		// get the next reference, if we are not directed anywhere
   904  		if nextRef == 0 {
   905  			// note: if the call cannot be retrieved it will use the
   906  			// zero value, which is 0 in this case; i.e. if !ok => ref = 0
   907  			nextRefs, _ := e.calls.Load(curRef)
   908  			cnt := len(nextRefs)
   909  			if cnt != 0 {
   910  				nextRef = nextRefs[0]
   911  				remaining = append(remaining, nextRefs[1:]...)
   912  				continue
   913  			}
   914  
   915  			cnt = len(remaining)
   916  			if cnt == 0 {
   917  				break
   918  			}
   919  			nextRef = remaining[0]
   920  			remaining = remaining[1:]
   921  		}
   922  	}
   923  }
   924  
   925  // triggerChain when a reference has a new value set
   926  // unlike runChain this will not execute the ref chunk, but rather
   927  // try to move to the next called chunk - or if it's not available
   928  // handle the result
   929  func (e *blockExecutor) triggerChain(ref uint64, data *RawData) {
   930  	// before we do anything else, we may have to provide the value from
   931  	// this callback point
   932  	if codeID, ok := e.callbackPoints[ref]; ok {
   933  		e.callback(&RawResult{Data: data, CodeID: codeID})
   934  	}
   935  
   936  	nxt, ok := e.calls.Load(ref)
   937  	if ok {
   938  		if len(nxt) == 0 {
   939  			panic("internal state error: cannot trigger next call on chain because it points to a zero ref")
   940  		}
   941  		for i := range nxt {
   942  			e.runChain(nxt[i])
   943  		}
   944  		return
   945  	}
   946  
   947  	codeID := e.callbackPoints[ref]
   948  	res, ok := e.cache.Load(ref)
   949  	if !ok {
   950  		e.callback(errorResultMsg("exec> cannot find results to chunk reference "+strconv.FormatInt(int64(ref), 10), codeID))
   951  		return
   952  	}
   953  
   954  	log.Trace().Uint64("ref", ref).Msgf("exec> trigger callback")
   955  	e.callback(&RawResult{Data: res.Result, CodeID: codeID})
   956  }
   957  
   958  func (e *blockExecutor) triggerChainError(ref uint64, err error) {
   959  	cur := ref
   960  	var remaining []uint64
   961  	for cur > 0 {
   962  		if codeID, ok := e.callbackPoints[cur]; ok {
   963  			e.callback(&RawResult{
   964  				Data: &RawData{
   965  					Error: err,
   966  				},
   967  				CodeID: codeID,
   968  			})
   969  		}
   970  
   971  		nxt, ok := e.calls.Load(cur)
   972  		if !ok {
   973  			if len(remaining) == 0 {
   974  				break
   975  			}
   976  			cur = remaining[0]
   977  			remaining = remaining[1:]
   978  		}
   979  		if len(nxt) == 0 {
   980  			panic("internal state error: cannot trigger next call on chain because it points to a zero ref")
   981  		}
   982  		cur = nxt[0]
   983  		remaining = append(remaining, nxt[1:]...)
   984  	}
   985  }