This commit is contained in:
@@ -16,6 +16,13 @@ import (
|
||||
// The reader and heartbeat goroutines share one connection lifecycle. Closing
|
||||
// the connection must wake pending submitters before scheduling pool recovery.
|
||||
|
||||
// Caller holds c.mu across sequence selection, wire write and pending registration.
|
||||
func (c *connection) sequenceAvailable(sequence uint32) bool {
|
||||
_, submit := c.pending[sequence]
|
||||
_, heartbeat := c.heartbeatPending[sequence]
|
||||
return !submit && !heartbeat
|
||||
}
|
||||
|
||||
type connection struct {
|
||||
channelID string
|
||||
config queue.UpstreamConfig
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
// mobile-originated content uses a separate long-message assembly path.
|
||||
|
||||
type deliverPacket struct {
|
||||
version cmpp.Type
|
||||
seqID uint32
|
||||
msgID uint64
|
||||
destID string
|
||||
@@ -26,7 +27,7 @@ type deliverPacket struct {
|
||||
}
|
||||
|
||||
func deliverPacketFromCMPP2(pkt *cmpp.Cmpp2DeliverReqPkt) deliverPacket {
|
||||
return deliverPacket{
|
||||
return deliverPacket{version: cmpp.V20,
|
||||
seqID: pkt.SeqId,
|
||||
msgID: pkt.MsgId,
|
||||
destID: pkt.DestId,
|
||||
@@ -39,7 +40,7 @@ func deliverPacketFromCMPP2(pkt *cmpp.Cmpp2DeliverReqPkt) deliverPacket {
|
||||
}
|
||||
|
||||
func deliverPacketFromCMPP3(pkt *cmpp.Cmpp3DeliverReqPkt) deliverPacket {
|
||||
return deliverPacket{
|
||||
return deliverPacket{version: cmpp.V30,
|
||||
seqID: pkt.SeqId,
|
||||
msgID: pkt.MsgId,
|
||||
destID: pkt.DestId,
|
||||
@@ -54,7 +55,7 @@ func deliverPacketFromCMPP3(pkt *cmpp.Cmpp3DeliverReqPkt) deliverPacket {
|
||||
func (c *connection) handleDeliver(pkt deliverPacket) error {
|
||||
if pkt.registerDelivery == 1 {
|
||||
var receipt cmpp.CmppReceiptPkt
|
||||
if err := receipt.Unpack([]byte(pkt.msgContent)); err != nil {
|
||||
if err := receipt.UnpackVersion([]byte(pkt.msgContent), pkt.version); 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
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ func (c *connection) sendHeartbeat() bool {
|
||||
return false
|
||||
}
|
||||
c.sendMu.Lock()
|
||||
seq, err := c.client.SendReqPkt(&cmpp.CmppActiveTestReqPkt{})
|
||||
seq, err := c.client.SendReqPktAvailable(&cmpp.CmppActiveTestReqPkt{}, c.sequenceAvailable)
|
||||
c.sendMu.Unlock()
|
||||
if err != nil {
|
||||
c.mu.Unlock()
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package upstream
|
||||
|
||||
import (
|
||||
"cmpp-platform/gateway/internal/queue"
|
||||
"encoding/json"
|
||||
cmpp "github.com/bigwhite/gocmpp"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestCMPP3ReceiptKeepsUnsignedFieldsAndFullDestination(t *testing.T) {
|
||||
events := make(chan queue.ReceiptEvent, 1)
|
||||
api := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var e queue.ReceiptEvent
|
||||
if err := json.NewDecoder(r.Body).Decode(&e); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
events <- e
|
||||
w.WriteHeader(200)
|
||||
}))
|
||||
defer api.Close()
|
||||
receipt := cmpp.CmppReceiptPkt{MsgId: ^uint64(0), Stat: "DELIVRD", DestTerminalId: strings.Repeat("9", 32), SmscSequence: ^uint32(0)}
|
||||
raw, err := receipt.PackVersion(cmpp.V30)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
conn := &connection{channelID: "qa", apiBaseURL: api.URL, httpClient: api.Client()}
|
||||
packet := deliverPacketFromCMPP3(&cmpp.Cmpp3DeliverReqPkt{SeqId: ^uint32(0), RegisterDelivery: 1, MsgContent: string(raw)})
|
||||
if err = conn.handleDeliver(packet); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
event := <-events
|
||||
if event.GatewayMessageID != "18446744073709551615" || event.SequenceID != ^uint32(0) || event.PhoneNumber != receipt.DestTerminalId {
|
||||
t.Fatalf("truncated callback: %+v", event)
|
||||
}
|
||||
packet.msgContent = string(raw[:60])
|
||||
if conn.handleDeliver(packet) == nil {
|
||||
t.Fatal("legacy 60-byte body silently accepted on CMPP3")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpstreamSequenceAvailabilityIncludesHeartbeatAndSubmit(t *testing.T) {
|
||||
conn := &connection{pending: map[uint32]chan submitPartResponse{0: make(chan submitPartResponse)}, heartbeatPending: map[uint32]time.Time{^uint32(0): time.Now()}}
|
||||
if conn.sequenceAvailable(0) || conn.sequenceAvailable(^uint32(0)) || !conn.sequenceAvailable(1) {
|
||||
t.Fatal("wrapped sequence overwrites outstanding request")
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ func (c *connection) emitDeliverResponse(pkt deliverPacket, responseErr error) {
|
||||
channelID := c.channelID
|
||||
if pkt.registerDelivery == 1 {
|
||||
var receipt cmpp.CmppReceiptPkt
|
||||
if err := receipt.Unpack([]byte(pkt.msgContent)); err == nil {
|
||||
if err := receipt.UnpackVersion([]byte(pkt.msgContent), pkt.version); err == nil {
|
||||
gatewayMessageID = fmt.Sprint(receipt.MsgId)
|
||||
phone = strings.TrimSpace(receipt.DestTerminalId)
|
||||
if cmd, ok := c.commandFor(receipt.MsgId); ok {
|
||||
|
||||
@@ -101,7 +101,7 @@ func (p *connectionPool) submit(
|
||||
return result, publishErr
|
||||
}
|
||||
}
|
||||
if firstSequence == 0 {
|
||||
if len(segments) == 1 {
|
||||
firstSequence = seq
|
||||
}
|
||||
if firstGatewayMessageID == "" {
|
||||
@@ -146,7 +146,7 @@ func (c *connection) submitPart(ctx context.Context, cmd queue.SubmitCommand, pa
|
||||
}
|
||||
c.sendMu.Lock()
|
||||
wireSource = "write_uncertain"
|
||||
seq, err := client.SendReqPkt(pkt)
|
||||
seq, err := client.SendReqPktAvailable(pkt, c.sequenceAvailable)
|
||||
if err == nil {
|
||||
at := time.Now().UTC()
|
||||
wireAt = &at
|
||||
|
||||
Reference in New Issue
Block a user