This commit is contained in:
Vendored
+22
-7
@@ -15,6 +15,7 @@ package cmpp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
@@ -72,7 +73,7 @@ func (cli *Client) Connect(servAddr, user, password string, timeout time.Duratio
|
||||
}
|
||||
|
||||
var ok bool
|
||||
var status uint8
|
||||
var status uint32
|
||||
if cli.typ == V20 || cli.typ == V21 {
|
||||
var rsp *Cmpp2ConnRspPkt
|
||||
rsp, ok = p.(*Cmpp2ConnRspPkt)
|
||||
@@ -80,7 +81,7 @@ func (cli *Client) Connect(servAddr, user, password string, timeout time.Duratio
|
||||
err = ErrRespNotMatch
|
||||
return err
|
||||
}
|
||||
status = rsp.Status
|
||||
status = uint32(rsp.Status)
|
||||
} else {
|
||||
var rsp *Cmpp3ConnRspPkt
|
||||
rsp, ok = p.(*Cmpp3ConnRspPkt)
|
||||
@@ -88,15 +89,16 @@ func (cli *Client) Connect(servAddr, user, password string, timeout time.Duratio
|
||||
err = ErrRespNotMatch
|
||||
return err
|
||||
}
|
||||
status = uint8(rsp.Status)
|
||||
status = rsp.Status
|
||||
}
|
||||
|
||||
if status != 0 {
|
||||
if status <= ErrnoConnOthers { //ErrnoConnOthers = 5
|
||||
err = ConnRspStatusErrMap[status]
|
||||
if status <= uint32(ErrnoConnOthers) { //ErrnoConnOthers = 5
|
||||
err = ConnRspStatusErrMap[uint8(status)]
|
||||
} else {
|
||||
err = ConnRspStatusErrMap[ErrnoConnOthers]
|
||||
}
|
||||
err = fmt.Errorf("CMPP CONNECT_RESP status=%d: %w", status, err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -112,8 +114,21 @@ func (cli *Client) Disconnect() {
|
||||
|
||||
// SendReqPkt pack the cmpp request packet structure and send it to the other peer.
|
||||
func (cli *Client) SendReqPkt(packet Packer) (uint32, error) {
|
||||
seq := <-cli.conn.SeqId
|
||||
return seq, cli.conn.SendPkt(packet, seq)
|
||||
return cli.SendReqPktAvailable(packet, nil)
|
||||
}
|
||||
|
||||
// The caller holds its pending-request lock until the returned sequence is registered.
|
||||
// A wrapped sequence must never replace an outstanding request.
|
||||
func (cli *Client) SendReqPktAvailable(packet Packer, available func(uint32) bool) (uint32, error) {
|
||||
for {
|
||||
seq, ok := <-cli.conn.SeqId
|
||||
if !ok {
|
||||
return 0, ErrConnIsClosed
|
||||
}
|
||||
if available == nil || available(seq) {
|
||||
return seq, cli.conn.SendPkt(packet, seq)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SendRspPkt pack the cmpp response packet structure and send it to the other peer.
|
||||
|
||||
Vendored
+1
-1
@@ -46,7 +46,7 @@ const (
|
||||
CMPP_HEADER_LEN uint32 = 12
|
||||
CMPP2_PACKET_MAX uint32 = 2477
|
||||
CMPP2_PACKET_MIN uint32 = 12
|
||||
CMPP3_PACKET_MAX uint32 = 3335
|
||||
CMPP3_PACKET_MAX uint32 = Cmpp3SubmitReqPktMaxLen
|
||||
CMPP3_PACKET_MIN uint32 = 12
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
package cmpp
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestReceiptVersionLayout(t *testing.T) {
|
||||
for _, version := range []Type{V20, V21, V30} {
|
||||
width, size := 21, 60
|
||||
if version == V30 {
|
||||
width, size = 32, 71
|
||||
}
|
||||
original := CmppReceiptPkt{MsgId: ^uint64(0), Stat: "DELIVRD", SubmitTime: "2609201200", DoneTime: "2609201201", DestTerminalId: strings.Repeat("9", width), SmscSequence: ^uint32(0)}
|
||||
raw, err := original.PackVersion(version)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(raw) != size || binary.BigEndian.Uint32(raw[size-4:]) != ^uint32(0) {
|
||||
t.Fatalf("wrong layout: %x", raw)
|
||||
}
|
||||
var decoded CmppReceiptPkt
|
||||
if err = decoded.UnpackVersion(raw, version); err != nil || !reflect.DeepEqual(decoded, original) {
|
||||
t.Fatalf("roundtrip: %+v %v", decoded, err)
|
||||
}
|
||||
for _, bad := range [][]byte{raw[:len(raw)-1], append(append([]byte{}, raw...), 0)} {
|
||||
if decoded.UnpackVersion(bad, version) == nil {
|
||||
t.Fatal("invalid length accepted")
|
||||
}
|
||||
}
|
||||
other := V30
|
||||
if version == V30 {
|
||||
other = V20
|
||||
}
|
||||
if decoded.UnpackVersion(raw, other) == nil {
|
||||
t.Fatal("wrong version accepted")
|
||||
}
|
||||
original.DestTerminalId += "1"
|
||||
if _, err = original.PackVersion(version); err == nil {
|
||||
t.Fatal("truncated destination")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Real TCP peers exercise the bounded reader, not only Pack/Unpack in memory.
|
||||
func tcpPair(t *testing.T, version Type) (*Conn, net.Conn) {
|
||||
t.Helper()
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer listener.Close()
|
||||
client, err := net.Dial("tcp", listener.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
server, err := listener.Accept()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
conn := NewConn(server, version)
|
||||
conn.SetState(CONN_AUTHOK)
|
||||
t.Cleanup(func() { conn.Close(); client.Close() })
|
||||
return conn, client
|
||||
}
|
||||
|
||||
func TestCMPP3LargeSubmitAndMalformedPackets(t *testing.T) {
|
||||
for _, length := range []int{140, 159} {
|
||||
p := Cmpp3SubmitReqPkt{DestUsrTl: 99, DestTerminalId: make([]string, 99), MsgLength: uint8(length), MsgContent: strings.Repeat("x", length)}
|
||||
if length == 140 {
|
||||
p.MsgFmt = 8
|
||||
}
|
||||
raw, err := p.Pack(^uint32(0))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(raw) != 3331+length {
|
||||
t.Fatalf("size %d", len(raw))
|
||||
}
|
||||
conn, peer := tcpPair(t, V30)
|
||||
go peer.Write(raw)
|
||||
pkt, err := conn.RecvAndUnpackPkt(time.Second)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
decoded := pkt.(*Cmpp3SubmitReqPkt)
|
||||
if decoded.SeqId != ^uint32(0) || len(decoded.DestTerminalId) != 99 || decoded.MsgContent != p.MsgContent {
|
||||
t.Fatal("wire mismatch")
|
||||
}
|
||||
var d Cmpp3SubmitReqPkt
|
||||
if d.Unpack(raw[8:len(raw)-1]) == nil || d.Unpack(append(raw[8:], 0)) == nil {
|
||||
t.Fatal("malformed body accepted")
|
||||
}
|
||||
p.DestUsrTl = 100
|
||||
p.DestTerminalId = append(p.DestTerminalId, "")
|
||||
if _, err = p.Pack(0); err == nil {
|
||||
t.Fatal("100 destinations accepted")
|
||||
}
|
||||
}
|
||||
for _, size := range []uint32{0, 11, CMPP3_PACKET_MAX + 1, ^uint32(0)} {
|
||||
conn, peer := tcpPair(t, V30)
|
||||
raw := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(raw, size)
|
||||
go peer.Write(raw)
|
||||
if _, err := conn.RecvAndUnpackPkt(time.Second); err == nil {
|
||||
t.Fatalf("accepted length %d", size)
|
||||
}
|
||||
}
|
||||
p := Cmpp3SubmitReqPkt{DestUsrTl: 1, DestTerminalId: []string{"1"}, MsgFmt: 8, MsgLength: 141, MsgContent: strings.Repeat("a", 141)}
|
||||
if _, err := p.Pack(0); err == nil {
|
||||
t.Fatal("oversized non-ASCII accepted")
|
||||
}
|
||||
p.MsgFmt, p.MsgLength, p.MsgContent = 0, 160, strings.Repeat("a", 160)
|
||||
if _, err := p.Pack(0); err == nil {
|
||||
t.Fatal("ASCII must be strictly shorter than 160 bytes")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnectStatusKeepsAll32Bits(t *testing.T) {
|
||||
for _, status := range []uint32{0, 5, 255, 256, ^uint32(0)} {
|
||||
t.Run(fmt.Sprint(status), func(t *testing.T) {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer listener.Close()
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
peer, e := listener.Accept()
|
||||
if e != nil {
|
||||
done <- e
|
||||
return
|
||||
}
|
||||
conn := NewConn(peer, V30)
|
||||
defer conn.Close()
|
||||
conn.SetState(CONN_CONNECTED)
|
||||
req, e := conn.RecvAndUnpackPkt(time.Second)
|
||||
if e == nil {
|
||||
e = conn.SendPkt(&Cmpp3ConnRspPkt{Status: status, Version: V30}, req.(*CmppConnReqPkt).SeqId)
|
||||
}
|
||||
done <- e
|
||||
}()
|
||||
client := NewClient(V30)
|
||||
defer client.Disconnect()
|
||||
err = client.Connect(listener.Addr().String(), "123456", "secret", time.Second)
|
||||
if status == 0 && err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if status != 0 && (err == nil || !strings.Contains(err.Error(), fmt.Sprintf("status=%d", status))) {
|
||||
t.Fatalf("status truncated: %v", err)
|
||||
}
|
||||
if e := <-done; e != nil {
|
||||
t.Fatal(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestSequenceSkipsInFlightAcrossWrap(t *testing.T) {
|
||||
conn, peer := tcpPair(t, V30)
|
||||
sequences := make(chan uint32, 3)
|
||||
sequences <- ^uint32(0)
|
||||
sequences <- 0
|
||||
sequences <- 1
|
||||
conn.SeqId = sequences
|
||||
client := &Client{conn: conn, typ: V30}
|
||||
read := make(chan error, 1)
|
||||
go func() { raw := make([]byte, 12); _, err := peer.Read(raw); read <- err }()
|
||||
seq, err := client.SendReqPktAvailable(&CmppActiveTestReqPkt{}, func(n uint32) bool { return n != ^uint32(0) })
|
||||
if err != nil || seq != 0 {
|
||||
t.Fatalf("zero lost: %d %v", seq, err)
|
||||
}
|
||||
if err = <-read; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
+41
-5
@@ -13,11 +13,15 @@
|
||||
|
||||
package cmpp
|
||||
|
||||
import "encoding/binary"
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Packet length const for cmpp receipt packet.
|
||||
const (
|
||||
CmppReceiptPktLen uint32 = 60 //60d, 0x3c
|
||||
Cmpp3ReceiptPktLen uint32 = 71
|
||||
CmppReceiptPktLen uint32 = 60 //60d, 0x3c
|
||||
)
|
||||
|
||||
type CmppReceiptPkt struct {
|
||||
@@ -31,7 +35,18 @@ type CmppReceiptPkt struct {
|
||||
|
||||
// Pack packs the CmppReceiptPkt to bytes stream for client side.
|
||||
func (p *CmppReceiptPkt) Pack() ([]byte, error) {
|
||||
var pktLen uint32 = CmppReceiptPktLen
|
||||
return p.PackVersion(V20)
|
||||
}
|
||||
|
||||
// PackVersion uses the negotiated connection version, never a body-length guess.
|
||||
func (p *CmppReceiptPkt) PackVersion(version Type) ([]byte, error) {
|
||||
pktLen, width, err := receiptLayout(version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(p.Stat) > 7 || len(p.SubmitTime) > 10 || len(p.DoneTime) > 10 || len(p.DestTerminalId) > width {
|
||||
return nil, fmt.Errorf("receipt field exceeds protocol width")
|
||||
}
|
||||
|
||||
var w = newPacketWriter(pktLen)
|
||||
|
||||
@@ -39,7 +54,7 @@ func (p *CmppReceiptPkt) Pack() ([]byte, error) {
|
||||
w.WriteFixedSizeString(p.Stat, 7)
|
||||
w.WriteFixedSizeString(p.SubmitTime, 10)
|
||||
w.WriteFixedSizeString(p.DoneTime, 10)
|
||||
w.WriteFixedSizeString(p.DestTerminalId, 21)
|
||||
w.WriteFixedSizeString(p.DestTerminalId, width)
|
||||
w.WriteInt(binary.BigEndian, p.SmscSequence)
|
||||
|
||||
return w.Bytes()
|
||||
@@ -49,6 +64,27 @@ func (p *CmppReceiptPkt) Pack() ([]byte, error) {
|
||||
// After unpack, you will get all value of fields in
|
||||
// CmppReceiptPkt struct.
|
||||
func (p *CmppReceiptPkt) Unpack(data []byte) error {
|
||||
return p.UnpackVersion(data, V20)
|
||||
}
|
||||
|
||||
func receiptLayout(version Type) (uint32, int, error) {
|
||||
switch version {
|
||||
case V20, V21:
|
||||
return CmppReceiptPktLen, 21, nil
|
||||
case V30:
|
||||
return Cmpp3ReceiptPktLen, 32, nil
|
||||
}
|
||||
return 0, 0, fmt.Errorf("unsupported receipt version: %v", version)
|
||||
}
|
||||
|
||||
func (p *CmppReceiptPkt) UnpackVersion(data []byte, version Type) error {
|
||||
size, width, err := receiptLayout(version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(data) != int(size) {
|
||||
return fmt.Errorf("invalid receipt length %d for version %v (expected %d)", len(data), version, size)
|
||||
}
|
||||
var r = newPacketReader(data)
|
||||
|
||||
r.ReadInt(binary.BigEndian, &p.MsgId)
|
||||
@@ -62,7 +98,7 @@ func (p *CmppReceiptPkt) Unpack(data []byte) error {
|
||||
doneTime := r.ReadCString(10)
|
||||
p.DoneTime = string(doneTime)
|
||||
|
||||
destTerminalId := r.ReadCString(21)
|
||||
destTerminalId := r.ReadCString(width)
|
||||
p.DestTerminalId = string(destTerminalId)
|
||||
|
||||
r.ReadInt(binary.BigEndian, &p.SmscSequence)
|
||||
|
||||
Vendored
+39
-2
@@ -152,6 +152,9 @@ type Cmpp3SubmitRspPkt struct {
|
||||
// Before calling Pack, you should initialize a Cmpp2SubmitReqPkt variable
|
||||
// with correct field value.
|
||||
func (p *Cmpp2SubmitReqPkt) Pack(seqId uint32) ([]byte, error) {
|
||||
if err := validateSubmit(p.DestUsrTl, p.DestTerminalId, p.MsgFmt, p.MsgLength, p.MsgContent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var pktLen uint32 = CMPP_HEADER_LEN + 117 + uint32(p.DestUsrTl)*21 + 1 + uint32(p.MsgLength) + 8
|
||||
|
||||
var w = newPacketWriter(pktLen)
|
||||
@@ -200,6 +203,8 @@ func (p *Cmpp2SubmitReqPkt) Pack(seqId uint32) ([]byte, error) {
|
||||
// Usually it is used in server side. After unpack, you will get all value of fields in
|
||||
// Cmpp2SubmitReqPkt struct.
|
||||
func (p *Cmpp2SubmitReqPkt) Unpack(data []byte) error {
|
||||
p.DestTerminalId = nil
|
||||
|
||||
var r = newPacketReader(data)
|
||||
|
||||
// Sequence Id
|
||||
@@ -259,7 +264,13 @@ func (p *Cmpp2SubmitReqPkt) Unpack(data []byte) error {
|
||||
reserve := r.ReadCString(8)
|
||||
p.Reserve = string(reserve)
|
||||
|
||||
return r.Error()
|
||||
if err := r.Error(); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(data) != 130+int(p.DestUsrTl)*21+int(p.MsgLength) {
|
||||
return errSubmitInvalidStruct
|
||||
}
|
||||
return validateSubmit(p.DestUsrTl, p.DestTerminalId, p.MsgFmt, p.MsgLength, p.MsgContent)
|
||||
}
|
||||
|
||||
// Pack packs the Cmpp2SubmitRspPkt to bytes stream for Server side.
|
||||
@@ -302,6 +313,9 @@ func (p *Cmpp2SubmitRspPkt) Unpack(data []byte) error {
|
||||
// Before calling Pack, you should initialize a Cmpp3SubmitReqPkt variable
|
||||
// with correct field value.
|
||||
func (p *Cmpp3SubmitReqPkt) Pack(seqId uint32) ([]byte, error) {
|
||||
if err := validateSubmit(p.DestUsrTl, p.DestTerminalId, p.MsgFmt, p.MsgLength, p.MsgContent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var pktLen uint32 = CMPP_HEADER_LEN + 129 + uint32(p.DestUsrTl)*32 + 1 + 1 + uint32(p.MsgLength) + 20
|
||||
|
||||
var w = newPacketWriter(pktLen)
|
||||
@@ -352,6 +366,8 @@ func (p *Cmpp3SubmitReqPkt) Pack(seqId uint32) ([]byte, error) {
|
||||
// Usually it is used in server side. After unpack, you will get all value of fields in
|
||||
// Cmpp3SubmitReqPkt struct.
|
||||
func (p *Cmpp3SubmitReqPkt) Unpack(data []byte) error {
|
||||
p.DestTerminalId = nil
|
||||
|
||||
var r = newPacketReader(data)
|
||||
|
||||
// Sequence Id
|
||||
@@ -413,7 +429,13 @@ func (p *Cmpp3SubmitReqPkt) Unpack(data []byte) error {
|
||||
linkId := r.ReadCString(20)
|
||||
p.LinkId = string(linkId)
|
||||
|
||||
return r.Error()
|
||||
if err := r.Error(); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(data) != 155+int(p.DestUsrTl)*32+int(p.MsgLength) {
|
||||
return errSubmitInvalidStruct
|
||||
}
|
||||
return validateSubmit(p.DestUsrTl, p.DestTerminalId, p.MsgFmt, p.MsgLength, p.MsgContent)
|
||||
}
|
||||
|
||||
// Pack packs the Cmpp3SubmitRspPkt to bytes stream for Server side.
|
||||
@@ -451,3 +473,18 @@ func (p *Cmpp3SubmitRspPkt) Unpack(data []byte) error {
|
||||
|
||||
return r.Error()
|
||||
}
|
||||
|
||||
// The receive buffer is bounded separately; validate counts before accepting the body.
|
||||
func validateSubmit(count uint8, destinations []string, format, length uint8, content string) error {
|
||||
if count == 0 || count > 99 || int(count) != len(destinations) {
|
||||
return errSubmitInvalidStruct
|
||||
}
|
||||
limit := 140
|
||||
if format == 0 {
|
||||
limit = 159 // CMPP specifies ASCII <160 bytes; other formats <=140.
|
||||
}
|
||||
if int(length) != len(content) || len(content) > limit {
|
||||
return errSubmitInvalidMsgLength
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user