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  }