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  }