github.com/enbility/spine-go@v0.7.0/spine/feature_local.go (about)

     1  package spine
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"reflect"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/enbility/ship-go/logging"
    11  	"github.com/enbility/spine-go/api"
    12  	"github.com/enbility/spine-go/model"
    13  	"github.com/enbility/spine-go/util"
    14  )
    15  
    16  type FeatureLocal struct {
    17  	*Feature
    18  
    19  	entity              api.EntityLocalInterface
    20  	functionDataMap     map[model.FunctionType]api.FunctionDataCmdInterface
    21  	muxResponseCB       sync.Mutex
    22  	responseMsgCallback map[model.MsgCounterType][]func(result api.ResponseMessage)
    23  	resultCallbacks     []func(result api.ResponseMessage)
    24  
    25  	writeTimeout           time.Duration
    26  	writeApprovalCallbacks []api.WriteApprovalCallbackFunc
    27  	muxWriteReceived       sync.Mutex
    28  	writeApprovalReceived  map[string]map[model.MsgCounterType]int
    29  	pendingWriteApprovals  map[string]map[model.MsgCounterType]*time.Timer
    30  
    31  	bindings      []*model.FeatureAddressType // bindings to remote features
    32  	subscriptions []*model.FeatureAddressType // subscriptions to remote features
    33  
    34  	mux sync.Mutex
    35  }
    36  
    37  func NewFeatureLocal(id uint, entity api.EntityLocalInterface, ftype model.FeatureTypeType, role model.RoleType) *FeatureLocal {
    38  	res := &FeatureLocal{
    39  		Feature: NewFeature(
    40  			featureAddressType(id, entity.Address()),
    41  			ftype,
    42  			role),
    43  		entity:                entity,
    44  		functionDataMap:       make(map[model.FunctionType]api.FunctionDataCmdInterface),
    45  		responseMsgCallback:   make(map[model.MsgCounterType][]func(result api.ResponseMessage)),
    46  		writeApprovalReceived: make(map[string]map[model.MsgCounterType]int),
    47  		pendingWriteApprovals: make(map[string]map[model.MsgCounterType]*time.Timer),
    48  		writeTimeout:          defaultMaxResponseDelay,
    49  	}
    50  
    51  	for _, fd := range CreateFunctionData[api.FunctionDataCmdInterface](ftype) {
    52  		res.functionDataMap[fd.FunctionType()] = fd
    53  	}
    54  	res.operations = make(map[model.FunctionType]api.OperationsInterface)
    55  
    56  	return res
    57  }
    58  
    59  var _ api.FeatureLocalInterface = (*FeatureLocal)(nil)
    60  
    61  /* FeatureLocalInterface */
    62  
    63  func (r *FeatureLocal) Device() api.DeviceLocalInterface {
    64  	return r.entity.Device()
    65  }
    66  
    67  func (r *FeatureLocal) Entity() api.EntityLocalInterface {
    68  	return r.entity
    69  }
    70  
    71  // Add supported function to the feature if its role is Server or Special
    72  func (r *FeatureLocal) AddFunctionType(function model.FunctionType, read, write bool) {
    73  	if r.role != model.RoleTypeServer && r.role != model.RoleTypeSpecial {
    74  		return
    75  	}
    76  	if r.operations[function] != nil {
    77  		return
    78  	}
    79  	writePartial := false
    80  	if write {
    81  		// partials are not supported on all features and functions, so check if this function supports it
    82  		if fctData := r.functionData(function); fctData != nil {
    83  			writePartial = fctData.SupportsPartialWrite()
    84  		}
    85  	}
    86  	// partial reads are currently not supported!
    87  	r.operations[function] = NewOperations(read, false, write, writePartial)
    88  
    89  	if r.role == model.RoleTypeServer &&
    90  		r.ftype == model.FeatureTypeTypeDeviceDiagnosis &&
    91  		function == model.FunctionTypeDeviceDiagnosisHeartbeatData {
    92  		// Update HeartbeatManager
    93  		r.Entity().HeartbeatManager().SetLocalFeature(r.Entity(), r)
    94  	}
    95  }
    96  
    97  func (r *FeatureLocal) Functions() []model.FunctionType {
    98  	var fcts []model.FunctionType
    99  
   100  	for key := range r.operations {
   101  		fcts = append(fcts, key)
   102  	}
   103  
   104  	return fcts
   105  }
   106  
   107  // Add a callback function to be invoked when SPINE message comes in with a given msgCounterReference value
   108  //
   109  // Returns an error if there is already a callback for the msgCounter set
   110  func (r *FeatureLocal) AddResponseCallback(msgCounterReference model.MsgCounterType, function func(msg api.ResponseMessage)) error {
   111  	r.muxResponseCB.Lock()
   112  	defer r.muxResponseCB.Unlock()
   113  
   114  	if _, ok := r.responseMsgCallback[msgCounterReference]; ok {
   115  		for _, cb := range r.responseMsgCallback[msgCounterReference] {
   116  			if reflect.ValueOf(cb).Pointer() == reflect.ValueOf(function).Pointer() {
   117  				return errors.New("callback already set")
   118  			}
   119  		}
   120  	}
   121  
   122  	r.responseMsgCallback[msgCounterReference] = append(r.responseMsgCallback[msgCounterReference], function)
   123  
   124  	return nil
   125  }
   126  
   127  func (r *FeatureLocal) processResponseMsgCallbacks(msgCounterReference model.MsgCounterType, msg api.ResponseMessage) {
   128  	r.muxResponseCB.Lock()
   129  	defer r.muxResponseCB.Unlock()
   130  
   131  	cbs, ok := r.responseMsgCallback[msgCounterReference]
   132  	if !ok {
   133  		return
   134  	}
   135  
   136  	for _, cb := range cbs {
   137  		go cb(msg)
   138  	}
   139  
   140  	delete(r.responseMsgCallback, msgCounterReference)
   141  }
   142  
   143  // Add a callback function to be invoked when a result message comes in for this feature
   144  func (r *FeatureLocal) AddResultCallback(function func(msg api.ResponseMessage)) {
   145  	r.muxResponseCB.Lock()
   146  	defer r.muxResponseCB.Unlock()
   147  
   148  	r.resultCallbacks = append(r.resultCallbacks, function)
   149  }
   150  
   151  func (r *FeatureLocal) processResultCallbacks(msg api.ResponseMessage) {
   152  	r.muxResponseCB.Lock()
   153  	defer r.muxResponseCB.Unlock()
   154  
   155  	for _, cb := range r.resultCallbacks {
   156  		go cb(msg)
   157  	}
   158  }
   159  
   160  func (r *FeatureLocal) AddWriteApprovalCallback(function api.WriteApprovalCallbackFunc) error {
   161  	if r.Role() != model.RoleTypeServer {
   162  		return errors.New("only allowed on a server feature")
   163  	}
   164  
   165  	r.muxResponseCB.Lock()
   166  	defer r.muxResponseCB.Unlock()
   167  
   168  	r.writeApprovalCallbacks = append(r.writeApprovalCallbacks, function)
   169  
   170  	return nil
   171  }
   172  
   173  func (r *FeatureLocal) processWriteApprovalCallbacks(msg *api.Message) {
   174  	r.muxResponseCB.Lock()
   175  	defer r.muxResponseCB.Unlock()
   176  
   177  	for _, cb := range r.writeApprovalCallbacks {
   178  		go cb(msg)
   179  	}
   180  }
   181  
   182  func (r *FeatureLocal) addPendingApproval(msg *api.Message) {
   183  	if r.Role() != model.RoleTypeServer ||
   184  		msg.DeviceRemote == nil ||
   185  		msg.RequestHeader == nil ||
   186  		msg.RequestHeader.MsgCounter == nil {
   187  		return
   188  	}
   189  
   190  	ski := msg.DeviceRemote.Ski()
   191  
   192  	newTimer := time.AfterFunc(r.writeTimeout, func() {
   193  		r.muxResponseCB.Lock()
   194  		delete(r.pendingWriteApprovals[ski], *msg.RequestHeader.MsgCounter)
   195  		r.muxResponseCB.Unlock()
   196  
   197  		err := model.NewErrorTypeFromString("write not approved in time by application")
   198  		_ = msg.FeatureRemote.Device().Sender().ResultError(msg.RequestHeader, r.Address(), err)
   199  	})
   200  
   201  	r.muxResponseCB.Lock()
   202  	if _, ok := r.pendingWriteApprovals[ski]; !ok {
   203  		r.pendingWriteApprovals[ski] = make(map[model.MsgCounterType]*time.Timer)
   204  	}
   205  	r.pendingWriteApprovals[ski][*msg.RequestHeader.MsgCounter] = newTimer
   206  	r.muxResponseCB.Unlock()
   207  }
   208  
   209  func (r *FeatureLocal) ApproveOrDenyWrite(msg *api.Message, err model.ErrorType) {
   210  	if r.Role() != model.RoleTypeServer ||
   211  		msg.DeviceRemote == nil {
   212  		return
   213  	}
   214  
   215  	ski := msg.DeviceRemote.Ski()
   216  
   217  	r.muxResponseCB.Lock()
   218  	timer, ok := r.pendingWriteApprovals[ski][*msg.RequestHeader.MsgCounter]
   219  	count := len(r.writeApprovalCallbacks)
   220  	r.muxResponseCB.Unlock()
   221  
   222  	// if there is no timer running, we are too late and error has already been sent
   223  	if !ok || timer == nil {
   224  		return
   225  	}
   226  
   227  	// do we have enough approvals?
   228  	r.muxWriteReceived.Lock()
   229  	defer r.muxWriteReceived.Unlock()
   230  	if count > 1 && err.ErrorNumber == 0 {
   231  		amount, ok := r.writeApprovalReceived[ski][*msg.RequestHeader.MsgCounter]
   232  		if ok {
   233  			r.writeApprovalReceived[ski][*msg.RequestHeader.MsgCounter] = amount + 1
   234  		} else {
   235  			r.writeApprovalReceived[ski] = make(map[model.MsgCounterType]int)
   236  			r.writeApprovalReceived[ski][*msg.RequestHeader.MsgCounter] = 1
   237  		}
   238  		// do we have enough approve messages, if not exit
   239  		if r.writeApprovalReceived[ski][*msg.RequestHeader.MsgCounter] < count {
   240  			return
   241  		}
   242  	}
   243  
   244  	timer.Stop()
   245  
   246  	delete(r.writeApprovalReceived[ski], *msg.RequestHeader.MsgCounter)
   247  
   248  	r.muxResponseCB.Lock()
   249  	defer r.muxResponseCB.Unlock()
   250  	delete(r.pendingWriteApprovals[ski], *msg.RequestHeader.MsgCounter)
   251  
   252  	if err.ErrorNumber == 0 {
   253  		r.processWrite(msg)
   254  		return
   255  	}
   256  
   257  	_ = msg.FeatureRemote.Device().Sender().ResultError(msg.RequestHeader, r.Address(), &err)
   258  }
   259  
   260  func (r *FeatureLocal) SetWriteApprovalTimeout(duration time.Duration) {
   261  	r.writeTimeout = duration
   262  }
   263  
   264  func (r *FeatureLocal) CleanWriteApprovalCaches(ski string) {
   265  	r.muxResponseCB.Lock()
   266  	defer r.muxResponseCB.Unlock()
   267  
   268  	delete(r.pendingWriteApprovals, ski)
   269  	delete(r.writeApprovalReceived, ski)
   270  }
   271  
   272  // Remove subscriptions and bindings from local cache for a remote device
   273  // used if a remote device is getting disconnected
   274  func (r *FeatureLocal) CleanRemoteDeviceCaches(remoteAddress *model.DeviceAddressType) {
   275  	if remoteAddress == nil ||
   276  		remoteAddress.Device == nil {
   277  		return
   278  	}
   279  
   280  	r.mux.Lock()
   281  	defer r.mux.Unlock()
   282  
   283  	var subscriptions []*model.FeatureAddressType
   284  
   285  	for _, item := range r.subscriptions {
   286  		if item.Device == nil ||
   287  			*item.Device != *remoteAddress.Device {
   288  			subscriptions = append(subscriptions, item)
   289  		}
   290  	}
   291  
   292  	r.subscriptions = subscriptions
   293  
   294  	var bindings []*model.FeatureAddressType
   295  
   296  	for _, item := range r.bindings {
   297  		if item.Device == nil ||
   298  			*item.Device != *remoteAddress.Device {
   299  			bindings = append(bindings, item)
   300  		}
   301  	}
   302  
   303  	r.bindings = bindings
   304  }
   305  
   306  // Remove subscriptions and bindings from local cache for a remote entity
   307  // used if a remote entity is removed
   308  func (r *FeatureLocal) CleanRemoteEntityCaches(remoteAddress *model.EntityAddressType) {
   309  	if remoteAddress == nil ||
   310  		remoteAddress.Device == nil ||
   311  		remoteAddress.Entity == nil {
   312  		return
   313  	}
   314  
   315  	r.mux.Lock()
   316  	defer r.mux.Unlock()
   317  
   318  	var subscriptions []*model.FeatureAddressType
   319  
   320  	for _, item := range r.subscriptions {
   321  		if item.Device == nil || item.Entity == nil ||
   322  			*item.Device != *remoteAddress.Device ||
   323  			!reflect.DeepEqual(item.Entity, remoteAddress.Entity) {
   324  			subscriptions = append(subscriptions, item)
   325  		}
   326  	}
   327  
   328  	r.subscriptions = subscriptions
   329  
   330  	var bindings []*model.FeatureAddressType
   331  
   332  	for _, item := range r.bindings {
   333  		if item.Device == nil || item.Entity == nil ||
   334  			*item.Device != *remoteAddress.Device ||
   335  			!reflect.DeepEqual(item.Entity, remoteAddress.Entity) {
   336  			bindings = append(bindings, item)
   337  		}
   338  	}
   339  
   340  	r.bindings = bindings
   341  }
   342  
   343  func (r *FeatureLocal) DataCopy(function model.FunctionType) any {
   344  	r.mux.Lock()
   345  	defer r.mux.Unlock()
   346  
   347  	fctData := r.functionData(function)
   348  	if fctData == nil {
   349  		return nil
   350  	}
   351  
   352  	return fctData.DataCopyAny()
   353  }
   354  
   355  func (r *FeatureLocal) SetData(function model.FunctionType, data any) {
   356  	fctData, err := r.updateData(false, function, data, nil, nil)
   357  
   358  	if err != nil {
   359  		logging.Log().Debug(err.String())
   360  	}
   361  
   362  	if fctData != nil && err == nil {
   363  		r.Device().NotifySubscribers(r.Address(), fctData.NotifyOrWriteCmdType(nil, nil, false, nil))
   364  	}
   365  }
   366  
   367  func (r *FeatureLocal) UpdateData(function model.FunctionType, data any, filterPartial *model.FilterType, filterDelete *model.FilterType) *model.ErrorType {
   368  	fctData, err := r.updateData(false, function, data, filterPartial, filterDelete)
   369  
   370  	if err != nil {
   371  		logging.Log().Debug(err.String())
   372  	}
   373  
   374  	if fctData != nil && err == nil {
   375  		var deleteSelector, deleteElements, partialSelector any
   376  
   377  		if filterDelete != nil {
   378  			if fDelete, err := filterDelete.Data(); err == nil {
   379  				if fDelete.Selector != nil {
   380  					deleteSelector = fDelete.Selector
   381  				}
   382  				if fDelete.Elements != nil {
   383  					deleteElements = fDelete.Elements
   384  				}
   385  			}
   386  		}
   387  
   388  		if filterPartial != nil {
   389  			if fPartial, err := filterPartial.Data(); err == nil && fPartial.Selector != nil {
   390  				partialSelector = fPartial.Selector
   391  			}
   392  		}
   393  
   394  		r.Device().NotifySubscribers(r.Address(), fctData.NotifyOrWriteCmdType(deleteSelector, partialSelector, partialSelector == nil, deleteElements))
   395  	}
   396  
   397  	return err
   398  }
   399  
   400  func (r *FeatureLocal) updateData(remoteWrite bool, function model.FunctionType, data any, filterPartial *model.FilterType, filterDelete *model.FilterType) (api.FunctionDataCmdInterface, *model.ErrorType) {
   401  	r.mux.Lock()
   402  	defer r.mux.Unlock()
   403  
   404  	fctData := r.functionData(function)
   405  	if fctData == nil {
   406  		return nil, model.NewErrorTypeFromString("data not found")
   407  	}
   408  
   409  	_, err := fctData.UpdateDataAny(remoteWrite, true, data, filterPartial, filterDelete)
   410  
   411  	return fctData, err
   412  }
   413  
   414  func (r *FeatureLocal) RequestRemoteData(
   415  	function model.FunctionType,
   416  	selector any,
   417  	elements any,
   418  	destination api.FeatureRemoteInterface) (*model.MsgCounterType, *model.ErrorType) {
   419  	fd := r.functionData(function)
   420  	if fd == nil {
   421  		return nil, model.NewErrorTypeFromString("function data not found")
   422  	}
   423  
   424  	cmd := fd.ReadCmdType(selector, elements)
   425  
   426  	return r.RequestRemoteDataBySenderAddress(cmd, destination.Device().Sender(), destination.Device().Ski(), destination.Address(), destination.MaxResponseDelayDuration())
   427  }
   428  
   429  func (r *FeatureLocal) RequestRemoteDataBySenderAddress(
   430  	cmd model.CmdType,
   431  	sender api.SenderInterface,
   432  	deviceSki string,
   433  	destinationAddress *model.FeatureAddressType,
   434  	maxDelay time.Duration) (*model.MsgCounterType, *model.ErrorType) {
   435  	msgCounter, err := sender.Request(model.CmdClassifierTypeRead, r.Address(), destinationAddress, false, []model.CmdType{cmd})
   436  	if err == nil {
   437  		return msgCounter, nil
   438  	}
   439  
   440  	return msgCounter, model.NewErrorType(model.ErrorNumberTypeGeneralError, err.Error())
   441  }
   442  
   443  // check if there already is a subscription to a remote feature
   444  func (r *FeatureLocal) HasSubscriptionToRemote(remoteAddress *model.FeatureAddressType) bool {
   445  	r.mux.Lock()
   446  	defer r.mux.Unlock()
   447  
   448  	for _, item := range r.subscriptions {
   449  		if reflect.DeepEqual(*remoteAddress, *item) {
   450  			return true
   451  		}
   452  	}
   453  
   454  	return false
   455  }
   456  
   457  // SubscribeToRemote to a remote feature
   458  func (r *FeatureLocal) SubscribeToRemote(remoteAddress *model.FeatureAddressType) (*model.MsgCounterType, *model.ErrorType) {
   459  	if remoteAddress.Device == nil {
   460  		return nil, model.NewErrorTypeFromString("device not found")
   461  	}
   462  	remoteDevice := r.entity.Device().RemoteDeviceForAddress(*remoteAddress.Device)
   463  	if remoteDevice == nil {
   464  		return nil, model.NewErrorTypeFromString("device not found")
   465  	}
   466  
   467  	if r.Role() == model.RoleTypeServer {
   468  		return nil, model.NewErrorTypeFromString(fmt.Sprintf("the server feature '%s' cannot request a subscription", r.Feature.String()))
   469  	}
   470  
   471  	msgCounter, err := remoteDevice.Sender().Subscribe(r.Address(), remoteAddress, r.ftype)
   472  	if err != nil {
   473  		return nil, model.NewErrorTypeFromString(err.Error())
   474  	}
   475  
   476  	r.mux.Lock()
   477  	r.subscriptions = append(r.subscriptions, remoteAddress)
   478  	r.mux.Unlock()
   479  
   480  	return msgCounter, nil
   481  }
   482  
   483  // Remove a subscriptions to a remote feature
   484  func (r *FeatureLocal) RemoveRemoteSubscription(remoteAddress *model.FeatureAddressType) (*model.MsgCounterType, *model.ErrorType) {
   485  	if remoteAddress.Device == nil {
   486  		return nil, model.NewErrorTypeFromString("device not found")
   487  	}
   488  	remoteDevice := r.entity.Device().RemoteDeviceForAddress(*remoteAddress.Device)
   489  	if remoteDevice == nil {
   490  		return nil, model.NewErrorTypeFromString("device not found")
   491  	}
   492  
   493  	msgCounter, err := remoteDevice.Sender().Unsubscribe(r.Address(), remoteAddress)
   494  	if err != nil {
   495  		return nil, model.NewErrorTypeFromString("device not found")
   496  	}
   497  
   498  	var subscriptions []*model.FeatureAddressType
   499  
   500  	r.mux.Lock()
   501  	defer r.mux.Unlock()
   502  
   503  	for _, item := range r.subscriptions {
   504  		if reflect.DeepEqual(item, remoteAddress) {
   505  			continue
   506  		}
   507  
   508  		subscriptions = append(subscriptions, item)
   509  	}
   510  
   511  	r.subscriptions = subscriptions
   512  
   513  	return msgCounter, nil
   514  }
   515  
   516  // Remove all subscriptions to remote features
   517  func (r *FeatureLocal) RemoveAllRemoteSubscriptions() {
   518  	for _, item := range r.subscriptions {
   519  		_, _ = r.RemoveRemoteSubscription(item)
   520  	}
   521  }
   522  
   523  // check if there already is a binding to a remote feature
   524  func (r *FeatureLocal) HasBindingToRemote(remoteAddress *model.FeatureAddressType) bool {
   525  	r.mux.Lock()
   526  	defer r.mux.Unlock()
   527  
   528  	for _, item := range r.bindings {
   529  		if reflect.DeepEqual(*remoteAddress, *item) {
   530  			return true
   531  		}
   532  	}
   533  
   534  	return false
   535  }
   536  
   537  // BindToRemote to a remote feature
   538  func (r *FeatureLocal) BindToRemote(remoteAddress *model.FeatureAddressType) (*model.MsgCounterType, *model.ErrorType) {
   539  	if remoteAddress.Device == nil {
   540  		return nil, model.NewErrorTypeFromString("device not found")
   541  	}
   542  	remoteDevice := r.entity.Device().RemoteDeviceForAddress(*remoteAddress.Device)
   543  	if remoteDevice == nil {
   544  		return nil, model.NewErrorTypeFromString("device not found")
   545  	}
   546  
   547  	if r.Role() == model.RoleTypeServer {
   548  		return nil, model.NewErrorTypeFromString(fmt.Sprintf("the server feature '%s' cannot request a binding", r.Feature.String()))
   549  	}
   550  
   551  	msgCounter, err := remoteDevice.Sender().Bind(r.Address(), remoteAddress, r.ftype)
   552  	if err != nil {
   553  		return nil, model.NewErrorTypeFromString(err.Error())
   554  	}
   555  
   556  	r.mux.Lock()
   557  	r.bindings = append(r.bindings, remoteAddress)
   558  	r.mux.Unlock()
   559  
   560  	return msgCounter, nil
   561  }
   562  
   563  // Remove a binding to a remote feature
   564  func (r *FeatureLocal) RemoveRemoteBinding(remoteAddress *model.FeatureAddressType) (*model.MsgCounterType, *model.ErrorType) {
   565  	if remoteAddress.Device == nil {
   566  		return nil, model.NewErrorTypeFromString("device not found")
   567  	}
   568  	remoteDevice := r.entity.Device().RemoteDeviceForAddress(*remoteAddress.Device)
   569  	if remoteDevice == nil {
   570  		return nil, model.NewErrorTypeFromString("device not found")
   571  	}
   572  
   573  	msgCounter, err := remoteDevice.Sender().Unbind(r.Address(), remoteAddress)
   574  	if err != nil {
   575  		return nil, model.NewErrorTypeFromString(err.Error())
   576  	}
   577  
   578  	var bindings []*model.FeatureAddressType
   579  
   580  	r.mux.Lock()
   581  	defer r.mux.Unlock()
   582  
   583  	for _, item := range r.bindings {
   584  		if reflect.DeepEqual(item, remoteAddress) {
   585  			continue
   586  		}
   587  
   588  		bindings = append(bindings, item)
   589  	}
   590  
   591  	r.bindings = bindings
   592  
   593  	return msgCounter, nil
   594  }
   595  
   596  // Remove all subscriptions to remote features
   597  func (r *FeatureLocal) RemoveAllRemoteBindings() {
   598  	for _, item := range r.bindings {
   599  		_, _ = r.RemoveRemoteBinding(item)
   600  	}
   601  }
   602  
   603  func (r *FeatureLocal) HandleMessage(message *api.Message) *model.ErrorType {
   604  	cmdData, err := message.Cmd.Data()
   605  	if err != nil {
   606  		return model.NewErrorType(model.ErrorNumberTypeCommandNotSupported, err.Error())
   607  	}
   608  	if cmdData.Function == nil {
   609  		return model.NewErrorType(model.ErrorNumberTypeCommandNotSupported, "No function found for cmd data")
   610  	}
   611  
   612  	switch message.CmdClassifier {
   613  	case model.CmdClassifierTypeResult:
   614  		if err := r.processResult(message); err != nil {
   615  			return err
   616  		}
   617  	case model.CmdClassifierTypeRead:
   618  		if err := r.processRead(*cmdData.Function, message.RequestHeader, message.FeatureRemote); err != nil {
   619  			return err
   620  		}
   621  	case model.CmdClassifierTypeReply:
   622  		if err := r.processReply(message); err != nil {
   623  			return err
   624  		}
   625  	case model.CmdClassifierTypeNotify:
   626  		if err := r.processNotify(*cmdData.Function, cmdData.Value, message.FilterPartial, message.FilterDelete, message.FeatureRemote); err != nil {
   627  			return err
   628  		}
   629  	case model.CmdClassifierTypeWrite:
   630  		// if there is a write permission check callback set, invoke this instead of directly allowing the write
   631  		if len(r.writeApprovalCallbacks) > 0 {
   632  			r.addPendingApproval(message)
   633  			r.processWriteApprovalCallbacks(message)
   634  		} else {
   635  			// this method handles ack and error results, so no need to return an error
   636  			r.processWrite(message)
   637  		}
   638  	default:
   639  		return model.NewErrorTypeFromString(fmt.Sprintf("CmdClassifier not implemented: %s", message.CmdClassifier))
   640  	}
   641  
   642  	return nil
   643  }
   644  
   645  func (r *FeatureLocal) processResult(message *api.Message) *model.ErrorType {
   646  	if message.Cmd.ResultData == nil || message.Cmd.ResultData.ErrorNumber == nil {
   647  		return model.NewErrorType(
   648  			model.ErrorNumberTypeGeneralError,
   649  			fmt.Sprintf("ResultData CmdClassifierType %s not implemented", message.CmdClassifier))
   650  	}
   651  
   652  	if *message.Cmd.ResultData.ErrorNumber != model.ErrorNumberTypeNoError {
   653  		// error numbers explained in Resource Spec 3.11
   654  		errorString := fmt.Sprintf("Error Result received %d", *message.Cmd.ResultData.ErrorNumber)
   655  		if message.Cmd.ResultData.Description != nil {
   656  			errorString += fmt.Sprintf(": %s", *message.Cmd.ResultData.Description)
   657  		}
   658  		logging.Log().Debug(errorString)
   659  	}
   660  
   661  	// we don't need to populate this message if there is no MsgCounterReference
   662  	if message.RequestHeader == nil || message.RequestHeader.MsgCounterReference == nil {
   663  		return nil
   664  	}
   665  
   666  	responseMsg := api.ResponseMessage{
   667  		MsgCounterReference: *message.RequestHeader.MsgCounterReference,
   668  		Data:                message.Cmd.ResultData,
   669  		FeatureLocal:        r,
   670  		FeatureRemote:       message.FeatureRemote,
   671  		EntityRemote:        message.EntityRemote,
   672  		DeviceRemote:        message.DeviceRemote,
   673  	}
   674  
   675  	r.processResponseMsgCallbacks(*message.RequestHeader.MsgCounterReference, responseMsg)
   676  	r.processResultCallbacks(responseMsg)
   677  
   678  	return nil
   679  }
   680  
   681  func (r *FeatureLocal) processRead(function model.FunctionType, requestHeader *model.HeaderType, featureRemote api.FeatureRemoteInterface) *model.ErrorType {
   682  	// is this a read request to a local server/special feature?
   683  	if r.role == model.RoleTypeClient {
   684  		// Read requests to a client feature are not allowed
   685  		return model.NewErrorTypeFromNumber(model.ErrorNumberTypeCommandRejected)
   686  	}
   687  
   688  	fd := r.functionData(function)
   689  	if fd == nil {
   690  		return model.NewErrorTypeFromString("function data not found")
   691  	}
   692  
   693  	cmd := fd.ReplyCmdType(false)
   694  	if err := featureRemote.Device().Sender().Reply(requestHeader, r.Address(), cmd); err != nil {
   695  		return model.NewErrorTypeFromString(err.Error())
   696  	}
   697  
   698  	return nil
   699  }
   700  
   701  func (r *FeatureLocal) processReply(message *api.Message) *model.ErrorType {
   702  	// function model.FunctionType, data any, filterPartial *model.FilterType, filterDelete *model.FilterType, featureRemote api.FeatureRemoteInterface)
   703  
   704  	// the error is handled already in the caller
   705  	cmdData, _ := message.Cmd.Data()
   706  	featureRemote := message.FeatureRemote
   707  
   708  	if _, err := featureRemote.UpdateData(true, *cmdData.Function, cmdData.Value, message.FilterPartial, message.FilterDelete); err != nil {
   709  		return err
   710  	}
   711  
   712  	// the data was updated, so send an event, other event handlers may watch out for this as well
   713  	payload := api.EventPayload{
   714  		Ski:           featureRemote.Device().Ski(),
   715  		EventType:     api.EventTypeDataChange,
   716  		ChangeType:    api.ElementChangeUpdate,
   717  		Feature:       featureRemote,
   718  		Device:        featureRemote.Device(),
   719  		Entity:        featureRemote.Entity(),
   720  		LocalFeature:  r,
   721  		Function:      *cmdData.Function,
   722  		CmdClassifier: util.Ptr(model.CmdClassifierTypeReply),
   723  		Data:          cmdData.Value,
   724  	}
   725  	Events.Publish(payload)
   726  
   727  	// we don't need to populate this message if there is no MsgCounterReference
   728  	if message.RequestHeader == nil || message.RequestHeader.MsgCounterReference == nil {
   729  		return nil
   730  	}
   731  
   732  	responseMsg := api.ResponseMessage{
   733  		MsgCounterReference: *message.RequestHeader.MsgCounterReference,
   734  		Data:                cmdData.Value,
   735  		FeatureLocal:        r,
   736  		FeatureRemote:       message.FeatureRemote,
   737  		EntityRemote:        message.EntityRemote,
   738  		DeviceRemote:        message.DeviceRemote,
   739  	}
   740  
   741  	r.processResponseMsgCallbacks(*message.RequestHeader.MsgCounterReference, responseMsg)
   742  
   743  	return nil
   744  }
   745  
   746  func (r *FeatureLocal) processNotify(function model.FunctionType, data any, filterPartial *model.FilterType, filterDelete *model.FilterType, featureRemote api.FeatureRemoteInterface) *model.ErrorType {
   747  	if _, err := featureRemote.UpdateData(true, function, data, filterPartial, filterDelete); err != nil {
   748  		return err
   749  	}
   750  
   751  	payload := api.EventPayload{
   752  		Ski:           featureRemote.Device().Ski(),
   753  		EventType:     api.EventTypeDataChange,
   754  		ChangeType:    api.ElementChangeUpdate,
   755  		Feature:       featureRemote,
   756  		Device:        featureRemote.Device(),
   757  		Entity:        featureRemote.Entity(),
   758  		LocalFeature:  r,
   759  		Function:      function,
   760  		CmdClassifier: util.Ptr(model.CmdClassifierTypeNotify),
   761  		Data:          data,
   762  	}
   763  	Events.Publish(payload)
   764  
   765  	return nil
   766  }
   767  
   768  func (r *FeatureLocal) processWrite(msg *api.Message) {
   769  	if err := r.executeWrite(msg); err != nil {
   770  		_ = msg.FeatureRemote.Device().Sender().ResultError(msg.RequestHeader, r.Address(), err)
   771  	} else if msg.RequestHeader != nil {
   772  		ackRequest := msg.RequestHeader.AckRequest
   773  		if ackRequest != nil && *ackRequest {
   774  			_ = msg.FeatureRemote.Device().Sender().ResultSuccess(msg.RequestHeader, r.Address())
   775  		}
   776  	}
   777  }
   778  
   779  func (r *FeatureLocal) executeWrite(msg *api.Message) *model.ErrorType {
   780  	cmdData, err := msg.Cmd.Data()
   781  	if err != nil {
   782  		return model.NewErrorType(model.ErrorNumberTypeCommandNotSupported, err.Error())
   783  	}
   784  	if cmdData.Function == nil {
   785  		return model.NewErrorType(model.ErrorNumberTypeCommandNotSupported, "No function found for cmd data")
   786  	}
   787  
   788  	fctData, err1 := r.updateData(true, *cmdData.Function, cmdData.Value, msg.FilterPartial, msg.FilterDelete)
   789  	if err1 != nil {
   790  		return err1
   791  	} else if fctData == nil {
   792  		return model.NewErrorTypeFromString("function not found")
   793  	}
   794  
   795  	r.Device().NotifySubscribers(r.Address(), fctData.NotifyOrWriteCmdType(nil, nil, false, nil))
   796  
   797  	payload := api.EventPayload{
   798  		Ski:           msg.FeatureRemote.Device().Ski(),
   799  		EventType:     api.EventTypeDataChange,
   800  		ChangeType:    api.ElementChangeUpdate,
   801  		Feature:       msg.FeatureRemote,
   802  		Device:        msg.FeatureRemote.Device(),
   803  		Entity:        msg.FeatureRemote.Entity(),
   804  		LocalFeature:  r,
   805  		Function:      *cmdData.Function,
   806  		CmdClassifier: util.Ptr(model.CmdClassifierTypeWrite),
   807  		Data:          cmdData.Value,
   808  	}
   809  	Events.Publish(payload)
   810  
   811  	return nil
   812  }
   813  
   814  func (r *FeatureLocal) functionData(function model.FunctionType) api.FunctionDataCmdInterface {
   815  	fd, found := r.functionDataMap[function]
   816  	if !found {
   817  		logging.Log().Errorf("Data was not found for function '%s'", function)
   818  		return nil
   819  	}
   820  	return fd
   821  }
   822  
   823  func (r *FeatureLocal) Information() *model.NodeManagementDetailedDiscoveryFeatureInformationType {
   824  	var funs []model.FunctionPropertyType
   825  	for fun, operations := range r.operations {
   826  		var functionType = model.FunctionType(fun)
   827  		sf := model.FunctionPropertyType{
   828  			Function:           &functionType,
   829  			PossibleOperations: operations.Information(),
   830  		}
   831  
   832  		funs = append(funs, sf)
   833  	}
   834  
   835  	res := model.NodeManagementDetailedDiscoveryFeatureInformationType{
   836  		Description: &model.NetworkManagementFeatureDescriptionDataType{
   837  			FeatureAddress:    r.Address(),
   838  			FeatureType:       &r.ftype,
   839  			Role:              &r.role,
   840  			Description:       r.description,
   841  			SupportedFunction: funs,
   842  		},
   843  	}
   844  
   845  	return &res
   846  }