From ea0acaf039b0b85321e40a4bf67a982260aa1e6a Mon Sep 17 00:00:00 2001 From: Jozef Kralik Date: Wed, 17 Jan 2024 15:24:15 +0000 Subject: [PATCH] cleanup interfaces --- bridge/device/device.go | 67 +++++++++++++++++++++-------- bridge/resources/device/resource.go | 50 +++++++++++++++++---- bridge/resources/resource.go | 24 +++++++---- bridge/service/service.go | 66 ++++++++++++++-------------- cmd/ocfbridge/main.go | 10 +++-- 5 files changed, 147 insertions(+), 70 deletions(-) diff --git a/bridge/device/device.go b/bridge/device/device.go index 580dd8c4..5b9cc632 100644 --- a/bridge/device/device.go +++ b/bridge/device/device.go @@ -38,9 +38,21 @@ import ( "github.com/plgd-dev/go-coap/v3/pkg/sync" ) +type Resource interface { + Close() + ETag() []byte + GetHref() string + GetResourceTypes() []string + GetResourceInterfaces() []string + HandleRequest(req *net.Request) (*pool.Message, error) + GetPolicyBitMask() schema.BitMask + SetObserveHandler(createSubscription resources.CreateSubscriptionFunc) + UpdateETag() +} + type Device struct { cfg Config - resources *sync.Map[string, *resources.Resource] + resources *sync.Map[string, Resource] cloudManager *cloud.Manager onDeviceUpdated func(d *Device) } @@ -95,7 +107,7 @@ func (cfg *Config) Validate() error { return nil } -func New(cfg Config, onDeviceUpdated func(d *Device)) *Device { +func New(cfg Config, onDeviceUpdated func(d *Device), additionalProperties resourcesDevice.GetAdditionalPropertiesForResponseFunc) *Device { if onDeviceUpdated == nil { onDeviceUpdated = func(d *Device) { // do nothing @@ -104,23 +116,23 @@ func New(cfg Config, onDeviceUpdated func(d *Device)) *Device { cfg.ResourceTypes = resources.Unique(append(cfg.ResourceTypes, plgdDevice.ResourceType)) d := &Device{ cfg: cfg, - resources: sync.NewMap[string, *resources.Resource](), + resources: sync.NewMap[string, Resource](), onDeviceUpdated: onDeviceUpdated, } - d.AddResource(resourcesDevice.New(plgdDevice.ResourceURI, d).Resource) + d.AddResource(resourcesDevice.New(plgdDevice.ResourceURI, d, additionalProperties)) if cfg.Cloud.Enabled { d.cloudManager = cloud.New(d.cfg.ID, func() { d.onDeviceUpdated(d) }, d.HandleRequest, d.GetLinksFilteredBy, cfg.MaxMessageSize) - d.AddResource(cloudResource.New(cloudSchema.ResourceURI, d.cloudManager).Resource) + d.AddResource(cloudResource.New(cloudSchema.ResourceURI, d.cloudManager)) d.cloudManager.ImportConfig(cfg.Cloud.Config) } return d } -func (d *Device) AddResource(resource *resources.Resource) { - d.resources.Store(resource.Href, resource) +func (d *Device) AddResource(resource Resource) { + d.resources.Store(resource.GetHref(), resource) } func (d *Device) Init() { @@ -133,12 +145,23 @@ func (d *Device) UnregisterFromCloud() { } } -func (d *Device) Range(f func(key string, resource *resources.Resource) bool) { +func (d *Device) Range(f func(resourceHref string, resource Resource) bool) { d.resources.Range(f) } -func (d *Device) GetResource(key string) (*resources.Resource, bool) { - return d.resources.Load(key) +func (d *Device) GetResource(resourceHref string) (Resource, bool) { + return d.resources.Load(resourceHref) +} + +func hasResourceTypes(resourceTypes []string, oneOf []string) bool { + for _, rt := range oneOf { + for _, rrt := range resourceTypes { + if rt == rrt { + return true + } + } + } + return false } func (d *Device) GetLinksFilteredBy(endpoints schema.Endpoints, deviceIDfilter uuid.UUID, resourceTypesFitler []string, policyBitMaskFitler schema.BitMask) (links schema.ResourceLinks) { @@ -147,19 +170,19 @@ func (d *Device) GetLinksFilteredBy(endpoints schema.Endpoints, deviceIDfilter u return nil } links = make(schema.ResourceLinks, 0, d.resources.Length()) - d.resources.Range(func(key string, resource *resources.Resource) bool { - if len(resourceTypesFitler) > 0 && !resource.HasResourceTypes(resourceTypesFitler) { + d.resources.Range(func(key string, resource Resource) bool { + if len(resourceTypesFitler) > 0 && !hasResourceTypes(resource.GetResourceTypes(), resourceTypesFitler) { return true } - if policyBitMaskFitler != 0 && resource.PolicyBitMask&policyBitMaskFitler == 0 { + if policyBitMaskFitler != 0 && resource.GetPolicyBitMask()&policyBitMaskFitler == 0 { return true } links = append(links, schema.ResourceLink{ Href: key, - ResourceTypes: resource.ResourceTypes, - Interfaces: resource.ResourceInterfaces, + ResourceTypes: resource.GetResourceTypes(), + Interfaces: resource.GetResourceInterfaces(), Policy: &schema.Policy{ - BitMask: resource.PolicyBitMask & (^resources.PublishToCloud), + BitMask: resource.GetPolicyBitMask() & (^resources.PublishToCloud), }, Anchor: "ocf://" + d.GetID().String(), DeviceID: d.GetID().String(), @@ -174,8 +197,16 @@ func (d *Device) GetLinks(request *net.Request) (links schema.ResourceLinks) { return d.GetLinksFilteredBy(request.Endpoints, request.DeviceID(), request.ResourceTypes(), 0) } -func (d *Device) RemoveResource(resource *resources.Resource) { - d.resources.Delete(resource.Href) +func (d *Device) LoadAndDeleteResource(resourceHref string) (Resource, bool) { + return d.resources.LoadAndDelete(resourceHref) +} + +func (d *Device) CloseAndDeleteResource(resourceHref string) bool { + r, ok := d.resources.LoadAndDelete(resourceHref) + if ok { + r.Close() + } + return ok } func createResponseNotFound(ctx context.Context, uri string, token message.Token) *pool.Message { diff --git a/bridge/resources/device/resource.go b/bridge/resources/device/resource.go index 2ddda04d..d47c4225 100644 --- a/bridge/resources/device/resource.go +++ b/bridge/resources/device/resource.go @@ -39,21 +39,55 @@ type Device interface { GetProtocolIndependentID() uuid.UUID } +type GetAdditionalPropertiesForResponseFunc func() map[string]interface{} + type Resource struct { *resources.Resource - device Device + device Device + getAdditionalProperties GetAdditionalPropertiesForResponseFunc } -func New(uri string, dev Device) *Resource { +func New(uri string, dev Device, getAdditionalProperties GetAdditionalPropertiesForResponseFunc) *Resource { + if getAdditionalProperties == nil { + getAdditionalProperties = func() map[string]interface{} { return nil } + } d := &Resource{ - device: dev, + device: dev, + getAdditionalProperties: getAdditionalProperties, } d.Resource = resources.NewResource(uri, d.Get, nil, dev.GetResourceTypes(), []string{interfaces.OC_IF_BASELINE, interfaces.OC_IF_R}) return d } +func mergeCBORStructs(a ...interface{}) interface{} { + var merged map[interface{}]interface{} + for _, v := range a { + if v == nil { + continue + } + data, err := cbor.Encode(v) + if err != nil { + continue + } + var m map[interface{}]interface{} + err = cbor.Decode(data, &m) + if err != nil { + continue + } + if merged == nil { + merged = m + } else { + for k, v := range m { + merged[k] = v + } + } + } + return merged +} + func (d *Resource) Get(request *net.Request) (*pool.Message, error) { - v := device.Device{ + additionalProperties := d.getAdditionalProperties() + deviceProperties := device.Device{ ID: d.device.GetID().String(), Name: d.device.GetName(), ProtocolIndependentID: d.device.GetProtocolIndependentID().String(), @@ -61,14 +95,14 @@ func (d *Resource) Get(request *net.Request) (*pool.Message, error) { //SpecificationVersion: "ocf.2.0.5", } if request.Interface() == interfaces.OC_IF_BASELINE { - v.ResourceTypes = d.Resource.ResourceTypes - v.Interfaces = d.Resource.ResourceInterfaces + deviceProperties.ResourceTypes = d.Resource.ResourceTypes + deviceProperties.Interfaces = d.Resource.ResourceInterfaces } - + properties := mergeCBORStructs(additionalProperties, deviceProperties) res := pool.NewMessage(request.Context()) res.SetCode(codes.Content) res.SetContentFormat(message.AppOcfCbor) - data, err := cbor.Encode(v) + data, err := cbor.Encode(properties) if err != nil { return nil, err } diff --git a/bridge/resources/resource.go b/bridge/resources/resource.go index 0ea0bee8..dd695e6b 100644 --- a/bridge/resources/resource.go +++ b/bridge/resources/resource.go @@ -73,15 +73,20 @@ type Resource struct { etag atomic.Uint64 } -func (r *Resource) HasResourceTypes(resourceTypes []string) bool { - for _, rt := range resourceTypes { - for _, rrt := range r.ResourceTypes { - if rt == rrt { - return true - } - } - } - return false +func (r *Resource) GetPolicyBitMask() schema.BitMask { + return r.PolicyBitMask +} + +func (r *Resource) GetHref() string { + return r.Href +} + +func (r *Resource) GetResourceTypes() []string { + return r.ResourceTypes +} + +func (r *Resource) GetResourceInterfaces() []string { + return r.ResourceInterfaces } func Unique(strSlice []string) []string { @@ -165,6 +170,7 @@ func (r *Resource) wakeWatchSubscriptions() { } } +// Close closes resource and cancel all subscriptions func (r *Resource) Close() { if !r.closed.CompareAndSwap(false, true) { return diff --git a/bridge/service/service.go b/bridge/service/service.go index 2b2e24fa..8c586777 100644 --- a/bridge/service/service.go +++ b/bridge/service/service.go @@ -32,23 +32,24 @@ type Device interface { HandleRequest(req *net.Request) (*pool.Message, error) - Range(f func(key string, resource *resources.Resource) bool) - AddResource(resource *resources.Resource) - RemoveResource(resource *resources.Resource) - GetResource(key string) (*resources.Resource, bool) + Range(f func(key string, resource device.Resource) bool) + AddResource(resource device.Resource) + LoadAndDeleteResource(resourceHref string) (device.Resource, bool) + CloseAndDeleteResource(resourceHref string) bool + GetResource(resourceHref string) (device.Resource, bool) UnregisterFromCloud() // unregister device from cloud } -type Service[D Device] struct { +type Service struct { cfg Config net *net.Net - devices *coapSync.Map[uuid.UUID, D] + devices *coapSync.Map[uuid.UUID, Device] done chan struct{} onDiscoveryDevices func(req *net.Request) } -func (c *Service[D]) LoadDevice(di uuid.UUID) (D, error) { +func (c *Service) LoadDevice(di uuid.UUID) (Device, error) { d, ok := c.devices.Load(di) if !ok { return d, fmt.Errorf("invalid queries: device with di %v not found", di) @@ -56,7 +57,7 @@ func (c *Service[D]) LoadDevice(di uuid.UUID) (D, error) { return d, nil } -func (c *Service[D]) handleDiscoverAllLinks(req *net.Request) (*pool.Message, error) { +func (c *Service) handleDiscoverAllLinks(req *net.Request) (*pool.Message, error) { if req.Message.Type() != message.Acknowledgement && req.Message.Type() != message.Reset { // discovery is only allowed for CON, NON, UNSET messages c.onDiscoveryDevices(req) @@ -87,7 +88,7 @@ func (c *Service[D]) handleDiscoverAllLinks(req *net.Request) (*pool.Message, er return res.Get(req) } -func (c *Service[D]) DefaultRequestHandler(req *net.Request) (*pool.Message, error) { +func (c *Service) DefaultRequestHandler(req *net.Request) (*pool.Message, error) { uriPath := req.URIPath() if uriPath == "" { return nil, nil @@ -109,27 +110,27 @@ func (c *Service[D]) DefaultRequestHandler(req *net.Request) (*pool.Message, err return d.HandleRequest(req) } -type OptionsCfg[D Device] struct { +type OptionsCfg struct { OnDiscoveryDevices func(req *net.Request) } -func WithOnDiscoveryDevices[D Device](f func(req *net.Request)) Option[D] { - return func(o *OptionsCfg[D]) { +func WithOnDiscoveryDevices(f func(req *net.Request)) Option { + return func(o *OptionsCfg) { if f != nil { o.OnDiscoveryDevices = f } } } -type Option[D Device] func(*OptionsCfg[D]) +type Option func(*OptionsCfg) -func New[D Device](cfg Config, opts ...Option[D]) (*Service[D], error) { +func New(cfg Config, opts ...Option) (*Service, error) { err := cfg.Validate() if err != nil { return nil, err } - o := OptionsCfg[D]{ + o := OptionsCfg{ OnDiscoveryDevices: func(req *net.Request) { // nothing to do }, @@ -138,9 +139,9 @@ func New[D Device](cfg Config, opts ...Option[D]) (*Service[D], error) { opt(&o) } - c := Service[D]{ + c := Service{ cfg: cfg, - devices: coapSync.NewMap[uuid.UUID, D](), + devices: coapSync.NewMap[uuid.UUID, Device](), onDiscoveryDevices: o.OnDiscoveryDevices, } n, err := net.New(cfg.API.CoAP.Config, c.DefaultRequestHandler) @@ -152,16 +153,16 @@ func New[D Device](cfg Config, opts ...Option[D]) (*Service[D], error) { return &c, nil } -func (c *Service[D]) Serve() error { +func (c *Service) Serve() error { defer close(c.done) return c.net.Serve() } -type NewDeviceFunc[D Device] func(id uuid.UUID, piid uuid.UUID) D +type NewDeviceFunc func(id uuid.UUID, piid uuid.UUID) Device -func (c *Service[D]) CreateDevice(id uuid.UUID, newDevice NewDeviceFunc[D]) (D, bool) { - var d D - _, oldLoaded := c.devices.ReplaceWithFunc(id, func(oldValue D, oldLoaded bool) (newValue D, doDelete bool) { +func (c *Service) CreateDevice(id uuid.UUID, newDevice NewDeviceFunc) (Device, bool) { + var d Device + _, oldLoaded := c.devices.ReplaceWithFunc(id, func(oldValue Device, oldLoaded bool) (newValue Device, doDelete bool) { if oldLoaded { return oldValue, false } @@ -174,41 +175,42 @@ func (c *Service[D]) CreateDevice(id uuid.UUID, newDevice NewDeviceFunc[D]) (D, return d, true } -func (c *Service[D]) GetOrCreateDevice(id uuid.UUID, newDevice NewDeviceFunc[D]) (d D, loaded bool) { - return c.devices.LoadOrStoreWithFunc(id, func(value D) D { +func (c *Service) GetOrCreateDevice(id uuid.UUID, newDevice NewDeviceFunc) (d Device, loaded bool) { + return c.devices.LoadOrStoreWithFunc(id, func(value Device) Device { return value - }, func() D { + }, func() Device { return newDevice(id, resources.ToUUID(c.cfg.API.CoAP.ID)) }) } -func (c *Service[D]) GetDevice(id uuid.UUID) (D, bool) { +func (c *Service) GetDevice(id uuid.UUID) (Device, bool) { return c.devices.Load(id) } -func (c *Service[D]) CopyDevices() map[uuid.UUID]D { +func (c *Service) CopyDevices() map[uuid.UUID]Device { return c.devices.CopyData() } -func (c *Service[D]) Range(f func(key uuid.UUID, value D) bool) { +func (c *Service) Range(f func(key uuid.UUID, value Device) bool) { c.devices.Range(f) } -func (c *Service[D]) RangeWithLock(f func(key uuid.UUID, value D) bool) { +func (c *Service) RangeWithLock(f func(key uuid.UUID, value Device) bool) { c.devices.Range2(f) } -func (c *Service[D]) Length() int { +func (c *Service) Length() int { return c.devices.Length() } -func (c *Service[D]) GetAndDeleteDevice(id uuid.UUID) (D, bool) { +func (c *Service) GetAndDeleteDevice(id uuid.UUID) (Device, bool) { return c.devices.LoadAndDelete(id) } -func (c *Service[D]) DeleteAndCloseDevice(id uuid.UUID) { +func (c *Service) DeleteAndCloseDevice(id uuid.UUID) bool { d, ok := c.devices.LoadAndDelete(id) if ok { d.Close() } + return ok } diff --git a/cmd/ocfbridge/main.go b/cmd/ocfbridge/main.go index d62e97d5..5ae3f31b 100644 --- a/cmd/ocfbridge/main.go +++ b/cmd/ocfbridge/main.go @@ -37,12 +37,12 @@ func main() { if err != nil { panic(err) } - s, err := service.New[*device.Device](cfg.Config) + s, err := service.New(cfg.Config) if err != nil { panic(err) } for i := 0; i < cfg.NumGeneratedBridgedDevices; i++ { - newDevice := func(id uuid.UUID, piid uuid.UUID) *device.Device { + newDevice := func(id uuid.UUID, piid uuid.UUID) service.Device { d := device.New(device.Config{ Name: fmt.Sprintf("bridged-device-%d", i), ResourceTypes: []string{"oic.d.virtual"}, @@ -52,7 +52,11 @@ func main() { Cloud: device.CloudConfig{ Enabled: true, }, - }, nil) + }, nil, func() map[string]interface{} { + return map[string]interface{}{ + "my-property": "my-value", + } + }) return d } d, ok := s.CreateDevice(uuid.New(), newDevice)