github.com/enbility/spine-go@v0.7.0/spine/binding_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 BindingManager struct { 17 localDevice api.DeviceLocalInterface 18 19 bindingNum uint64 20 bindingEntries []*api.BindingEntry 21 22 mux sync.Mutex 23 // TODO: add persistence 24 } 25 26 func NewBindingManager(localDevice api.DeviceLocalInterface) *BindingManager { 27 c := &BindingManager{ 28 bindingNum: 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 *BindingManager) AddBinding(remoteDevice api.DeviceRemoteInterface, data model.BindingManagementRequestCallType) 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 data.ServerFeatureType == nil { 42 return errors.New("serverFeatureType is missing but required") 43 } 44 if err := c.checkRoleAndType(serverFeature, model.RoleTypeServer, *data.ServerFeatureType); err != nil { 45 return err 46 } 47 48 // a local feature can only have one remote binding 49 bindings := c.BindingsOnFeature(*serverFeature.Address()) 50 if len(bindings) > 0 { 51 return errors.New("the server feature already has a binding") 52 } 53 54 clientFeature := remoteDevice.FeatureByAddress(data.ClientAddress) 55 if clientFeature == nil { 56 return fmt.Errorf("client feature '%s' in remote device '%s' not found", data.ClientAddress, *remoteDevice.Address()) 57 } 58 if err := c.checkRoleAndType(clientFeature, model.RoleTypeClient, *data.ServerFeatureType); err != nil { 59 return err 60 } 61 62 bindingEntry := &api.BindingEntry{ 63 Id: c.bindingId(), 64 ServerFeature: serverFeature, 65 ClientFeature: clientFeature, 66 } 67 68 c.mux.Lock() 69 defer c.mux.Unlock() 70 71 c.bindingEntries = append(c.bindingEntries, bindingEntry) 72 73 payload := api.EventPayload{ 74 Ski: remoteDevice.Ski(), 75 EventType: api.EventTypeBindingChange, 76 ChangeType: api.ElementChangeAdd, 77 Data: data, 78 Device: remoteDevice, 79 Entity: clientFeature.Entity(), 80 Feature: clientFeature, 81 LocalFeature: serverFeature, 82 } 83 Events.Publish(payload) 84 85 return nil 86 } 87 88 func (c *BindingManager) RemoveBinding(data model.BindingManagementDeleteCallType, remoteDevice api.DeviceRemoteInterface) error { 89 var newBindingEntries []*api.BindingEntry 90 91 // according to the spec 7.4.4 92 // a. The absence of "bindingDelete. clientAddress. device" SHALL be treated as if it was 93 // present and set to the sender's "device" address part. 94 // b. The absence of "bindingDelete. serverAddress. device" SHALL be treated as if it was 95 // present and set to the recipient's "device" address part. 96 97 var clientAddress model.FeatureAddressType 98 util.DeepCopy(data.ClientAddress, &clientAddress) 99 if data.ClientAddress.Device == nil { 100 clientAddress.Device = remoteDevice.Address() 101 } 102 103 clientFeature := remoteDevice.FeatureByAddress(data.ClientAddress) 104 if clientFeature == nil { 105 return fmt.Errorf("client feature '%s' in remote device '%s' not found", data.ClientAddress, *remoteDevice.Address()) 106 } 107 108 serverFeature := c.localDevice.FeatureByAddress(data.ServerAddress) 109 if serverFeature == nil { 110 return fmt.Errorf("server feature '%s' in local device '%s' not found", data.ServerAddress, *c.localDevice.Address()) 111 } 112 113 if err := c.checkRoleAndType(serverFeature, model.RoleTypeServer, serverFeature.Type()); err != nil { 114 return err 115 } 116 117 if !c.HasLocalFeatureRemoteBinding(serverFeature.Address(), clientFeature.Address()) { 118 return fmt.Errorf("the feature '%s' address has no binding", data.ClientAddress) 119 } 120 121 c.mux.Lock() 122 defer c.mux.Unlock() 123 124 for _, item := range c.bindingEntries { 125 itemAddress := item.ClientFeature.Address() 126 127 if !reflect.DeepEqual(*itemAddress, clientAddress) && 128 !reflect.DeepEqual(item.ServerFeature, serverFeature) { 129 newBindingEntries = append(newBindingEntries, item) 130 } 131 } 132 133 if len(newBindingEntries) == len(c.bindingEntries) { 134 return errors.New("could not find requested binding to be removed") 135 } 136 137 c.bindingEntries = newBindingEntries 138 139 payload := api.EventPayload{ 140 Ski: remoteDevice.Ski(), 141 EventType: api.EventTypeBindingChange, 142 ChangeType: api.ElementChangeRemove, 143 Data: data, 144 Device: remoteDevice, 145 Entity: clientFeature.Entity(), 146 Feature: clientFeature, 147 LocalFeature: serverFeature, 148 } 149 Events.Publish(payload) 150 151 return nil 152 } 153 154 // Remove all existing bindings for a given remote device 155 func (c *BindingManager) RemoveBindingsForDevice(remoteDevice api.DeviceRemoteInterface) { 156 if remoteDevice == nil { 157 return 158 } 159 160 for _, entity := range remoteDevice.Entities() { 161 c.RemoveBindingsForEntity(entity) 162 } 163 } 164 165 // Remove all existing bindings for a given remote device entity 166 func (c *BindingManager) RemoveBindingsForEntity(remoteEntity api.EntityRemoteInterface) { 167 if remoteEntity == nil { 168 return 169 } 170 171 c.mux.Lock() 172 defer c.mux.Unlock() 173 174 var newBindingEntries []*api.BindingEntry 175 for _, item := range c.bindingEntries { 176 if !reflect.DeepEqual(item.ClientFeature.Address().Entity, remoteEntity.Address().Entity) { 177 newBindingEntries = append(newBindingEntries, item) 178 continue 179 } 180 181 serverFeature := c.localDevice.FeatureByAddress(item.ServerFeature.Address()) 182 clientFeature := remoteEntity.FeatureOfAddress(item.ClientFeature.Address().Feature) 183 payload := api.EventPayload{ 184 Ski: remoteEntity.Device().Ski(), 185 EventType: api.EventTypeBindingChange, 186 ChangeType: api.ElementChangeRemove, 187 Device: remoteEntity.Device(), 188 Entity: remoteEntity, 189 Feature: clientFeature, 190 LocalFeature: serverFeature, 191 } 192 Events.Publish(payload) 193 } 194 195 c.bindingEntries = newBindingEntries 196 } 197 198 func (c *BindingManager) Bindings(remoteDevice api.DeviceRemoteInterface) []*api.BindingEntry { 199 var result []*api.BindingEntry 200 201 c.mux.Lock() 202 defer c.mux.Unlock() 203 204 linq.From(c.bindingEntries).WhereT(func(s *api.BindingEntry) bool { 205 return s.ClientFeature.Device().Ski() == remoteDevice.Ski() 206 }).ToSlice(&result) 207 208 return result 209 } 210 211 // checks if a remote address has a binding on the local feature 212 func (c *BindingManager) HasLocalFeatureRemoteBinding(localAddress, remoteAddress *model.FeatureAddressType) bool { 213 bindings := c.BindingsOnFeature(*localAddress) 214 215 for _, item := range bindings { 216 if reflect.DeepEqual(item.ClientFeature.Address(), remoteAddress) { 217 return true 218 } 219 } 220 221 return false 222 } 223 224 func (c *BindingManager) BindingsOnFeature(featureAddress model.FeatureAddressType) []*api.BindingEntry { 225 var result []*api.BindingEntry 226 227 c.mux.Lock() 228 defer c.mux.Unlock() 229 230 linq.From(c.bindingEntries).WhereT(func(s *api.BindingEntry) bool { 231 return reflect.DeepEqual(*s.ServerFeature.Address(), featureAddress) 232 }).ToSlice(&result) 233 234 return result 235 } 236 237 func (c *BindingManager) bindingId() uint64 { 238 i := atomic.AddUint64(&c.bindingNum, 1) 239 return i 240 } 241 242 func (c *BindingManager) checkRoleAndType(feature api.FeatureInterface, role model.RoleType, featureType model.FeatureTypeType) error { 243 if feature.Role() != model.RoleTypeSpecial && feature.Role() != role { 244 return fmt.Errorf("found feature %s is not matching required role %s", feature.Type(), role) 245 } 246 247 if feature.Type() != featureType && feature.Type() != model.FeatureTypeTypeGeneric { 248 return fmt.Errorf("found feature %s is not matching required type %s", feature.Type(), featureType) 249 } 250 251 return nil 252 }