fix: log cmpp packet decode failures

This commit is contained in:
hectorzhao
2026-07-11 10:40:46 +08:00
parent 208a6c23f8
commit 20f89d14aa
20 changed files with 3462 additions and 2 deletions
+2
View File
@@ -2,6 +2,8 @@ module cmpp-platform/gateway
go 1.26
replace github.com/bigwhite/gocmpp => ./third_party/gocmpp
require (
github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect
github.com/alicebob/miniredis/v2 v2.34.0 // indirect
+3 -2
View File
@@ -25,6 +25,7 @@ type Server struct {
Addr string
APIBaseURL string
HTTPClient *http.Client
LogWriter io.Writer
PendingFlushInterval time.Duration
PresenceStore PresenceStore
RecoveryStore RecoveryStore
@@ -116,7 +117,7 @@ func (s Server) ListenAndServe() error {
s.logRecoveryCandidates(log.Default())
go s.recoverPendingCandidates(log.Default())
go s.runPendingFlusher(log.Default())
return cmpp.ListenAndServe(addr, cmpp.V30, 30*time.Second, 3, nil,
return cmpp.ListenAndServe(addr, cmpp.V30, 30*time.Second, 3, s.LogWriter,
cmpp.HandlerFunc(s.handleLogin),
cmpp.HandlerFunc(s.handleSubmit),
)
@@ -158,7 +159,7 @@ func (s Server) handleLogin(response *cmpp.Response, packet *cmpp.Packet, logger
go s.flushPending(defaultString(auth.Account, account), logger)
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(),
cmppVersionName(req.Version), uint8(req.Version), account, packet.Conn.Conn.RemoteAddr(),
)
return false, nil
}
+46
View File
@@ -3,11 +3,13 @@ package inbound
import (
"bytes"
"context"
"encoding/binary"
"encoding/json"
"log"
"net"
"net/http"
"net/http/httptest"
"strings"
"sync"
"testing"
"time"
@@ -26,6 +28,23 @@ type memoryRecoveryStore struct {
completed []DownstreamRecoveryStatus
}
type synchronizedBuffer struct {
mu sync.Mutex
buffer bytes.Buffer
}
func (b *synchronizedBuffer) Write(data []byte) (int, error) {
b.mu.Lock()
defer b.mu.Unlock()
return b.buffer.Write(data)
}
func (b *synchronizedBuffer) String() string {
b.mu.Lock()
defer b.mu.Unlock()
return b.buffer.String()
}
func (m *memoryPresenceStore) TouchAccount(_ context.Context, snapshot DownstreamPresence) error {
if m.snapshots == nil {
m.snapshots = map[string]DownstreamPresence{}
@@ -181,6 +200,33 @@ func TestPostIncludesAPIErrorResponseBody(t *testing.T) {
}
}
func TestInboundServerLogsReadUnpackFailure(t *testing.T) {
addr := reserveTCPAddr(t)
var logs synchronizedBuffer
go func() {
_ = (Server{Addr: addr, LogWriter: &logs}).ListenAndServe()
}()
time.Sleep(300 * time.Millisecond)
conn, err := net.DialTimeout("tcp", addr, time.Second)
if err != nil {
t.Fatalf("connect inbound server: %v", err)
}
defer conn.Close()
if err := binary.Write(conn, binary.BigEndian, uint32(1)); err != nil {
t.Fatalf("write invalid packet length: %v", err)
}
deadline := time.Now().Add(2 * time.Second)
for time.Now().Before(deadline) {
if strings.Contains(logs.String(), "read/unpack packet failed") && strings.Contains(logs.String(), "total_length") {
return
}
time.Sleep(20 * time.Millisecond)
}
t.Fatalf("missing read/unpack failure log: %s", logs.String())
}
func TestNormalizeInboundSubmitSupportsCMPP2AndCMPP3(t *testing.T) {
tests := []struct {
name string
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+86
View File
@@ -0,0 +1,86 @@
// Copyright 2015 Tony Bai.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package cmpp
import "encoding/binary"
// Packet length const for cmpp active test request and response packets.
const (
CmppActiveTestReqPktLen uint32 = 12 //12d, 0xc
CmppActiveTestRspPktLen uint32 = 12 + 1 //13d, 0xd
)
type CmppActiveTestReqPkt struct {
// session info
SeqId uint32
}
type CmppActiveTestRspPkt struct {
Reserved uint8
// session info
SeqId uint32
}
// Pack packs the CmppActiveTestReqPkt to bytes stream for client side.
func (p *CmppActiveTestReqPkt) Pack(seqId uint32) ([]byte, error) {
var pktLen = CmppActiveTestReqPktLen
var w = newPacketWriter(pktLen)
// Pack header
w.WriteInt(binary.BigEndian, pktLen)
w.WriteInt(binary.BigEndian, CMPP_ACTIVE_TEST)
w.WriteInt(binary.BigEndian, seqId)
p.SeqId = seqId
return w.Bytes()
}
// Unpack unpack the binary byte stream to a CmppActiveTestReqPkt variable.
// After unpack, you will get all value of fields in
// CmppActiveTestReqPkt struct.
func (p *CmppActiveTestReqPkt) Unpack(data []byte) error {
var r = newPacketReader(data)
// Sequence Id
r.ReadInt(binary.BigEndian, &p.SeqId)
return r.Error()
}
// Pack packs the CmppActiveTestRspPkt to bytes stream for client side.
func (p *CmppActiveTestRspPkt) Pack(seqId uint32) ([]byte, error) {
var pktLen = CmppActiveTestRspPktLen
var w = newPacketWriter(pktLen)
// Pack header
w.WriteInt(binary.BigEndian, pktLen)
w.WriteInt(binary.BigEndian, CMPP_ACTIVE_TEST_RESP)
w.WriteInt(binary.BigEndian, seqId)
w.WriteByte(p.Reserved)
p.SeqId = seqId
return w.Bytes()
}
// Unpack unpack the binary byte stream to a CmppActiveTestRspPkt variable.
// After unpack, you will get all value of fields in
// CmppActiveTestRspPkt struct.
func (p *CmppActiveTestRspPkt) Unpack(data []byte) error {
var r = newPacketReader(data)
// Sequence Id
r.ReadInt(binary.BigEndian, &p.SeqId)
p.Reserved = r.ReadByte()
return r.Error()
}
+127
View File
@@ -0,0 +1,127 @@
// Copyright 2015 Tony Bai.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package cmpp
import (
"errors"
"net"
"time"
)
var ErrNotCompleted = errors.New("data not being handled completed")
var ErrRespNotMatch = errors.New("the response is not matched with the request")
// Client stands for one client-side instance, just like a session.
// It may connect to the server, send & recv cmpp packets and terminate the connection.
type Client struct {
conn *Conn
typ Type
}
// New establishes a new cmpp client.
func NewClient(typ Type) *Client {
return &Client{
typ: typ,
}
}
// Connect connect to the cmpp server in block mode.
// It sends login packet, receive and parse connect response packet.
func (cli *Client) Connect(servAddr, user, password string, timeout time.Duration) error {
var err error
conn, err := net.DialTimeout("tcp", servAddr, timeout)
if err != nil {
return err
}
cli.conn = NewConn(conn, cli.typ)
defer func() {
if err != nil {
if cli.conn != nil {
cli.conn.Close()
}
}
}()
cli.conn.SetState(CONN_CONNECTED)
// Login to the server.
req := &CmppConnReqPkt{
SrcAddr: user,
Secret: password,
Version: cli.typ,
}
_, err = cli.SendReqPkt(req)
if err != nil {
return err
}
p, err := cli.conn.RecvAndUnpackPkt(timeout)
if err != nil {
return err
}
var ok bool
var status uint8
if cli.typ == V20 || cli.typ == V21 {
var rsp *Cmpp2ConnRspPkt
rsp, ok = p.(*Cmpp2ConnRspPkt)
if !ok {
err = ErrRespNotMatch
return err
}
status = rsp.Status
} else {
var rsp *Cmpp3ConnRspPkt
rsp, ok = p.(*Cmpp3ConnRspPkt)
if !ok {
err = ErrRespNotMatch
return err
}
status = uint8(rsp.Status)
}
if status != 0 {
if status <= ErrnoConnOthers { //ErrnoConnOthers = 5
err = ConnRspStatusErrMap[status]
} else {
err = ConnRspStatusErrMap[ErrnoConnOthers]
}
return err
}
cli.conn.SetState(CONN_AUTHOK)
return nil
}
func (cli *Client) Disconnect() {
if cli.conn != nil {
cli.conn.Close()
}
}
// 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)
}
// SendRspPkt pack the cmpp response packet structure and send it to the other peer.
func (cli *Client) SendRspPkt(packet Packer, seqId uint32) error {
return cli.conn.SendPkt(packet, seqId)
}
// RecvAndUnpackPkt receives cmpp byte stream, and unpack it to some cmpp packet structure.
func (cli *Client) RecvAndUnpackPkt(timeout time.Duration) (interface{}, error) {
return cli.conn.RecvAndUnpackPkt(timeout)
}
+271
View File
@@ -0,0 +1,271 @@
// Copyright 2015 Tony Bai.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package cmpp
import (
"encoding/binary"
"errors"
"io"
"net"
"sync"
"time"
)
type State uint8
// Errors for conn operations
var (
ErrConnIsClosed = errors.New("connection is closed")
ErrReadCmdIDTimeout = errors.New("read commandId timeout")
ErrReadPktBodyTimeout = errors.New("read packet body timeout")
)
var noDeadline = time.Time{}
// Conn States
const (
CONN_CLOSED State = iota
CONN_CONNECTED
CONN_AUTHOK
)
type Conn struct {
net.Conn
State State
Typ Type
// for SeqId generator goroutine
SeqId <-chan uint32
done chan<- struct{}
}
func newSeqIdGenerator() (<-chan uint32, chan<- struct{}) {
out := make(chan uint32)
done := make(chan struct{})
go func() {
var i uint32
for {
select {
case out <- i:
i++
case <-done:
close(out)
return
}
}
}()
return out, done
}
// New returns an abstract structure for successfully
// established underlying net.Conn.
func NewConn(conn net.Conn, typ Type) *Conn {
seqId, done := newSeqIdGenerator()
c := &Conn{
Conn: conn,
Typ: typ,
SeqId: seqId,
done: done,
}
tc := c.Conn.(*net.TCPConn) // Always tcpconn
tc.SetKeepAlive(true) //Keepalive as default
return c
}
func (c *Conn) Close() {
if c != nil {
if c.State == CONN_CLOSED {
return
}
close(c.done) // let the SeqId goroutine exit.
c.Conn.Close() // close the underlying net.Conn
c.State = CONN_CLOSED
}
}
func (c *Conn) SetState(state State) {
c.State = state
}
// SendPkt pack the cmpp packet structure and send it to the other peer.
func (c *Conn) SendPkt(packet Packer, seqId uint32) error {
if c.State == CONN_CLOSED {
return ErrConnIsClosed
}
data, err := packet.Pack(seqId)
if err != nil {
return err
}
_, err = c.Conn.Write(data) //block write
if err != nil {
return err
}
return nil
}
const (
defaultReadBufferSize = 4096
)
// readBuffer is used to optimize the performance of
// RecvAndUnpackPkt.
type readBuffer struct {
totalLen uint32
commandId CommandId
leftData [defaultReadBufferSize]byte
}
var readBufferPool = sync.Pool{
New: func() interface{} {
return &readBuffer{}
},
}
// RecvAndUnpackPkt receives cmpp byte stream, and unpack it to some cmpp packet structure.
func (c *Conn) RecvAndUnpackPkt(timeout time.Duration) (interface{}, error) {
if c.State == CONN_CLOSED {
return nil, ErrConnIsClosed
}
defer c.SetReadDeadline(noDeadline)
rb := readBufferPool.Get().(*readBuffer)
defer readBufferPool.Put(rb)
// Total_Length in packet
if timeout != 0 {
c.SetReadDeadline(time.Now().Add(timeout))
}
err := binary.Read(c.Conn, binary.BigEndian, &rb.totalLen)
if err != nil {
return nil, err
}
if c.Typ == V30 {
if rb.totalLen < CMPP3_PACKET_MIN || rb.totalLen > CMPP3_PACKET_MAX {
return nil, ErrTotalLengthInvalid
}
}
if c.Typ == V21 || c.Typ == V20 {
if rb.totalLen < CMPP2_PACKET_MIN || rb.totalLen > CMPP2_PACKET_MAX {
return nil, ErrTotalLengthInvalid
}
}
// Command_Id
if timeout != 0 {
c.SetReadDeadline(time.Now().Add(timeout))
}
err = binary.Read(c.Conn, binary.BigEndian, &rb.commandId)
if err != nil {
netErr, ok := err.(net.Error)
if ok {
if netErr.Timeout() {
return nil, ErrReadCmdIDTimeout
}
}
return nil, err
}
if !((rb.commandId > CMPP_REQUEST_MIN && rb.commandId < CMPP_REQUEST_MAX) ||
(rb.commandId > CMPP_RESPONSE_MIN && rb.commandId < CMPP_RESPONSE_MAX)) {
return nil, ErrCommandIdInvalid
}
// The left packet data (start from seqId in header).
if timeout != 0 {
c.SetReadDeadline(time.Now().Add(timeout))
}
var leftData = rb.leftData[0:(rb.totalLen - 8)]
_, err = io.ReadFull(c.Conn, leftData)
if err != nil {
netErr, ok := err.(net.Error)
if ok {
if netErr.Timeout() {
return nil, ErrReadPktBodyTimeout
}
}
return nil, err
}
var p Packer
switch rb.commandId {
case CMPP_CONNECT:
p = &CmppConnReqPkt{}
case CMPP_CONNECT_RESP:
if c.Typ == V30 {
p = &Cmpp3ConnRspPkt{}
} else {
p = &Cmpp2ConnRspPkt{}
}
case CMPP_TERMINATE:
p = &CmppTerminateReqPkt{}
case CMPP_TERMINATE_RESP:
p = &CmppTerminateRspPkt{}
case CMPP_SUBMIT:
if c.Typ == V30 {
p = &Cmpp3SubmitReqPkt{}
} else {
p = &Cmpp2SubmitReqPkt{}
}
case CMPP_SUBMIT_RESP:
if c.Typ == V30 {
p = &Cmpp3SubmitRspPkt{}
} else {
p = &Cmpp2SubmitRspPkt{}
}
case CMPP_DELIVER:
if c.Typ == V30 {
p = &Cmpp3DeliverReqPkt{}
} else {
p = &Cmpp2DeliverReqPkt{}
}
case CMPP_DELIVER_RESP:
if c.Typ == V30 {
p = &Cmpp3DeliverRspPkt{}
} else {
p = &Cmpp2DeliverRspPkt{}
}
case CMPP_FWD:
if c.Typ == V30 {
p = &Cmpp3FwdReqPkt{}
} else {
p = &Cmpp2FwdReqPkt{}
}
case CMPP_FWD_RESP:
if c.Typ == V30 {
p = &Cmpp3FwdRspPkt{}
} else {
p = &Cmpp2FwdRspPkt{}
}
case CMPP_ACTIVE_TEST:
p = &CmppActiveTestReqPkt{}
case CMPP_ACTIVE_TEST_RESP:
p = &CmppActiveTestRspPkt{}
default:
p = nil
return nil, ErrCommandIdNotSupported
}
err = p.Unpack(leftData)
if err != nil {
return nil, err
}
return p, nil
}
+283
View File
@@ -0,0 +1,283 @@
// Copyright 2015 Tony Bai.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package cmpp
import (
"bytes"
"crypto/md5"
"encoding/binary"
"errors"
"strconv"
"time"
cmpputils "github.com/bigwhite/gocmpp/utils"
)
// Packet length const for cmpp connect request and response packets.
const (
CmppConnReqPktLen uint32 = 4 + 4 + 4 + 6 + 16 + 1 + 4 //39d, 0x27
Cmpp2ConnRspPktLen uint32 = 4 + 4 + 4 + 1 + 16 + 1 //30d, 0x1e
Cmpp3ConnRspPktLen uint32 = 4 + 4 + 4 + 4 + 16 + 1 //33d, 0x21
)
// Errors for connect resp status.
var (
ErrnoConnInvalidStruct uint8 = 1
ErrnoConnInvalidSrcAddr uint8 = 2
ErrnoConnAuthFailed uint8 = 3
ErrnoConnVerTooHigh uint8 = 4
ErrnoConnOthers uint8 = 5
ConnRspStatusErrMap = map[uint8]error{
ErrnoConnInvalidStruct: errConnInvalidStruct,
ErrnoConnInvalidSrcAddr: errConnInvalidSrcAddr,
ErrnoConnAuthFailed: errConnAuthFailed,
ErrnoConnVerTooHigh: errConnVerTooHigh,
ErrnoConnOthers: errConnOthers,
}
errConnInvalidStruct = errors.New("connect response status: invalid protocol structure")
errConnInvalidSrcAddr = errors.New("connect response status: invalid source address")
errConnAuthFailed = errors.New("connect response status: auth failed")
errConnVerTooHigh = errors.New("connect response status: protocol version is too high")
errConnOthers = errors.New("connect response status: other errors")
)
func now() (string, uint32) {
s := time.Now().Format("0102150405")
i, _ := strconv.Atoi(s)
return s, uint32(i)
}
// CmppConnReqPkt represents a Cmpp2 or Cmpp3 connect request packet.
//
// when used in client side(pack), you should initialize it with
// correct SourceAddr(SrcAddr), Secret and Version.
//
// when used in server side(unpack), nothing needed to be initialized.
// unpack will fill the SourceAddr(SrcAddr), AuthSrc, Version, Timestamp
// and SeqId
//
type CmppConnReqPkt struct {
SrcAddr string
AuthSrc string
Version Type
Timestamp uint32
Secret string
SeqId uint32
}
// Cmpp2ConnRspPkt represents a Cmpp2 connect response packet.
//
// when used in server side(pack), you should initialize it with
// correct Status, AuthSrc, Secret and Version.
//
// when used in client side(unpack), nothing needed to be initialized.
// unpack will fill the Status, AuthImsg, Version and SeqId
//
type Cmpp2ConnRspPkt struct {
Status uint8
AuthIsmg string
Version Type
Secret string
AuthSrc string
SeqId uint32
}
// Cmpp3ConnRspPkt represents a Cmpp3 connect response packet.
//
// when used in server side(pack), you should initialize it with
// correct Status, AuthSrc, Secret and Version.
//
// when used in client side(unpack), nothing needed to be initialized.
// unpack will fill the Status, AuthImsg, Version and SeqId
//
type Cmpp3ConnRspPkt struct {
Status uint32
AuthIsmg string
Version Type
Secret string
AuthSrc string
SeqId uint32
}
// Pack packs the CmppConnReqPkt to bytes stream for client side.
// Before calling Pack, you should initialize a CmppConnReqPkt variable
// with correct SourceAddr(SrcAddr), Secret and Version.
func (p *CmppConnReqPkt) Pack(seqId uint32) ([]byte, error) {
var w = newPacketWriter(CmppConnReqPktLen)
// Pack header
w.WriteInt(binary.BigEndian, CmppConnReqPktLen)
w.WriteInt(binary.BigEndian, CMPP_CONNECT)
w.WriteInt(binary.BigEndian, seqId)
p.SeqId = seqId
var ts string
if p.Timestamp == 0 {
ts, p.Timestamp = now() //default: current time.
} else {
ts = cmpputils.TimeStamp2Str(p.Timestamp)
}
// Pack body
srcAddr := cmpputils.OctetString(p.SrcAddr, 6)
w.WriteString(srcAddr)
md5 := md5.Sum(bytes.Join([][]byte{[]byte(srcAddr),
make([]byte, 9),
[]byte(p.Secret),
[]byte(ts)},
nil))
p.AuthSrc = string(md5[:])
w.WriteString(p.AuthSrc)
w.WriteInt(binary.BigEndian, p.Version)
w.WriteInt(binary.BigEndian, p.Timestamp)
return w.Bytes()
}
// Unpack unpack the binary byte stream to a CmppConnReqPkt variable.
// Usually it is used in server side. After unpack, you will get SeqId, SourceAddr,
// AuthenticatorSource, Version and Timestamp.
func (p *CmppConnReqPkt) Unpack(data []byte) error {
var r = newPacketReader(data)
// Sequence Id
r.ReadInt(binary.BigEndian, &p.SeqId)
// Body: Source_Addr
var sa = make([]byte, 6)
r.ReadBytes(sa)
p.SrcAddr = string(sa)
// Body: AuthSrc
var as = make([]byte, 16)
r.ReadBytes(as)
p.AuthSrc = string(as)
// Body: Version
r.ReadInt(binary.BigEndian, &p.Version)
// Body: timestamp
r.ReadInt(binary.BigEndian, &p.Timestamp)
return r.Error()
}
// Pack packs the Cmpp2ConnRspPkt to bytes stream for server side.
// Before calling Pack, you should initialize a Cmpp2ConnRspPkt variable
// with correct Status,AuthenticatorSource, Secret and Version.
func (p *Cmpp2ConnRspPkt) Pack(seqId uint32) ([]byte, error) {
var w = newPacketWriter(Cmpp2ConnRspPktLen)
// pack header
w.WriteInt(binary.BigEndian, Cmpp2ConnRspPktLen)
w.WriteInt(binary.BigEndian, CMPP_CONNECT_RESP)
w.WriteInt(binary.BigEndian, seqId)
p.SeqId = seqId
// pack body
w.WriteInt(binary.BigEndian, p.Status)
md5 := md5.Sum(bytes.Join([][]byte{[]byte{p.Status},
[]byte(p.AuthSrc),
[]byte(p.Secret)},
nil))
p.AuthIsmg = string(md5[:])
w.WriteString(p.AuthIsmg)
w.WriteInt(binary.BigEndian, p.Version)
return w.Bytes()
}
// Unpack unpack the binary byte stream to a Cmpp2ConnRspPkt variable.
// Usually it is used in client side. After unpack, you will get SeqId, Status,
// AuthenticatorIsmg, and Version.
// Parameter data contains seqId in header and the whole packet body.
func (p *Cmpp2ConnRspPkt) Unpack(data []byte) error {
var r = newPacketReader(data)
// Sequence Id
r.ReadInt(binary.BigEndian, &p.SeqId)
// Body: Status
r.ReadInt(binary.BigEndian, &p.Status)
// Body: AuthenticatorISMG
var s = make([]byte, 16)
r.ReadBytes(s)
p.AuthIsmg = string(s)
// Body: Version
r.ReadInt(binary.BigEndian, &p.Version)
return r.Error()
}
// Pack packs the Cmpp3ConnRspPkt to bytes stream for server side.
// Before calling Pack, you should initialize a Cmpp3ConnRspPkt variable
// with correct Status,AuthenticatorSource, Secret and Version.
func (p *Cmpp3ConnRspPkt) Pack(seqId uint32) ([]byte, error) {
var w = newPacketWriter(Cmpp3ConnRspPktLen)
// pack header
w.WriteInt(binary.BigEndian, Cmpp3ConnRspPktLen)
w.WriteInt(binary.BigEndian, CMPP_CONNECT_RESP)
w.WriteInt(binary.BigEndian, seqId)
p.SeqId = seqId
// pack body
w.WriteInt(binary.BigEndian, p.Status)
var statusBuf = new(bytes.Buffer)
err := binary.Write(statusBuf, binary.BigEndian, p.Status)
if err != nil {
return nil, err
}
md5 := md5.Sum(bytes.Join([][]byte{statusBuf.Bytes(),
[]byte(p.AuthSrc),
[]byte(p.Secret)},
nil))
p.AuthIsmg = string(md5[:])
w.WriteString(p.AuthIsmg)
w.WriteInt(binary.BigEndian, p.Version)
return w.Bytes()
}
// Unpack unpack the binary byte stream to a Cmpp3ConnRspPkt variable.
// Usually it is used in client side. After unpack, you will get SeqId, Status,
// AuthenticatorIsmg, and Version.
// Parameter data contains seqId in header and the whole packet body.
func (p *Cmpp3ConnRspPkt) Unpack(data []byte) error {
var r = newPacketReader(data)
// Sequence Id
r.ReadInt(binary.BigEndian, &p.SeqId)
// Body: Status
r.ReadInt(binary.BigEndian, &p.Status)
// Body: AuthenticatorISMG
var s = make([]byte, 16)
r.ReadBytes(s)
p.AuthIsmg = string(s)
// Body: Version
r.ReadInt(binary.BigEndian, &p.Version)
return r.Error()
}
+314
View File
@@ -0,0 +1,314 @@
// Copyright 2015 Tony Bai.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package cmpp
import (
"encoding/binary"
"errors"
)
// Packet length const for cmpp deliver request and response packets.
const (
Cmpp2DeliverReqPktMaxLen uint32 = 12 + 233 //245d, 0xf5
Cmpp2DeliverRspPktLen uint32 = 12 + 8 + 1 //21d, 0x15
Cmpp3DeliverReqPktMaxLen uint32 = 12 + 257 //269d, 0x10d
Cmpp3DeliverRspPktLen uint32 = 12 + 8 + 4 //24d, 0x18
)
// Errors for result in deliver resp.
var (
ErrnoDeliverInvalidStruct uint8 = 1
ErrnoDeliverInvalidCommandId uint8 = 2
ErrnoDeliverInvalidSequence uint8 = 3
ErrnoDeliverInvalidMsgLength uint8 = 4
ErrnoDeliverInvalidFeeCode uint8 = 5
ErrnoDeliverExceedMaxMsgLength uint8 = 6
ErrnoDeliverInvalidServiceId uint8 = 7
ErrnoDeliverNotPassFlowControl uint8 = 8
ErrnoDeliverOtherError uint8 = 9
DeliverRspResultErrMap = map[uint8]error{
ErrnoDeliverInvalidStruct: errDeliverInvalidStruct,
ErrnoDeliverInvalidCommandId: errDeliverInvalidCommandId,
ErrnoDeliverInvalidSequence: errDeliverInvalidSequence,
ErrnoDeliverInvalidMsgLength: errDeliverInvalidMsgLength,
ErrnoDeliverInvalidFeeCode: errDeliverInvalidFeeCode,
ErrnoDeliverExceedMaxMsgLength: errDeliverExceedMaxMsgLength,
ErrnoDeliverInvalidServiceId: errDeliverInvalidServiceId,
ErrnoDeliverNotPassFlowControl: errDeliverNotPassFlowControl,
ErrnoDeliverOtherError: errDeliverOtherError,
}
errDeliverInvalidStruct = errors.New("deliver response status: invalid protocol structure")
errDeliverInvalidCommandId = errors.New("deliver response status: invalid command id")
errDeliverInvalidSequence = errors.New("deliver response status: invalid message sequence")
errDeliverInvalidMsgLength = errors.New("deliver response status: invalid message length")
errDeliverInvalidFeeCode = errors.New("deliver response status: invalid fee code")
errDeliverExceedMaxMsgLength = errors.New("deliver response status: exceed max message length")
errDeliverInvalidServiceId = errors.New("deliver response status: invalid service id")
errDeliverNotPassFlowControl = errors.New("deliver response status: not pass the flow control")
errDeliverOtherError = errors.New("deliver response status: other error")
)
type Cmpp2DeliverReqPkt struct {
MsgId uint64
DestId string
ServiceId string
TpPid uint8
TpUdhi uint8
MsgFmt uint8
SrcTerminalId string
RegisterDelivery uint8
MsgLength uint8
MsgContent string
Reserve string
//session info
SeqId uint32
}
type Cmpp2DeliverRspPkt struct {
MsgId uint64
Result uint8
//session info
SeqId uint32
}
type Cmpp3DeliverReqPkt struct {
MsgId uint64
DestId string
ServiceId string
TpPid uint8
TpUdhi uint8
MsgFmt uint8
SrcTerminalId string
SrcTerminalType uint8
RegisterDelivery uint8
MsgLength uint8
MsgContent string
LinkId string
//session info
SeqId uint32
}
type Cmpp3DeliverRspPkt struct {
MsgId uint64
Result uint32
//session info
SeqId uint32
}
// Pack packs the Cmpp2DeliverReqPkt to bytes stream for client side.
func (p *Cmpp2DeliverReqPkt) Pack(seqId uint32) ([]byte, error) {
var pktLen uint32 = CMPP_HEADER_LEN + 65 + uint32(p.MsgLength) + 8
var w = newPacketWriter(pktLen)
// Pack header
w.WriteInt(binary.BigEndian, pktLen)
w.WriteInt(binary.BigEndian, CMPP_DELIVER)
w.WriteInt(binary.BigEndian, seqId)
p.SeqId = seqId
// Pack Body
w.WriteInt(binary.BigEndian, p.MsgId)
w.WriteFixedSizeString(p.DestId, 21)
w.WriteFixedSizeString(p.ServiceId, 10)
w.WriteByte(p.TpPid)
w.WriteByte(p.TpUdhi)
w.WriteByte(p.MsgFmt)
w.WriteFixedSizeString(p.SrcTerminalId, 21)
w.WriteByte(p.RegisterDelivery)
w.WriteByte(p.MsgLength)
w.WriteString(p.MsgContent)
w.WriteFixedSizeString(p.Reserve, 8)
return w.Bytes()
}
// Unpack unpack the binary byte stream to a Cmpp2DeliverReqPkt variable.
// After unpack, you will get all value of fields in
// Cmpp2DeliverReqPkt struct.
func (p *Cmpp2DeliverReqPkt) Unpack(data []byte) error {
var r = newPacketReader(data)
// Sequence Id
r.ReadInt(binary.BigEndian, &p.SeqId)
// Body
r.ReadInt(binary.BigEndian, &p.MsgId)
destId := r.ReadCString(21)
p.DestId = string(destId)
serviceId := r.ReadCString(10)
p.ServiceId = string(serviceId)
p.TpPid = r.ReadByte()
p.TpUdhi = r.ReadByte()
p.MsgFmt = r.ReadByte()
srcTerminalId := r.ReadCString(21)
p.SrcTerminalId = string(srcTerminalId)
p.RegisterDelivery = r.ReadByte()
p.MsgLength = r.ReadByte()
msgContent := make([]byte, p.MsgLength)
r.ReadBytes(msgContent)
p.MsgContent = string(msgContent)
reserve := r.ReadCString(8)
p.Reserve = string(reserve)
return r.Error()
}
// Pack packs the Cmpp2DeliverRspPkt to bytes stream for client side.
func (p *Cmpp2DeliverRspPkt) Pack(seqId uint32) ([]byte, error) {
var pktLen uint32 = Cmpp2DeliverRspPktLen
var w = newPacketWriter(pktLen)
// Pack header
w.WriteInt(binary.BigEndian, pktLen)
w.WriteInt(binary.BigEndian, CMPP_DELIVER_RESP)
w.WriteInt(binary.BigEndian, seqId)
p.SeqId = seqId
// Pack Body
w.WriteInt(binary.BigEndian, p.MsgId)
w.WriteByte(p.Result)
return w.Bytes()
}
// Unpack unpack the binary byte stream to a Cmpp2DeliverRspPkt variable.
// After unpack, you will get all value of fields in
// Cmpp2DeliverRspPkt struct.
func (p *Cmpp2DeliverRspPkt) Unpack(data []byte) error {
var r = newPacketReader(data)
// Sequence Id
r.ReadInt(binary.BigEndian, &p.SeqId)
r.ReadInt(binary.BigEndian, &p.MsgId)
p.Result = r.ReadByte()
return r.Error()
}
// Pack packs the Cmpp3DeliverReqPkt to bytes stream for client side.
func (p *Cmpp3DeliverReqPkt) Pack(seqId uint32) ([]byte, error) {
var pktLen uint32 = CMPP_HEADER_LEN + 77 + uint32(p.MsgLength) + 20
var w = newPacketWriter(pktLen)
// Pack header
w.WriteInt(binary.BigEndian, pktLen)
w.WriteInt(binary.BigEndian, CMPP_DELIVER)
w.WriteInt(binary.BigEndian, seqId)
p.SeqId = seqId
// Pack Body
w.WriteInt(binary.BigEndian, p.MsgId)
w.WriteFixedSizeString(p.DestId, 21)
w.WriteFixedSizeString(p.ServiceId, 10)
w.WriteByte(p.TpPid)
w.WriteByte(p.TpUdhi)
w.WriteByte(p.MsgFmt)
w.WriteFixedSizeString(p.SrcTerminalId, 32)
w.WriteByte(p.SrcTerminalType)
w.WriteByte(p.RegisterDelivery)
w.WriteByte(p.MsgLength)
w.WriteString(p.MsgContent)
w.WriteFixedSizeString(p.LinkId, 20)
return w.Bytes()
}
// Unpack unpack the binary byte stream to a Cmpp3DeliverReqPkt variable.
// After unpack, you will get all value of fields in
// Cmpp3DeliverReqPkt struct.
func (p *Cmpp3DeliverReqPkt) Unpack(data []byte) error {
var r = newPacketReader(data)
// Sequence Id
r.ReadInt(binary.BigEndian, &p.SeqId)
// Body
r.ReadInt(binary.BigEndian, &p.MsgId)
destId := r.ReadCString(21)
p.DestId = string(destId)
serviceId := r.ReadCString(10)
p.ServiceId = string(serviceId)
p.TpPid = r.ReadByte()
p.TpUdhi = r.ReadByte()
p.MsgFmt = r.ReadByte()
srcTerminalId := r.ReadCString(32)
p.SrcTerminalId = string(srcTerminalId)
p.SrcTerminalType = r.ReadByte()
p.RegisterDelivery = r.ReadByte()
p.MsgLength = r.ReadByte()
msgContent := make([]byte, p.MsgLength)
r.ReadBytes(msgContent)
p.MsgContent = string(msgContent)
linkId := r.ReadCString(20)
p.LinkId = string(linkId)
return r.Error()
}
// Pack packs the Cmpp3DeliverRspPkt to bytes stream for client side.
func (p *Cmpp3DeliverRspPkt) Pack(seqId uint32) ([]byte, error) {
var pktLen uint32 = Cmpp3DeliverRspPktLen
var w = newPacketWriter(pktLen)
// Pack header
w.WriteInt(binary.BigEndian, pktLen)
w.WriteInt(binary.BigEndian, CMPP_DELIVER_RESP)
w.WriteInt(binary.BigEndian, seqId)
p.SeqId = seqId
// Pack Body
w.WriteInt(binary.BigEndian, p.MsgId)
w.WriteInt(binary.BigEndian, p.Result)
return w.Bytes()
}
// Unpack unpack the binary byte stream to a Cmpp3DeliverRspPkt variable.
// After unpack, you will get all value of fields in
// Cmpp3DeliverRspPkt struct.
func (p *Cmpp3DeliverRspPkt) Unpack(data []byte) error {
var r = newPacketReader(data)
// Sequence Id
r.ReadInt(binary.BigEndian, &p.SeqId)
r.ReadInt(binary.BigEndian, &p.MsgId)
r.ReadInt(binary.BigEndian, &p.Result)
return r.Error()
}
+489
View File
@@ -0,0 +1,489 @@
// Copyright 2015 Tony Bai.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package cmpp
import (
"encoding/binary"
"errors"
)
// Packet length const for cmpp fwd request and response packets.
const (
Cmpp2FwdReqPktMaxLen uint32 = 12 + 2379 //2277d, 0x957
Cmpp2FwdRspPktLen uint32 = 12 + 8 + 1 + 1 + 1 //23d, 0x17
Cmpp3FwdReqPktMaxLen uint32 = 12 + 2491 //2503d, 0x9c7
Cmpp3FwdRspPktLen uint32 = 12 + 8 + 1 + 1 + 4 //26d, 0x1a
)
// Errors for result in fwd resp.
var (
ErrnoFwdInvalidStruct uint8 = 1
ErrnoFwdInvalidCommandId uint8 = 2
ErrnoFwdInvalidSequence uint8 = 3
ErrnoFwdInvalidMsgLength uint8 = 4
ErrnoFwdInvalidFeeCode uint8 = 5
ErrnoFwdExceedMaxMsgLength uint8 = 6
ErrnoFwdInvalidServiceId uint8 = 7
ErrnoFwdNotPassFlowControl uint8 = 8
ErrnoFwdNoPrivilege uint8 = 9
FwdRspResultErrMap = map[uint8]error{
ErrnoFwdInvalidStruct: errFwdInvalidStruct,
ErrnoFwdInvalidCommandId: errFwdInvalidCommandId,
ErrnoFwdInvalidSequence: errFwdInvalidSequence,
ErrnoFwdInvalidMsgLength: errFwdInvalidMsgLength,
ErrnoFwdInvalidFeeCode: errFwdInvalidFeeCode,
ErrnoFwdExceedMaxMsgLength: errFwdExceedMaxMsgLength,
ErrnoFwdInvalidServiceId: errFwdInvalidServiceId,
ErrnoFwdNotPassFlowControl: errFwdNotPassFlowControl,
ErrnoFwdNoPrivilege: errFwdNoPrivilege,
}
errFwdInvalidStruct = errors.New("fwd response status: invalid protocol structure")
errFwdInvalidCommandId = errors.New("fwd response status: invalid command id")
errFwdInvalidSequence = errors.New("fwd response status: invalid message sequence")
errFwdInvalidMsgLength = errors.New("fwd response status: invalid message length")
errFwdInvalidFeeCode = errors.New("fwd response status: invalid fee code")
errFwdExceedMaxMsgLength = errors.New("fwd response status: exceed max message length")
errFwdInvalidServiceId = errors.New("fwd response status: invalid service id")
errFwdNotPassFlowControl = errors.New("fwd response status: not pass the flow control")
errFwdNoPrivilege = errors.New("fwd response status: msg has no fwd privilege")
)
type Cmpp2FwdReqPkt struct {
SourceId string
DestinationId string
NodesCount uint8
MsgFwdType uint8
MsgId uint64
PkTotal uint8
PkNumber uint8
RegisteredDelivery uint8
MsgLevel uint8
ServiceId string
FeeUserType uint8
FeeTerminalId string
TpPid uint8
TpUdhi uint8
MsgFmt uint8
MsgSrc string
FeeType string
FeeCode string
ValidTime string
AtTime string
SrcId string
DestUsrTl uint8
DestId []string
MsgLength uint8
MsgContent string
Reserve string
// session info
SeqId uint32
}
type Cmpp2FwdRspPkt struct {
MsgId uint64
PkTotal uint8
PkNumber uint8
Result uint8
// session info
SeqId uint32
}
type Cmpp3FwdReqPkt struct {
SourceId string
DestinationId string
NodesCount uint8
MsgFwdType uint8
MsgId uint64
PkTotal uint8
PkNumber uint8
RegisteredDelivery uint8
MsgLevel uint8
ServiceId string
FeeUserType uint8
FeeTerminalId string
FeeTerminalPseudo string
FeeTerminalUserType uint8
TpPid uint8
TpUdhi uint8
MsgFmt uint8
MsgSrc string
FeeType string
FeeCode string
ValidTime string
AtTime string
SrcId string
SrcPseudo string
SrcUserType uint8
SrcType uint8
DestUsrTl uint8
DestId []string
DestPseudo string
DestUserType uint8
MsgLength uint8
MsgContent string
LinkId string
// session info
SeqId uint32
}
type Cmpp3FwdRspPkt struct {
MsgId uint64
PkTotal uint8
PkNumber uint8
Result uint32
// session info
SeqId uint32
}
// Pack packs the Cmpp2FwdReqPkt to bytes stream for client side.
// Before calling Pack, you should initialize a Cmpp2FwdReqPkt variable
// with correct field value.
func (p *Cmpp2FwdReqPkt) Pack(seqId uint32) ([]byte, error) {
var pktLen uint32 = CMPP_HEADER_LEN + 131 + uint32(p.DestUsrTl)*21 + 1 + uint32(p.MsgLength) + 8
var w = newPacketWriter(pktLen)
// Pack header
w.WriteInt(binary.BigEndian, pktLen)
w.WriteInt(binary.BigEndian, CMPP_FWD)
w.WriteInt(binary.BigEndian, seqId)
p.SeqId = seqId
// Pack Body
w.WriteFixedSizeString(p.SourceId, 6)
w.WriteFixedSizeString(p.DestinationId, 6)
w.WriteByte(p.NodesCount)
w.WriteByte(p.MsgFwdType)
w.WriteInt(binary.BigEndian, p.MsgId)
if p.PkTotal == 0 && p.PkNumber == 0 {
p.PkTotal, p.PkNumber = 1, 1
}
w.WriteByte(p.PkTotal)
w.WriteByte(p.PkNumber)
w.WriteByte(p.RegisteredDelivery)
w.WriteByte(p.MsgLevel)
w.WriteFixedSizeString(p.ServiceId, 10)
w.WriteByte(p.FeeUserType)
w.WriteFixedSizeString(p.FeeTerminalId, 21)
w.WriteByte(p.TpPid)
w.WriteByte(p.TpUdhi)
w.WriteByte(p.MsgFmt)
w.WriteFixedSizeString(p.MsgSrc, 6)
w.WriteFixedSizeString(p.FeeType, 2)
w.WriteFixedSizeString(p.FeeCode, 6)
w.WriteFixedSizeString(p.ValidTime, 17)
w.WriteFixedSizeString(p.AtTime, 17)
w.WriteFixedSizeString(p.SrcId, 21)
w.WriteByte(p.DestUsrTl)
for _, d := range p.DestId {
w.WriteFixedSizeString(d, 21)
}
w.WriteByte(p.MsgLength)
w.WriteString(p.MsgContent)
w.WriteFixedSizeString(p.Reserve, 8)
return w.Bytes()
}
// Unpack unpack the binary byte stream to a Cmpp2FwdReqPkt variable.
// After unpack, you will get all value of fields in Cmpp2FwdReqPkt struct.
func (p *Cmpp2FwdReqPkt) Unpack(data []byte) error {
var r = newPacketReader(data)
// Sequence Id
r.ReadInt(binary.BigEndian, &p.SeqId)
sourceId := r.ReadCString(6)
p.SourceId = string(sourceId)
destinationId := r.ReadCString(6)
p.DestinationId = string(destinationId)
p.NodesCount = r.ReadByte()
p.MsgFwdType = r.ReadByte()
r.ReadInt(binary.BigEndian, &p.MsgId)
p.PkTotal = r.ReadByte()
p.PkNumber = r.ReadByte()
p.RegisteredDelivery = r.ReadByte()
p.MsgLevel = r.ReadByte()
serviceId := r.ReadCString(10)
p.ServiceId = string(serviceId)
p.FeeUserType = r.ReadByte()
feeTerminalId := r.ReadCString(21)
p.FeeTerminalId = string(feeTerminalId)
p.TpPid = r.ReadByte()
p.TpUdhi = r.ReadByte()
p.MsgFmt = r.ReadByte()
msgSrc := r.ReadCString(6)
p.MsgSrc = string(msgSrc)
feeType := make([]byte, 2)
r.ReadBytes(feeType)
p.FeeType = string(feeType)
feeCode := r.ReadCString(6)
p.FeeCode = string(feeCode)
validTime := r.ReadCString(17)
p.ValidTime = string(validTime)
atTime := r.ReadCString(17)
p.AtTime = string(atTime)
srcId := r.ReadCString(21)
p.SrcId = string(srcId)
p.DestUsrTl = r.ReadByte()
for i := 0; i < int(p.DestUsrTl); i++ {
destId := r.ReadCString(21)
p.DestId = append(p.DestId, string(destId))
}
p.MsgLength = r.ReadByte()
msgContent := make([]byte, p.MsgLength)
r.ReadBytes(msgContent)
p.MsgContent = string(msgContent)
reserve := r.ReadCString(8)
p.Reserve = string(reserve)
return r.Error()
}
// Pack packs the Cmpp2FwdRspPkt to bytes stream for server side.
// Before calling Pack, you should initialize a Cmpp2FwdRspPkt variable
// with correct field value.
func (p *Cmpp2FwdRspPkt) Pack(seqId uint32) ([]byte, error) {
var pktLen = Cmpp2FwdRspPktLen
var w = newPacketWriter(pktLen)
// Pack header
w.WriteInt(binary.BigEndian, pktLen)
w.WriteInt(binary.BigEndian, CMPP_FWD_RESP)
w.WriteInt(binary.BigEndian, seqId)
p.SeqId = seqId
// Pack Body
w.WriteInt(binary.BigEndian, p.MsgId)
w.WriteByte(p.PkTotal)
w.WriteByte(p.PkNumber)
w.WriteByte(p.Result)
return w.Bytes()
}
// Unpack unpack the binary byte stream to a Cmpp2FwdRspPkt variable.
// After unpack, you will get all value of fields in Cmpp2FwdRspPkt struct.
func (p *Cmpp2FwdRspPkt) Unpack(data []byte) error {
var r = newPacketReader(data)
// Sequence Id
r.ReadInt(binary.BigEndian, &p.SeqId)
r.ReadInt(binary.BigEndian, &p.MsgId)
p.PkTotal = r.ReadByte()
p.PkNumber = r.ReadByte()
p.Result = r.ReadByte()
return r.Error()
}
// Pack packs the Cmpp3FwdReqPkt to bytes stream for client side.
// Before calling Pack, you should initialize a Cmpp3FwdReqPkt variable
// with correct field value.
func (p *Cmpp3FwdReqPkt) Pack(seqId uint32) ([]byte, error) {
var pktLen uint32 = CMPP_HEADER_LEN + 198 + uint32(p.DestUsrTl)*21 + 32 + 1 + 1 + uint32(p.MsgLength) + 20
var w = newPacketWriter(pktLen)
// Pack header
w.WriteInt(binary.BigEndian, pktLen)
w.WriteInt(binary.BigEndian, CMPP_FWD)
w.WriteInt(binary.BigEndian, seqId)
p.SeqId = seqId
// Pack Body
w.WriteFixedSizeString(p.SourceId, 6)
w.WriteFixedSizeString(p.DestinationId, 6)
w.WriteByte(p.NodesCount)
w.WriteByte(p.MsgFwdType)
w.WriteInt(binary.BigEndian, p.MsgId)
if p.PkTotal == 0 && p.PkNumber == 0 {
p.PkTotal, p.PkNumber = 1, 1
}
w.WriteByte(p.PkTotal)
w.WriteByte(p.PkNumber)
w.WriteByte(p.RegisteredDelivery)
w.WriteByte(p.MsgLevel)
w.WriteFixedSizeString(p.ServiceId, 10)
w.WriteByte(p.FeeUserType)
w.WriteFixedSizeString(p.FeeTerminalId, 21)
w.WriteFixedSizeString(p.FeeTerminalPseudo, 32)
w.WriteByte(p.FeeTerminalUserType)
w.WriteByte(p.TpPid)
w.WriteByte(p.TpUdhi)
w.WriteByte(p.MsgFmt)
w.WriteFixedSizeString(p.MsgSrc, 6)
w.WriteFixedSizeString(p.FeeType, 2)
w.WriteFixedSizeString(p.FeeCode, 6)
w.WriteFixedSizeString(p.ValidTime, 17)
w.WriteFixedSizeString(p.AtTime, 17)
w.WriteFixedSizeString(p.SrcId, 21)
w.WriteFixedSizeString(p.SrcPseudo, 32)
w.WriteByte(p.SrcUserType)
w.WriteByte(p.SrcType)
w.WriteByte(p.DestUsrTl)
for _, d := range p.DestId {
w.WriteFixedSizeString(d, 21)
}
w.WriteFixedSizeString(p.DestPseudo, 32)
w.WriteByte(p.DestUserType)
w.WriteByte(p.MsgLength)
w.WriteString(p.MsgContent)
w.WriteFixedSizeString(p.LinkId, 20)
return w.Bytes()
}
// Unpack unpack the binary byte stream to a Cmpp3FwdReqPkt variable.
// After unpack, you will get all value of fields in Cmpp3FwdReqPkt struct.
func (p *Cmpp3FwdReqPkt) Unpack(data []byte) error {
var r = newPacketReader(data)
// Sequence Id
r.ReadInt(binary.BigEndian, &p.SeqId)
// Body
sourceId := r.ReadCString(6)
p.SourceId = string(sourceId)
destinationId := r.ReadCString(6)
p.DestinationId = string(destinationId)
p.NodesCount = r.ReadByte()
p.MsgFwdType = r.ReadByte()
r.ReadInt(binary.BigEndian, &p.MsgId)
p.PkTotal = r.ReadByte()
p.PkNumber = r.ReadByte()
p.RegisteredDelivery = r.ReadByte()
p.MsgLevel = r.ReadByte()
serviceId := r.ReadCString(10)
p.ServiceId = string(serviceId)
p.FeeUserType = r.ReadByte()
feeTerminalId := r.ReadCString(21)
p.FeeTerminalId = string(feeTerminalId)
feeTerminalPseudo := r.ReadCString(32)
p.FeeTerminalPseudo = string(feeTerminalPseudo)
p.FeeTerminalUserType = r.ReadByte()
p.TpPid = r.ReadByte()
p.TpUdhi = r.ReadByte()
p.MsgFmt = r.ReadByte()
msgSrc := r.ReadCString(6)
p.MsgSrc = string(msgSrc)
feeType := make([]byte, 2)
r.ReadBytes(feeType)
p.FeeType = string(feeType)
feeCode := r.ReadCString(6)
p.FeeCode = string(feeCode)
validTime := r.ReadCString(17)
p.ValidTime = string(validTime)
atTime := r.ReadCString(17)
p.AtTime = string(atTime)
srcId := r.ReadCString(21)
p.SrcId = string(srcId)
srcPseudo := r.ReadCString(32)
p.SrcPseudo = string(srcPseudo)
p.SrcUserType = r.ReadByte()
p.SrcType = r.ReadByte()
p.DestUsrTl = r.ReadByte()
for i := 0; i < int(p.DestUsrTl); i++ {
destId := r.ReadCString(21)
p.DestId = append(p.DestId, string(destId))
}
destPseudo := r.ReadCString(32)
p.DestPseudo = string(destPseudo)
p.DestUserType = r.ReadByte()
p.MsgLength = r.ReadByte()
msgContent := make([]byte, p.MsgLength)
r.ReadBytes(msgContent)
p.MsgContent = string(msgContent)
linkId := r.ReadCString(20)
p.LinkId = string(linkId)
return r.Error()
}
// Pack packs the Cmpp3FwdRspPkt to bytes stream for server side.
// Before calling Pack, you should initialize a Cmpp3FwdRspPkt variable
// with correct field value.
func (p *Cmpp3FwdRspPkt) Pack(seqId uint32) ([]byte, error) {
var pktLen = Cmpp3FwdRspPktLen
var w = newPacketWriter(pktLen)
// Pack header
w.WriteInt(binary.BigEndian, pktLen)
w.WriteInt(binary.BigEndian, CMPP_FWD_RESP)
w.WriteInt(binary.BigEndian, seqId)
p.SeqId = seqId
// Pack Body
w.WriteInt(binary.BigEndian, p.MsgId)
w.WriteByte(p.PkTotal)
w.WriteByte(p.PkNumber)
w.WriteInt(binary.BigEndian, p.Result)
return w.Bytes()
}
// Unpack unpack the binary byte stream to a Cmpp3FwdRspPkt variable.
// After unpack, you will get all value of fields in Cmpp3FwdRspPkt struct.
func (p *Cmpp3FwdRspPkt) Unpack(data []byte) error {
var r = newPacketReader(data)
// Sequence Id
r.ReadInt(binary.BigEndian, &p.SeqId)
r.ReadInt(binary.BigEndian, &p.MsgId)
p.PkTotal = r.ReadByte()
p.PkNumber = r.ReadByte()
r.ReadInt(binary.BigEndian, &p.Result)
return r.Error()
}
+8
View File
@@ -0,0 +1,8 @@
module github.com/bigwhite/gocmpp
require (
github.com/dvyukov/go-fuzz v0.0.0-20190516070045-5cc3605ccbb6
golang.org/x/text v0.3.8
)
go 1.13
+27
View File
@@ -0,0 +1,27 @@
github.com/dvyukov/go-fuzz v0.0.0-20190516070045-5cc3605ccbb6 h1:JQBgIQumUT/1PnZs9cWQzx/xH+djYh+oGDLO8H68SWE=
github.com/dvyukov/go-fuzz v0.0.0-20190516070045-5cc3605ccbb6/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+384
View File
@@ -0,0 +1,384 @@
// Copyright 2015 Tony Bai.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package cmpp
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"strings"
)
type Type int8
const (
V30 Type = 0x30
V21 Type = 0x21
V20 Type = 0x20
)
func (t Type) String() string {
switch {
case t == V30:
return "cmpp30"
case t == V21:
return "cmpp21"
case t == V20:
return "cmpp20"
default:
return "unknown"
}
}
const (
CMPP_HEADER_LEN uint32 = 12
CMPP2_PACKET_MAX uint32 = 2477
CMPP2_PACKET_MIN uint32 = 12
CMPP3_PACKET_MAX uint32 = 3335
CMPP3_PACKET_MIN uint32 = 12
)
// Common errors.
var ErrMethodParamsInvalid = errors.New("params passed to method is invalid")
// Protocol errors.
var ErrTotalLengthInvalid = errors.New("total_length in Packet data is invalid")
var ErrCommandIdInvalid = errors.New("command_Id in Packet data is invalid")
var ErrCommandIdNotSupported = errors.New("command_Id in Packet data is not supported")
type CommandId uint32
const (
CMPP_REQUEST_MIN, CMPP_RESPONSE_MIN CommandId = iota, 0x80000000 + iota
CMPP_CONNECT, CMPP_CONNECT_RESP
CMPP_TERMINATE, CMPP_TERMINATE_RESP
_, _
CMPP_SUBMIT, CMPP_SUBMIT_RESP
CMPP_DELIVER, CMPP_DELIVER_RESP
CMPP_QUERY, CMPP_QUERY_RESP
CMPP_CANCEL, CMPP_CANCEL_RESP
CMPP_ACTIVE_TEST, CMPP_ACTIVE_TEST_RESP
CMPP_FWD, CMPP_FWD_RESP
CMPP_MT_ROUTE, CMPP_MT_ROUTE_RESP CommandId = 0x00000010 - 10 + iota, 0x80000010 - 10 + iota
CMPP_MO_ROUTE, CMPP_MO_ROUTE_RESP
CMPP_GET_MT_ROUTE, CMPP_GET_MT_ROUTE_RESP
CMPP_MT_ROUTE_UPDATE, CMPP_MT_ROUTE_UPDATE_RESP
CMPP_MO_ROUTE_UPDATE, CMPP_MO_ROUTE_UPDATE_RESP
CMPP_PUSH_MT_ROUTE_UPDATE, CMPP_PUSH_MT_ROUTE_UPDATE_RESP
CMPP_PUSH_MO_ROUTE_UPDATE, CMPP_PUSH_MO_ROUTE_UPDATE_RESP
CMPP_GET_MO_ROUTE, CMPP_GET_MO_ROUTE_RESP
CMPP_REQUEST_MAX, CMPP_RESPONSE_MAX
)
func (id CommandId) String() string {
if id <= CMPP_FWD && id > CMPP_REQUEST_MIN {
return []string{
"CMPP_CONNECT",
"CMPP_TERMINATE",
"CMPP_UNKNOWN",
"CMPP_SUBMIT",
"CMPP_DELIVER",
"CMPP_QUERY",
"CMPP_CANCEL",
"CMPP_ACTIVE_TEST",
"CMPP_FWD",
}[id-1]
} else if id >= CMPP_MT_ROUTE && id < CMPP_REQUEST_MAX {
return []string{
"CMPP_MT_ROUTE",
"CMPP_MO_ROUTE",
"CMPP_GET_MT_ROUTE",
"CMPP_MT_ROUTE_UPDATE",
"CMPP_MO_ROUTE_UPDATE",
"CMPP_PUSH_MT_ROUTE_UPDATE",
"CMPP_PUSH_MO_ROUTE_UPDATE",
"CMPP_GET_MO_ROUTE",
}[id-0x00000010]
}
if id <= CMPP_FWD_RESP && id > CMPP_RESPONSE_MIN {
return []string{
"CMPP_CONNECT_RESP",
"CMPP_TERMINATE_RESP",
"CMPP_UNKNOWN",
"CMPP_SUBMIT_RESP",
"CMPP_DELIVER_RESP",
"CMPP_QUERY_RESP",
"CMPP_CANCEL_RESP",
"CMPP_ACTIVE_TEST_RESP",
"CMPP_FWD_RESP",
}[id-0x80000001]
} else if id >= CMPP_MT_ROUTE_RESP && id < CMPP_RESPONSE_MAX {
return []string{
"CMPP_MT_ROUTE_RESP",
"CMPP_MO_ROUTE_RESP",
"CMPP_GET_MT_ROUTE_RESP",
"CMPP_MT_ROUTE_UPDATE_RESP",
"CMPP_MO_ROUTE_UPDATE_RESP",
"CMPP_PUSH_MT_ROUTE_UPDATE_RESP",
"CMPP_PUSH_MO_ROUTE_UPDATE_RESP",
"CMPP_GET_MO_ROUTE_RESP",
}[id-0x80000010]
}
return "unknown"
}
type Packer interface {
Pack(seqId uint32) ([]byte, error)
Unpack(data []byte) error
}
// OpError is the error type usually returned by functions in the cmpppacket
// package. It describes the operation and the error which the operation caused.
type OpError struct {
// err is the error that occurred during the operation.
// it is the origin error.
err error
// op is the operation which caused the error, such as
// some "read" or "write" in packetWriter or packetReader.
op string
}
func NewOpError(e error, op string) *OpError {
return &OpError{
err: e,
op: op,
}
}
func (e *OpError) Error() string {
if e.err == nil {
return "<nil>"
}
return e.op + " error: " + e.err.Error()
}
func (e *OpError) Cause() error {
return e.err
}
func (e *OpError) Op() string {
return e.op
}
type packetWriter struct {
wb *bytes.Buffer
err *OpError
}
func newPacketWriter(initSize uint32) *packetWriter {
buf := make([]byte, 0, initSize)
return &packetWriter{
wb: bytes.NewBuffer(buf),
}
}
// Bytes returns a slice of the contents of the inner buffer;
// If the caller changes the contents of the
// returned slice, the contents of the buffer will change provided there
// are no intervening method calls on the Buffer.
func (w *packetWriter) Bytes() ([]byte, error) {
if w.err != nil {
return nil, w.err
}
len := w.wb.Len()
return (w.wb.Bytes())[:len], nil
}
// WriteByte appends the byte of b to the inner buffer, growing the buffer as
// needed.
func (w *packetWriter) WriteByte(b byte) {
if w.err != nil {
return
}
err := w.wb.WriteByte(b)
if err != nil {
w.err = NewOpError(err,
fmt.Sprintf("packetWriter.WriteByte writes: %x", b))
return
}
}
// WriteFixedSizeString writes a string to buffer, if the length of s is less than size,
// Pad binary zero to the right.
func (w *packetWriter) WriteFixedSizeString(s string, size int) {
if w.err != nil {
return
}
l1 := len(s)
l2 := l1
if l2 > 10 {
l2 = 10
}
if l1 > size {
w.err = NewOpError(ErrMethodParamsInvalid,
fmt.Sprintf("packetWriter.WriteFixedSizeString writes: %s", s[0:l2]))
return
}
w.WriteString(strings.Join([]string{s, string(make([]byte, size-l1))}, ""))
}
// WriteString appends the contents of s to the inner buffer, growing the buffer as
// needed.
func (w *packetWriter) WriteString(s string) {
if w.err != nil {
return
}
l1 := len(s)
l2 := l1
if l2 > 10 {
l2 = 10
}
n, err := w.wb.WriteString(s)
if err != nil {
w.err = NewOpError(err,
fmt.Sprintf("packetWriter.WriteString writes: %s", s[0:l2]))
return
}
if n != l1 {
w.err = NewOpError(fmt.Errorf("WriteString writes %d bytes, not equal to %d we expected", n, l1),
fmt.Sprintf("packetWriter.WriteString writes: %s", s[0:l2]))
return
}
}
// WriteInt appends the content of data to the inner buffer in order, growing the buffer as
// needed.
func (w *packetWriter) WriteInt(order binary.ByteOrder, data interface{}) {
if w.err != nil {
return
}
err := binary.Write(w.wb, order, data)
if err != nil {
w.err = NewOpError(err,
fmt.Sprintf("packetWriter.WriteInt writes: %#v", data))
return
}
}
const maxCStringSize = 160
type packetReader struct {
rb *bytes.Buffer
err *OpError
cbuf [maxCStringSize]byte
}
func newPacketReader(data []byte) *packetReader {
return &packetReader{
rb: bytes.NewBuffer(data),
}
}
// ReadByte reads and returns the next byte from the inner buffer.
// If no byte is available, it returns an OpError.
func (r *packetReader) ReadByte() byte {
if r.err != nil {
return 0
}
b, err := r.rb.ReadByte()
if err != nil {
r.err = NewOpError(err,
"packetReader.ReadByte")
return 0
}
return b
}
// ReadInt reads reads structured binary data from r into data.
// Data must be a pointer to a fixed-size value or a slice
// of fixed-size values.
// Bytes read from r are decoded using the specified byte order
// and written to successive fields of the data.
func (r *packetReader) ReadInt(order binary.ByteOrder, data interface{}) {
if r.err != nil {
return
}
err := binary.Read(r.rb, order, data)
if err != nil {
r.err = NewOpError(err,
"packetReader.ReadInt")
return
}
}
// ReadBytes reads the next len(s) bytes from the inner buffer to s.
// If the buffer has no data to return, an OpError would be stored in r.err.
func (r *packetReader) ReadBytes(s []byte) {
if r.err != nil {
return
}
n, err := r.rb.Read(s)
if err != nil {
r.err = NewOpError(err,
"packetReader.ReadBytes")
return
}
if n != len(s) {
r.err = NewOpError(fmt.Errorf("ReadBytes reads %d bytes, not equal to %d we expected", n, len(s)),
"packetWriter.ReadBytes")
return
}
}
// ReadCString read bytes from packerReader's inner buffer,
// it would trim the tail-zero byte and the bytes after that.
func (r *packetReader) ReadCString(length int) []byte {
if r.err != nil {
return nil
}
var tmp = r.cbuf[:length]
n, err := r.rb.Read(tmp)
if err != nil {
r.err = NewOpError(err,
"packetReader.ReadCString")
return nil
}
if n != length {
r.err = NewOpError(fmt.Errorf("ReadCString reads %d bytes, not equal to %d we expected", n, length),
"packetWriter.ReadCString")
return nil
}
i := bytes.IndexByte(tmp, 0)
if i == -1 {
return tmp
} else {
return tmp[:i]
}
}
// Error return the inner err.
func (r *packetReader) Error() error {
if r.err != nil {
return r.err
}
return nil
}
+70
View File
@@ -0,0 +1,70 @@
// Copyright 2015 Tony Bai.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package cmpp
import "encoding/binary"
// Packet length const for cmpp receipt packet.
const (
CmppReceiptPktLen uint32 = 60 //60d, 0x3c
)
type CmppReceiptPkt struct {
MsgId uint64
Stat string
SubmitTime string // YYMMDDHHMM
DoneTime string // YYMMDDHHMM
DestTerminalId string
SmscSequence uint32
}
// Pack packs the CmppReceiptPkt to bytes stream for client side.
func (p *CmppReceiptPkt) Pack() ([]byte, error) {
var pktLen uint32 = CmppReceiptPktLen
var w = newPacketWriter(pktLen)
w.WriteInt(binary.BigEndian, p.MsgId)
w.WriteFixedSizeString(p.Stat, 7)
w.WriteFixedSizeString(p.SubmitTime, 10)
w.WriteFixedSizeString(p.DoneTime, 10)
w.WriteFixedSizeString(p.DestTerminalId, 21)
w.WriteInt(binary.BigEndian, p.SmscSequence)
return w.Bytes()
}
// Unpack unpack the binary byte stream to a CmppReceiptPkt variable.
// After unpack, you will get all value of fields in
// CmppReceiptPkt struct.
func (p *CmppReceiptPkt) Unpack(data []byte) error {
var r = newPacketReader(data)
r.ReadInt(binary.BigEndian, &p.MsgId)
stat := r.ReadCString(7)
p.Stat = string(stat)
submitTime := r.ReadCString(10)
p.SubmitTime = string(submitTime)
doneTime := r.ReadCString(10)
p.DoneTime = string(doneTime)
destTerminalId := r.ReadCString(21)
p.DestTerminalId = string(destTerminalId)
r.ReadInt(binary.BigEndian, &p.SmscSequence)
return r.Error()
}
+507
View File
@@ -0,0 +1,507 @@
// Copyright 2015 Tony Bai.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package cmpp
import (
"errors"
"fmt"
"io"
"log"
"net"
"os"
"sync/atomic"
"time"
)
// errors for cmpp server
var (
ErrEmptyServerAddr = errors.New("cmpp server listen: empty server addr")
ErrNoHandlers = errors.New("cmpp server: no connection handler")
ErrUnsupportedPkt = errors.New("cmpp server read packet: receive a unsupported pkt")
)
type Packet struct {
Packer
*Conn
}
type Response struct {
*Packet
Packer
SeqId uint32
}
type Handler interface {
ServeCmpp(*Response, *Packet, *log.Logger) (bool, error)
}
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as Cmpp handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler object that calls f.
//
// The first return value indicates whether to invoke next handler in
// the chain of handlers.
//
// The second return value shows the error returned from the handler. And
// if it is non-nil, server will close the client connection
// after sending back the corresponding response.
type HandlerFunc func(*Response, *Packet, *log.Logger) (bool, error)
// ServeCmpp calls f(r, p).
func (f HandlerFunc) ServeCmpp(r *Response, p *Packet, l *log.Logger) (bool, error) {
return f(r, p, l)
}
type Server struct {
Addr string
Handler Handler
// protocol info
Typ Type
T time.Duration // interval betwwen two active tests
N int32 // continuous send times when no response back
// ErrorLog specifies an optional logger for errors accepting
// connections and unexpected behavior from handlers.
// If nil, logging goes to os.Stderr via the log package's
// standard logger.
ErrorLog *log.Logger
}
// A conn represents the server side of a Cmpp connection.
type conn struct {
*Conn
server *Server // the Server on which the connection arrived
// for active test
t time.Duration // interval betwwen two active tests
n int32 // continuous send times when no response back
done chan struct{}
exceed chan struct{}
counter int32
}
// Serve accepts incoming connections on the Listener l, creating a
// new service goroutine for each. The service goroutines read requests and
// then call srv.Handler to reply to them.
func (srv *Server) Serve(l net.Listener) error {
defer l.Close()
var tempDelay time.Duration // how long to sleep on accept failure
for {
rw, e := l.Accept()
if e != nil {
if ne, ok := e.(net.Error); ok && ne.Temporary() {
if tempDelay == 0 {
tempDelay = 5 * time.Millisecond
} else {
tempDelay *= 2
}
if max := 1 * time.Second; tempDelay > max {
tempDelay = max
}
srv.ErrorLog.Printf("accept error: %v; retrying in %v", e, tempDelay)
time.Sleep(tempDelay)
continue
}
return e
}
tempDelay = 0
c, err := srv.newConn(rw)
if err != nil {
continue
}
srv.ErrorLog.Printf("accept a connection from %v\n", c.Conn.RemoteAddr())
go c.serve()
}
}
func (c *conn) readPacket() (*Response, error) {
readTimeout := time.Second * 2
i, err := c.Conn.RecvAndUnpackPkt(readTimeout)
if err != nil {
return nil, err
}
typ := c.server.Typ
var pkt *Packet
var rsp *Response
switch p := i.(type) {
case *CmppConnReqPkt:
pkt = &Packet{
Packer: p,
Conn: c.Conn,
}
if typ == V30 {
rsp = &Response{
Packet: pkt,
Packer: &Cmpp3ConnRspPkt{
SeqId: p.SeqId,
},
SeqId: p.SeqId,
}
c.server.ErrorLog.Printf("receive a cmpp30 connect request from %v[%d]\n",
c.Conn.RemoteAddr(), p.SeqId)
} else {
rsp = &Response{
Packet: pkt,
Packer: &Cmpp2ConnRspPkt{
SeqId: p.SeqId,
},
SeqId: p.SeqId,
}
c.server.ErrorLog.Printf("receive a cmpp20 connect request from %v[%d]\n",
c.Conn.RemoteAddr(), p.SeqId)
}
case *Cmpp2SubmitReqPkt:
pkt = &Packet{
Packer: p,
Conn: c.Conn,
}
rsp = &Response{
Packet: pkt,
Packer: &Cmpp2SubmitRspPkt{
SeqId: p.SeqId,
},
SeqId: p.SeqId,
}
c.server.ErrorLog.Printf("receive a cmpp20 submit request from %v[%d]\n",
c.Conn.RemoteAddr(), p.SeqId)
case *Cmpp3SubmitReqPkt:
pkt = &Packet{
Packer: p,
Conn: c.Conn,
}
rsp = &Response{
Packet: pkt,
Packer: &Cmpp3SubmitRspPkt{
SeqId: p.SeqId,
},
SeqId: p.SeqId,
}
c.server.ErrorLog.Printf("receive a cmpp30 submit request from %v[%d]\n",
c.Conn.RemoteAddr(), p.SeqId)
case *Cmpp2FwdReqPkt:
pkt = &Packet{
Packer: p,
Conn: c.Conn,
}
rsp = &Response{
Packet: pkt,
Packer: &Cmpp2FwdRspPkt{
SeqId: p.SeqId,
},
SeqId: p.SeqId,
}
c.server.ErrorLog.Printf("receive a cmpp20 forward request from %v[%d]\n",
c.Conn.RemoteAddr(), p.SeqId)
case *Cmpp3FwdReqPkt:
pkt = &Packet{
Packer: p,
Conn: c.Conn,
}
rsp = &Response{
Packet: pkt,
Packer: &Cmpp3FwdRspPkt{
SeqId: p.SeqId,
},
SeqId: p.SeqId,
}
c.server.ErrorLog.Printf("receive a cmpp30 forward request from %v[%d]\n",
c.Conn.RemoteAddr(), p.SeqId)
case *Cmpp2DeliverRspPkt:
pkt = &Packet{
Packer: p,
Conn: c.Conn,
}
rsp = &Response{
Packet: pkt,
}
c.server.ErrorLog.Printf("receive a cmpp20 deliver response from %v[%d]\n",
c.Conn.RemoteAddr(), p.SeqId)
case *Cmpp3DeliverRspPkt:
pkt = &Packet{
Packer: p,
Conn: c.Conn,
}
rsp = &Response{
Packet: pkt,
}
c.server.ErrorLog.Printf("receive a cmpp30 deliver response from %v[%d]\n",
c.Conn.RemoteAddr(), p.SeqId)
case *CmppActiveTestReqPkt:
pkt = &Packet{
Packer: p,
Conn: c.Conn,
}
rsp = &Response{
Packet: pkt,
Packer: &CmppActiveTestRspPkt{
SeqId: p.SeqId,
},
SeqId: p.SeqId,
}
c.server.ErrorLog.Printf("receive a cmpp active request from %v[%d]\n",
c.Conn.RemoteAddr(), p.SeqId)
case *CmppActiveTestRspPkt:
pkt = &Packet{
Packer: p,
Conn: c.Conn,
}
rsp = &Response{
Packet: pkt,
}
c.server.ErrorLog.Printf("receive a cmpp active response from %v[%d]\n",
c.Conn.RemoteAddr(), p.SeqId)
case *CmppTerminateReqPkt:
pkt = &Packet{
Packer: p,
Conn: c.Conn,
}
rsp = &Response{
Packet: pkt,
Packer: &CmppTerminateRspPkt{
SeqId: p.SeqId,
},
SeqId: p.SeqId,
}
c.server.ErrorLog.Printf("receive a cmpp terminate request from %v[%d]\n",
c.Conn.RemoteAddr(), p.SeqId)
case *CmppTerminateRspPkt:
pkt = &Packet{
Packer: p,
Conn: c.Conn,
}
rsp = &Response{
Packet: pkt,
}
c.server.ErrorLog.Printf("receive a cmpp terminate response from %v[%d]\n",
c.Conn.RemoteAddr(), p.SeqId)
default:
return nil, NewOpError(ErrUnsupportedPkt,
fmt.Sprintf("readPacket: receive unsupported packet type: %#v", p))
}
return rsp, nil
}
// Close the connection.
func (c *conn) close() {
p := &CmppTerminateReqPkt{}
err := c.Conn.SendPkt(p, <-c.Conn.SeqId)
if err != nil {
c.server.ErrorLog.Printf("send cmpp terminate request packet to %v error: %v\n", c.Conn.RemoteAddr(), err)
}
close(c.done)
c.server.ErrorLog.Printf("close connection with %v!\n", c.Conn.RemoteAddr())
c.Conn.Close()
}
func (c *conn) finishPacket(r *Response) error {
if _, ok := r.Packet.Packer.(*CmppActiveTestRspPkt); ok {
atomic.AddInt32(&c.counter, -1)
return nil
}
if r.Packer == nil {
// For response packet received, it need not
// to send anything back.
return nil
}
return c.Conn.SendPkt(r.Packer, r.SeqId)
}
func startActiveTest(c *conn) {
exceed, done := make(chan struct{}), make(chan struct{})
c.done = done
c.exceed = exceed
go func() {
t := time.NewTicker(c.t)
defer t.Stop()
for {
select {
case <-done:
// once conn close, the goroutine should exit
return
case <-t.C:
// check whether c.counter exceeds
if atomic.LoadInt32(&c.counter) >= c.n {
c.server.ErrorLog.Printf("no cmpp active test response returned from %v for %d times!",
c.Conn.RemoteAddr(), c.n)
exceed <- struct{}{}
break
}
// send a active test packet to peer, increase the active test counter
p := &CmppActiveTestReqPkt{}
err := c.Conn.SendPkt(p, <-c.Conn.SeqId)
if err != nil {
c.server.ErrorLog.Printf("send cmpp active test request to %v error: %v", c.Conn.RemoteAddr(), err)
} else {
atomic.AddInt32(&c.counter, 1)
}
}
}
}()
}
// Serve a new connection.
func (c *conn) serve() {
defer func() {
if err := recover(); err != nil {
c.server.ErrorLog.Printf("panic serving %v: %v\n", c.Conn.RemoteAddr(), err)
}
}()
defer c.close()
// start a goroutine for sending active test.
startActiveTest(c)
for {
select {
case <-c.exceed:
return // close the connection.
default:
}
r, err := c.readPacket()
if err != nil {
if e, ok := err.(net.Error); ok && e.Timeout() {
continue
}
if errors.Is(err, io.EOF) {
break
}
c.server.ErrorLog.Printf(
"read/unpack packet failed remote=%v protocol=%s err_type=%T err=%v",
c.Conn.RemoteAddr(), c.Conn.Typ, err, err,
)
break
}
_, err = c.server.Handler.ServeCmpp(r, r.Packet, c.server.ErrorLog)
if err1 := c.finishPacket(r); err1 != nil {
c.server.ErrorLog.Printf(
"send response packet failed remote=%v protocol=%s packet_type=%T seq=%d err_type=%T err=%v",
c.Conn.RemoteAddr(), c.Conn.Typ, r.Packer, r.SeqId, err1, err1,
)
break
}
if err != nil {
c.server.ErrorLog.Printf(
"handler failed remote=%v protocol=%s packet_type=%T seq=%d err_type=%T err=%v",
c.Conn.RemoteAddr(), c.Conn.Typ, r.Packet.Packer, r.SeqId, err, err,
)
break
}
}
}
// Create new connection from rwc.
func (srv *Server) newConn(rwc net.Conn) (c *conn, err error) {
c = new(conn)
c.server = srv
c.Conn = NewConn(rwc, srv.Typ)
c.Conn.SetState(CONN_CONNECTED)
c.n = c.server.N
c.t = c.server.T
return c, nil
}
func (srv *Server) listenAndServe() error {
if srv.Addr == "" {
return ErrEmptyServerAddr
}
ln, err := net.Listen("tcp", srv.Addr)
if err != nil {
return err
}
return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}
// ListenAndServe listens on the TCP network address addr
// and then calls Serve with handler to handle requests.
func ListenAndServe(addr string, typ Type, t time.Duration, n int32, logWriter io.Writer, handlers ...Handler) error {
if addr == "" {
return ErrEmptyServerAddr
}
if handlers == nil {
return ErrNoHandlers
}
var handler Handler
handler = HandlerFunc(func(r *Response, p *Packet, l *log.Logger) (bool, error) {
for _, h := range handlers {
next, err := h.ServeCmpp(r, p, l)
if err != nil || !next {
return next, err
}
}
return false, nil
})
if logWriter == nil {
logWriter = os.Stderr
}
server := &Server{Addr: addr, Handler: handler, Typ: typ,
T: t, N: n,
ErrorLog: log.New(logWriter, "cmppserver: ", log.LstdFlags)}
return server.listenAndServe()
}
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections. It's used by ListenAndServe so
// dead TCP connections (e.g. closing laptop mid-download) eventually
// go away. the tcpKeepAliveListener's implementation is copied from
// http package.
type tcpKeepAliveListener struct {
*net.TCPListener
}
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
tc, err := ln.AcceptTCP()
if err != nil {
return
}
tc.SetKeepAlive(true)
tc.SetKeepAlivePeriod(1 * time.Minute) // 1min
return tc, nil
}
+453
View File
@@ -0,0 +1,453 @@
// Copyright 2015 Tony Bai.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package cmpp
import (
"encoding/binary"
"errors"
)
// Packet length const for cmpp submit request and response packets.
const (
Cmpp2SubmitReqPktMaxLen uint32 = 12 + 2265 //2277d, 0x8e5
Cmpp2SubmitRspPktLen uint32 = 12 + 8 + 1 //21d, 0x15
Cmpp3SubmitReqPktMaxLen uint32 = 12 + 3479 //3491d, 0xda3
Cmpp3SubmitRspPktLen uint32 = 12 + 8 + 4 //24d, 0x18
)
// Errors for result in submit resp.
var (
ErrnoSubmitInvalidStruct uint8 = 1
ErrnoSubmitInvalidCommandId uint8 = 2
ErrnoSubmitInvalidSequence uint8 = 3
ErrnoSubmitInvalidMsgLength uint8 = 4
ErrnoSubmitInvalidFeeCode uint8 = 5
ErrnoSubmitExceedMaxMsgLength uint8 = 6
ErrnoSubmitInvalidServiceId uint8 = 7
ErrnoSubmitNotPassFlowControl uint8 = 8
ErrnoSubmitNotServeFeeTerminalId uint8 = 9
ErrnoSubmitInvalidSrcId uint8 = 10
ErrnoSubmitInvalidMsgSrc uint8 = 11
ErrnoSubmitInvalidFeeTerminalId uint8 = 12
ErrnoSubmitInvalidDestTerminalId uint8 = 13
SubmitRspResultErrMap = map[uint8]error{
ErrnoSubmitInvalidStruct: errSubmitInvalidStruct,
ErrnoSubmitInvalidCommandId: errSubmitInvalidCommandId,
ErrnoSubmitInvalidSequence: errSubmitInvalidSequence,
ErrnoSubmitInvalidMsgLength: errSubmitInvalidMsgLength,
ErrnoSubmitInvalidFeeCode: errSubmitInvalidFeeCode,
ErrnoSubmitExceedMaxMsgLength: errSubmitExceedMaxMsgLength,
ErrnoSubmitInvalidServiceId: errSubmitInvalidServiceId,
ErrnoSubmitNotPassFlowControl: errSubmitNotPassFlowControl,
ErrnoSubmitNotServeFeeTerminalId: errSubmitNotServeFeeTerminalId,
ErrnoSubmitInvalidSrcId: errSubmitInvalidSrcId,
ErrnoSubmitInvalidMsgSrc: errSubmitInvalidMsgSrc,
ErrnoSubmitInvalidFeeTerminalId: errSubmitInvalidFeeTerminalId,
ErrnoSubmitInvalidDestTerminalId: errSubmitInvalidDestTerminalId,
}
errSubmitInvalidStruct = errors.New("submit response status: invalid protocol structure")
errSubmitInvalidCommandId = errors.New("submit response status: invalid command id")
errSubmitInvalidSequence = errors.New("submit response status: invalid message sequence")
errSubmitInvalidMsgLength = errors.New("submit response status: invalid message length")
errSubmitInvalidFeeCode = errors.New("submit response status: invalid fee code")
errSubmitExceedMaxMsgLength = errors.New("submit response status: exceed max message length")
errSubmitInvalidServiceId = errors.New("submit response status: invalid service id")
errSubmitNotPassFlowControl = errors.New("submit response status: not pass the flow control")
errSubmitNotServeFeeTerminalId = errors.New("submit response status: feeTerminalId is not served")
errSubmitInvalidSrcId = errors.New("submit response status: invalid srcId")
errSubmitInvalidMsgSrc = errors.New("submit response status: invalid msgSrc")
errSubmitInvalidFeeTerminalId = errors.New("submit response status: invalid feeTerminalId")
errSubmitInvalidDestTerminalId = errors.New("submit response status: invalid destTerminalId")
)
type Cmpp2SubmitReqPkt struct {
MsgId uint64
PkTotal uint8
PkNumber uint8
RegisteredDelivery uint8
MsgLevel uint8
ServiceId string
FeeUserType uint8
FeeTerminalId string
TpPid uint8
TpUdhi uint8
MsgFmt uint8
MsgSrc string
FeeType string
FeeCode string
ValidTime string
AtTime string
SrcId string
DestUsrTl uint8
DestTerminalId []string
MsgLength uint8
MsgContent string
Reserve string
// session info
SeqId uint32
}
type Cmpp2SubmitRspPkt struct {
MsgId uint64
Result uint8
// session info
SeqId uint32
}
type Cmpp3SubmitReqPkt struct {
MsgId uint64
PkTotal uint8
PkNumber uint8
RegisteredDelivery uint8
MsgLevel uint8
ServiceId string
FeeUserType uint8
FeeTerminalId string
FeeTerminalType uint8
TpPid uint8
TpUdhi uint8
MsgFmt uint8
MsgSrc string
FeeType string
FeeCode string
ValidTime string
AtTime string
SrcId string
DestUsrTl uint8
DestTerminalId []string
DestTerminalType uint8
MsgLength uint8
MsgContent string
LinkId string
// session info
SeqId uint32
}
type Cmpp3SubmitRspPkt struct {
MsgId uint64
Result uint32
// session info
SeqId uint32
}
// Pack packs the Cmpp2SubmitReqPkt to bytes stream for client side.
// Before calling Pack, you should initialize a Cmpp2SubmitReqPkt variable
// with correct field value.
func (p *Cmpp2SubmitReqPkt) Pack(seqId uint32) ([]byte, error) {
var pktLen uint32 = CMPP_HEADER_LEN + 117 + uint32(p.DestUsrTl)*21 + 1 + uint32(p.MsgLength) + 8
var w = newPacketWriter(pktLen)
// Pack header
w.WriteInt(binary.BigEndian, pktLen)
w.WriteInt(binary.BigEndian, CMPP_SUBMIT)
w.WriteInt(binary.BigEndian, seqId)
p.SeqId = seqId
// Pack Body
w.WriteInt(binary.BigEndian, p.MsgId)
if p.PkTotal == 0 && p.PkNumber == 0 {
p.PkTotal, p.PkNumber = 1, 1
}
w.WriteByte(p.PkTotal)
w.WriteByte(p.PkNumber)
w.WriteByte(p.RegisteredDelivery)
w.WriteByte(p.MsgLevel)
w.WriteFixedSizeString(p.ServiceId, 10)
w.WriteByte(p.FeeUserType)
w.WriteFixedSizeString(p.FeeTerminalId, 21)
w.WriteByte(p.TpPid)
w.WriteByte(p.TpUdhi)
w.WriteByte(p.MsgFmt)
w.WriteFixedSizeString(p.MsgSrc, 6)
w.WriteFixedSizeString(p.FeeType, 2)
w.WriteFixedSizeString(p.FeeCode, 6)
w.WriteFixedSizeString(p.ValidTime, 17)
w.WriteFixedSizeString(p.AtTime, 17)
w.WriteFixedSizeString(p.SrcId, 21)
w.WriteByte(p.DestUsrTl)
for _, d := range p.DestTerminalId {
w.WriteFixedSizeString(d, 21)
}
w.WriteByte(p.MsgLength)
w.WriteString(p.MsgContent)
w.WriteFixedSizeString(p.Reserve, 8)
return w.Bytes()
}
// Unpack unpack the binary byte stream to a Cmpp2SubmitReqPkt variable.
// 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 {
var r = newPacketReader(data)
// Sequence Id
r.ReadInt(binary.BigEndian, &p.SeqId)
r.ReadInt(binary.BigEndian, &p.MsgId)
p.PkTotal = r.ReadByte()
p.PkNumber = r.ReadByte()
p.RegisteredDelivery = r.ReadByte()
p.MsgLevel = r.ReadByte()
serviceId := r.ReadCString(10)
p.ServiceId = string(serviceId)
p.FeeUserType = r.ReadByte()
feeTerminalId := r.ReadCString(21)
p.FeeTerminalId = string(feeTerminalId)
p.TpPid = r.ReadByte()
p.TpUdhi = r.ReadByte()
p.MsgFmt = r.ReadByte()
msgSrc := r.ReadCString(6)
p.MsgSrc = string(msgSrc)
feeType := make([]byte, 2)
r.ReadBytes(feeType)
p.FeeType = string(feeType)
feeCode := r.ReadCString(6)
p.FeeCode = string(feeCode)
validTime := r.ReadCString(17)
p.ValidTime = string(validTime)
atTime := r.ReadCString(17)
p.AtTime = string(atTime)
srcId := r.ReadCString(21)
p.SrcId = string(srcId)
p.DestUsrTl = r.ReadByte()
for i := 0; i < int(p.DestUsrTl); i++ {
destTerminalId := r.ReadCString(21)
p.DestTerminalId = append(p.DestTerminalId, string(destTerminalId))
}
p.MsgLength = r.ReadByte()
msgContent := make([]byte, p.MsgLength)
r.ReadBytes(msgContent)
p.MsgContent = string(msgContent)
reserve := r.ReadCString(8)
p.Reserve = string(reserve)
return r.Error()
}
// Pack packs the Cmpp2SubmitRspPkt to bytes stream for Server side.
// Before calling Pack, you should initialize a Cmpp2SubmitRspPkt variable
// with correct field value.
func (p *Cmpp2SubmitRspPkt) Pack(seqId uint32) ([]byte, error) {
var pktLen uint32 = CMPP_HEADER_LEN + 8 + 1
var w = newPacketWriter(pktLen)
// Pack header
w.WriteInt(binary.BigEndian, pktLen)
w.WriteInt(binary.BigEndian, CMPP_SUBMIT_RESP)
w.WriteInt(binary.BigEndian, seqId)
p.SeqId = seqId
// Pack Body
w.WriteInt(binary.BigEndian, p.MsgId)
w.WriteByte(p.Result)
return w.Bytes()
}
// Unpack unpack the binary byte stream to a Cmpp2SubmitRspPkt variable.
// Usually it is used in client side. After unpack, you will get all value of fields in
// Cmpp2SubmitRspPkt struct.
func (p *Cmpp2SubmitRspPkt) Unpack(data []byte) error {
var r = newPacketReader(data)
// Sequence Id
r.ReadInt(binary.BigEndian, &p.SeqId)
r.ReadInt(binary.BigEndian, &p.MsgId)
p.Result = r.ReadByte()
return r.Error()
}
// Pack packs the Cmpp3SubmitReqPkt to bytes stream for client side.
// Before calling Pack, you should initialize a Cmpp3SubmitReqPkt variable
// with correct field value.
func (p *Cmpp3SubmitReqPkt) Pack(seqId uint32) ([]byte, error) {
var pktLen uint32 = CMPP_HEADER_LEN + 129 + uint32(p.DestUsrTl)*32 + 1 + 1 + uint32(p.MsgLength) + 20
var w = newPacketWriter(pktLen)
// Pack header
w.WriteInt(binary.BigEndian, pktLen)
w.WriteInt(binary.BigEndian, CMPP_SUBMIT)
w.WriteInt(binary.BigEndian, seqId)
p.SeqId = seqId
// Pack Body
w.WriteInt(binary.BigEndian, p.MsgId)
if p.PkTotal == 0 && p.PkNumber == 0 {
p.PkTotal, p.PkNumber = 1, 1
}
w.WriteByte(p.PkTotal)
w.WriteByte(p.PkNumber)
w.WriteByte(p.RegisteredDelivery)
w.WriteByte(p.MsgLevel)
w.WriteFixedSizeString(p.ServiceId, 10)
w.WriteByte(p.FeeUserType)
w.WriteFixedSizeString(p.FeeTerminalId, 32)
w.WriteByte(p.FeeTerminalType)
w.WriteByte(p.TpPid)
w.WriteByte(p.TpUdhi)
w.WriteByte(p.MsgFmt)
w.WriteFixedSizeString(p.MsgSrc, 6)
w.WriteFixedSizeString(p.FeeType, 2)
w.WriteFixedSizeString(p.FeeCode, 6)
w.WriteFixedSizeString(p.ValidTime, 17)
w.WriteFixedSizeString(p.AtTime, 17)
w.WriteFixedSizeString(p.SrcId, 21)
w.WriteByte(p.DestUsrTl)
for _, d := range p.DestTerminalId {
w.WriteFixedSizeString(d, 32)
}
w.WriteByte(p.DestTerminalType)
w.WriteByte(p.MsgLength)
w.WriteString(p.MsgContent)
w.WriteFixedSizeString(p.LinkId, 20)
return w.Bytes()
}
// Unpack unpack the binary byte stream to a Cmpp3SubmitReqPkt variable.
// 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 {
var r = newPacketReader(data)
// Sequence Id
r.ReadInt(binary.BigEndian, &p.SeqId)
r.ReadInt(binary.BigEndian, &p.MsgId)
p.PkTotal = r.ReadByte()
p.PkNumber = r.ReadByte()
p.RegisteredDelivery = r.ReadByte()
p.MsgLevel = r.ReadByte()
serviceId := r.ReadCString(10)
p.ServiceId = string(serviceId)
p.FeeUserType = r.ReadByte()
feeTerminalId := r.ReadCString(32)
p.FeeTerminalId = string(feeTerminalId)
p.FeeTerminalType = r.ReadByte()
p.TpPid = r.ReadByte()
p.TpUdhi = r.ReadByte()
p.MsgFmt = r.ReadByte()
msgSrc := r.ReadCString(6)
p.MsgSrc = string(msgSrc)
feeType := make([]byte, 2)
r.ReadBytes(feeType)
p.FeeType = string(feeType)
feeCode := r.ReadCString(6)
p.FeeCode = string(feeCode)
validTime := r.ReadCString(17)
p.ValidTime = string(validTime)
atTime := r.ReadCString(17)
p.AtTime = string(atTime)
srcId := r.ReadCString(21)
p.SrcId = string(srcId)
p.DestUsrTl = r.ReadByte()
for i := 0; i < int(p.DestUsrTl); i++ {
destTerminalId := r.ReadCString(32)
p.DestTerminalId = append(p.DestTerminalId, string(destTerminalId))
}
p.DestTerminalType = r.ReadByte()
p.MsgLength = r.ReadByte()
msgContent := make([]byte, p.MsgLength)
r.ReadBytes(msgContent)
p.MsgContent = string(msgContent)
linkId := r.ReadCString(20)
p.LinkId = string(linkId)
return r.Error()
}
// Pack packs the Cmpp3SubmitRspPkt to bytes stream for Server side.
// Before calling Pack, you should initialize a Cmpp3SubmitRspPkt variable
// with correct field value.
func (p *Cmpp3SubmitRspPkt) Pack(seqId uint32) ([]byte, error) {
var pktLen uint32 = CMPP_HEADER_LEN + 8 + 4
var w = newPacketWriter(pktLen)
// Pack header
w.WriteInt(binary.BigEndian, pktLen)
w.WriteInt(binary.BigEndian, CMPP_SUBMIT_RESP)
w.WriteInt(binary.BigEndian, seqId)
p.SeqId = seqId
// Pack Body
w.WriteInt(binary.BigEndian, p.MsgId)
w.WriteInt(binary.BigEndian, p.Result)
return w.Bytes()
}
// Unpack unpack the binary byte stream to a Cmpp3SubmitRspPkt variable.
// Usually it is used in client side. After unpack, you will get all value of fields in
// Cmpp3SubmitRspPkt struct.
func (p *Cmpp3SubmitRspPkt) Unpack(data []byte) error {
var r = newPacketReader(data)
// Sequence Id
r.ReadInt(binary.BigEndian, &p.SeqId)
r.ReadInt(binary.BigEndian, &p.MsgId)
r.ReadInt(binary.BigEndian, &p.Result)
return r.Error()
}
+83
View File
@@ -0,0 +1,83 @@
// Copyright 2015 Tony Bai.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package cmpp
import "encoding/binary"
// Packet length const for cmpp terminate request and response packets.
const (
CmppTerminateReqPktLen uint32 = 12 //12d, 0xc
CmppTerminateRspPktLen uint32 = 12 //12d, 0xc
)
type CmppTerminateReqPkt struct {
// session info
SeqId uint32
}
type CmppTerminateRspPkt struct {
// session info
SeqId uint32
}
// Pack packs the CmppTerminateReqPkt to bytes stream for client side.
func (p *CmppTerminateReqPkt) Pack(seqId uint32) ([]byte, error) {
var pktLen = CmppTerminateReqPktLen
var w = newPacketWriter(pktLen)
// Pack header
w.WriteInt(binary.BigEndian, pktLen)
w.WriteInt(binary.BigEndian, CMPP_TERMINATE)
w.WriteInt(binary.BigEndian, seqId)
p.SeqId = seqId
return w.Bytes()
}
// Unpack unpack the binary byte stream to a CmppTerminateReqPkt variable.
// After unpack, you will get all value of fields in
// CmppTerminateReqPkt struct.
func (p *CmppTerminateReqPkt) Unpack(data []byte) error {
var r = newPacketReader(data)
// Sequence Id
r.ReadInt(binary.BigEndian, &p.SeqId)
return r.Error()
}
// Pack packs the CmppTerminateRspPkt to bytes stream for client side.
func (p *CmppTerminateRspPkt) Pack(seqId uint32) ([]byte, error) {
var pktLen = CmppTerminateRspPktLen
var w = newPacketWriter(pktLen)
// Pack header
w.WriteInt(binary.BigEndian, pktLen)
w.WriteInt(binary.BigEndian, CMPP_TERMINATE_RESP)
w.WriteInt(binary.BigEndian, seqId)
p.SeqId = seqId
return w.Bytes()
}
// Unpack unpack the binary byte stream to a CmppTerminateRspPkt variable.
// After unpack, you will get all value of fields in
// CmppTerminateRspPkt struct.
func (p *CmppTerminateRspPkt) Unpack(data []byte) error {
var r = newPacketReader(data)
// Sequence Id
r.ReadInt(binary.BigEndian, &p.SeqId)
return r.Error()
}
+105
View File
@@ -0,0 +1,105 @@
// Copyright 2015 Tony Bai.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package cmpputils
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"strings"
"unicode/utf8"
"unsafe"
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/encoding/unicode"
"golang.org/x/text/transform"
)
var ErrInvalidUtf8Rune = errors.New("Not Invalid Utf8 runes")
func IsBigEndian() bool {
var i uint16 = 0x1234
var p *[2]byte = (*[2]byte)(unsafe.Pointer(&i))
if (*p)[0] == 0x12 {
return true
}
return false
}
// TimeStamp2Str converts a timestamp(MMDDHHMMSS) int to a string(10 bytes).
func TimeStamp2Str(t uint32) string {
return fmt.Sprintf("%010d", t)
}
func Utf8ToUcs2(in string) (string, error) {
if !utf8.ValidString(in) {
return "", ErrInvalidUtf8Rune
}
r := bytes.NewReader([]byte(in))
t := transform.NewReader(r, unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM).NewEncoder()) //UTF-16 bigendian, no-bom
out, err := ioutil.ReadAll(t)
if err != nil {
return "", err
}
return string(out), nil
}
func Ucs2ToUtf8(in string) (string, error) {
r := bytes.NewReader([]byte(in))
t := transform.NewReader(r, unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM).NewDecoder()) //UTF-16 bigendian, no-bom
out, err := ioutil.ReadAll(t)
if err != nil {
return "", err
}
return string(out), nil
}
func Utf8ToGB18030(in string) (string, error) {
if !utf8.ValidString(in) {
return "", ErrInvalidUtf8Rune
}
r := bytes.NewReader([]byte(in))
t := transform.NewReader(r, simplifiedchinese.GB18030.NewEncoder())
out, err := ioutil.ReadAll(t)
if err != nil {
return "", err
}
return string(out), nil
}
func GB18030ToUtf8(in string) (string, error) {
r := bytes.NewReader([]byte(in))
t := transform.NewReader(r, simplifiedchinese.GB18030.NewDecoder())
out, err := ioutil.ReadAll(t)
if err != nil {
return "", err
}
return string(out), nil
}
func OctetString(s string, fixedLength int) string {
length := len(s)
if length == fixedLength {
return s
}
if length > fixedLength {
return s[length-fixedLength:]
}
return strings.Join([]string{s, string(make([]byte, fixedLength-length))}, "")
}