mirror of
https://github.com/mainflux/mainflux.git
synced 2025-05-02 22:17:10 +08:00
161 lines
3.9 KiB
Go
161 lines
3.9 KiB
Go
// Package coap contains the domain concept definitions needed to support
|
|
// Mainflux coap adapter service functionality. All constant values are taken
|
|
// from RFC, and could be adjusted based on specific use case.
|
|
package coap
|
|
|
|
import (
|
|
"errors"
|
|
"math"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/mainflux/mainflux"
|
|
"github.com/mainflux/mainflux/coap/nats"
|
|
broker "github.com/nats-io/go-nats"
|
|
)
|
|
|
|
const (
|
|
responseBackoffMultiplier = 1.5
|
|
|
|
// AckTimeout is the amount of time to wait for a response.
|
|
AckTimeout = int(2 * time.Second)
|
|
|
|
// MaxRetransmit is the maximum number of times a message will be retransmitted.
|
|
MaxRetransmit = 4
|
|
)
|
|
|
|
var (
|
|
// ErrFailedMessagePublish indicates that message publishing failed.
|
|
ErrFailedMessagePublish = errors.New("failed to publish message")
|
|
|
|
// ErrFailedSubscription indicates that client couldn't subscribe to specified channel.
|
|
ErrFailedSubscription = errors.New("failed to subscribe to a channel")
|
|
|
|
// ErrFailedConnection indicates that service couldn't connect to message broker.
|
|
ErrFailedConnection = errors.New("failed to connect to message broker")
|
|
|
|
// extracted to avoid recomputation
|
|
maxTimeout = int(float64(AckTimeout) * ((math.Pow(2, float64(MaxRetransmit))) - 1) * responseBackoffMultiplier)
|
|
)
|
|
|
|
// Service specifies coap service API.
|
|
type Service interface {
|
|
mainflux.MessagePublisher
|
|
|
|
// Subscribes to channel with specified id and adds subscription to
|
|
// service map of subscriptions under given ID.
|
|
Subscribe(string, string, nats.Channel) error
|
|
|
|
// Unsubscribe method is used to stop observing resource.
|
|
Unsubscribe(string)
|
|
|
|
// SetTimeout sets timeout to wait CONF messages.
|
|
SetTimeout(string, *time.Timer, int) (chan bool, error)
|
|
|
|
// RemoveTimeout removes timeout when ACK message is received from client
|
|
// if timeout existed.
|
|
RemoveTimeout(string)
|
|
}
|
|
|
|
var _ Service = (*adapterService)(nil)
|
|
|
|
type adapterService struct {
|
|
pubsub nats.Service
|
|
subs map[string]nats.Channel
|
|
mu sync.Mutex
|
|
}
|
|
|
|
// New instantiates the CoAP adapter implementation.
|
|
func New(pubsub nats.Service) Service {
|
|
return &adapterService{
|
|
pubsub: pubsub,
|
|
subs: make(map[string]nats.Channel),
|
|
mu: sync.Mutex{},
|
|
}
|
|
}
|
|
|
|
func (svc *adapterService) get(clientID string) (nats.Channel, bool) {
|
|
svc.mu.Lock()
|
|
obs, ok := svc.subs[clientID]
|
|
svc.mu.Unlock()
|
|
return obs, ok
|
|
}
|
|
|
|
func (svc *adapterService) put(clientID string, obs nats.Channel) {
|
|
svc.mu.Lock()
|
|
svc.subs[clientID] = obs
|
|
svc.mu.Unlock()
|
|
}
|
|
|
|
func (svc *adapterService) remove(clientID string) {
|
|
svc.mu.Lock()
|
|
obs, ok := svc.subs[clientID]
|
|
if ok {
|
|
obs.Closed <- true
|
|
delete(svc.subs, clientID)
|
|
}
|
|
svc.mu.Unlock()
|
|
}
|
|
|
|
func (svc *adapterService) Publish(msg mainflux.RawMessage) error {
|
|
if err := svc.pubsub.Publish(msg); err != nil {
|
|
switch err {
|
|
case broker.ErrConnectionClosed, broker.ErrInvalidConnection:
|
|
return ErrFailedConnection
|
|
default:
|
|
return ErrFailedMessagePublish
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (svc *adapterService) Subscribe(chanID, clientID string, ch nats.Channel) error {
|
|
// Remove entry if already exists.
|
|
svc.Unsubscribe(clientID)
|
|
if err := svc.pubsub.Subscribe(chanID, ch); err != nil {
|
|
return ErrFailedSubscription
|
|
}
|
|
svc.put(clientID, ch)
|
|
return nil
|
|
}
|
|
|
|
func (svc *adapterService) Unsubscribe(clientID string) {
|
|
svc.remove(clientID)
|
|
}
|
|
|
|
func (svc *adapterService) SetTimeout(clientID string, timer *time.Timer, duration int) (chan bool, error) {
|
|
sub, ok := svc.get(clientID)
|
|
if !ok {
|
|
return nil, errors.New("observer entry not found")
|
|
}
|
|
go func() {
|
|
for {
|
|
select {
|
|
case _, ok := <-sub.Timer:
|
|
timer.Stop()
|
|
if ok {
|
|
sub.Notify <- false
|
|
}
|
|
return
|
|
case <-timer.C:
|
|
duration *= 2
|
|
if duration >= maxTimeout {
|
|
timer.Stop()
|
|
sub.Notify <- false
|
|
svc.Unsubscribe(clientID)
|
|
return
|
|
}
|
|
timer.Reset(time.Duration(duration))
|
|
sub.Notify <- true
|
|
}
|
|
}
|
|
}()
|
|
return sub.Notify, nil
|
|
}
|
|
|
|
func (svc *adapterService) RemoveTimeout(clientID string) {
|
|
if sub, ok := svc.get(clientID); ok {
|
|
sub.Timer <- false
|
|
}
|
|
}
|