feat: add phone frequency controls and modularize codebase
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
package upstream
|
||||
|
||||
import (
|
||||
"cmpp-platform/gateway/internal/queue"
|
||||
"context"
|
||||
"fmt"
|
||||
cmpp "github.com/bigwhite/gocmpp"
|
||||
cmpputils "github.com/bigwhite/gocmpp/utils"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Receipt packets are correlated through the per-connection Submit tracker;
|
||||
// mobile-originated content uses a separate long-message assembly path.
|
||||
|
||||
type deliverPacket struct {
|
||||
seqID uint32
|
||||
msgID uint64
|
||||
destID string
|
||||
tpUdhi uint8
|
||||
msgFmt uint8
|
||||
srcTerminalID string
|
||||
registerDelivery uint8
|
||||
msgContent string
|
||||
}
|
||||
|
||||
func deliverPacketFromCMPP2(pkt *cmpp.Cmpp2DeliverReqPkt) deliverPacket {
|
||||
return deliverPacket{
|
||||
seqID: pkt.SeqId,
|
||||
msgID: pkt.MsgId,
|
||||
destID: pkt.DestId,
|
||||
tpUdhi: pkt.TpUdhi,
|
||||
msgFmt: pkt.MsgFmt,
|
||||
srcTerminalID: pkt.SrcTerminalId,
|
||||
registerDelivery: pkt.RegisterDelivery,
|
||||
msgContent: pkt.MsgContent,
|
||||
}
|
||||
}
|
||||
|
||||
func deliverPacketFromCMPP3(pkt *cmpp.Cmpp3DeliverReqPkt) deliverPacket {
|
||||
return deliverPacket{
|
||||
seqID: pkt.SeqId,
|
||||
msgID: pkt.MsgId,
|
||||
destID: pkt.DestId,
|
||||
tpUdhi: pkt.TpUdhi,
|
||||
msgFmt: pkt.MsgFmt,
|
||||
srcTerminalID: pkt.SrcTerminalId,
|
||||
registerDelivery: pkt.RegisterDelivery,
|
||||
msgContent: pkt.MsgContent,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *connection) handleDeliver(pkt deliverPacket) error {
|
||||
if pkt.registerDelivery == 1 {
|
||||
var receipt cmpp.CmppReceiptPkt
|
||||
if err := receipt.Unpack([]byte(pkt.msgContent)); err != nil {
|
||||
log.Printf("protocol_event protocol=cmpp direction=channel_to_platform event=deliver_receipt status=parse_failed channel_id=%s sequence_id=%d packet_msg_id=%d error=%q", c.channelID, pkt.seqID, pkt.msgID, err)
|
||||
return err
|
||||
}
|
||||
log.Printf("protocol_event protocol=cmpp direction=channel_to_platform event=deliver_receipt status=received channel_id=%s sequence_id=%d gateway_message_id=%d raw_status=%s", c.channelID, pkt.seqID, receipt.MsgId, strings.TrimSpace(receipt.Stat))
|
||||
cmd, ok := c.commandFor(receipt.MsgId)
|
||||
if !ok {
|
||||
cmd, ok = c.commandFor(pkt.msgID)
|
||||
}
|
||||
traceID := fmt.Sprintf("receipt-%d", receipt.MsgId)
|
||||
messageID := fmt.Sprintf("receipt-%d", receipt.MsgId)
|
||||
channelID := c.channelID
|
||||
if ok {
|
||||
traceID = cmd.TraceID
|
||||
messageID = cmd.MessageID
|
||||
channelID = cmd.ChannelID
|
||||
}
|
||||
event := queue.ReceiptEvent{
|
||||
Envelope: queue.Envelope{
|
||||
SchemaVersion: queue.SchemaVersion,
|
||||
MessageType: queue.MessageTypeReceiptEvent,
|
||||
TraceID: traceID,
|
||||
MessageID: messageID,
|
||||
ChannelID: channelID,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
},
|
||||
SequenceID: pkt.seqID,
|
||||
GatewayMessageID: fmt.Sprint(receipt.MsgId),
|
||||
PhoneNumber: strings.TrimSpace(receipt.DestTerminalId),
|
||||
ReceiptStatus: receiptStatus(receipt.Stat),
|
||||
RawStatus: strings.TrimSpace(receipt.Stat),
|
||||
DeliveredAt: time.Now().UTC(),
|
||||
ConnectionID: c.identity(),
|
||||
}
|
||||
if err := postJSON(context.Background(), c.httpClient, c.apiBaseURL, "/gateway/events/receipt/intake", event); err != nil {
|
||||
log.Printf("protocol_event protocol=cmpp direction=gateway_to_api event=deliver_receipt status=forward_failed channel_id=%s sequence_id=%d gateway_message_id=%d error=%q", c.channelID, pkt.seqID, receipt.MsgId, err)
|
||||
return err
|
||||
} else {
|
||||
log.Printf("protocol_event protocol=cmpp direction=gateway_to_api event=deliver_receipt status=forwarded channel_id=%s sequence_id=%d gateway_message_id=%d", c.channelID, pkt.seqID, receipt.MsgId)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
content, complete, err := c.decodeUplinkContent(pkt)
|
||||
if err != nil {
|
||||
log.Printf("protocol_event protocol=cmpp direction=channel_to_platform event=deliver_uplink status=decode_failed channel_id=%s sequence_id=%d packet_msg_id=%d error=%q", c.channelID, pkt.seqID, pkt.msgID, err)
|
||||
return err
|
||||
}
|
||||
if !complete {
|
||||
return nil
|
||||
}
|
||||
cmd, _ := c.commandFor(pkt.msgID)
|
||||
event := queue.UplinkEvent{
|
||||
Envelope: queue.Envelope{
|
||||
SchemaVersion: queue.SchemaVersion,
|
||||
MessageType: queue.MessageTypeUplinkEvent,
|
||||
TraceID: cmd.TraceID,
|
||||
MessageID: cmd.MessageID,
|
||||
ChannelID: c.channelID,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
},
|
||||
SequenceID: pkt.seqID,
|
||||
PhoneNumber: strings.TrimSpace(pkt.srcTerminalID),
|
||||
DestID: strings.TrimSpace(pkt.destID),
|
||||
Content: content,
|
||||
ReceivedAt: time.Now().UTC(),
|
||||
}
|
||||
if err := postJSON(context.Background(), c.httpClient, c.apiBaseURL, "/gateway/events/uplink", event); err != nil {
|
||||
log.Printf("protocol_event protocol=cmpp direction=gateway_to_api event=deliver_uplink status=forward_failed channel_id=%s sequence_id=%d packet_msg_id=%d error=%q", c.channelID, pkt.seqID, pkt.msgID, err)
|
||||
} else {
|
||||
log.Printf("protocol_event protocol=cmpp direction=gateway_to_api event=deliver_uplink status=forwarded channel_id=%s sequence_id=%d packet_msg_id=%d", c.channelID, pkt.seqID, pkt.msgID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *connection) decodeUplinkContent(pkt deliverPacket) (string, bool, error) {
|
||||
if pkt.tpUdhi != 1 {
|
||||
content, err := decodeContent(pkt.msgFmt, pkt.msgContent)
|
||||
return content, true, err
|
||||
}
|
||||
ref, total, number, payload, ok := parseConcatSegment(pkt.msgContent)
|
||||
if !ok {
|
||||
content, err := decodeContent(pkt.msgFmt, pkt.msgContent)
|
||||
return content, true, err
|
||||
}
|
||||
key := fmt.Sprintf("%s:%s:%s:%d:%d", c.channelID, strings.TrimSpace(pkt.srcTerminalID), strings.TrimSpace(pkt.destID), ref, total)
|
||||
c.mu.Lock()
|
||||
if c.longUplink == nil {
|
||||
c.longUplink = make(map[string]*longUplinkAssembly)
|
||||
}
|
||||
pruneLongUplinkAssemblies(c.longUplink, time.Now(), 10*time.Minute)
|
||||
content, complete, err := assembleLongUplink(c.longUplink, key, pkt.msgFmt, total, number, payload)
|
||||
c.mu.Unlock()
|
||||
return content, complete, err
|
||||
}
|
||||
|
||||
func (c *connection) commandFor(gatewayMsgID uint64) (queue.SubmitCommand, bool) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
cmd, ok := c.tracker[gatewayMsgID]
|
||||
return cmd, ok
|
||||
}
|
||||
|
||||
func decodeContent(format uint8, content string) (string, error) {
|
||||
switch format {
|
||||
case 8:
|
||||
return cmpputils.Ucs2ToUtf8(content)
|
||||
case 15:
|
||||
return cmpputils.GB18030ToUtf8(content)
|
||||
default:
|
||||
return content, nil
|
||||
}
|
||||
}
|
||||
|
||||
func receiptStatus(stat string) string {
|
||||
switch strings.ToUpper(strings.TrimSpace(stat)) {
|
||||
case "DELIVRD":
|
||||
return "delivered"
|
||||
case "":
|
||||
return "unknown"
|
||||
default:
|
||||
return "undelivered"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user