mirror of
https://github.com/mainflux/mainflux.git
synced 2025-04-29 13:49:28 +08:00

* 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>
274 lines
6.5 KiB
Go
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()
|
|
}
|