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

     1  package spine
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"reflect"
     7  	"slices"
     8  	"sync"
     9  
    10  	shipapi "github.com/enbility/ship-go/api"
    11  	"github.com/enbility/ship-go/logging"
    12  	"github.com/enbility/spine-go/api"
    13  	"github.com/enbility/spine-go/model"
    14  	"github.com/enbility/spine-go/util"
    15  )
    16  
    17  type DeviceLocal struct {
    18  	*Device
    19  	entities            []api.EntityLocalInterface
    20  	subscriptionManager api.SubscriptionManagerInterface
    21  	bindingManager      api.BindingManagerInterface
    22  	nodeManagement      *NodeManagement
    23  
    24  	remoteDevices map[string]api.DeviceRemoteInterface
    25  
    26  	brandName    string
    27  	deviceModel  string
    28  	deviceCode   string
    29  	serialNumber string
    30  
    31  	mux sync.Mutex
    32  }
    33  
    34  // BrandName is the brand
    35  // DeviceModel is the model
    36  // SerialNumber is the serial number
    37  // DeviceCode is the SHIP id (accessMethods.id)
    38  // DeviceAddress is the SPINE device address
    39  func NewDeviceLocal(
    40  	brandName, deviceModel, serialNumber, deviceCode, deviceAddress string,
    41  	deviceType model.DeviceTypeType,
    42  	featureSet model.NetworkManagementFeatureSetType) *DeviceLocal {
    43  	address := model.AddressDeviceType(deviceAddress)
    44  
    45  	var fSet *model.NetworkManagementFeatureSetType
    46  	if len(featureSet) != 0 {
    47  		fSet = &featureSet
    48  	}
    49  
    50  	res := &DeviceLocal{
    51  		Device:        NewDevice(&address, &deviceType, fSet),
    52  		remoteDevices: make(map[string]api.DeviceRemoteInterface),
    53  		brandName:     brandName,
    54  		deviceModel:   deviceModel,
    55  		serialNumber:  serialNumber,
    56  		deviceCode:    deviceCode,
    57  	}
    58  
    59  	res.subscriptionManager = NewSubscriptionManager(res)
    60  	res.bindingManager = NewBindingManager(res)
    61  
    62  	res.addDeviceInformation()
    63  	return res
    64  }
    65  
    66  var _ api.EventHandlerInterface = (*DeviceLocal)(nil)
    67  
    68  /* EventHandlerInterface */
    69  
    70  // React to some specific events
    71  func (r *DeviceLocal) HandleEvent(payload api.EventPayload) {
    72  	// Subscribe to NodeManagement after DetailedDiscovery is received
    73  	if payload.EventType != api.EventTypeDeviceChange || payload.ChangeType != api.ElementChangeAdd {
    74  		return
    75  	}
    76  
    77  	if payload.Data == nil {
    78  		return
    79  	}
    80  
    81  	if len(payload.Ski) == 0 {
    82  		return
    83  	}
    84  
    85  	remoteDevice := r.RemoteDeviceForSki(payload.Ski)
    86  	if remoteDevice == nil {
    87  		return
    88  	}
    89  
    90  	// the codefactor warning is invalid, as .(type) check can not be replaced with if then
    91  	//revive:disable-next-line
    92  	switch payload.Data.(type) {
    93  	case *model.NodeManagementDetailedDiscoveryDataType:
    94  		address := payload.Feature.Address()
    95  		if address.Device == nil {
    96  			address.Device = remoteDevice.Address()
    97  		}
    98  		_, _ = r.nodeManagement.SubscribeToRemote(address)
    99  
   100  		// Request Use Case Data
   101  		_, _ = r.nodeManagement.RequestUseCaseData(payload.Device.Ski(), remoteDevice.Address(), payload.Device.Sender())
   102  	}
   103  }
   104  
   105  var _ api.DeviceLocalInterface = (*DeviceLocal)(nil)
   106  
   107  /* DeviceLocalInterface */
   108  
   109  // Setup a new remote device with a given SKI and triggers SPINE requesting device details
   110  func (r *DeviceLocal) SetupRemoteDevice(ski string, writeI shipapi.ShipConnectionDataWriterInterface) shipapi.ShipConnectionDataReaderInterface {
   111  	sender := NewSender(writeI)
   112  	rDevice := NewDeviceRemote(r, ski, sender)
   113  
   114  	r.AddRemoteDeviceForSki(ski, rDevice)
   115  
   116  	// always add subscription, as it checks if it already exists
   117  	_ = Events.subscribe(api.EventHandlerLevelCore, r)
   118  
   119  	// Request Detailed Discovery Data
   120  	_, _ = r.RequestRemoteDetailedDiscoveryData(rDevice)
   121  
   122  	// TODO: Add error handling
   123  	// If the request returned an error, it should be retried until it does not
   124  
   125  	return rDevice
   126  }
   127  
   128  func (r *DeviceLocal) RequestRemoteDetailedDiscoveryData(rDevice api.DeviceRemoteInterface) (*model.MsgCounterType, *model.ErrorType) {
   129  	// Request Detailed Discovery Data
   130  	return r.nodeManagement.RequestDetailedDiscovery(rDevice.Ski(), rDevice.Address(), rDevice.Sender())
   131  }
   132  
   133  // Helper method used by tests and AddRemoteDevice
   134  func (r *DeviceLocal) AddRemoteDeviceForSki(ski string, rDevice api.DeviceRemoteInterface) {
   135  	r.mux.Lock()
   136  	defer r.mux.Unlock()
   137  
   138  	r.remoteDevices[ski] = rDevice
   139  }
   140  
   141  func (r *DeviceLocal) RemoveRemoteDeviceConnection(ski string) {
   142  	remoteDevice := r.RemoteDeviceForSki(ski)
   143  
   144  	r.RemoveRemoteDevice(ski)
   145  
   146  	// inform about the disconnection
   147  	payload := api.EventPayload{
   148  		Ski:        ski,
   149  		EventType:  api.EventTypeDeviceChange,
   150  		ChangeType: api.ElementChangeRemove,
   151  		Device:     remoteDevice,
   152  	}
   153  	Events.Publish(payload)
   154  }
   155  
   156  func (r *DeviceLocal) RemoveRemoteDevice(ski string) {
   157  	remoteDevice := r.RemoteDeviceForSki(ski)
   158  	if remoteDevice == nil {
   159  		return
   160  	}
   161  
   162  	// remove all subscriptions for this device
   163  	subscriptionMgr := r.SubscriptionManager()
   164  	subscriptionMgr.RemoveSubscriptionsForDevice(r.remoteDevices[ski])
   165  
   166  	// remove all bindings for this device
   167  	bindingMgr := r.BindingManager()
   168  	bindingMgr.RemoveBindingsForDevice(r.remoteDevices[ski])
   169  
   170  	delete(r.remoteDevices, ski)
   171  
   172  	// only unsubscribe if we don't have any remote devices left
   173  	if len(r.remoteDevices) == 0 {
   174  		_ = Events.unsubscribe(api.EventHandlerLevelCore, r)
   175  	}
   176  
   177  	remoteDeviceAddress := &model.DeviceAddressType{
   178  		Device: remoteDevice.Address(),
   179  	}
   180  	// remove all data caches for this device
   181  	for _, entity := range r.entities {
   182  		for _, feature := range entity.Features() {
   183  			feature.CleanWriteApprovalCaches(ski)
   184  			feature.CleanRemoteDeviceCaches(remoteDeviceAddress)
   185  		}
   186  	}
   187  }
   188  
   189  func (r *DeviceLocal) RemoteDevices() []api.DeviceRemoteInterface {
   190  	r.mux.Lock()
   191  	defer r.mux.Unlock()
   192  
   193  	res := make([]api.DeviceRemoteInterface, 0)
   194  	for _, rDevice := range r.remoteDevices {
   195  		res = append(res, rDevice)
   196  	}
   197  
   198  	return res
   199  }
   200  
   201  func (r *DeviceLocal) RemoteDeviceForAddress(address model.AddressDeviceType) api.DeviceRemoteInterface {
   202  	r.mux.Lock()
   203  	defer r.mux.Unlock()
   204  
   205  	for _, item := range r.remoteDevices {
   206  		if item.Address() != nil && *item.Address() == address {
   207  			return item
   208  		}
   209  	}
   210  
   211  	return nil
   212  }
   213  
   214  func (r *DeviceLocal) RemoteDeviceForSki(ski string) api.DeviceRemoteInterface {
   215  	r.mux.Lock()
   216  	defer r.mux.Unlock()
   217  
   218  	return r.remoteDevices[ski]
   219  }
   220  
   221  func (r *DeviceLocal) AddEntity(entity api.EntityLocalInterface) {
   222  	r.mux.Lock()
   223  
   224  	r.entities = append(r.entities, entity)
   225  
   226  	r.mux.Unlock()
   227  
   228  	r.notifySubscribersOfEntity(entity, model.NetworkManagementStateChangeTypeAdded)
   229  }
   230  
   231  func (r *DeviceLocal) RemoveEntity(entity api.EntityLocalInterface) {
   232  	entity.RemoveAllUseCaseSupports()
   233  	entity.RemoveAllSubscriptions()
   234  	entity.RemoveAllBindings()
   235  
   236  	if heartbeatMgr := entity.HeartbeatManager(); heartbeatMgr != nil {
   237  		heartbeatMgr.StopHeartbeat()
   238  	}
   239  
   240  	r.mux.Lock()
   241  
   242  	var entities []api.EntityLocalInterface
   243  	for _, e := range r.entities {
   244  		if e != entity {
   245  			entities = append(entities, e)
   246  		}
   247  	}
   248  
   249  	r.entities = entities
   250  
   251  	r.mux.Unlock()
   252  
   253  	r.notifySubscribersOfEntity(entity, model.NetworkManagementStateChangeTypeRemoved)
   254  }
   255  
   256  func (r *DeviceLocal) Entities() []api.EntityLocalInterface {
   257  	r.mux.Lock()
   258  	defer r.mux.Unlock()
   259  
   260  	return r.entities
   261  }
   262  
   263  func (r *DeviceLocal) Entity(id []model.AddressEntityType) api.EntityLocalInterface {
   264  	r.mux.Lock()
   265  	defer r.mux.Unlock()
   266  
   267  	for _, e := range r.entities {
   268  		if reflect.DeepEqual(id, e.Address().Entity) {
   269  			return e
   270  		}
   271  	}
   272  	return nil
   273  }
   274  
   275  func (r *DeviceLocal) EntityForType(entityType model.EntityTypeType) api.EntityLocalInterface {
   276  	r.mux.Lock()
   277  	defer r.mux.Unlock()
   278  
   279  	for _, e := range r.entities {
   280  		if e.EntityType() == entityType {
   281  			return e
   282  		}
   283  	}
   284  	return nil
   285  }
   286  
   287  func (r *DeviceLocal) FeatureByAddress(address *model.FeatureAddressType) api.FeatureLocalInterface {
   288  	entity := r.Entity(address.Entity)
   289  	if entity != nil {
   290  		return entity.FeatureOfAddress(address.Feature)
   291  	}
   292  	return nil
   293  }
   294  
   295  func (r *DeviceLocal) CleanRemoteEntityCaches(remoteAddress *model.EntityAddressType) {
   296  	for _, entity := range r.entities {
   297  		for _, feature := range entity.Features() {
   298  			feature.CleanRemoteEntityCaches(remoteAddress)
   299  		}
   300  	}
   301  }
   302  
   303  func (r *DeviceLocal) ProcessCmd(datagram model.DatagramType, remoteDevice api.DeviceRemoteInterface) error {
   304  	destAddr := datagram.Header.AddressDestination
   305  	localFeature := r.FeatureByAddress(destAddr)
   306  
   307  	cmdClassifier := datagram.Header.CmdClassifier
   308  	if len(datagram.Payload.Cmd) == 0 {
   309  		return errors.New("no payload cmd content available")
   310  	}
   311  	cmd := datagram.Payload.Cmd[0]
   312  
   313  	// TODO check if cmd.Function is the same as the provided cmd value
   314  	filterPartial, filterDelete := cmd.ExtractFilter()
   315  
   316  	remoteEntity := remoteDevice.Entity(datagram.Header.AddressSource.Entity)
   317  	remoteFeature := remoteDevice.FeatureByAddress(datagram.Header.AddressSource)
   318  	if remoteFeature == nil {
   319  		return fmt.Errorf("invalid remote feature address: '%s'", datagram.Header.AddressSource)
   320  	}
   321  
   322  	message := &api.Message{
   323  		RequestHeader: &datagram.Header,
   324  		Cmd:           cmd,
   325  		FilterPartial: filterPartial,
   326  		FilterDelete:  filterDelete,
   327  		FeatureRemote: remoteFeature,
   328  		EntityRemote:  remoteEntity,
   329  		DeviceRemote:  remoteDevice,
   330  	}
   331  
   332  	if cmdClassifier != nil {
   333  		message.CmdClassifier = *cmdClassifier
   334  	} else {
   335  		errorMessage := "cmdClassifier may not be empty"
   336  
   337  		_ = remoteFeature.Device().Sender().ResultError(message.RequestHeader, destAddr, model.NewErrorType(model.ErrorNumberTypeDestinationUnknown, errorMessage))
   338  
   339  		return errors.New(errorMessage)
   340  	}
   341  
   342  	if localFeature == nil {
   343  		errorMessage := "invalid feature address"
   344  		_ = remoteFeature.Device().Sender().ResultError(message.RequestHeader, destAddr, model.NewErrorType(model.ErrorNumberTypeDestinationUnknown, errorMessage))
   345  
   346  		return errors.New(errorMessage)
   347  	}
   348  
   349  	lfType := string(localFeature.Type())
   350  	rfType := string(remoteFeature.Type())
   351  
   352  	logging.Log().Debug(datagram.PrintMessageOverview(false, lfType, rfType))
   353  
   354  	// check if this is a write with an existing binding and if write is allowed on this feature
   355  	if message.CmdClassifier == model.CmdClassifierTypeWrite {
   356  		cmdData, err := cmd.Data()
   357  		if err != nil || cmdData.Function == nil {
   358  			err := model.NewErrorTypeFromString("no function found for cmd data")
   359  			_ = remoteFeature.Device().Sender().ResultError(message.RequestHeader, localFeature.Address(), err)
   360  			return errors.New(err.String())
   361  		}
   362  
   363  		if operations, ok := localFeature.Operations()[*cmdData.Function]; !ok || !operations.Write() {
   364  			err := model.NewErrorTypeFromString("write is not allowed on this function")
   365  			_ = remoteFeature.Device().Sender().ResultError(message.RequestHeader, localFeature.Address(), err)
   366  			return errors.New(err.String())
   367  		}
   368  
   369  		if !r.BindingManager().HasLocalFeatureRemoteBinding(localFeature.Address(), remoteFeature.Address()) {
   370  			err := model.NewErrorTypeFromString("write denied due to missing binding")
   371  			_ = remoteFeature.Device().Sender().ResultError(message.RequestHeader, localFeature.Address(), err)
   372  			return errors.New(err.String())
   373  		}
   374  	}
   375  
   376  	err := localFeature.HandleMessage(message)
   377  	if err != nil {
   378  		// TODO: add error description in a useful format
   379  
   380  		// Don't send error responses for incoming resulterror messages
   381  		if message.CmdClassifier != model.CmdClassifierTypeResult {
   382  			_ = remoteFeature.Device().Sender().ResultError(message.RequestHeader, localFeature.Address(), err)
   383  		}
   384  
   385  		// if this is an error for a notify message, automatically trigger a read request for the same
   386  		if message.CmdClassifier == model.CmdClassifierTypeNotify {
   387  			// set the command function to empty
   388  
   389  			if cmdData, err := message.Cmd.Data(); err == nil {
   390  				_, _ = localFeature.RequestRemoteData(*cmdData.Function, nil, nil, remoteFeature)
   391  			}
   392  		}
   393  
   394  		return errors.New(err.String())
   395  	}
   396  
   397  	ackRequest := message.RequestHeader.AckRequest
   398  	ackClassifiers := []model.CmdClassifierType{
   399  		model.CmdClassifierTypeCall,
   400  		model.CmdClassifierTypeReply,
   401  		model.CmdClassifierTypeNotify}
   402  
   403  	if ackRequest != nil && *ackRequest && slices.Contains(ackClassifiers, message.CmdClassifier) {
   404  		// return success as defined in SPINE chapter 5.2.4
   405  		_ = remoteFeature.Device().Sender().ResultSuccess(message.RequestHeader, localFeature.Address())
   406  	}
   407  
   408  	return nil
   409  }
   410  
   411  func (r *DeviceLocal) NodeManagement() api.NodeManagementInterface {
   412  	return r.nodeManagement
   413  }
   414  
   415  func (r *DeviceLocal) SubscriptionManager() api.SubscriptionManagerInterface {
   416  	return r.subscriptionManager
   417  }
   418  
   419  func (r *DeviceLocal) BindingManager() api.BindingManagerInterface {
   420  	return r.bindingManager
   421  }
   422  
   423  func (r *DeviceLocal) Information() *model.NodeManagementDetailedDiscoveryDeviceInformationType {
   424  	res := model.NodeManagementDetailedDiscoveryDeviceInformationType{
   425  		Description: &model.NetworkManagementDeviceDescriptionDataType{
   426  			DeviceAddress: &model.DeviceAddressType{
   427  				Device: r.address,
   428  			},
   429  			DeviceType:        r.dType,
   430  			NetworkFeatureSet: r.featureSet,
   431  		},
   432  	}
   433  	return &res
   434  }
   435  
   436  func (r *DeviceLocal) NotifySubscribers(featureAddress *model.FeatureAddressType, cmd model.CmdType) {
   437  	subscriptions := r.SubscriptionManager().SubscriptionsOnFeature(*featureAddress)
   438  	for _, subscription := range subscriptions {
   439  		// TODO: error handling
   440  		_, _ = subscription.ClientFeature.Device().Sender().Notify(subscription.ServerFeature.Address(), subscription.ClientFeature.Address(), cmd)
   441  	}
   442  }
   443  
   444  func (r *DeviceLocal) notifySubscribersOfEntity(entity api.EntityLocalInterface, state model.NetworkManagementStateChangeType) {
   445  	deviceInformation := r.Information()
   446  	entityInformation := *entity.Information()
   447  	entityInformation.Description.LastStateChange = &state
   448  
   449  	var featureInformation []model.NodeManagementDetailedDiscoveryFeatureInformationType
   450  	if state == model.NetworkManagementStateChangeTypeAdded {
   451  		for _, f := range entity.Features() {
   452  			featureInformation = append(featureInformation, *f.Information())
   453  		}
   454  	}
   455  
   456  	cmd := model.CmdType{
   457  		Function: util.Ptr(model.FunctionTypeNodeManagementDetailedDiscoveryData),
   458  		Filter:   filterEmptyPartial(),
   459  		NodeManagementDetailedDiscoveryData: &model.NodeManagementDetailedDiscoveryDataType{
   460  			SpecificationVersionList: &model.NodeManagementSpecificationVersionListType{
   461  				SpecificationVersion: []model.SpecificationVersionDataType{model.SpecificationVersionDataType(SpecificationVersion)},
   462  			},
   463  			DeviceInformation:  deviceInformation,
   464  			EntityInformation:  []model.NodeManagementDetailedDiscoveryEntityInformationType{entityInformation},
   465  			FeatureInformation: featureInformation,
   466  		},
   467  	}
   468  
   469  	r.NotifySubscribers(r.nodeManagement.Address(), cmd)
   470  }
   471  
   472  func (r *DeviceLocal) addDeviceInformation() {
   473  	entityType := model.EntityTypeTypeDeviceInformation
   474  	entity := NewEntityLocal(r, entityType, []model.AddressEntityType{model.AddressEntityType(DeviceInformationEntityId)}, 0)
   475  
   476  	{
   477  		r.nodeManagement = NewNodeManagement(entity.NextFeatureId(), entity)
   478  		entity.AddFeature(r.nodeManagement)
   479  	}
   480  	{
   481  		f := NewFeatureLocal(entity.NextFeatureId(), entity, model.FeatureTypeTypeDeviceClassification, model.RoleTypeServer)
   482  
   483  		f.AddFunctionType(model.FunctionTypeDeviceClassificationManufacturerData, true, false)
   484  
   485  		manufacturerData := &model.DeviceClassificationManufacturerDataType{
   486  			BrandName:    util.Ptr(model.DeviceClassificationStringType(r.brandName)),
   487  			VendorName:   util.Ptr(model.DeviceClassificationStringType(r.brandName)),
   488  			DeviceName:   util.Ptr(model.DeviceClassificationStringType(r.deviceModel)),
   489  			DeviceCode:   util.Ptr(model.DeviceClassificationStringType(r.deviceCode)),
   490  			SerialNumber: util.Ptr(model.DeviceClassificationStringType(r.serialNumber)),
   491  		}
   492  		f.SetData(model.FunctionTypeDeviceClassificationManufacturerData, manufacturerData)
   493  
   494  		entity.AddFeature(f)
   495  	}
   496  
   497  	r.entities = append(r.entities, entity)
   498  }