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 }