feat: integrate analytics and fragment receipt improvements
This commit is contained in:
@@ -142,8 +142,13 @@ func pushReceiptWithResult(event DownstreamReceipt, allowRecovery bool) (Downstr
|
||||
when = parsed
|
||||
}
|
||||
}
|
||||
// Every inbound CMPP_SUBMIT fragment receives its own SUBMIT_RESP Msg_Id.
|
||||
// Rebuild that exact client Msg_Id from the fragment's original Sequence_Id;
|
||||
// the live session stores only connection state and must not collapse a long
|
||||
// message back to the first fragment's Msg_Id.
|
||||
receiptMessageID := downstreamReceiptMessageID(event, session)
|
||||
receipt := &cmpp.CmppReceiptPkt{
|
||||
MsgId: session.gatewayMsgID,
|
||||
MsgId: receiptMessageID,
|
||||
Stat: stat,
|
||||
SubmitTime: when.Format("0601021504"),
|
||||
DoneTime: when.Format("0601021504"),
|
||||
@@ -154,10 +159,20 @@ func pushReceiptWithResult(event DownstreamReceipt, allowRecovery bool) (Downstr
|
||||
if err != nil {
|
||||
return DownstreamSendResult{}, err
|
||||
}
|
||||
deliver := downstreamDeliverPacket(session, session.gatewayMsgID, session.srcID, defaultString(event.PhoneNumber, session.phoneNumber), 0, 1, string(receiptBytes))
|
||||
deliver := downstreamDeliverPacket(session, receiptMessageID, session.srcID, defaultString(event.PhoneNumber, session.phoneNumber), 0, 1, string(receiptBytes))
|
||||
return sendDownstream(session, deliver, event.DeliveryID)
|
||||
}
|
||||
|
||||
func downstreamReceiptMessageID(event DownstreamReceipt, session *downstreamSession) uint64 {
|
||||
if event.SubmitSequenceID != 0 {
|
||||
return messageIDFrom(defaultString(event.SubmitGroupMessageID, event.MessageID), event.SubmitSequenceID)
|
||||
}
|
||||
if session == nil {
|
||||
return 0
|
||||
}
|
||||
return session.gatewayMsgID
|
||||
}
|
||||
|
||||
func findReceiptSession(messageID string, account string) *downstreamSession {
|
||||
downstreamRegistry.RLock()
|
||||
defer downstreamRegistry.RUnlock()
|
||||
|
||||
@@ -364,6 +364,9 @@ func TestInboundServerForwardsLongMessageFragmentsWithoutUDHAndAcknowledgesEachS
|
||||
if submit.Content != parts[index] {
|
||||
t.Fatalf("fragment %d content = %q", index+1, submit.Content)
|
||||
}
|
||||
if submit.RegisteredDelivery != 1 {
|
||||
t.Fatalf("fragment %d Registered_Delivery = %d, want 1", index+1, submit.RegisteredDelivery)
|
||||
}
|
||||
if submit.LongMessage == nil || submit.LongMessage.Reference != 0x22 ||
|
||||
submit.LongMessage.Total != 2 || submit.LongMessage.Index != index+1 || submit.LongMessage.Format != 8 {
|
||||
t.Fatalf("fragment %d metadata = %+v", index+1, submit.LongMessage)
|
||||
@@ -1015,6 +1018,22 @@ func TestReceiptLookupDoesNotFallbackToAccountBeforeSubmitMappingExists(t *testi
|
||||
}
|
||||
}
|
||||
|
||||
func TestLongMessageReceiptsUseEachOriginalFragmentMsgID(t *testing.T) {
|
||||
session := &downstreamSession{gatewayMsgID: messageIDFrom("MSG-GROUP", 101)}
|
||||
first := downstreamReceiptMessageID(DownstreamReceipt{
|
||||
MessageID: "MSG-CHILD", SubmitGroupMessageID: "MSG-GROUP", SubmitSequenceID: 101,
|
||||
}, session)
|
||||
second := downstreamReceiptMessageID(DownstreamReceipt{
|
||||
MessageID: "MSG-CHILD", SubmitGroupMessageID: "MSG-GROUP", SubmitSequenceID: 102,
|
||||
}, session)
|
||||
if first != messageIDFrom("MSG-GROUP", 101) || second != messageIDFrom("MSG-GROUP", 102) {
|
||||
t.Fatalf("fragment receipt Msg_Id mismatch: first=%d second=%d", first, second)
|
||||
}
|
||||
if first == second {
|
||||
t.Fatalf("different fragment Sequence_Id values produced the same Msg_Id: %d", first)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendDownstreamRejectsZeroMessageID(t *testing.T) {
|
||||
_, err := sendDownstream(
|
||||
&downstreamSession{mu: &sync.Mutex{}},
|
||||
|
||||
@@ -130,10 +130,9 @@ func rememberDownstream(session downstreamSession) {
|
||||
session.touchPresence("connected", true, false)
|
||||
downstreamRegistry.Lock()
|
||||
if existing := downstreamRegistry.byMessageID[session.messageID]; existing != nil && existing.conn == session.conn {
|
||||
// A downstream long message returns one SUBMIT_RESP per fragment but is
|
||||
// persisted as one platform message. Keep the first fragment Msg_Id so
|
||||
// online delivery and restart recovery (which persists the first
|
||||
// Sequence_Id) address the same client-side message.
|
||||
// Keep a stable message-level lookup for the live connection. Receipt
|
||||
// delivery no longer uses this value for long-message fragments; it
|
||||
// reconstructs every fragment Msg_Id from that fragment's Sequence_Id.
|
||||
session.gatewayMsgID = existing.gatewayMsgID
|
||||
}
|
||||
downstreamRegistry.byMessageID[session.messageID] = &session
|
||||
|
||||
@@ -18,15 +18,16 @@ import (
|
||||
// one internal message mapping per destination while CMPP receives one response.
|
||||
|
||||
type submitRequest struct {
|
||||
Account string `json:"account"`
|
||||
PhoneNumber string `json:"phoneNumber,omitempty"`
|
||||
PhoneNumbers []string `json:"phoneNumbers,omitempty"`
|
||||
Content string `json:"content"`
|
||||
SrcID string `json:"srcId,omitempty"`
|
||||
DestID string `json:"destId,omitempty"`
|
||||
SequenceID uint32 `json:"sequenceId,omitempty"`
|
||||
RemoteIP string `json:"remoteIp,omitempty"`
|
||||
LongMessage *inboundLongMessageFragment `json:"longMessage,omitempty"`
|
||||
Account string `json:"account"`
|
||||
PhoneNumber string `json:"phoneNumber,omitempty"`
|
||||
PhoneNumbers []string `json:"phoneNumbers,omitempty"`
|
||||
Content string `json:"content"`
|
||||
SrcID string `json:"srcId,omitempty"`
|
||||
DestID string `json:"destId,omitempty"`
|
||||
SequenceID uint32 `json:"sequenceId,omitempty"`
|
||||
RegisteredDelivery uint8 `json:"registeredDelivery"`
|
||||
RemoteIP string `json:"remoteIp,omitempty"`
|
||||
LongMessage *inboundLongMessageFragment `json:"longMessage,omitempty"`
|
||||
}
|
||||
|
||||
type inboundLongMessageFragment struct {
|
||||
@@ -106,15 +107,16 @@ func (s Server) handleSubmit(response *cmpp.Response, packet *cmpp.Packet, logge
|
||||
startedAt := time.Now()
|
||||
releaseSubmitBarrier := beginDownstreamSubmitBarrier(packet.Conn)
|
||||
result, err := s.submit(remote, submitRequest{
|
||||
Account: account,
|
||||
PhoneNumber: phone,
|
||||
PhoneNumbers: phones,
|
||||
Content: content,
|
||||
SrcID: req.srcID,
|
||||
DestID: phone,
|
||||
SequenceID: req.sequenceID,
|
||||
RemoteIP: remoteIP(remote),
|
||||
LongMessage: longMessage,
|
||||
Account: account,
|
||||
PhoneNumber: phone,
|
||||
PhoneNumbers: phones,
|
||||
Content: content,
|
||||
SrcID: req.srcID,
|
||||
DestID: phone,
|
||||
SequenceID: req.sequenceID,
|
||||
RegisteredDelivery: req.registeredDelivery,
|
||||
RemoteIP: remoteIP(remote),
|
||||
LongMessage: longMessage,
|
||||
})
|
||||
if err != nil || !result.Accepted {
|
||||
reason := "api returned accepted=false"
|
||||
@@ -206,16 +208,17 @@ func (s Server) handleSubmit(response *cmpp.Response, packet *cmpp.Packet, logge
|
||||
}
|
||||
|
||||
type inboundSubmitPacket struct {
|
||||
protocol string
|
||||
pkTotal uint8
|
||||
pkNumber uint8
|
||||
tpUdhi uint8
|
||||
msgFmt uint8
|
||||
msgSrc string
|
||||
srcID string
|
||||
destTerminalIDs []string
|
||||
msgContent string
|
||||
sequenceID uint32
|
||||
protocol string
|
||||
pkTotal uint8
|
||||
pkNumber uint8
|
||||
tpUdhi uint8
|
||||
msgFmt uint8
|
||||
msgSrc string
|
||||
srcID string
|
||||
destTerminalIDs []string
|
||||
msgContent string
|
||||
sequenceID uint32
|
||||
registeredDelivery uint8
|
||||
}
|
||||
|
||||
func normalizeInboundSubmit(packet any) (inboundSubmitPacket, bool) {
|
||||
@@ -224,13 +227,13 @@ func normalizeInboundSubmit(packet any) (inboundSubmitPacket, bool) {
|
||||
return inboundSubmitPacket{
|
||||
protocol: "cmpp20", pkTotal: req.PkTotal, pkNumber: req.PkNumber, tpUdhi: req.TpUdhi, msgFmt: req.MsgFmt,
|
||||
msgSrc: req.MsgSrc, srcID: req.SrcId, destTerminalIDs: req.DestTerminalId,
|
||||
msgContent: req.MsgContent, sequenceID: req.SeqId,
|
||||
msgContent: req.MsgContent, sequenceID: req.SeqId, registeredDelivery: req.RegisteredDelivery,
|
||||
}, true
|
||||
case *cmpp.Cmpp3SubmitReqPkt:
|
||||
return inboundSubmitPacket{
|
||||
protocol: "cmpp30", pkTotal: req.PkTotal, pkNumber: req.PkNumber, tpUdhi: req.TpUdhi, msgFmt: req.MsgFmt,
|
||||
msgSrc: req.MsgSrc, srcID: req.SrcId, destTerminalIDs: req.DestTerminalId,
|
||||
msgContent: req.MsgContent, sequenceID: req.SeqId,
|
||||
msgContent: req.MsgContent, sequenceID: req.SeqId, registeredDelivery: req.RegisteredDelivery,
|
||||
}, true
|
||||
default:
|
||||
return inboundSubmitPacket{}, false
|
||||
|
||||
Reference in New Issue
Block a user