fix: complete first version issue remediation
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -86,6 +87,7 @@ type DownstreamUplink struct {
|
||||
type downstreamSession struct {
|
||||
messageID string
|
||||
account string
|
||||
protocol string
|
||||
srcID string
|
||||
phoneNumber string
|
||||
gatewayMsgID uint64
|
||||
@@ -143,6 +145,7 @@ func (s Server) handleLogin(response *cmpp.Response, packet *cmpp.Packet, logger
|
||||
resp.AuthIsmg = string(authISMG[:])
|
||||
session := downstreamSession{
|
||||
account: strings.TrimSpace(defaultString(auth.Account, account)),
|
||||
protocol: cmppVersionName(req.Version),
|
||||
srcID: strings.TrimSpace(auth.Account),
|
||||
remoteIP: remoteIP(packet.Conn.Conn.RemoteAddr()),
|
||||
connectedAt: time.Now().UTC(),
|
||||
@@ -153,59 +156,150 @@ func (s Server) handleLogin(response *cmpp.Response, packet *cmpp.Packet, logger
|
||||
}
|
||||
rememberAccount(session)
|
||||
go s.flushPending(defaultString(auth.Account, account), logger)
|
||||
logger.Printf("cmpp inbound account=%s login ok remote=%s", account, packet.Conn.Conn.RemoteAddr())
|
||||
logger.Printf(
|
||||
"cmpp inbound event=login_accepted protocol=%s requested_version=0x%02x response_version=0x30 account=%s remote=%s",
|
||||
cmppVersionName(req.Version), req.Version, account, packet.Conn.Conn.RemoteAddr(),
|
||||
)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (s Server) handleSubmit(response *cmpp.Response, packet *cmpp.Packet, logger *log.Logger) (bool, error) {
|
||||
req, ok := packet.Packer.(*cmpp.Cmpp3SubmitReqPkt)
|
||||
req, ok := normalizeInboundSubmit(packet.Packer)
|
||||
if !ok {
|
||||
return true, nil
|
||||
}
|
||||
resp := response.Packer.(*cmpp.Cmpp3SubmitRspPkt)
|
||||
account := strings.TrimRight(req.MsgSrc, "\x00")
|
||||
account := strings.TrimRight(req.msgSrc, "\x00")
|
||||
phone := ""
|
||||
if len(req.DestTerminalId) > 0 {
|
||||
phone = strings.TrimRight(req.DestTerminalId[0], "\x00")
|
||||
if len(req.destTerminalIDs) > 0 {
|
||||
phone = strings.TrimRight(req.destTerminalIDs[0], "\x00")
|
||||
}
|
||||
content, err := decodeContent(req.MsgFmt, req.MsgContent)
|
||||
remote := packet.Conn.Conn.RemoteAddr()
|
||||
clientProtocol := inboundClientProtocol(account, packet.Conn, req.protocol)
|
||||
logger.Printf(
|
||||
"cmpp inbound event=submit_received protocol=%s packet_type=%s account=%s remote=%s seq=%d phone=%s src_id=%s msg_fmt=%d pk=%d/%d dest_count=%d content_bytes=%d",
|
||||
clientProtocol, req.protocol, account, remote, req.sequenceID, phone, strings.TrimSpace(req.srcID), req.msgFmt,
|
||||
req.pkNumber, req.pkTotal, len(req.destTerminalIDs), len(req.msgContent),
|
||||
)
|
||||
content, err := decodeContent(req.msgFmt, req.msgContent)
|
||||
if err != nil {
|
||||
logger.Printf("cmpp inbound decode submit failed account=%s seq=%d err=%v", account, req.SeqId, err)
|
||||
resp.Result = 9
|
||||
logger.Printf(
|
||||
"cmpp inbound event=submit_rejected protocol=%s packet_type=%s account=%s remote=%s seq=%d phone=%s result=9 stage=decode reason=%q",
|
||||
clientProtocol, req.protocol, account, remote, req.sequenceID, phone, err,
|
||||
)
|
||||
setInboundSubmitResponse(response.Packer, 0, 9)
|
||||
return false, nil
|
||||
}
|
||||
result, err := s.submit(packet.Conn.Conn.RemoteAddr(), submitRequest{
|
||||
contentHash := fmt.Sprintf("%x", md5.Sum([]byte(content)))
|
||||
startedAt := time.Now()
|
||||
result, err := s.submit(remote, submitRequest{
|
||||
Account: account,
|
||||
PhoneNumber: phone,
|
||||
Content: content,
|
||||
SrcID: req.SrcId,
|
||||
SrcID: req.srcID,
|
||||
DestID: phone,
|
||||
SequenceID: req.SeqId,
|
||||
RemoteIP: remoteIP(packet.Conn.Conn.RemoteAddr()),
|
||||
SequenceID: req.sequenceID,
|
||||
RemoteIP: remoteIP(remote),
|
||||
})
|
||||
if err != nil || !result.Accepted {
|
||||
logger.Printf("cmpp inbound submit rejected account=%s phone=%s seq=%d err=%v", account, phone, req.SeqId, err)
|
||||
resp.Result = 9
|
||||
reason := "api returned accepted=false"
|
||||
if err != nil {
|
||||
reason = err.Error()
|
||||
}
|
||||
logger.Printf(
|
||||
"cmpp inbound event=submit_rejected protocol=%s packet_type=%s account=%s remote=%s seq=%d phone=%s result=9 stage=business duration_ms=%d content_chars=%d content_hash=%s reason=%q",
|
||||
clientProtocol, req.protocol, account, remote, req.sequenceID, phone, time.Since(startedAt).Milliseconds(), len([]rune(content)), contentHash, reason,
|
||||
)
|
||||
setInboundSubmitResponse(response.Packer, 0, 9)
|
||||
return false, nil
|
||||
}
|
||||
resp.MsgId = messageIDFrom(result.MessageID, req.SeqId)
|
||||
resp.Result = 0
|
||||
gatewayMsgID := messageIDFrom(result.MessageID, req.sequenceID)
|
||||
setInboundSubmitResponse(response.Packer, gatewayMsgID, 0)
|
||||
rememberDownstream(downstreamSession{
|
||||
messageID: result.MessageID,
|
||||
account: account,
|
||||
srcID: strings.TrimSpace(req.SrcId),
|
||||
protocol: clientProtocol,
|
||||
srcID: strings.TrimSpace(req.srcID),
|
||||
phoneNumber: phone,
|
||||
gatewayMsgID: resp.MsgId,
|
||||
remoteIP: remoteIP(packet.Conn.Conn.RemoteAddr()),
|
||||
gatewayMsgID: gatewayMsgID,
|
||||
remoteIP: remoteIP(remote),
|
||||
connectedAt: time.Now().UTC(),
|
||||
conn: packet.Conn,
|
||||
mu: &sync.Mutex{},
|
||||
presence: s.PresenceStore,
|
||||
instanceID: s.gatewayInstanceID(),
|
||||
})
|
||||
logger.Printf(
|
||||
"cmpp inbound event=submit_accepted protocol=%s packet_type=%s account=%s remote=%s seq=%d phone=%s result=0 message_id=%s gateway_message_id=%d duration_ms=%d content_chars=%d content_hash=%s",
|
||||
clientProtocol, req.protocol, account, remote, req.sequenceID, phone, result.MessageID, gatewayMsgID, time.Since(startedAt).Milliseconds(), len([]rune(content)), contentHash,
|
||||
)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
type inboundSubmitPacket struct {
|
||||
protocol string
|
||||
pkTotal uint8
|
||||
pkNumber uint8
|
||||
msgFmt uint8
|
||||
msgSrc string
|
||||
srcID string
|
||||
destTerminalIDs []string
|
||||
msgContent string
|
||||
sequenceID uint32
|
||||
}
|
||||
|
||||
func normalizeInboundSubmit(packet any) (inboundSubmitPacket, bool) {
|
||||
switch req := packet.(type) {
|
||||
case *cmpp.Cmpp2SubmitReqPkt:
|
||||
return inboundSubmitPacket{
|
||||
protocol: "cmpp20", pkTotal: req.PkTotal, pkNumber: req.PkNumber, msgFmt: req.MsgFmt,
|
||||
msgSrc: req.MsgSrc, srcID: req.SrcId, destTerminalIDs: req.DestTerminalId,
|
||||
msgContent: req.MsgContent, sequenceID: req.SeqId,
|
||||
}, true
|
||||
case *cmpp.Cmpp3SubmitReqPkt:
|
||||
return inboundSubmitPacket{
|
||||
protocol: "cmpp30", pkTotal: req.PkTotal, pkNumber: req.PkNumber, msgFmt: req.MsgFmt,
|
||||
msgSrc: req.MsgSrc, srcID: req.SrcId, destTerminalIDs: req.DestTerminalId,
|
||||
msgContent: req.MsgContent, sequenceID: req.SeqId,
|
||||
}, true
|
||||
default:
|
||||
return inboundSubmitPacket{}, false
|
||||
}
|
||||
}
|
||||
|
||||
func setInboundSubmitResponse(packet any, messageID uint64, result uint32) {
|
||||
switch resp := packet.(type) {
|
||||
case *cmpp.Cmpp2SubmitRspPkt:
|
||||
resp.MsgId = messageID
|
||||
resp.Result = uint8(result)
|
||||
case *cmpp.Cmpp3SubmitRspPkt:
|
||||
resp.MsgId = messageID
|
||||
resp.Result = result
|
||||
}
|
||||
}
|
||||
|
||||
func inboundClientProtocol(account string, conn *cmpp.Conn, fallback string) string {
|
||||
downstreamRegistry.RLock()
|
||||
defer downstreamRegistry.RUnlock()
|
||||
session := downstreamRegistry.byAccount[account]
|
||||
if session != nil && session.conn == conn && session.protocol != "" {
|
||||
return session.protocol
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func cmppVersionName(version cmpp.Type) string {
|
||||
switch version {
|
||||
case cmpp.V20:
|
||||
return "cmpp20"
|
||||
case cmpp.V21:
|
||||
return "cmpp21"
|
||||
case cmpp.V30:
|
||||
return "cmpp30"
|
||||
default:
|
||||
return fmt.Sprintf("unknown_0x%02x", uint8(version))
|
||||
}
|
||||
}
|
||||
|
||||
func (s Server) authenticate(remote net.Addr, account string, authSource string, timestamp uint32) (authResponse, error) {
|
||||
payload := authRequest{
|
||||
Account: account,
|
||||
@@ -320,11 +414,22 @@ func (s Server) post(ctx context.Context, path string, payload any, result any)
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
responseBody, err := io.ReadAll(io.LimitReader(resp.Body, 64*1024))
|
||||
if err != nil {
|
||||
return fmt.Errorf("read api response: %w", err)
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("api returned %s", resp.Status)
|
||||
detail := strings.TrimSpace(string(responseBody))
|
||||
if detail == "" {
|
||||
return fmt.Errorf("api returned %s", resp.Status)
|
||||
}
|
||||
return fmt.Errorf("api returned %s: %s", resp.Status, detail)
|
||||
}
|
||||
if result != nil {
|
||||
return json.NewDecoder(resp.Body).Decode(result)
|
||||
if len(responseBody) == 0 {
|
||||
return io.EOF
|
||||
}
|
||||
return json.Unmarshal(responseBody, result)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log"
|
||||
@@ -166,6 +167,69 @@ func TestInboundServerAuthenticatesAndSubmits(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostIncludesAPIErrorResponseBody(t *testing.T) {
|
||||
api := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
_, _ = w.Write([]byte(`{"message":"CMPP submit content does not match an approved template and signature","statusCode":400}`))
|
||||
}))
|
||||
defer api.Close()
|
||||
|
||||
err := (Server{APIBaseURL: api.URL}).post(context.Background(), "/inbound/submit", map[string]string{"account": "100001"}, nil)
|
||||
if err == nil || !bytes.Contains([]byte(err.Error()), []byte("CMPP submit content does not match an approved template and signature")) {
|
||||
t.Fatalf("expected API response body in error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeInboundSubmitSupportsCMPP2AndCMPP3(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
packet any
|
||||
protocol string
|
||||
}{
|
||||
{name: "cmpp2", packet: &cmpp.Cmpp2SubmitReqPkt{MsgSrc: "100001", SeqId: 20}, protocol: "cmpp20"},
|
||||
{name: "cmpp3", packet: &cmpp.Cmpp3SubmitReqPkt{MsgSrc: "100001", SeqId: 30}, protocol: "cmpp30"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
got, ok := normalizeInboundSubmit(test.packet)
|
||||
if !ok || got.protocol != test.protocol || got.msgSrc != "100001" {
|
||||
t.Fatalf("unexpected normalized packet: %+v ok=%v", got, ok)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetInboundSubmitResponseSupportsCMPP2AndCMPP3(t *testing.T) {
|
||||
cmpp2 := &cmpp.Cmpp2SubmitRspPkt{}
|
||||
setInboundSubmitResponse(cmpp2, 101, 9)
|
||||
if cmpp2.MsgId != 101 || cmpp2.Result != 9 {
|
||||
t.Fatalf("unexpected CMPP2 response: %+v", cmpp2)
|
||||
}
|
||||
cmpp3 := &cmpp.Cmpp3SubmitRspPkt{}
|
||||
setInboundSubmitResponse(cmpp3, 202, 9)
|
||||
if cmpp3.MsgId != 202 || cmpp3.Result != 9 {
|
||||
t.Fatalf("unexpected CMPP3 response: %+v", cmpp3)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInboundClientProtocolUsesConnectRequestVersion(t *testing.T) {
|
||||
resetDownstreamRegistry()
|
||||
defer resetDownstreamRegistry()
|
||||
conn := &cmpp.Conn{}
|
||||
downstreamRegistry.byAccount["100001"] = &downstreamSession{
|
||||
account: "100001",
|
||||
protocol: "cmpp20",
|
||||
conn: conn,
|
||||
}
|
||||
if got := inboundClientProtocol("100001", conn, "cmpp30"); got != "cmpp20" {
|
||||
t.Fatalf("protocol = %s, want cmpp20", got)
|
||||
}
|
||||
if got := inboundClientProtocol("missing", conn, "cmpp30"); got != "cmpp30" {
|
||||
t.Fatalf("fallback protocol = %s, want cmpp30", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlushOnlineAccountsFetchesPendingDeliveries(t *testing.T) {
|
||||
resetDownstreamRegistry()
|
||||
defer resetDownstreamRegistry()
|
||||
|
||||
Reference in New Issue
Block a user