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 }