From 20f89d14aa52e714777e2d50b5332500faee1e48 Mon Sep 17 00:00:00 2001 From: hectorzhao Date: Sat, 11 Jul 2026 10:40:46 +0800 Subject: [PATCH] fix: log cmpp packet decode failures --- docs/system-functional-test-cases.md | 1 + docs/testing-progress.md | 2 + gateway/go.mod | 2 + gateway/internal/inbound/server.go | 5 +- gateway/internal/inbound/server_test.go | 46 ++ gateway/third_party/gocmpp/LICENSE | 201 +++++++++ gateway/third_party/gocmpp/activetest.go | 86 ++++ gateway/third_party/gocmpp/client.go | 127 ++++++ gateway/third_party/gocmpp/conn.go | 271 ++++++++++++ gateway/third_party/gocmpp/connect.go | 283 ++++++++++++ gateway/third_party/gocmpp/deliver.go | 314 ++++++++++++++ gateway/third_party/gocmpp/fwd.go | 489 +++++++++++++++++++++ gateway/third_party/gocmpp/go.mod | 8 + gateway/third_party/gocmpp/go.sum | 27 ++ gateway/third_party/gocmpp/packet.go | 384 ++++++++++++++++ gateway/third_party/gocmpp/receipt.go | 70 +++ gateway/third_party/gocmpp/server.go | 507 ++++++++++++++++++++++ gateway/third_party/gocmpp/submit.go | 453 +++++++++++++++++++ gateway/third_party/gocmpp/terminate.go | 83 ++++ gateway/third_party/gocmpp/utils/utils.go | 105 +++++ 20 files changed, 3462 insertions(+), 2 deletions(-) create mode 100644 gateway/third_party/gocmpp/LICENSE create mode 100644 gateway/third_party/gocmpp/activetest.go create mode 100644 gateway/third_party/gocmpp/client.go create mode 100644 gateway/third_party/gocmpp/conn.go create mode 100644 gateway/third_party/gocmpp/connect.go create mode 100644 gateway/third_party/gocmpp/deliver.go create mode 100644 gateway/third_party/gocmpp/fwd.go create mode 100644 gateway/third_party/gocmpp/go.mod create mode 100644 gateway/third_party/gocmpp/go.sum create mode 100644 gateway/third_party/gocmpp/packet.go create mode 100644 gateway/third_party/gocmpp/receipt.go create mode 100644 gateway/third_party/gocmpp/server.go create mode 100644 gateway/third_party/gocmpp/submit.go create mode 100644 gateway/third_party/gocmpp/terminate.go create mode 100644 gateway/third_party/gocmpp/utils/utils.go diff --git a/docs/system-functional-test-cases.md b/docs/system-functional-test-cases.md index 857fc1b..3139b5c 100644 --- a/docs/system-functional-test-cases.md +++ b/docs/system-functional-test-cases.md @@ -1009,6 +1009,7 @@ - submit 被接受后返回 CMPP SubmitResp 成功,并在真实数据库创建 `sourceType=cmpp` 的发送记录,进入真实发送链路。 - submit 内容不匹配审核模板、余额不足、短信接口关闭、无可用通道时返回明确失败,不得伪造成功。 - Gateway 对每次 submit 记录 `submit_received` 和 `submit_accepted`/`submit_rejected`;日志可按账号、IP、sequenceId、号码和 messageId 定位,拒绝时包含 NestJS 真实业务原因和 CMPP result,但不包含明文短信正文。 + - CMPP 包在进入 handler 前因长度、命令字、读包或 Unpack 失败时,Gateway 记录 `read/unpack packet failed`、远端地址、协议模式、错误类型和原始错误,不得静默断开。 ### TC-GW-007 CMPP 客户到上游 SMSC 完整闭环 diff --git a/docs/testing-progress.md b/docs/testing-progress.md index 7b24042..62abfb0 100644 --- a/docs/testing-progress.md +++ b/docs/testing-progress.md @@ -657,12 +657,14 @@ npm run verify:phase8 - Gateway 入站 Submit 日志增加 `submit_received`、`submit_accepted`、`submit_rejected` 结构化事件,同时记录 CONNECT 声明的客户协议版本与 Go 实际解包类型,并记录账号、客户 IP、sequenceId、号码、srcId、编码、分片、CMPP result、平台 messageId、CMPP Msg_Id 和处理耗时。 - Gateway HTTP 回调在 NestJS 返回非 2xx 时保留最多 64KB 响应体,客户 Submit 失败日志可直接显示模板不匹配、IP 白名单、余额或路由等真实业务原因,不再只显示 HTTP 状态码。 - 日志不记录明文短信正文,仅记录字符数和 MD5 哈希,便于比对同一内容且避免日志泄露。 +- 将当前 gocmpp 版本固定为仓库内小型 fork,仅在 server 循环补充底层诊断:包在进入业务 handler 之前发生长度、命令字、包体读取或 Unpack 失败时,记录 `read/unpack packet failed`、远端地址、库解析协议模式、Go 错误类型和原始错误;正常 EOF 断开不记为解包失败。 ### 验证状态 - `go test ./internal/inbound -count=1`:通过。 - `go test ./... -count=1`:通过。 - `go build ./cmd/gateway`:通过。 +- 真实 TCP 非法包用例:向入站端口写入非法 `total_length`,确认业务 handler 未执行时仍产生 `read/unpack packet failed` 日志。 ## 2026-07-07 Gateway 上游提交与下游 Deliver 闭环补齐 diff --git a/gateway/go.mod b/gateway/go.mod index 8c7dc84..1296336 100644 --- a/gateway/go.mod +++ b/gateway/go.mod @@ -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 diff --git a/gateway/internal/inbound/server.go b/gateway/internal/inbound/server.go index 7aa5a71..942db36 100644 --- a/gateway/internal/inbound/server.go +++ b/gateway/internal/inbound/server.go @@ -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 } diff --git a/gateway/internal/inbound/server_test.go b/gateway/internal/inbound/server_test.go index da3e1ab..31339fe 100644 --- a/gateway/internal/inbound/server_test.go +++ b/gateway/internal/inbound/server_test.go @@ -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 diff --git a/gateway/third_party/gocmpp/LICENSE b/gateway/third_party/gocmpp/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/gateway/third_party/gocmpp/LICENSE @@ -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. diff --git a/gateway/third_party/gocmpp/activetest.go b/gateway/third_party/gocmpp/activetest.go new file mode 100644 index 0000000..23c2d19 --- /dev/null +++ b/gateway/third_party/gocmpp/activetest.go @@ -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() +} diff --git a/gateway/third_party/gocmpp/client.go b/gateway/third_party/gocmpp/client.go new file mode 100644 index 0000000..90d5330 --- /dev/null +++ b/gateway/third_party/gocmpp/client.go @@ -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) +} diff --git a/gateway/third_party/gocmpp/conn.go b/gateway/third_party/gocmpp/conn.go new file mode 100644 index 0000000..a76cf9b --- /dev/null +++ b/gateway/third_party/gocmpp/conn.go @@ -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 +} diff --git a/gateway/third_party/gocmpp/connect.go b/gateway/third_party/gocmpp/connect.go new file mode 100644 index 0000000..50f1fc6 --- /dev/null +++ b/gateway/third_party/gocmpp/connect.go @@ -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() +} diff --git a/gateway/third_party/gocmpp/deliver.go b/gateway/third_party/gocmpp/deliver.go new file mode 100644 index 0000000..6d28672 --- /dev/null +++ b/gateway/third_party/gocmpp/deliver.go @@ -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() +} diff --git a/gateway/third_party/gocmpp/fwd.go b/gateway/third_party/gocmpp/fwd.go new file mode 100644 index 0000000..f62cc3f --- /dev/null +++ b/gateway/third_party/gocmpp/fwd.go @@ -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() +} diff --git a/gateway/third_party/gocmpp/go.mod b/gateway/third_party/gocmpp/go.mod new file mode 100644 index 0000000..ab9cbce --- /dev/null +++ b/gateway/third_party/gocmpp/go.mod @@ -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 diff --git a/gateway/third_party/gocmpp/go.sum b/gateway/third_party/gocmpp/go.sum new file mode 100644 index 0000000..8afe1e6 --- /dev/null +++ b/gateway/third_party/gocmpp/go.sum @@ -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= diff --git a/gateway/third_party/gocmpp/packet.go b/gateway/third_party/gocmpp/packet.go new file mode 100644 index 0000000..a1cc64d --- /dev/null +++ b/gateway/third_party/gocmpp/packet.go @@ -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 "" + } + 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 +} diff --git a/gateway/third_party/gocmpp/receipt.go b/gateway/third_party/gocmpp/receipt.go new file mode 100644 index 0000000..ffa02b3 --- /dev/null +++ b/gateway/third_party/gocmpp/receipt.go @@ -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() +} diff --git a/gateway/third_party/gocmpp/server.go b/gateway/third_party/gocmpp/server.go new file mode 100644 index 0000000..1b5a975 --- /dev/null +++ b/gateway/third_party/gocmpp/server.go @@ -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 +} diff --git a/gateway/third_party/gocmpp/submit.go b/gateway/third_party/gocmpp/submit.go new file mode 100644 index 0000000..37f4cac --- /dev/null +++ b/gateway/third_party/gocmpp/submit.go @@ -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() +} diff --git a/gateway/third_party/gocmpp/terminate.go b/gateway/third_party/gocmpp/terminate.go new file mode 100644 index 0000000..0f23592 --- /dev/null +++ b/gateway/third_party/gocmpp/terminate.go @@ -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() +} diff --git a/gateway/third_party/gocmpp/utils/utils.go b/gateway/third_party/gocmpp/utils/utils.go new file mode 100644 index 0000000..fa37d9b --- /dev/null +++ b/gateway/third_party/gocmpp/utils/utils.go @@ -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))}, "") +}