feat: integrate analytics and fragment receipt improvements

This commit is contained in:
hectorzhao
2026-08-03 15:34:41 +08:00
parent 3357ace7e1
commit 530a65de80
89 changed files with 1964 additions and 324 deletions
+17 -2
View File
@@ -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()
+19
View File
@@ -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{}},
+3 -4
View File
@@ -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
+33 -30
View File
@@ -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