github.com/enbility/spine-go@v0.7.0/spine/subscription_manager.go (about) 1 package spine 2 3 import ( 4 "errors" 5 "fmt" 6 "reflect" 7 "sync" 8 "sync/atomic" 9 10 "github.com/ahmetb/go-linq/v3" 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 SubscriptionManager struct { 17 localDevice api.DeviceLocalInterface 18 19 subscriptionNum uint64 20 subscriptionEntries []*api.SubscriptionEntry 21 22 mux sync.Mutex 23 // TODO: add persistence 24 } 25 26 func NewSubscriptionManager(localDevice api.DeviceLocalInterface) *SubscriptionManager { 27 c := &SubscriptionManager{ 28 subscriptionNum: 0, 29 localDevice: localDevice, 30 } 31 32 return c 33 } 34 35 // is sent from the client (remote device) to the server (local device) 36 func (c *SubscriptionManager) AddSubscription(remoteDevice api.DeviceRemoteInterface, data model.SubscriptionManagementRequestCallType) error { 37 serverFeature := c.localDevice.FeatureByAddress(data.ServerAddress) 38 if serverFeature == nil { 39 return fmt.Errorf("server feature '%s' in local device '%s' not found", data.ServerAddress, *c.localDevice.Address()) 40 } 41 if err := c.checkRoleAndType(serverFeature, model.RoleTypeServer, *data.ServerFeatureType); err != nil { 42 return err 43 } 44 45 clientFeature := remoteDevice.FeatureByAddress(data.ClientAddress) 46 if clientFeature == nil { 47 return fmt.Errorf("client feature '%s' in remote device '%s' not found", data.ClientAddress, *remoteDevice.Address()) 48 } 49 if err := c.checkRoleAndType(clientFeature, model.RoleTypeClient, *data.ServerFeatureType); err != nil { 50 return err 51 } 52 53 subscriptionEntry := &api.SubscriptionEntry{ 54 Id: c.subscriptionId(), 55 ServerFeature: serverFeature, 56 ClientFeature: clientFeature, 57 } 58 59 c.mux.Lock() 60 defer c.mux.Unlock() 61 62 for _, item := range c.subscriptionEntries { 63 if reflect.DeepEqual(item.ServerFeature, serverFeature) && reflect.DeepEqual(item.ClientFeature, clientFeature) { 64 return fmt.Errorf("requested subscription is already present") 65 } 66 } 67 68 c.subscriptionEntries = append(c.subscriptionEntries, subscriptionEntry) 69 70 payload := api.EventPayload{ 71 Ski: remoteDevice.Ski(), 72 EventType: api.EventTypeSubscriptionChange, 73 ChangeType: api.ElementChangeAdd, 74 Data: data, 75 Device: remoteDevice, 76 Entity: clientFeature.Entity(), 77 Feature: clientFeature, 78 LocalFeature: serverFeature, 79 } 80 Events.Publish(payload) 81 82 return nil 83 } 84 85 // Remove a specific subscription that is provided by a delete message from a remote device 86 func (c *SubscriptionManager) RemoveSubscription(data model.SubscriptionManagementDeleteCallType, remoteDevice api.DeviceRemoteInterface) error { 87 var newSubscriptionEntries []*api.SubscriptionEntry 88 89 // according to the spec 7.4.4 90 // a. The absence of "subscriptionDelete. clientAddress. device" SHALL be treated as if it was 91 // present and set to the sender's "device" address part. 92 // b. The absence of "subscriptionDelete. serverAddress. device" SHALL be treated as if it was 93 // present and set to the recipient's "device" address part. 94 95 var clientAddress model.FeatureAddressType 96 util.DeepCopy(data.ClientAddress, &clientAddress) 97 if data.ClientAddress.Device == nil { 98 clientAddress.Device = remoteDevice.Address() 99 } 100 101 clientFeature := remoteDevice.FeatureByAddress(data.ClientAddress) 102 if clientFeature == nil { 103 return fmt.Errorf("client feature '%s' in remote device '%s' not found", data.ClientAddress, *remoteDevice.Address()) 104 } 105 106 serverFeature := c.localDevice.FeatureByAddress(data.ServerAddress) 107 if serverFeature == nil { 108 return fmt.Errorf("server feature '%s' in local device '%s' not found", data.ServerAddress, *c.localDevice.Address()) 109 } 110 111 c.mux.Lock() 112 defer c.mux.Unlock() 113 114 for _, item := range c.subscriptionEntries { 115 itemAddress := item.ClientFeature.Address() 116 117 if !reflect.DeepEqual(itemAddress.Device, clientAddress.Device) || 118 !reflect.DeepEqual(itemAddress.Entity, clientAddress.Entity) || 119 !reflect.DeepEqual(itemAddress.Feature, clientAddress.Feature) || 120 !reflect.DeepEqual(item.ServerFeature, serverFeature) { 121 newSubscriptionEntries = append(newSubscriptionEntries, item) 122 } 123 } 124 125 if len(newSubscriptionEntries) == len(c.subscriptionEntries) { 126 return errors.New("could not find requested SubscriptionId to be removed") 127 } 128 129 c.subscriptionEntries = newSubscriptionEntries 130 131 payload := api.EventPayload{ 132 Ski: remoteDevice.Ski(), 133 EventType: api.EventTypeSubscriptionChange, 134 ChangeType: api.ElementChangeRemove, 135 Data: data, 136 Device: remoteDevice, 137 Entity: clientFeature.Entity(), 138 Feature: clientFeature, 139 LocalFeature: serverFeature, 140 } 141 Events.Publish(payload) 142 143 return nil 144 } 145 146 // Remove all existing subscriptions for a given remote device 147 func (c *SubscriptionManager) RemoveSubscriptionsForDevice(remoteDevice api.DeviceRemoteInterface) { 148 if remoteDevice == nil { 149 return 150 } 151 152 for _, entity := range remoteDevice.Entities() { 153 c.RemoveSubscriptionsForEntity(entity) 154 } 155 } 156 157 // Remove all existing subscriptions for a given remote device entity 158 func (c *SubscriptionManager) RemoveSubscriptionsForEntity(remoteEntity api.EntityRemoteInterface) { 159 if remoteEntity == nil { 160 return 161 } 162 163 c.mux.Lock() 164 defer c.mux.Unlock() 165 166 var newSubscriptionEntries []*api.SubscriptionEntry 167 for _, item := range c.subscriptionEntries { 168 if !reflect.DeepEqual(item.ClientFeature.Address().Device, remoteEntity.Address().Device) || 169 !reflect.DeepEqual(item.ClientFeature.Address().Entity, remoteEntity.Address().Entity) { 170 newSubscriptionEntries = append(newSubscriptionEntries, item) 171 continue 172 } 173 174 serverFeature := c.localDevice.FeatureByAddress(item.ServerFeature.Address()) 175 clientFeature := remoteEntity.FeatureOfAddress(item.ClientFeature.Address().Feature) 176 payload := api.EventPayload{ 177 Ski: remoteEntity.Device().Ski(), 178 EventType: api.EventTypeSubscriptionChange, 179 ChangeType: api.ElementChangeRemove, 180 Device: remoteEntity.Device(), 181 Entity: remoteEntity, 182 Feature: clientFeature, 183 LocalFeature: serverFeature, 184 } 185 Events.Publish(payload) 186 } 187 188 c.subscriptionEntries = newSubscriptionEntries 189 } 190 191 func (c *SubscriptionManager) Subscriptions(remoteDevice api.DeviceRemoteInterface) []*api.SubscriptionEntry { 192 var result []*api.SubscriptionEntry 193 194 c.mux.Lock() 195 defer c.mux.Unlock() 196 197 linq.From(c.subscriptionEntries).WhereT(func(s *api.SubscriptionEntry) bool { 198 return s.ClientFeature.Device().Ski() == remoteDevice.Ski() 199 }).ToSlice(&result) 200 201 return result 202 } 203 204 func (c *SubscriptionManager) SubscriptionsOnFeature(featureAddress model.FeatureAddressType) []*api.SubscriptionEntry { 205 var result []*api.SubscriptionEntry 206 207 c.mux.Lock() 208 defer c.mux.Unlock() 209 210 linq.From(c.subscriptionEntries).WhereT(func(s *api.SubscriptionEntry) bool { 211 return reflect.DeepEqual(*s.ServerFeature.Address(), featureAddress) 212 }).ToSlice(&result) 213 214 return result 215 } 216 217 func (c *SubscriptionManager) subscriptionId() uint64 { 218 i := atomic.AddUint64(&c.subscriptionNum, 1) 219 return i 220 } 221 222 func (c *SubscriptionManager) checkRoleAndType(feature api.FeatureInterface, role model.RoleType, featureType model.FeatureTypeType) error { 223 if feature.Role() != model.RoleTypeSpecial && feature.Role() != role { 224 return fmt.Errorf("found feature %s is not matching required role %s", feature.Type(), role) 225 } 226 227 if feature.Type() != featureType && feature.Type() != model.FeatureTypeTypeGeneric { 228 return fmt.Errorf("found feature %s is not matching required type %s", feature.Type(), featureType) 229 } 230 231 return nil 232 }