1
0
mirror of https://github.com/mainflux/mainflux.git synced 2025-04-29 13:49:28 +08:00
Ivan Milošević 66d3da0531
NOISSUE - Add SMPP notifier (#1464)
* Add SMPP notifier

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* fix readme
remove env file

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* resolve conversations

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* Remove debug log

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* Rename transmiter and transformer fields in struct

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* fix typo

Signed-off-by: Ivan Milosevic <iva@blokovi.com>
2021-10-06 14:34:23 +02:00

274 lines
6.5 KiB
Go

// Copyright 2015 go-smpp authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package smpp
import (
"bytes"
"crypto/tls"
"fmt"
"sync"
"time"
"github.com/fiorix/go-smpp/smpp/pdu"
"github.com/fiorix/go-smpp/smpp/pdu/pdufield"
)
// Receiver implements an SMPP client receiver.
type Receiver struct {
Addr string
User string
Passwd string
SystemType string
EnquireLink time.Duration
EnquireLinkTimeout time.Duration // Time after last EnquireLink response when connection considered down
BindInterval time.Duration // Binding retry interval
MergeInterval time.Duration // Time in which Receiver waits for the parts of the long messages
MergeCleanupInterval time.Duration // How often to cleanup expired message parts
TLS *tls.Config
Handler HandlerFunc
SkipAutoRespondIDs []pdu.ID
chanClose chan struct{}
// struct which holds the map of MergeHolders for the merging of the long incoming messages.
// It is used only if the incoming PDU holds UDH data and Receiver has MergeInterval > 0.
mg struct {
mergeHolders map[int]*MergeHolder
sync.Mutex
}
cl struct {
*client
sync.Mutex
}
}
// HandlerFunc is the handler function that a Receiver calls
// when a new PDU arrives.
type HandlerFunc func(p pdu.Body)
// MergeHolder is a struct which holds the slice of MessageParts for the merging of a long incoming message.
type MergeHolder struct {
MessageID int
MessageParts []*MessagePart // Slice with the parts of the message
PartsCount int
LastWriteTime time.Time
}
// MessagePart is a struct which holds the data of the part of a long incoming message.
type MessagePart struct {
PartID int
Data *bytes.Buffer
}
// Bind starts the Receiver. It creates a persistent connection
// to the server, update its status via the returned channel,
// and calls the registered Handler when new PDU arrives.
//
// Bind implements the ClientConn interface.
func (r *Receiver) Bind() <-chan ConnStatus {
r.cl.Lock()
defer r.cl.Unlock()
r.chanClose = make(chan struct{})
if r.cl.client != nil {
return r.cl.Status
}
c := &client{
Addr: r.Addr,
TLS: r.TLS,
EnquireLink: r.EnquireLink,
EnquireLinkTimeout: r.EnquireLinkTimeout,
Status: make(chan ConnStatus, 1),
BindFunc: r.bindFunc,
BindInterval: r.BindInterval,
}
r.cl.client = c
c.init()
go c.Bind()
// Set up message merging if requested
if r.MergeInterval > 0 {
if r.MergeCleanupInterval == 0 {
r.MergeCleanupInterval = 1 * time.Second
}
r.mg.mergeHolders = make(map[int]*MergeHolder)
go r.mergeCleaner()
}
return c.Status
}
func (r *Receiver) bindFunc(c Conn) error {
p := pdu.NewBindReceiver()
f := p.Fields()
f.Set(pdufield.SystemID, r.User)
f.Set(pdufield.Password, r.Passwd)
f.Set(pdufield.SystemType, r.SystemType)
resp, err := bind(c, p)
if err != nil {
return err
}
if resp.Header().ID != pdu.BindReceiverRespID {
return fmt.Errorf("unexpected response for BindReceiver: %s",
resp.Header().ID)
}
// Clean the map in case of rebind, because message id numbering resets after reconnection
// and older IDs are no longer valid
if r.MergeInterval > 0 {
r.mg.Lock()
r.mg.mergeHolders = make(map[int]*MergeHolder)
r.mg.Unlock()
}
if r.Handler != nil {
go r.handlePDU()
}
return nil
}
func idInList(id pdu.ID, list []pdu.ID) bool {
for _, x := range list {
if x == id {
return true
}
}
return false
}
func (r *Receiver) handlePDU() {
var (
ok bool
sm *pdufield.SM
udhList *pdufield.UDHList
msgID, partsCount int
mh *MergeHolder
orderedBodies []*bytes.Buffer
)
autoRespondDeliver := !idInList(pdu.DeliverSMID, r.SkipAutoRespondIDs)
loop:
for {
p, err := r.cl.Read()
if err != nil || p == nil {
break
}
if p.Header().ID == pdu.DeliverSMID && autoRespondDeliver { // Send DeliverSMResp
pResp := pdu.NewDeliverSMRespSeq(p.Header().Seq)
r.cl.Write(pResp)
}
if r.MergeInterval == 0 { // Handle the PDU if merging is not needed
r.Handler(p)
continue
}
sm, ok = p.Fields()[pdufield.ShortMessage].(*pdufield.SM)
if !ok {
// PDU is malformed, do not process
continue
}
udhList, ok = p.Fields()[pdufield.GSMUserData].(*pdufield.UDHList)
if !ok { // Check if GSMUserData is present inside the PDU, do not try to merge if it's not
r.Handler(p)
continue
}
for _, udh := range udhList.Data {
switch udh.IEI.Data {
case 0x00: // Concatenated short messages, 8-bit reference number
if int(udh.IELength.Data) != 3 { // Contains message ID, parts count and part number
// PDU is malformed, do not process
break
}
// Get message ID and total count of its parts
msgID = int(udh.IEData.Data[0])
partsCount = int(udh.IEData.Data[1])
// Check if message part was already added to a MergeHolder
r.mg.Lock()
if mh, ok = r.mg.mergeHolders[msgID]; !ok {
mh = &MergeHolder{
MessageID: msgID,
PartsCount: partsCount,
}
r.mg.mergeHolders[msgID] = mh
}
r.mg.Unlock()
// Add current part of the message to the slice
mh.MessageParts = append(mh.MessageParts, &MessagePart{
PartID: int(udh.IEData.Data[2]),
Data: bytes.NewBuffer(sm.Data),
})
mh.LastWriteTime = time.Now()
// Check if we have all the parts of the message
if len(mh.MessageParts) != mh.PartsCount {
continue loop
}
// Order up PDUs
orderedBodies = make([]*bytes.Buffer, partsCount)
for _, mp := range mh.MessageParts {
orderedBodies[mp.PartID-1] = mp.Data
}
// Merge PDUs
var buf bytes.Buffer
for _, body := range orderedBodies {
buf.Write(body.Bytes())
}
p.Fields().Set(pdufield.ShortMessage, buf.Bytes())
// Handle
r.Handler(p)
}
}
}
}
func (r *Receiver) mergeCleaner() {
timer := time.NewTimer(r.MergeCleanupInterval)
for {
select {
case <-timer.C:
r.mg.Lock()
for _, mHolder := range r.mg.mergeHolders {
if time.Since(mHolder.LastWriteTime) > r.MergeInterval { // Message has expired, remove
delete(r.mg.mergeHolders, mHolder.MessageID)
}
}
r.mg.Unlock()
case <-r.chanClose:
return
}
}
}
// Close implements the ClientConn interface.
func (r *Receiver) Close() error {
r.cl.Lock()
defer r.cl.Unlock()
if r.cl.client == nil {
return ErrNotConnected
}
close(r.chanClose)
return r.cl.Close()
}