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) } }