├── .github └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── cmd ├── mockserver │ └── main.go ├── receiver │ └── main.go └── transmitter │ └── main.go ├── cmpp ├── cmpp.go ├── cmpptest │ ├── server.go │ └── session.go ├── conn │ └── conn.go └── protocol │ ├── consts.go │ ├── consts_test.go │ ├── header.go │ ├── header_test.go │ ├── op_activetest.go │ ├── op_activetest_test.go │ ├── op_cancel.go │ ├── op_cancel_test.go │ ├── op_connect.go │ ├── op_connect_test.go │ ├── op_deliver.go │ ├── op_deliver_test.go │ ├── op_query.go │ ├── op_query_test.go │ ├── op_submit.go │ ├── op_submit_test.go │ ├── op_terminate.go │ ├── op_terminate_test.go │ ├── operation.go │ ├── report.go │ ├── report_test.go │ ├── types.go │ ├── types_test.go │ └── utils.go ├── go.mod └── go.sum /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Test && Build 5 | 6 | on: 7 | push: 8 | branches: [ "*" ] 9 | pull_request: 10 | branches: [ "master" ] 11 | 12 | jobs: 13 | 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v3 21 | with: 22 | go-version: 1.19 23 | 24 | - name: Test && Build 25 | run: make 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | bin 15 | *.swp 16 | coverage.txt 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 dave 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Go parameters 2 | GOCMD=go 3 | GOBUILD=$(GOCMD) build 4 | GOTEST=$(GOCMD) test 5 | GOLIST=$(GOCMD) list 6 | GOFMT=$(GOCMD) fmt 7 | GOVET=$(GOCMD) vet 8 | 9 | 10 | all: unit build 11 | 12 | 13 | .PHONY: unit 14 | unit: ## @testing Run the unit tests 15 | $(GOFMT) ./... 16 | $(GOVET) ./cmpp/... 17 | $(GOTEST) -race -coverprofile=coverage.txt -covermode=atomic $(shell go list ./cmpp/...) 18 | 19 | .PHONY: build 20 | build: 21 | $(GOBUILD) -o ./bin/receiver ./cmd/receiver 22 | $(GOBUILD) -o ./bin/mockserver ./cmd/mockserver 23 | $(GOBUILD) -o ./bin/transmitter ./cmd/transmitter 24 | 25 | 26 | .PHONY: build_linux 27 | build_linux: clean 28 | GOOS=linux GOARCH=amd64 CGO_ENABLED=0 $(GOBUILD) -o ./bin/receiver ./cmd/receiver 29 | GOOS=linux GOARCH=amd64 CGO_ENABLED=0 $(GOBUILD) -o ./bin/mockserver ./cmd/mockserver 30 | GOOS=linux GOARCH=amd64 CGO_ENABLED=0 $(GOBUILD) -o ./bin/transmitter ./cmd/transmitter 31 | 32 | .PHONY: clean 33 | clean: 34 | rm -rf ./bin/ coverage.txt 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go_cmpp 2 | [![Build Status](https://github.com/yedamao/go_cmpp/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/yedamao/go_cmpp/actions/workflows/build.yml) 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/yedamao/go_cmpp)](https://goreportcard.com/report/github.com/yedamao/go_cmpp) 4 | [![codecov](https://codecov.io/gh/yedamao/go_cmpp/branch/master/graph/badge.svg)](https://codecov.io/gh/yedamao/go_cmpp) 5 | 6 | 7 | go_cmpp是为SP设计实现的CMPP2.0协议开发工具包。包括cmpp协议包和命令行工具。 8 | 9 | ## 安装 10 | ``` 11 | go get github.com/yedamao/go_cmpp/... 12 | cd $GOPATH/src/github.com/yedamao/go_cmpp && make 13 | ``` 14 | 15 | ## Cmpp协议包 16 | 17 | ### 支持操作 18 | - [x] CMPP_CONNECT 19 | - [x] CMPP_TERMINATE 20 | - [x] CMPP_TERMINATE_RESP 21 | - [x] CMPP_SUBMIT 22 | - [x] CMPP_QUERY 23 | - [x] CMPP_DELIVER_RESP 24 | - [x] CMPP_CANCEL 25 | - [x] CMPP_ACTIVE_TEST 26 | - [x] CMPP_ACTIVE_TEST_RESP 27 | 28 | ### Example 29 | 参照cmd/transmitter/main.go, cmd/receiver/main.go 30 | 31 | ## 命令行工具 32 | 33 | ### mockserver 34 | ISMG短信网关模拟器 35 | 36 | ``` 37 | Usage of ./bin/mockserver: 38 | -activeTest 39 | 是否activeTest 40 | -addr string 41 | addr(本地监听地址) (default ":7890") 42 | -mo 43 | 是否模拟上行短信 44 | -rpt 45 | 是否模拟上行状态报告 46 | ``` 47 | 48 | ### transmitter 49 | 提交单条短信至短信网关 50 | 51 | ``` 52 | Usage of ./bin/transmitter: 53 | -addr string 54 | smgw addr(运营商地址) (default ":7890") 55 | -dest-number string 56 | 接收手机号码, 86..., 多个使用,分割 57 | -msg string 58 | 短信内容 59 | -secret string 60 | 登陆密码 61 | -serviceId string 62 | 业务类型,是数字、字母和符号的组合 63 | -sourceAddr string 64 | 源地址,即SP的企业代码 65 | -srcId string 66 | SP的接入号码 67 | ``` 68 | 69 | ### receiver 70 | 接收运营商回执状态/上行短信 71 | 72 | ``` 73 | Usage of ./bin/receiver: 74 | -addr string 75 | smgw addr(运营商地址) (default ":7890") 76 | -secret string 77 | 登陆密码 78 | -sourceAddr string 79 | 源地址,即SP的企业代码 80 | ``` 81 | 82 | 83 | -------------------------------------------------------------------------------- /cmd/mockserver/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/signal" 9 | "sync" 10 | "syscall" 11 | 12 | "github.com/yedamao/go_cmpp/cmpp/cmpptest" 13 | ) 14 | 15 | var ( 16 | addr = flag.String("addr", ":7890", "addr(本地监听地址)") 17 | ) 18 | 19 | func init() { 20 | flag.Parse() 21 | } 22 | 23 | func main() { 24 | server, err := cmpptest.NewServer(*addr) 25 | if err != nil { 26 | flag.Usage() 27 | os.Exit(-1) 28 | } 29 | 30 | HandleSignals(server.Stop) 31 | 32 | server.Run() 33 | 34 | fmt.Println("Done") 35 | } 36 | 37 | func HandleSignals(stopFunction func()) { 38 | var callback sync.Once 39 | 40 | // On ^C or SIGTERM, gracefully stop the sniffer 41 | sigc := make(chan os.Signal, 1) 42 | signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM) 43 | go func() { 44 | <-sigc 45 | log.Println("service", "Received sigterm/sigint, stopping") 46 | callback.Do(stopFunction) 47 | }() 48 | } 49 | -------------------------------------------------------------------------------- /cmd/receiver/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | 9 | "github.com/yedamao/go_cmpp/cmpp" 10 | "github.com/yedamao/go_cmpp/cmpp/protocol" 11 | ) 12 | 13 | var ( 14 | addr = flag.String("addr", ":7890", "smgw addr(运营商地址)") 15 | sourceAddr = flag.String("sourceAddr", "", "源地址,即SP的企业代码") 16 | sharedSecret = flag.String("secret", "", "登陆密码") 17 | ) 18 | 19 | func init() { 20 | flag.Parse() 21 | } 22 | 23 | var sequenceID uint32 = 0 24 | 25 | func newSeqNum() uint32 { 26 | sequenceID++ 27 | 28 | return sequenceID 29 | } 30 | 31 | func main() { 32 | if "" == *sourceAddr || "" == *sharedSecret { 33 | fmt.Println("Arg error: sourceAddr or sharedSecret must not be empty .") 34 | flag.Usage() 35 | os.Exit(-1) 36 | } 37 | 38 | ts, err := cmpp.NewCmpp(*addr, *sourceAddr, *sharedSecret, newSeqNum) 39 | if err != nil { 40 | fmt.Println("Connection Err", err) 41 | os.Exit(-1) 42 | } 43 | fmt.Println("connect succ") 44 | 45 | for { 46 | op, err := ts.Read() // This is blocking 47 | if err != nil { 48 | fmt.Println("Read Err:", err) 49 | break 50 | } 51 | 52 | switch op.GetHeader().Command_Id { 53 | case protocol.CMPP_DELIVER: 54 | dlv, ok := op.(*protocol.Deliver) 55 | if !ok { 56 | log.Println("Type assert error: ", op) 57 | } 58 | 59 | if dlv.RegisteredDelivery == protocol.IS_REPORT { 60 | // 状态报告 61 | rpt, err := protocol.ParseReport(dlv.MsgContent) 62 | if err != nil { 63 | log.Println(err) 64 | } 65 | fmt.Println(rpt) 66 | } else { 67 | // 上行短信 68 | fmt.Println(dlv) 69 | } 70 | ts.DeliverResp(dlv.Header.Sequence_Id, dlv.MsgId, protocol.OK) 71 | 72 | case protocol.CMPP_ACTIVE_TEST: 73 | fmt.Println("recv ActiveTest") 74 | ts.ActiveTestResp(op.GetHeader().Sequence_Id) 75 | 76 | case protocol.CMPP_TERMINATE: 77 | fmt.Println("recv Terminate") 78 | ts.TerminateResp(op.GetHeader().Sequence_Id) 79 | ts.Close() 80 | return 81 | 82 | case protocol.CMPP_TERMINATE_RESP: 83 | fmt.Println("Terminate response") 84 | ts.Close() 85 | return 86 | 87 | default: 88 | fmt.Printf("Unexpect CmdId: %0x\n", op.GetHeader().Command_Id) 89 | ts.Close() 90 | return 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /cmd/transmitter/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "strings" 8 | 9 | "github.com/yedamao/encoding" 10 | 11 | "github.com/yedamao/go_cmpp/cmpp" 12 | "github.com/yedamao/go_cmpp/cmpp/protocol" 13 | ) 14 | 15 | var ( 16 | addr = flag.String("addr", ":7890", "smgw addr(运营商地址)") 17 | sourceAddr = flag.String("sourceAddr", "", "源地址,即SP的企业代码") 18 | sharedSecret = flag.String("secret", "", "登陆密码") 19 | 20 | serviceId = flag.String("serviceId", "", "业务类型,是数字、字母和符号的组合") 21 | 22 | srcId = flag.String("srcId", "", "SP的接入号码") 23 | destNumber = flag.String("dest-number", "", "接收手机号码, 86..., 多个使用,分割") 24 | msg = flag.String("msg", "", "短信内容") 25 | ) 26 | 27 | func init() { 28 | flag.Parse() 29 | } 30 | 31 | var sequenceID uint32 = 0 32 | 33 | func newSeqNum() uint32 { 34 | sequenceID++ 35 | 36 | return sequenceID 37 | } 38 | 39 | func main() { 40 | if "" == *sourceAddr || "" == *sharedSecret { 41 | fmt.Println("Arg error: sourceAddr or sharedSecret must not be empty .") 42 | flag.Usage() 43 | os.Exit(-1) 44 | } 45 | 46 | destNumbers := strings.Split(*destNumber, ",") 47 | fmt.Println("destNumbers: ", destNumbers) 48 | 49 | ts, err := cmpp.NewCmpp(*addr, *sourceAddr, *sharedSecret, newSeqNum) 50 | if err != nil { 51 | fmt.Println("Connection Err", err) 52 | os.Exit(-1) 53 | } 54 | fmt.Println("connect succ") 55 | // encoding msg 56 | content := encoding.UTF82GBK([]byte(*msg)) 57 | 58 | if len(content) > 140 { 59 | fmt.Println("msg Err: not suport long sms") 60 | } 61 | 62 | _, err = ts.Submit( 63 | 1, 1, 1, 0, *serviceId, 0, "", protocol.GB18030, 64 | "02", "", *srcId, destNumbers, content, 65 | ) 66 | if err != nil { 67 | fmt.Println("Submit err ", err) 68 | os.Exit(-1) 69 | } 70 | 71 | for { 72 | op, err := ts.Read() // This is blocking 73 | if err != nil { 74 | fmt.Println("Read Err:", err) 75 | break 76 | } 77 | 78 | switch op.GetHeader().Command_Id { 79 | case protocol.CMPP_SUBMIT_RESP: 80 | ts.Terminate() 81 | if err := op.Ok(); err != nil { 82 | fmt.Println(err) 83 | } else { 84 | fmt.Println("Submit Ok") 85 | } 86 | 87 | case protocol.CMPP_TERMINATE_RESP: 88 | fmt.Println("Terminate response") 89 | ts.Close() 90 | return 91 | 92 | default: 93 | fmt.Printf("Unexpect CmdId: %0x\n", op.GetHeader().Command_Id) 94 | ts.Close() 95 | return 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /cmpp/cmpp.go: -------------------------------------------------------------------------------- 1 | package cmpp 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | 7 | "github.com/yedamao/go_cmpp/cmpp/conn" 8 | "github.com/yedamao/go_cmpp/cmpp/protocol" 9 | ) 10 | 11 | type SequenceFunc func() uint32 12 | 13 | type Cmpp struct { 14 | conn.Conn 15 | newSeqNum SequenceFunc 16 | 17 | spId string // SP的企业代码 18 | } 19 | 20 | func NewCmpp( 21 | addr string, sourceAddr, sharedSecret string, 22 | newSeqNum SequenceFunc, 23 | ) (*Cmpp, error) { 24 | if nil == newSeqNum { 25 | return nil, errors.New("newSeqNum must not be nil") 26 | } 27 | 28 | s := &Cmpp{ 29 | newSeqNum: newSeqNum, 30 | spId: sourceAddr, 31 | } 32 | 33 | if err := s.connect(addr); err != nil { 34 | return nil, err 35 | } 36 | 37 | // 登陆 38 | if err := s.Connect(sourceAddr, sharedSecret); err != nil { 39 | return nil, err 40 | } 41 | 42 | return s, nil 43 | } 44 | 45 | func (s *Cmpp) connect(addr string) error { 46 | connection, err := net.Dial("tcp", addr) 47 | if err != nil { 48 | return err 49 | } 50 | s.Conn = *conn.NewConn(connection) 51 | 52 | return nil 53 | } 54 | 55 | func (s *Cmpp) Connect(sourceAddr, sharedSecret string) error { 56 | op, err := protocol.NewConnect(s.newSeqNum(), sourceAddr, sharedSecret) 57 | if err != nil { 58 | return err 59 | } 60 | if err = s.Write(op); err != nil { 61 | return err 62 | } 63 | 64 | // Read block 65 | var resp protocol.Operation 66 | if resp, err = s.Read(); err != nil { 67 | return err 68 | } 69 | 70 | if resp.GetHeader().Command_Id != protocol.CMPP_CONNECT_RESP { 71 | return errors.New("Connect Resp Wrong RequestID") 72 | } 73 | 74 | return resp.Ok() 75 | } 76 | 77 | func (s *Cmpp) Terminate() error { 78 | 79 | op, err := protocol.NewTerminate(s.newSeqNum()) 80 | if err != nil { 81 | return err 82 | } 83 | 84 | return s.Write(op) 85 | } 86 | 87 | func (s *Cmpp) TerminateResp(sequenceID uint32) error { 88 | 89 | op, err := protocol.NewTerminateResp(sequenceID) 90 | if err != nil { 91 | return err 92 | } 93 | 94 | return s.Write(op) 95 | } 96 | 97 | func (s *Cmpp) ActiveTest() error { 98 | 99 | op, err := protocol.NewActiveTest(s.newSeqNum()) 100 | if err != nil { 101 | return err 102 | } 103 | 104 | return s.Write(op) 105 | } 106 | 107 | func (s *Cmpp) ActiveTestResp(sequenceID uint32) error { 108 | 109 | op, err := protocol.NewActiveTestResp(sequenceID) 110 | if err != nil { 111 | return err 112 | } 113 | 114 | return s.Write(op) 115 | } 116 | 117 | func (s *Cmpp) Cancel(msgId uint64) error { 118 | 119 | op, err := protocol.NewCancel(s.newSeqNum(), msgId) 120 | if err != nil { 121 | return err 122 | } 123 | 124 | return s.Write(op) 125 | } 126 | 127 | func (s *Cmpp) DeliverResp(sequenceID uint32, msgId uint64, result uint8) error { 128 | 129 | op, err := protocol.NewDeliverResp(sequenceID, msgId, result) 130 | if err != nil { 131 | return err 132 | } 133 | 134 | return s.Write(op) 135 | } 136 | 137 | func (s *Cmpp) Submit( 138 | pkTotal, pkNumber, needReport, msgLevel uint8, 139 | serviceId string, feeUserType uint8, feeTerminalId string, 140 | msgFmt uint8, feeType, feeCode, srcId string, 141 | destTermId []string, content []byte, 142 | ) (uint32, error) { 143 | 144 | var ( 145 | TP_udhi uint8 = 0 146 | sequenceID uint32 = s.newSeqNum() 147 | ) 148 | 149 | if pkTotal > 1 { 150 | TP_udhi = 1 151 | } 152 | 153 | op, err := protocol.NewSubmit( 154 | sequenceID, 155 | pkTotal, pkNumber, needReport, msgLevel, 156 | serviceId, feeUserType, feeTerminalId, 157 | 0, TP_udhi, msgFmt, 158 | s.spId, feeType, feeCode, "", "", srcId, 159 | destTermId, content, 160 | ) 161 | 162 | if err != nil { 163 | return sequenceID, err 164 | } 165 | 166 | return sequenceID, s.Write(op) 167 | } 168 | 169 | func (s *Cmpp) Query(time, serviceId string) error { 170 | 171 | var ( 172 | queryTye uint8 173 | queryCode string 174 | ) 175 | if "" != serviceId { 176 | queryTye = 1 177 | queryCode = serviceId 178 | } 179 | 180 | op, err := protocol.NewQuery( 181 | s.newSeqNum(), time, queryTye, queryCode) 182 | if err != nil { 183 | return err 184 | } 185 | 186 | return s.Write(op) 187 | } 188 | -------------------------------------------------------------------------------- /cmpp/cmpptest/server.go: -------------------------------------------------------------------------------- 1 | package cmpptest 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "time" 7 | ) 8 | 9 | // Server is a mock SMGW server 10 | type Server struct { 11 | listener *net.TCPListener 12 | 13 | done chan struct{} 14 | } 15 | 16 | func NewServer(addr string) (*Server, error) { 17 | 18 | laddr, err := net.ResolveTCPAddr("tcp", addr) 19 | if err != nil { 20 | return nil, err 21 | } 22 | listener, err := net.ListenTCP("tcp", laddr) 23 | if err != nil { 24 | return nil, err 25 | } 26 | log.Println("Server Listen: ", listener.Addr()) 27 | 28 | return &Server{ 29 | listener: listener, 30 | done: make(chan struct{}), 31 | }, nil 32 | } 33 | 34 | func (s *Server) Run() { 35 | 36 | for { 37 | select { 38 | case <-s.done: 39 | log.Println("stopping listening on", s.listener.Addr()) 40 | return 41 | default: 42 | } 43 | 44 | s.listener.SetDeadline(time.Now().Add(1e9)) 45 | conn, err := s.listener.Accept() 46 | if err != nil { 47 | if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() { 48 | continue 49 | } 50 | log.Println(err) 51 | } 52 | log.Println("conn from: ", conn.RemoteAddr()) 53 | 54 | newSession(conn) 55 | } 56 | } 57 | 58 | func (s *Server) Stop() { 59 | close(s.done) 60 | } 61 | -------------------------------------------------------------------------------- /cmpp/cmpptest/session.go: -------------------------------------------------------------------------------- 1 | package cmpptest 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "net" 7 | "time" 8 | 9 | connp "github.com/yedamao/go_cmpp/cmpp/conn" 10 | "github.com/yedamao/go_cmpp/cmpp/protocol" 11 | ) 12 | 13 | var ( 14 | mo = flag.Bool("mo", false, "是否模拟上行短信") 15 | rpt = flag.Bool("rpt", false, "是否模拟上行状态报告") 16 | activeTest = flag.Bool("activeTest", false, "是否activeTest") 17 | ) 18 | 19 | func newSession(rawConn net.Conn) { 20 | s := &Session{*connp.NewConn(rawConn)} 21 | 22 | go s.start() 23 | 24 | if *mo || *rpt { 25 | go s.deliverWorker() 26 | } 27 | if *activeTest { 28 | go s.activeTestWorker() 29 | } 30 | } 31 | 32 | // 代表sp->运营商的一条连接 33 | type Session struct { 34 | connp.Conn 35 | // TODO newSeqFunc 36 | } 37 | 38 | func (s *Session) ConnectResp( 39 | sequenceID uint32, status uint8, auth string) error { 40 | 41 | op, err := protocol.NewConnectResp(sequenceID, status, auth) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | return s.Write(op) 47 | } 48 | 49 | func (s *Session) TerminateResp(sequenceID uint32) error { 50 | 51 | op, err := protocol.NewTerminateResp(sequenceID) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | return s.Write(op) 57 | } 58 | 59 | func (s *Session) ActiveTest() error { 60 | var mockId uint32 = 1 61 | 62 | op, err := protocol.NewActiveTest(mockId) 63 | if err != nil { 64 | return err 65 | } 66 | 67 | return s.Write(op) 68 | } 69 | 70 | func (s *Session) ActiveTestResp(sequenceID uint32) error { 71 | 72 | op, err := protocol.NewActiveTest(sequenceID) 73 | if err != nil { 74 | return err 75 | } 76 | 77 | return s.Write(op) 78 | } 79 | 80 | func (s *Session) SubmitResp(sequenceID uint32, msgId uint64, result uint8) error { 81 | 82 | op, err := protocol.NewSubmitResp(sequenceID, msgId, protocol.OK) 83 | if err != nil { 84 | return err 85 | } 86 | 87 | return s.Write(op) 88 | } 89 | 90 | func (s *Session) QueryResp( 91 | sequenceID uint32, 92 | time string, queryTye uint8, queryCode string, 93 | MT_TLMsg, MT_Tlusr uint32, 94 | MT_Scs, MT_WT, MT_FL, MO_Scs, MO_WT, MO_FL uint32, 95 | ) error { 96 | 97 | op, err := protocol.NewQueryResp( 98 | sequenceID, 99 | time, queryTye, queryCode, 100 | MT_TLMsg, MT_Tlusr, 101 | MT_Scs, MT_WT, MT_FL, MO_Scs, MO_WT, MO_FL, 102 | ) 103 | if err != nil { 104 | return err 105 | } 106 | 107 | return s.Write(op) 108 | } 109 | 110 | func (s *Session) Deliver(isReport uint8, content []byte) error { 111 | var mockId uint32 = 1 112 | 113 | op, err := protocol.NewDeliver( 114 | mockId, 12345, "1069000000", "", 0, 0, 0, "16611111111", 115 | isReport, content, 116 | ) 117 | 118 | if err != nil { 119 | return err 120 | } 121 | 122 | return s.Write(op) 123 | } 124 | 125 | // 模拟状态包 126 | func (s *Session) mockReport() error { 127 | rpt, err := protocol.NewReport(1234, "DELIVRD", "", "", "17600000000", 0) 128 | if err != nil { 129 | return err 130 | } 131 | 132 | return s.Deliver(protocol.IS_REPORT, rpt.Serialize()) 133 | } 134 | 135 | // 模拟上行短信 136 | func (s *Session) mockMo() error { 137 | 138 | return s.Deliver(protocol.NOT_REPORT, []byte("hello test msg")) 139 | } 140 | 141 | func (s *Session) start() { 142 | defer s.Close() 143 | 144 | for { 145 | op, err := s.Read() 146 | if err != nil { 147 | if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() { 148 | continue 149 | } 150 | log.Println(err) 151 | return 152 | } 153 | 154 | log.Println(op) 155 | 156 | switch op.GetHeader().Command_Id { 157 | case protocol.CMPP_CONNECT: 158 | s.ConnectResp(op.GetHeader().Sequence_Id, 0, "mockauth") 159 | 160 | case protocol.CMPP_SUBMIT: 161 | submit, ok := op.(*protocol.Submit) 162 | if !ok { 163 | log.Println("Type assert error: ", op) 164 | } 165 | s.SubmitResp(submit.Header.Sequence_Id, submit.MsgId, protocol.OK) 166 | 167 | case protocol.CMPP_QUERY: 168 | query, ok := op.(*protocol.Query) 169 | if !ok { 170 | log.Println("Type assert error: ", op) 171 | } 172 | s.QueryResp( 173 | query.Header.Sequence_Id, query.Time.String(), 174 | query.Query_Type, query.Query_Code.String(), 175 | 0, 0, 0, 0, 0, 0, 0, 0, 176 | ) 177 | 178 | case protocol.CMPP_DELIVER_RESP: 179 | log.Println("Deliver Response") 180 | 181 | case protocol.CMPP_ACTIVE_TEST: 182 | s.ActiveTestResp(op.GetHeader().Sequence_Id) 183 | log.Println("ActiveTest ... ") 184 | case protocol.CMPP_ACTIVE_TEST_RESP: 185 | log.Println("ActiveTest Response") 186 | 187 | case protocol.CMPP_TERMINATE: 188 | s.TerminateResp(op.GetHeader().Sequence_Id) 189 | log.Println("Terminate. Close Session") 190 | return 191 | case protocol.CMPP_TERMINATE_RESP: 192 | log.Println("Terminate response. Close Session") 193 | return 194 | 195 | default: 196 | log.Println("not support CmdId. close session.") 197 | return 198 | } 199 | } 200 | } 201 | 202 | func (s *Session) deliverWorker() { 203 | 204 | doFunc := s.mockMo 205 | if *rpt { 206 | log.Println("deliver (report) worker running") 207 | doFunc = s.mockReport 208 | } else { 209 | log.Println("deliver (mo) worker running") 210 | } 211 | 212 | for { 213 | tick := time.NewTicker(5 * time.Second) 214 | select { 215 | case <-tick.C: 216 | if err := doFunc(); err != nil { 217 | log.Println("Deliver error: ", err) 218 | return 219 | } 220 | } 221 | } 222 | } 223 | 224 | func (s *Session) activeTestWorker() { 225 | 226 | log.Println("activeTest worker running") 227 | 228 | for { 229 | tick := time.NewTicker(30 * time.Second) 230 | select { 231 | case <-tick.C: 232 | if err := s.ActiveTest(); err != nil { 233 | log.Println("ActiveTest error: ", err) 234 | return 235 | } 236 | log.Println("send ActiveTest ...") 237 | } 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /cmpp/conn/conn.go: -------------------------------------------------------------------------------- 1 | package conn 2 | 3 | import ( 4 | "bufio" 5 | "encoding/binary" 6 | "errors" 7 | "io" 8 | "net" 9 | 10 | "github.com/yedamao/go_cmpp/cmpp/protocol" 11 | ) 12 | 13 | var ErrReadLen = errors.New("Read length not match PacketLength") 14 | var ErrMaxSize = errors.New("Operation Len larger than MAX_OP_SIZE") 15 | 16 | // Conn is a cmpp connection can read/write protocol Operation 17 | type Conn struct { 18 | net.Conn 19 | r *bufio.Reader 20 | w *bufio.Writer 21 | } 22 | 23 | // new a cmpp Conn 24 | func NewConn(fd net.Conn) *Conn { 25 | return &Conn{ 26 | Conn: fd, 27 | r: bufio.NewReader(fd), 28 | w: bufio.NewWriter(fd), 29 | } 30 | } 31 | 32 | func (c *Conn) Read() (protocol.Operation, error) { 33 | l := make([]byte, 4) 34 | _, err := io.ReadFull(c.r, l) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | length := binary.BigEndian.Uint32(l) - 4 40 | if length > protocol.MAX_OP_SIZE { 41 | return nil, ErrMaxSize 42 | } 43 | 44 | data := make([]byte, length) 45 | _, err = io.ReadFull(c.r, data) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | pkt := append(l, data...) 51 | 52 | op, err := protocol.ParseOperation(pkt) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | return op, nil 58 | } 59 | 60 | func (c *Conn) Write(op protocol.Operation) error { 61 | _, err := c.Conn.Write(op.Serialize()) 62 | 63 | return err 64 | } 65 | 66 | func (c *Conn) Close() { 67 | c.Conn.Close() 68 | } 69 | -------------------------------------------------------------------------------- /cmpp/protocol/consts.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | const ( 4 | // 高位4bit表示主版本号,低位4bit表示次版本号 5 | VERSION uint8 = 0x20 6 | 7 | // 系统中每个消息包最大不超过2K字节 8 | MAX_OP_SIZE = 2048 9 | ) 10 | 11 | // Command_Id定义 12 | const ( 13 | CMPP_CONNECT = 0x00000001 14 | CMPP_TERMINATE = 0x00000002 15 | ) 16 | 17 | const ( 18 | CMPP_SUBMIT = 0x00000004 + iota 19 | CMPP_DELIVER 20 | CMPP_QUERY 21 | CMPP_CANCEL 22 | CMPP_ACTIVE_TEST 23 | ) 24 | 25 | const ( 26 | CMPP_CONNECT_RESP = 0x80000001 27 | CMPP_TERMINATE_RESP = 0x80000002 28 | ) 29 | 30 | const ( 31 | CMPP_SUBMIT_RESP = 0x80000004 + iota 32 | CMPP_DELIVER_RESP 33 | CMPP_QUERY_RESP 34 | CMPP_CANCEL_RESP 35 | CMPP_ACTIVE_TEST_RESP 36 | ) 37 | 38 | // Status/Result OK 39 | const ( 40 | OK = 0 41 | ) 42 | 43 | // 信息格式 44 | // 45 | // 0:ASCII串 46 | // 3:短信写卡操作 47 | // 4:二进制信息 48 | // 8:UCS2编码 49 | // 15:含GB汉字 50 | const ( 51 | ASCII = 0 // ASCII编码 52 | BINARY = 4 // 二进制短消息 53 | UCS2 = 8 // UCS2编码 54 | GB18030 = 15 // GB18030编码 55 | ) 56 | 57 | // 是否要求返回状态报告 58 | const ( 59 | NO_NEED_REPORT = 0 60 | NEED_REPORT = 1 61 | ) 62 | 63 | // 是否为状态报告 64 | const ( 65 | NOT_REPORT = 0 66 | IS_REPORT = 1 67 | ) 68 | -------------------------------------------------------------------------------- /cmpp/protocol/consts_test.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestConsts(t *testing.T) { 8 | if CMPP_SUBMIT != 0x00000004 || 9 | CMPP_SUBMIT_RESP != 0x80000004 { 10 | 11 | t.Error("const Command_Id error") 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /cmpp/protocol/header.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | ) 7 | 8 | // Message Header 9 | type Header struct { 10 | // 消息总长度(含消息头及消息体) 11 | Total_Length uint32 12 | // 命令或响应类型 13 | Command_Id uint32 14 | // 消息流水号,顺序累加,步长为1,循环使用(一对请求和应答消息的流水号必须相同) 15 | Sequence_Id uint32 16 | } 17 | 18 | func (p *Header) GetHeader() *Header { 19 | return p 20 | } 21 | 22 | func (p *Header) Serialize() []byte { 23 | b := packUi32(p.Total_Length) 24 | b = append(b, packUi32(p.Command_Id)...) 25 | b = append(b, packUi32(p.Sequence_Id)...) 26 | 27 | return b 28 | } 29 | 30 | func (p *Header) String() string { 31 | var b bytes.Buffer 32 | fmt.Fprintln(&b, "--- Header ---") 33 | fmt.Fprintln(&b, "Total_Length: ", p.Total_Length) 34 | fmt.Fprintf(&b, "Command_Id: 0x%x\n", p.Command_Id) 35 | fmt.Fprintln(&b, "Sequence_Id: ", p.Sequence_Id) 36 | 37 | return b.String() 38 | } 39 | 40 | func (p *Header) Parse(data []byte) *Header { 41 | 42 | p.Total_Length = unpackUi32(data[:4]) 43 | p.Command_Id = unpackUi32(data[4:8]) 44 | p.Sequence_Id = unpackUi32(data[8:12]) 45 | 46 | return p 47 | } 48 | 49 | func ParseHeader(data []byte) (*Header, error) { 50 | 51 | h := &Header{} 52 | h.Parse(data) 53 | 54 | return h, nil 55 | } 56 | -------------------------------------------------------------------------------- /cmpp/protocol/header_test.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestParseHeader(t *testing.T) { 8 | 9 | h := &Header{ 10 | Total_Length: 99, 11 | Command_Id: CMPP_CONNECT, 12 | Sequence_Id: 1, 13 | } 14 | 15 | parsed, _ := ParseHeader(h.Serialize()) 16 | 17 | if h.Total_Length != parsed.Total_Length || 18 | h.Command_Id != parsed.Command_Id || 19 | h.Sequence_Id != parsed.Sequence_Id { 20 | t.Error("header parsed not equal") 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /cmpp/protocol/op_activetest.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | ) 7 | 8 | // 链路检测(CMPP_ACTIVE_TEST)操作 9 | type ActiveTest struct { 10 | *Header 11 | 12 | // Body 13 | } 14 | 15 | func NewActiveTest(sequenceID uint32) (*ActiveTest, error) { 16 | op := &ActiveTest{} 17 | 18 | op.Header = &Header{} 19 | 20 | op.Total_Length = 4 + 4 + 4 21 | op.Command_Id = CMPP_ACTIVE_TEST 22 | op.Sequence_Id = sequenceID 23 | 24 | return op, nil 25 | } 26 | 27 | func ParseActiveTest(hdr *Header, data []byte) (*ActiveTest, error) { 28 | op := &ActiveTest{} 29 | op.Header = hdr 30 | 31 | return op, nil 32 | } 33 | 34 | func (op *ActiveTest) Serialize() []byte { 35 | 36 | return op.Header.Serialize() 37 | } 38 | 39 | func (op *ActiveTest) String() string { 40 | 41 | return op.Header.String() 42 | } 43 | 44 | func (op *ActiveTest) Ok() error { 45 | return nil 46 | } 47 | 48 | type ActiveTestResp struct { 49 | *Header 50 | 51 | // Body 52 | Reserved uint8 53 | } 54 | 55 | func NewActiveTestResp(sequenceID uint32) (*ActiveTestResp, error) { 56 | op := &ActiveTestResp{} 57 | 58 | op.Header = &Header{} 59 | var length uint32 = 4 + 4 + 4 60 | 61 | op.Reserved = 0 62 | length = length + 1 63 | 64 | op.Total_Length = length 65 | op.Command_Id = CMPP_ACTIVE_TEST_RESP 66 | op.Sequence_Id = sequenceID 67 | 68 | return op, nil 69 | } 70 | 71 | func ParseActiveTestResp(hdr *Header, data []byte) (*ActiveTestResp, error) { 72 | op := &ActiveTestResp{} 73 | op.Header = hdr 74 | 75 | p := 0 76 | op.Reserved = data[p] 77 | p = p + 1 78 | 79 | return op, nil 80 | } 81 | 82 | func (op *ActiveTestResp) Serialize() []byte { 83 | b := op.Header.Serialize() 84 | 85 | b = append(b, packUi8(op.Reserved)...) 86 | 87 | return b 88 | } 89 | 90 | func (op *ActiveTestResp) String() string { 91 | var b bytes.Buffer 92 | b.WriteString(op.Header.String()) 93 | 94 | fmt.Fprintln(&b, "--- ActiveTestResp ---") 95 | fmt.Fprintln(&b, "Reserved: ", op.Reserved) 96 | 97 | return b.String() 98 | } 99 | 100 | func (op *ActiveTestResp) Ok() error { 101 | return nil 102 | } 103 | -------------------------------------------------------------------------------- /cmpp/protocol/op_activetest_test.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestActiveTest(t *testing.T) { 8 | op, err := NewActiveTest(1) 9 | if err != nil { 10 | t.Error(err) 11 | } 12 | 13 | parsed, err := ParseOperation(op.Serialize()) 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | 18 | _, ok := parsed.(*ActiveTest) 19 | if !ok { 20 | t.Error("not equal") 21 | } 22 | } 23 | 24 | func TestActiveTestResp(t *testing.T) { 25 | op, err := NewActiveTestResp(1) 26 | if err != nil { 27 | t.Error(err) 28 | } 29 | 30 | parsed, err := ParseOperation(op.Serialize()) 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | 35 | resp := parsed.(*ActiveTestResp) 36 | 37 | if resp.Reserved != op.Reserved { 38 | t.Error("not equal") 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /cmpp/protocol/op_cancel.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | ) 8 | 9 | // SP向ISMG发起删除短信(CMPP_CANCEL)操作 10 | // 11 | // CMPP_CANCEL操作的目的是SP通过此操作可以将 12 | // 已经提交给ISMG的短信删除,ISMG将以CMPP_CANCEL_RESP 13 | // 回应删除操作的结果。 14 | type Cancel struct { 15 | *Header 16 | 17 | // Body 18 | MsgId uint64 // 信息标识(SP想要删除的信息标识) 19 | } 20 | 21 | func NewCancel(sequenceID uint32, msgId uint64) (*Cancel, error) { 22 | op := &Cancel{} 23 | 24 | op.Header = &Header{} 25 | var length uint32 = 4 + 4 + 4 // header length 26 | 27 | op.MsgId = msgId 28 | length = length + 8 29 | 30 | op.Total_Length = length 31 | op.Command_Id = CMPP_CANCEL 32 | op.Sequence_Id = sequenceID 33 | 34 | return op, nil 35 | } 36 | 37 | func ParseCancel(hdr *Header, data []byte) (*Cancel, error) { 38 | p := 0 39 | 40 | op := &Cancel{} 41 | op.Header = hdr 42 | 43 | op.MsgId = unpackUi64(data[p : p+8]) 44 | p = p + 8 45 | 46 | return op, nil 47 | } 48 | 49 | func (op *Cancel) Serialize() []byte { 50 | b := op.Header.Serialize() 51 | 52 | b = append(b, packUi64(op.MsgId)...) 53 | 54 | return b 55 | } 56 | 57 | func (op *Cancel) String() string { 58 | var b bytes.Buffer 59 | b.WriteString(op.Header.String()) 60 | 61 | fmt.Fprintln(&b, "--- Cancel ---") 62 | fmt.Fprintln(&b, "Msg_Id: ", op.MsgId) 63 | 64 | return b.String() 65 | } 66 | 67 | func (op *Cancel) Ok() error { 68 | return nil 69 | } 70 | 71 | type CancelResp struct { 72 | *Header 73 | 74 | // Body 75 | 76 | // 成功标识 77 | // 0:成功 78 | // 1:失败 79 | SuccessId uint8 80 | } 81 | 82 | func NewCancelResp(sequenceID uint32, successId uint8) (*CancelResp, error) { 83 | op := &CancelResp{} 84 | 85 | op.Header = &Header{} 86 | var length uint32 = 4 + 4 + 4 87 | 88 | op.SuccessId = successId 89 | length = length + 1 90 | 91 | op.Total_Length = length 92 | op.Command_Id = CMPP_CANCEL_RESP 93 | op.Sequence_Id = sequenceID 94 | 95 | return op, nil 96 | } 97 | 98 | func ParseCancelResp(hdr *Header, data []byte) (*CancelResp, error) { 99 | op := &CancelResp{} 100 | op.Header = hdr 101 | 102 | p := 0 103 | op.SuccessId = data[p] 104 | p = p + 1 105 | 106 | return op, nil 107 | } 108 | 109 | func (op *CancelResp) Serialize() []byte { 110 | b := op.Header.Serialize() 111 | 112 | b = append(b, packUi8(op.SuccessId)...) 113 | 114 | return b 115 | } 116 | 117 | func (op *CancelResp) String() string { 118 | var b bytes.Buffer 119 | b.WriteString(op.Header.String()) 120 | 121 | fmt.Fprintln(&b, "--- CancelResp ---") 122 | fmt.Fprintln(&b, "SuccessId: ", op.SuccessId) 123 | 124 | return b.String() 125 | } 126 | 127 | func (op *CancelResp) Ok() (err error) { 128 | 129 | if 0 == op.SuccessId { 130 | err = nil 131 | } else { 132 | err = errors.New("失败") 133 | } 134 | 135 | return err 136 | } 137 | -------------------------------------------------------------------------------- /cmpp/protocol/op_cancel_test.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestCancel(t *testing.T) { 8 | op, err := NewCancel(1, 123456789) 9 | if err != nil { 10 | t.Error(err) 11 | } 12 | 13 | parsed, err := ParseOperation(op.Serialize()) 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | 18 | cancel := parsed.(*Cancel) 19 | 20 | if cancel.MsgId != op.MsgId { 21 | t.Error("not equal") 22 | } 23 | } 24 | 25 | func TestCancelResp(t *testing.T) { 26 | op, err := NewCancelResp(1, 0) 27 | if err != nil { 28 | t.Error(err) 29 | } 30 | 31 | parsed, err := ParseOperation(op.Serialize()) 32 | if err != nil { 33 | t.Fatal(err) 34 | } 35 | 36 | cancelResp := parsed.(*CancelResp) 37 | 38 | if cancelResp.SuccessId != op.SuccessId { 39 | t.Error("not equal") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /cmpp/protocol/op_connect.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "strconv" 8 | ) 9 | 10 | // SP请求连接到ISMG(CMPP_CONNECT)操作 11 | // 12 | // CMPP_CONNECT操作的目的是SP向ISMG注册作为一个合法SP身份, 13 | // 若注册成功后即建立了应用层的连接,此后SP可以通过此ISMG 14 | // 接收和发送短信。 15 | // ISMG以CMPP_CONNECT_RESP消息响应SP的请求。 16 | type Connect struct { 17 | *Header 18 | 19 | // 源地址,此处为SP_Id,即SP的企业代码 20 | SourceAddr *OctetString 21 | // 用于鉴别源地址 22 | AuthenticatorSource *OctetString 23 | // 双方协商的版本号(高位4bit表示主版本号, 24 | // 低位4bit表示次版本号) 25 | Version uint8 26 | // 时间戳的明文,由客户端产生,格式为MMDDHHMMSS, 27 | // 即月日时分秒,10位数字的整型,右对齐 。 28 | Timestamp uint32 29 | } 30 | 31 | func NewConnect( 32 | sequenceID uint32, sourceAddr, sharedSecret string, 33 | ) (*Connect, error) { 34 | // TODO 35 | // gen AuthenticatorSource 36 | auth, err := genAuthenticatorSource(sourceAddr, sharedSecret, genTimestamp()) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | op := &Connect{} 42 | 43 | op.Header = &Header{} 44 | var length uint32 = 4 + 4 + 4 // header length 45 | 46 | op.SourceAddr = &OctetString{Data: []byte(sourceAddr), FixedLen: 6} 47 | length = length + 6 48 | 49 | op.AuthenticatorSource = &OctetString{Data: []byte(auth), FixedLen: 16} 50 | length = length + 16 51 | 52 | op.Version = VERSION 53 | length = length + 1 54 | 55 | op.Timestamp = genTimestamp() 56 | length = length + 4 57 | 58 | op.Total_Length = length 59 | op.Command_Id = CMPP_CONNECT 60 | op.Sequence_Id = sequenceID 61 | 62 | return op, nil 63 | } 64 | 65 | func ParseConnect(hdr *Header, data []byte) (*Connect, error) { 66 | p := 0 67 | 68 | op := &Connect{} 69 | op.Header = hdr 70 | 71 | op.SourceAddr = &OctetString{Data: data[p : p+6], FixedLen: 6} 72 | p = p + 6 73 | 74 | op.AuthenticatorSource = &OctetString{Data: data[p : p+16], FixedLen: 6} 75 | p = p + 16 76 | 77 | op.Version = data[p] 78 | p = p + 1 79 | 80 | op.Timestamp = unpackUi32(data[p : p+4]) 81 | p = p + 4 82 | 83 | return op, nil 84 | } 85 | 86 | func (op *Connect) Serialize() []byte { 87 | b := op.Header.Serialize() 88 | 89 | b = append(b, op.SourceAddr.Byte()...) 90 | b = append(b, op.AuthenticatorSource.Byte()...) 91 | b = append(b, packUi8(op.Version)...) 92 | b = append(b, packUi32(op.Timestamp)...) 93 | 94 | return b 95 | } 96 | 97 | func (op *Connect) String() string { 98 | var b bytes.Buffer 99 | b.WriteString(op.Header.String()) 100 | 101 | fmt.Fprintln(&b, "--- Login ---") 102 | fmt.Fprintln(&b, "SourceAddr: ", op.SourceAddr) 103 | fmt.Fprintln(&b, "AuthenticatorSource: ", op.AuthenticatorSource) 104 | fmt.Fprintf(&b, "Version: 0x%x\n", op.Version) 105 | fmt.Fprintln(&b, "Timestamp: ", op.Timestamp) 106 | 107 | return b.String() 108 | } 109 | 110 | func (op *Connect) Ok() error { 111 | // just for interface 112 | return nil 113 | } 114 | 115 | type ConnectResp struct { 116 | *Header 117 | 118 | // 状态 119 | Status uint8 120 | // ISMG认证码,用于鉴别ISMG 121 | AuthenticatorISMG *OctetString 122 | // 服务器支持的最高版本号 123 | Version uint8 124 | } 125 | 126 | func NewConnectResp( 127 | sequenceID uint32, status uint8, authISMG string, 128 | ) (*ConnectResp, error) { 129 | op := &ConnectResp{} 130 | 131 | op.Header = &Header{} 132 | var length uint32 = 4 + 4 + 4 133 | 134 | op.Status = status 135 | length = length + 1 136 | 137 | op.AuthenticatorISMG = &OctetString{Data: []byte(authISMG), FixedLen: 16} 138 | length = length + 16 139 | 140 | op.Version = VERSION 141 | length = length + 1 142 | 143 | op.Total_Length = length 144 | op.Command_Id = CMPP_CONNECT_RESP 145 | op.Sequence_Id = sequenceID 146 | 147 | return op, nil 148 | } 149 | 150 | func ParseConnectResp(hdr *Header, data []byte) (*ConnectResp, error) { 151 | op := &ConnectResp{} 152 | op.Header = hdr 153 | 154 | p := 0 155 | op.Status = data[p] 156 | p = p + 1 157 | 158 | op.AuthenticatorISMG = &OctetString{Data: data[p : p+16], FixedLen: 16} 159 | p = p + 16 160 | 161 | op.Version = data[p] 162 | p = p + 1 163 | 164 | return op, nil 165 | } 166 | 167 | func (op *ConnectResp) Serialize() []byte { 168 | b := op.Header.Serialize() 169 | 170 | b = append(b, packUi8(op.Status)...) 171 | b = append(b, op.AuthenticatorISMG.Byte()...) 172 | b = append(b, packUi8(op.Version)...) 173 | 174 | return b 175 | } 176 | 177 | func (op *ConnectResp) String() string { 178 | var b bytes.Buffer 179 | b.WriteString(op.Header.String()) 180 | 181 | fmt.Fprintln(&b, "--- ConnectResp ---") 182 | fmt.Fprintln(&b, "Status: ", op.Status) 183 | fmt.Fprintln(&b, "AuthenticatorISMG: ", op.AuthenticatorISMG) 184 | fmt.Fprintf(&b, "Version: 0x%x\n", op.Version) 185 | 186 | return b.String() 187 | } 188 | 189 | func (op *ConnectResp) Ok() (err error) { 190 | 191 | switch op.Status { 192 | case 0: // 正确 193 | err = nil 194 | case 1: 195 | err = errors.New("消息结构错") 196 | case 2: 197 | err = errors.New("非法源地址") 198 | case 3: 199 | err = errors.New("认证错") 200 | case 4: 201 | err = errors.New("版本太高") 202 | default: 203 | err = errors.New("其他错误: " + strconv.Itoa(int(op.Status))) 204 | } 205 | 206 | return err 207 | } 208 | -------------------------------------------------------------------------------- /cmpp/protocol/op_connect_test.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestConnect(t *testing.T) { 8 | op, err := NewConnect(1, "", "") 9 | if err != nil { 10 | t.Error(err) 11 | } 12 | 13 | parsed, err := ParseOperation(op.Serialize()) 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | 18 | parsedConnect := parsed.(*Connect) 19 | 20 | if parsedConnect.SourceAddr.String() != op.SourceAddr.String() || 21 | parsedConnect.AuthenticatorSource.String() != op.AuthenticatorSource.String() || 22 | parsedConnect.Timestamp != op.Timestamp || 23 | parsedConnect.Version != op.Version { 24 | t.Error("parsedLogin not equal") 25 | } 26 | } 27 | 28 | func TestConnectResp(t *testing.T) { 29 | op, err := NewConnectResp(1, 0, "") 30 | if err != nil { 31 | t.Error(err) 32 | } 33 | 34 | parsed, err := ParseOperation(op.Serialize()) 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | 39 | parsedConnectResp := parsed.(*ConnectResp) 40 | 41 | if parsedConnectResp.Status != op.Status || 42 | parsedConnectResp.AuthenticatorISMG.String() != op.AuthenticatorISMG.String() || 43 | parsedConnectResp.Version != op.Version { 44 | t.Error("parsedLoginResp not equal") 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /cmpp/protocol/op_deliver.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "strconv" 8 | ) 9 | 10 | // ISMG向SP送交短信(CMPP_DELIVER)操作 11 | // 12 | // CMPP_DELIVER操作的目的是ISMG把从短信中心 13 | // 或其它ISMG转发来的短信送交SP,SP以CMPP_DELIVER_RESP消息回应 14 | type Deliver struct { 15 | *Header 16 | 17 | // Body 18 | MsgId uint64 // 信息标识 19 | DestId *OctetString // 目的号码 20 | ServiceId *OctetString // 业务类型,是数字、字母和符号的组合 21 | TP_pid uint8 22 | TP_udhi uint8 23 | MsgFmt uint8 24 | SrcTerminalId *OctetString 25 | 26 | // 是否为状态报告 27 | // 0:非状态报告 28 | // 1:状态报告 29 | RegisteredDelivery uint8 30 | MsgLength uint8 31 | MsgContent []byte 32 | Reserved *OctetString 33 | } 34 | 35 | func NewDeliver( 36 | sequenceID uint32, msgId uint64, destId, serviceId string, 37 | TP_pid, TP_udhi, msgFmt uint8, srcTerminalId string, 38 | isReport uint8, content []byte, 39 | ) (*Deliver, error) { 40 | op := &Deliver{} 41 | 42 | op.Header = &Header{} 43 | var length uint32 = 4 + 4 + 4 // header length 44 | 45 | op.MsgId = msgId 46 | length = length + 8 47 | 48 | op.DestId = &OctetString{Data: []byte(destId), FixedLen: 21} 49 | length = length + 21 50 | 51 | op.ServiceId = &OctetString{Data: []byte(serviceId), FixedLen: 10} 52 | length = length + 10 53 | 54 | op.TP_pid = TP_pid 55 | length = length + 1 56 | 57 | op.TP_udhi = TP_udhi 58 | length = length + 1 59 | 60 | op.MsgFmt = msgFmt 61 | length = length + 1 62 | 63 | op.SrcTerminalId = &OctetString{Data: []byte(srcTerminalId), FixedLen: 21} 64 | length = length + 21 65 | 66 | op.RegisteredDelivery = isReport 67 | length = length + 1 68 | 69 | msgLen := len(content) 70 | op.MsgLength = uint8(msgLen) 71 | length = length + 1 72 | 73 | op.MsgContent = content 74 | length = length + uint32(msgLen) 75 | 76 | op.Reserved = &OctetString{FixedLen: 8} 77 | length = length + 8 78 | 79 | op.Total_Length = length 80 | op.Command_Id = CMPP_DELIVER 81 | op.Sequence_Id = sequenceID 82 | 83 | return op, nil 84 | } 85 | 86 | func ParseDeliver(hdr *Header, data []byte) (*Deliver, error) { 87 | p := 0 88 | op := &Deliver{} 89 | op.Header = hdr 90 | 91 | op.MsgId = unpackUi64(data[p : p+8]) 92 | p = p + 8 93 | 94 | op.DestId = &OctetString{Data: data[p : p+21], FixedLen: 21} 95 | p = p + 21 96 | 97 | op.ServiceId = &OctetString{Data: data[p : p+10], FixedLen: 10} 98 | p = p + 10 99 | 100 | op.TP_pid = data[p] 101 | p = p + 1 102 | op.TP_udhi = data[p] 103 | p = p + 1 104 | op.MsgFmt = data[p] 105 | p = p + 1 106 | 107 | op.SrcTerminalId = &OctetString{Data: data[p : p+21], FixedLen: 21} 108 | p = p + 21 109 | 110 | op.RegisteredDelivery = data[p] 111 | p = p + 1 112 | op.MsgLength = data[p] 113 | p = p + 1 114 | 115 | op.MsgContent = data[p : p+int(op.MsgLength)] 116 | p = p + int(op.MsgLength) 117 | 118 | op.Reserved = &OctetString{Data: data[p : p+8], FixedLen: 8} 119 | p = p + 8 120 | 121 | return op, nil 122 | } 123 | 124 | func (op *Deliver) Serialize() []byte { 125 | b := op.Header.Serialize() 126 | 127 | b = append(b, packUi64(op.MsgId)...) 128 | b = append(b, op.DestId.Byte()...) 129 | b = append(b, op.ServiceId.Byte()...) 130 | 131 | b = append(b, packUi8(op.TP_pid)...) 132 | b = append(b, packUi8(op.TP_udhi)...) 133 | b = append(b, packUi8(op.MsgFmt)...) 134 | 135 | b = append(b, op.SrcTerminalId.Byte()...) 136 | b = append(b, packUi8(op.RegisteredDelivery)...) 137 | b = append(b, packUi8(op.MsgLength)...) 138 | b = append(b, op.MsgContent...) 139 | b = append(b, op.Reserved.Byte()...) 140 | 141 | return b 142 | } 143 | 144 | func (op *Deliver) String() string { 145 | var b bytes.Buffer 146 | b.WriteString(op.Header.String()) 147 | 148 | fmt.Fprintln(&b, "--- Deliver ---") 149 | fmt.Fprintln(&b, "MsgID: ", op.MsgId) 150 | fmt.Fprintln(&b, "DestId: ", op.DestId) 151 | fmt.Fprintln(&b, "ServiceId: ", op.ServiceId) 152 | 153 | fmt.Fprintln(&b, "TP_pid: ", op.TP_pid) 154 | fmt.Fprintln(&b, "TP_udhi: ", op.TP_udhi) 155 | fmt.Fprintln(&b, "MsgFmt: ", op.MsgFmt) 156 | 157 | fmt.Fprintln(&b, "SrcTerminalId: ", op.SrcTerminalId) 158 | fmt.Fprintln(&b, "RegisteredDelivery: ", op.RegisteredDelivery) 159 | 160 | fmt.Fprintln(&b, "MsgLength: ", op.MsgLength) 161 | fmt.Fprintln(&b, "MsgContent: ", string(op.MsgContent)) 162 | 163 | return b.String() 164 | } 165 | 166 | func (op *Deliver) Ok() error { 167 | return nil 168 | } 169 | 170 | type DeliverResp struct { 171 | *Header 172 | 173 | // Body 174 | MsgId uint64 // 信息标识 175 | Result uint8 // 结果 176 | } 177 | 178 | func NewDeliverResp( 179 | sequenceID uint32, msgId uint64, result uint8) (*DeliverResp, error) { 180 | 181 | op := &DeliverResp{} 182 | 183 | op.Header = &Header{} 184 | var length uint32 = 4 + 4 + 4 185 | 186 | op.MsgId = msgId 187 | length = length + 8 188 | 189 | op.Result = result 190 | length = length + 1 191 | 192 | op.Total_Length = length 193 | op.Command_Id = CMPP_DELIVER_RESP 194 | op.Sequence_Id = sequenceID 195 | 196 | return op, nil 197 | } 198 | 199 | func ParseDeliverResp(hdr *Header, data []byte) (*DeliverResp, error) { 200 | op := &DeliverResp{} 201 | op.Header = hdr 202 | 203 | p := 0 204 | op.MsgId = unpackUi64(data[p : p+8]) 205 | p = p + 8 206 | 207 | op.Result = data[p] 208 | p = p + 1 209 | 210 | return op, nil 211 | } 212 | 213 | func (op *DeliverResp) Serialize() []byte { 214 | b := op.Header.Serialize() 215 | 216 | b = append(b, packUi64(op.MsgId)...) 217 | b = append(b, packUi8(op.Result)...) 218 | 219 | return b 220 | } 221 | 222 | func (op *DeliverResp) String() string { 223 | var b bytes.Buffer 224 | b.WriteString(op.Header.String()) 225 | 226 | fmt.Fprintln(&b, "--- DeliverResp ---") 227 | fmt.Fprintln(&b, "MsgID: ", op.MsgId) 228 | fmt.Fprintln(&b, "Result: ", op.Result) 229 | 230 | return b.String() 231 | } 232 | 233 | func (op *DeliverResp) Ok() (err error) { 234 | 235 | switch op.Result { 236 | case 0: // 正确 237 | err = nil 238 | case 1: 239 | err = errors.New("消息结构错") 240 | case 2: 241 | err = errors.New("命令字错") 242 | case 3: 243 | err = errors.New("消息序号重复") 244 | case 4: 245 | err = errors.New("消息长度错") 246 | case 5: 247 | err = errors.New("资费代码错") 248 | case 6: 249 | err = errors.New("超过最大信息长") 250 | case 7: 251 | err = errors.New("业务代码错") 252 | case 8: 253 | err = errors.New("流量控制错") 254 | default: 255 | err = errors.New("其他错误: " + strconv.Itoa(int(op.Result))) 256 | } 257 | 258 | return err 259 | } 260 | -------------------------------------------------------------------------------- /cmpp/protocol/op_deliver_test.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestDeliver(t *testing.T) { 8 | op, err := NewDeliver( 9 | 1, 12345, "1069000000", "", 0, 0, 0, "16600000000", 10 | 0, []byte("hello haha, test msg"), 11 | ) 12 | if err != nil { 13 | t.Error(err) 14 | } 15 | 16 | parsed, err := ParseOperation(op.Serialize()) 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | 21 | deliver := parsed.(*Deliver) 22 | 23 | if deliver.MsgId != op.MsgId || 24 | deliver.DestId.String() != op.DestId.String() || 25 | deliver.ServiceId.String() != op.ServiceId.String() || 26 | deliver.TP_pid != op.TP_pid || 27 | deliver.TP_udhi != op.TP_udhi || 28 | deliver.MsgFmt != op.MsgFmt || 29 | deliver.SrcTerminalId.String() != op.SrcTerminalId.String() || 30 | deliver.RegisteredDelivery != op.RegisteredDelivery { 31 | 32 | t.Error("parsedLogin not equal") 33 | } 34 | } 35 | 36 | func TestDeliverResp(t *testing.T) { 37 | op, err := NewDeliverResp(1, 12345, 0) 38 | if err != nil { 39 | t.Error(err) 40 | } 41 | 42 | parsed, err := ParseOperation(op.Serialize()) 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | 47 | resp := parsed.(*DeliverResp) 48 | 49 | if resp.MsgId != op.MsgId || 50 | resp.Result != op.Result { 51 | t.Error("parsedLoginResp not equal") 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /cmpp/protocol/op_query.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | ) 7 | 8 | // SP向ISMG查询发送短信状态(CMPP_QUERY)操作 9 | // 10 | // CMPP_QUERY操作的目的是SP向ISMG查询某时间的 11 | // 业务统计情况,可以按总数或按业务代码查询。 12 | // ISMG以CMPP_QUERY_RESP应答。 13 | type Query struct { 14 | *Header 15 | 16 | // Body 17 | // 时间YYYYMMDD(精确至日) 18 | Time *OctetString 19 | // 查询类别 20 | // 0:总数查询 21 | // 1:按业务类型查询 22 | Query_Type uint8 23 | // 查询码 24 | // 当Query_Type为0时,此项无效; 25 | // 当Query_Type为1时,此项填写业务类型Service_Id. 26 | Query_Code *OctetString 27 | Reserve *OctetString 28 | } 29 | 30 | func NewQuery( 31 | sequenceID uint32, 32 | time string, queryTye uint8, queryCode string, 33 | ) (*Query, error) { 34 | 35 | op := &Query{} 36 | 37 | op.Header = &Header{} 38 | var length uint32 = 4 + 4 + 4 // header length 39 | 40 | op.Time = &OctetString{Data: []byte(time), FixedLen: 8} 41 | length = length + 8 42 | 43 | op.Query_Type = queryTye 44 | length = length + 1 45 | 46 | op.Query_Code = &OctetString{Data: []byte(queryCode), FixedLen: 10} 47 | length = length + 10 48 | 49 | op.Reserve = &OctetString{FixedLen: 8} 50 | length = length + 8 51 | 52 | op.Total_Length = length 53 | op.Command_Id = CMPP_QUERY 54 | op.Sequence_Id = sequenceID 55 | 56 | return op, nil 57 | } 58 | 59 | func ParseQuery(hdr *Header, data []byte) (*Query, error) { 60 | p := 0 61 | 62 | op := &Query{} 63 | op.Header = hdr 64 | 65 | op.Time = &OctetString{Data: data[p : p+8]} 66 | p = p + 8 67 | 68 | op.Query_Type = data[p] 69 | p = p + 1 70 | 71 | op.Query_Code = &OctetString{Data: data[p : p+10]} 72 | p = p + 10 73 | 74 | op.Reserve = &OctetString{Data: data[p : p+8]} 75 | p = p + 8 76 | 77 | return op, nil 78 | } 79 | 80 | func (op *Query) Serialize() []byte { 81 | b := op.Header.Serialize() 82 | 83 | b = append(b, op.Time.Byte()...) 84 | b = append(b, packUi8(op.Query_Type)...) 85 | b = append(b, op.Query_Code.Byte()...) 86 | b = append(b, op.Reserve.Byte()...) 87 | 88 | return b 89 | } 90 | 91 | func (op *Query) String() string { 92 | var b bytes.Buffer 93 | b.WriteString(op.Header.String()) 94 | 95 | fmt.Fprintln(&b, "--- Query ---") 96 | fmt.Fprintln(&b, "Time: ", op.Time) 97 | fmt.Fprintln(&b, "Query_Type: ", op.Query_Type) 98 | fmt.Fprintln(&b, "Query_Code: ", op.Query_Code) 99 | fmt.Fprintln(&b, "Reserve: ", op.Reserve) 100 | 101 | return b.String() 102 | } 103 | 104 | func (op *Query) Ok() error { 105 | return nil 106 | } 107 | 108 | type QueryResp struct { 109 | *Header 110 | 111 | // Body 112 | // 时间YYYYMMDD(精确至日) 113 | Time *OctetString 114 | // 查询类别 115 | // 0:总数查询 116 | // 1:按业务类型查询 117 | Query_Type uint8 118 | // 查询码 119 | // 当Query_Type为0时,此项无效; 120 | // 当Query_Type为1时,此项填写业务类型Service_Id. 121 | Query_Code *OctetString 122 | 123 | MT_TLMsg uint32 // 从SP接收信息总数 124 | MT_Tlusr uint32 // 从SP接收用户总数 125 | 126 | MT_Scs uint32 // 成功转发数量 127 | MT_WT uint32 // 待转发数量 128 | MT_FL uint32 // 转发失败数量 129 | MO_Scs uint32 // 向SP成功送达数量 130 | MO_WT uint32 // 向SP待送达数量 131 | MO_FL uint32 // 向SP送达失败数量 132 | } 133 | 134 | func NewQueryResp( 135 | sequenceID uint32, 136 | time string, queryTye uint8, queryCode string, 137 | MT_TLMsg, MT_Tlusr uint32, 138 | MT_Scs, MT_WT, MT_FL, MO_Scs, MO_WT, MO_FL uint32, 139 | ) (*QueryResp, error) { 140 | 141 | op := &QueryResp{} 142 | 143 | op.Header = &Header{} 144 | var length uint32 = 4 + 4 + 4 // header length 145 | 146 | op.Time = &OctetString{Data: []byte(time), FixedLen: 8} 147 | length = length + 8 148 | 149 | op.Query_Type = queryTye 150 | length = length + 1 151 | 152 | op.Query_Code = &OctetString{Data: []byte(queryCode), FixedLen: 10} 153 | length = length + 10 154 | 155 | op.MT_TLMsg = MT_TLMsg 156 | length = length + 4 157 | op.MT_Tlusr = MT_Tlusr 158 | length = length + 4 159 | 160 | op.MT_Scs = MT_Scs 161 | length = length + 4 162 | op.MT_WT = MT_WT 163 | length = length + 4 164 | op.MT_FL = MT_FL 165 | length = length + 4 166 | 167 | op.MO_Scs = MO_Scs 168 | length = length + 4 169 | op.MO_WT = MO_WT 170 | length = length + 4 171 | op.MO_FL = MO_FL 172 | length = length + 4 173 | 174 | op.Total_Length = length 175 | op.Command_Id = CMPP_QUERY_RESP 176 | op.Sequence_Id = sequenceID 177 | 178 | return op, nil 179 | } 180 | 181 | func ParseQueryResp(hdr *Header, data []byte) (*QueryResp, error) { 182 | p := 0 183 | 184 | op := &QueryResp{} 185 | op.Header = hdr 186 | 187 | op.Time = &OctetString{Data: data[p : p+8]} 188 | p = p + 8 189 | 190 | op.Query_Type = data[p] 191 | p = p + 1 192 | 193 | op.Query_Code = &OctetString{Data: data[p : p+10]} 194 | p = p + 10 195 | 196 | op.MT_TLMsg = unpackUi32(data[p : p+4]) 197 | p = p + 4 198 | op.MT_Tlusr = unpackUi32(data[p : p+4]) 199 | p = p + 4 200 | 201 | op.MT_Scs = unpackUi32(data[p : p+4]) 202 | p = p + 4 203 | op.MT_WT = unpackUi32(data[p : p+4]) 204 | p = p + 4 205 | op.MT_FL = unpackUi32(data[p : p+4]) 206 | p = p + 4 207 | op.MO_Scs = unpackUi32(data[p : p+4]) 208 | p = p + 4 209 | op.MO_WT = unpackUi32(data[p : p+4]) 210 | p = p + 4 211 | op.MO_FL = unpackUi32(data[p : p+4]) 212 | p = p + 4 213 | 214 | return op, nil 215 | } 216 | 217 | func (op *QueryResp) Serialize() []byte { 218 | b := op.Header.Serialize() 219 | 220 | b = append(b, op.Time.Byte()...) 221 | b = append(b, packUi8(op.Query_Type)...) 222 | b = append(b, op.Query_Code.Byte()...) 223 | 224 | b = append(b, packUi32(op.MT_TLMsg)...) 225 | b = append(b, packUi32(op.MT_Tlusr)...) 226 | 227 | b = append(b, packUi32(op.MT_Scs)...) 228 | b = append(b, packUi32(op.MT_WT)...) 229 | b = append(b, packUi32(op.MT_FL)...) 230 | b = append(b, packUi32(op.MO_Scs)...) 231 | b = append(b, packUi32(op.MO_WT)...) 232 | b = append(b, packUi32(op.MO_FL)...) 233 | 234 | return b 235 | } 236 | 237 | func (op *QueryResp) String() string { 238 | var b bytes.Buffer 239 | b.WriteString(op.Header.String()) 240 | 241 | fmt.Fprintln(&b, "--- Query ---") 242 | fmt.Fprintln(&b, "Time: ", op.Time) 243 | fmt.Fprintln(&b, "Query_Type: ", op.Query_Type) 244 | fmt.Fprintln(&b, "Query_Code: ", op.Query_Code) 245 | 246 | fmt.Fprintln(&b, "从SP接收信息总数: ", op.MT_TLMsg) 247 | fmt.Fprintln(&b, "从SP接收用户总数: ", op.MT_Tlusr) 248 | 249 | fmt.Fprintln(&b, "成功转发数量: ", op.MT_Scs) 250 | fmt.Fprintln(&b, "待转发数量: ", op.MT_WT) 251 | fmt.Fprintln(&b, "转发失败数量: ", op.MT_FL) 252 | fmt.Fprintln(&b, "向SP成功送达数量: ", op.MO_Scs) 253 | fmt.Fprintln(&b, "向SP待送达数量: ", op.MO_WT) 254 | fmt.Fprintln(&b, "向SP送达失败数量: ", op.MO_FL) 255 | 256 | return b.String() 257 | } 258 | 259 | func (op *QueryResp) Ok() error { 260 | return nil 261 | } 262 | -------------------------------------------------------------------------------- /cmpp/protocol/op_query_test.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestQuery(t *testing.T) { 8 | op, err := NewQuery( 9 | 1, "20180612", 0, "", 10 | ) 11 | if err != nil { 12 | t.Error(err) 13 | } 14 | 15 | parsed, err := ParseOperation(op.Serialize()) 16 | if err != nil { 17 | t.Fatal(err) 18 | } 19 | 20 | query := parsed.(*Query) 21 | 22 | if query.Time.String() != op.Time.String() || 23 | query.Query_Type != op.Query_Type || 24 | query.Query_Code.String() != op.Query_Code.String() { 25 | t.Error("not equal") 26 | } 27 | } 28 | 29 | func TestQueryResp(t *testing.T) { 30 | op, err := NewQueryResp( 31 | 1, "20180612", 0, "", 32 | 499, 499, 9, 9, 9, 9, 9, 9, 33 | ) 34 | if err != nil { 35 | t.Error(err) 36 | } 37 | 38 | parsed, err := ParseOperation(op.Serialize()) 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | 43 | resp := parsed.(*QueryResp) 44 | 45 | if resp.MT_TLMsg != op.MT_TLMsg || 46 | resp.MT_Tlusr != op.MT_Tlusr || 47 | 48 | resp.MT_Scs != op.MT_Scs || 49 | resp.MT_WT != op.MT_WT || 50 | resp.MT_FL != op.MT_FL || 51 | resp.MO_Scs != op.MO_Scs || 52 | resp.MO_WT != op.MO_WT || 53 | resp.MO_FL != op.MO_FL { 54 | t.Error("not equal") 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /cmpp/protocol/op_submit.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "strconv" 8 | ) 9 | 10 | // SP向ISMG提交短信(CMPP_SUBMIT)操作 11 | // 12 | // CMPP_SUBMIT操作的目的是SP在与ISMG建立 13 | // 应用层连接后向ISMG提交短信。 14 | // ISMG以CMPP_SUBMIT_RESP消息响应 15 | type Submit struct { 16 | *Header 17 | 18 | // Body 19 | // 信息标识,由SP侧短信网关本身产生,本处填空。 20 | MsgId uint64 21 | // 相同Msg_Id的信息总条数,从1开始 22 | PkTotal uint8 23 | // 相同Msg_Id的信息序号,从1开始 24 | PkNumber uint8 25 | // 是否要求返回状态确认报告. 0:不需要 1:需要 26 | RegisteredDelivery uint8 27 | // 信息级别 28 | MsgLevel uint8 29 | // 业务类型,是数字、字母和符号的组合 30 | ServiceId *OctetString 31 | // 计费用户类型字段 32 | // 0:对目的终端MSISDN计费; 33 | // 1:对源终端MSISDN计费; 34 | // 2:对SP计费; 35 | // 3:表示本字段无效,对谁计费参见Fee_terminal_Id字段 36 | FeeUserType uint8 37 | // 被计费用户的号码(如本字节填空,则表示本字段无效, 38 | // 对谁计费参见Fee_UserType字段,本字段与Fee_UserType字段互斥) 39 | FeeTerminalId *OctetString 40 | TP_pid uint8 41 | TP_udhi uint8 42 | MsgFmt uint8 43 | // 信息内容来源(SP_Id) 44 | MsgSrc *OctetString 45 | // 资费类别 46 | // 01:对“计费用户号码”免费 47 | // 02:对“计费用户号码”按条计信息费 48 | // 03:对“计费用户号码”按包月收取信息费 49 | // 04:对“计费用户号码”的信息费封顶 50 | // 05:对“计费用户号码”的收费是由SP实现 51 | FeeType *OctetString 52 | // 资费代码(以分为单位) 53 | FeeCode *OctetString 54 | // 存活有效期,格式遵循SMPP3.3协议 55 | ValidTime *OctetString 56 | // 定时发送时间,格式遵循SMPP3.3协议 57 | AtTime *OctetString 58 | // 源号码 59 | SrcId *OctetString 60 | // 接收信息的用户数量(小于100个用户) 61 | DestUsrTl uint8 62 | // 接收短信的MSISDN号码 63 | DestTerminalId []*OctetString 64 | MsgLength uint8 65 | // 信息长度(Msg_Fmt值为0时:<160个字节;其它<=140个字节) 66 | MsgContent []byte 67 | Reserve *OctetString 68 | } 69 | 70 | func NewSubmit( 71 | sequenceID uint32, 72 | pkTotal, pkNumber, needReport, msgLevel uint8, 73 | serviceId string, feeUserType uint8, feeTerminalId string, 74 | TP_pid, TP_udhi, msgFmt uint8, 75 | msgSrc, feeType, feeCode, validTime, atTime, srcId string, 76 | destTermId []string, content []byte, 77 | ) (*Submit, error) { 78 | 79 | op := &Submit{} 80 | 81 | op.Header = &Header{} 82 | var length uint32 = 4 + 4 + 4 // header length 83 | 84 | op.MsgId = 0 // 本处填空 85 | length = length + 8 86 | 87 | op.PkTotal = pkTotal 88 | length = length + 1 89 | op.PkNumber = pkNumber 90 | length = length + 1 91 | op.RegisteredDelivery = needReport 92 | length = length + 1 93 | op.MsgLevel = msgLevel 94 | length = length + 1 95 | 96 | op.ServiceId = &OctetString{Data: []byte(serviceId), FixedLen: 10} 97 | length = length + 10 98 | 99 | op.FeeUserType = feeUserType 100 | length = length + 1 101 | 102 | op.FeeTerminalId = &OctetString{Data: []byte(feeTerminalId), FixedLen: 21} 103 | length = length + 21 104 | 105 | op.TP_pid = TP_pid 106 | length = length + 1 107 | op.TP_udhi = TP_udhi 108 | length = length + 1 109 | op.MsgFmt = msgFmt 110 | length = length + 1 111 | 112 | op.MsgSrc = &OctetString{Data: []byte(msgSrc), FixedLen: 6} 113 | length = length + 6 114 | 115 | op.FeeType = &OctetString{Data: []byte(feeType), FixedLen: 2} 116 | length = length + 2 117 | op.FeeCode = &OctetString{Data: []byte(feeCode), FixedLen: 6} 118 | length = length + 6 119 | op.ValidTime = &OctetString{Data: []byte(validTime), FixedLen: 17} 120 | length = length + 17 121 | op.AtTime = &OctetString{Data: []byte(atTime), FixedLen: 17} 122 | length = length + 17 123 | 124 | op.SrcId = &OctetString{Data: []byte(srcId), FixedLen: 21} 125 | length = length + 21 126 | 127 | // 短消息接收号码总数 128 | count := len(destTermId) 129 | if count > 100 { 130 | return nil, errors.New("too many destTermId") 131 | } 132 | op.DestUsrTl = uint8(count) 133 | length = length + 1 134 | 135 | for _, v := range destTermId { 136 | op.DestTerminalId = append( 137 | op.DestTerminalId, &OctetString{Data: []byte(v), FixedLen: 21}) 138 | length = length + 21 139 | } 140 | 141 | msgLen := len(content) 142 | op.MsgLength = uint8(msgLen) 143 | length = length + 1 144 | 145 | op.MsgContent = content 146 | length = length + uint32(msgLen) 147 | 148 | op.Reserve = &OctetString{FixedLen: 8} 149 | length = length + 8 150 | 151 | op.Total_Length = length 152 | op.Command_Id = CMPP_SUBMIT 153 | op.Sequence_Id = sequenceID 154 | 155 | return op, nil 156 | } 157 | 158 | func ParseSubmit(hdr *Header, data []byte) (*Submit, error) { 159 | p := 0 160 | op := &Submit{} 161 | op.Header = hdr 162 | 163 | op.MsgId = unpackUi64(data[p : p+8]) 164 | p = p + 8 165 | 166 | op.PkTotal = data[p] 167 | p = p + 1 168 | op.PkNumber = data[p] 169 | p = p + 1 170 | op.RegisteredDelivery = data[p] 171 | p = p + 1 172 | op.MsgLevel = data[p] 173 | p = p + 1 174 | 175 | op.ServiceId = &OctetString{Data: data[p : p+10], FixedLen: 10} 176 | p = p + 10 177 | 178 | op.FeeUserType = data[p] 179 | p = p + 1 180 | 181 | op.FeeTerminalId = &OctetString{Data: data[p : p+21], FixedLen: 21} 182 | p = p + 21 183 | 184 | op.TP_pid = data[p] 185 | p = p + 1 186 | op.TP_udhi = data[p] 187 | p = p + 1 188 | op.MsgFmt = data[p] 189 | p = p + 1 190 | 191 | op.MsgSrc = &OctetString{Data: data[p : p+6], FixedLen: 6} 192 | p = p + 6 193 | op.FeeType = &OctetString{Data: data[p : p+2], FixedLen: 2} 194 | p = p + 2 195 | op.FeeCode = &OctetString{Data: data[p : p+6], FixedLen: 6} 196 | p = p + 6 197 | op.ValidTime = &OctetString{Data: data[p : p+17], FixedLen: 17} 198 | p = p + 17 199 | op.AtTime = &OctetString{Data: data[p : p+17], FixedLen: 17} 200 | p = p + 17 201 | op.SrcId = &OctetString{Data: data[p : p+21], FixedLen: 21} 202 | p = p + 21 203 | 204 | op.DestUsrTl = data[p] 205 | p = p + 1 206 | 207 | for i := 0; i < int(op.DestUsrTl); i++ { 208 | op.DestTerminalId = append( 209 | op.DestTerminalId, &OctetString{Data: data[p : p+21], FixedLen: 21}) 210 | p = p + 21 211 | } 212 | 213 | op.MsgLength = data[p] 214 | p = p + 1 215 | 216 | op.MsgContent = data[p : p+int(op.MsgLength)] 217 | p = p + int(op.MsgLength) 218 | 219 | op.Reserve = &OctetString{Data: data[p : p+8], FixedLen: 8} 220 | p = p + 8 221 | 222 | return op, nil 223 | } 224 | 225 | func (op *Submit) Serialize() []byte { 226 | b := op.Header.Serialize() 227 | 228 | b = append(b, packUi64(op.MsgId)...) 229 | 230 | b = append(b, packUi8(op.PkTotal)...) 231 | b = append(b, packUi8(op.PkNumber)...) 232 | b = append(b, packUi8(op.RegisteredDelivery)...) 233 | b = append(b, packUi8(op.MsgLevel)...) 234 | 235 | b = append(b, op.ServiceId.Byte()...) 236 | b = append(b, packUi8(op.FeeUserType)...) 237 | b = append(b, op.FeeTerminalId.Byte()...) 238 | 239 | b = append(b, packUi8(op.TP_pid)...) 240 | b = append(b, packUi8(op.TP_udhi)...) 241 | b = append(b, packUi8(op.MsgFmt)...) 242 | 243 | b = append(b, op.MsgSrc.Byte()...) 244 | b = append(b, op.FeeType.Byte()...) 245 | b = append(b, op.FeeCode.Byte()...) 246 | b = append(b, op.ValidTime.Byte()...) 247 | b = append(b, op.AtTime.Byte()...) 248 | b = append(b, op.SrcId.Byte()...) 249 | 250 | b = append(b, packUi8(op.DestUsrTl)...) 251 | 252 | for i := 0; i < int(op.DestUsrTl); i++ { 253 | b = append(b, op.DestTerminalId[i].Byte()...) 254 | } 255 | 256 | b = append(b, packUi8(op.MsgLength)...) 257 | 258 | b = append(b, op.MsgContent...) 259 | b = append(b, op.Reserve.Byte()...) 260 | 261 | return b 262 | } 263 | 264 | func (op *Submit) String() string { 265 | var b bytes.Buffer 266 | b.WriteString(op.Header.String()) 267 | 268 | fmt.Fprintln(&b, "--- Submit ---") 269 | fmt.Fprintln(&b, "MsgId: ", op.MsgId) 270 | fmt.Fprintln(&b, "PkTotal: ", op.PkTotal) 271 | fmt.Fprintln(&b, "PkNumber: ", op.PkNumber) 272 | fmt.Fprintln(&b, "RegisteredDelivery: ", op.RegisteredDelivery) 273 | fmt.Fprintln(&b, "MsgLevel: ", op.MsgLevel) 274 | 275 | fmt.Fprintln(&b, "ServiceID: ", op.ServiceId) 276 | fmt.Fprintln(&b, "FeeUserType: ", op.FeeUserType) 277 | fmt.Fprintln(&b, "FeeTerminalId: ", op.FeeTerminalId) 278 | 279 | fmt.Fprintln(&b, "TP_pid: ", op.TP_pid) 280 | fmt.Fprintln(&b, "TP_udhi: ", op.TP_udhi) 281 | fmt.Fprintln(&b, "MsgFmt: ", op.MsgFmt) 282 | 283 | fmt.Fprintln(&b, "MsgSrc: ", op.MsgSrc) 284 | fmt.Fprintln(&b, "FeeType: ", op.FeeType) 285 | fmt.Fprintln(&b, "FeeCode: ", op.FeeCode) 286 | fmt.Fprintln(&b, "ValidTime: ", op.ValidTime) 287 | fmt.Fprintln(&b, "AtTime: ", op.AtTime) 288 | fmt.Fprintln(&b, "SrcId: ", op.SrcId) 289 | 290 | fmt.Fprintln(&b, "DestUsrTl: ", op.DestUsrTl) 291 | for i := 0; i < int(op.DestUsrTl); i++ { 292 | fmt.Fprintln(&b, "DestTerminalId: ", op.DestTerminalId[i]) 293 | } 294 | 295 | fmt.Fprintln(&b, "MsgLength: ", op.MsgLength) 296 | fmt.Fprintln(&b, "MsgContent: ", string(op.MsgContent)) 297 | 298 | return b.String() 299 | } 300 | 301 | func (op *Submit) Ok() error { 302 | return nil 303 | } 304 | 305 | type SubmitResp struct { 306 | *Header 307 | 308 | // Body 309 | MsgId uint64 // 信息标识 310 | Result uint8 // 结果 311 | } 312 | 313 | func NewSubmitResp( 314 | sequenceID uint32, msgId uint64, result uint8) (*SubmitResp, error) { 315 | 316 | op := &SubmitResp{} 317 | 318 | op.Header = &Header{} 319 | var length uint32 = 4 + 4 + 4 320 | 321 | op.MsgId = msgId 322 | length = length + 8 323 | 324 | op.Result = result 325 | length = length + 1 326 | 327 | op.Total_Length = length 328 | op.Command_Id = CMPP_SUBMIT_RESP 329 | op.Sequence_Id = sequenceID 330 | 331 | return op, nil 332 | } 333 | 334 | func ParseSubmitResp(hdr *Header, data []byte) (*SubmitResp, error) { 335 | op := &SubmitResp{} 336 | op.Header = hdr 337 | 338 | p := 0 339 | op.MsgId = unpackUi64(data[p : p+8]) 340 | p = p + 8 341 | 342 | op.Result = data[p] 343 | p = p + 1 344 | 345 | return op, nil 346 | } 347 | 348 | func (op *SubmitResp) Serialize() []byte { 349 | b := op.Header.Serialize() 350 | 351 | b = append(b, packUi64(op.MsgId)...) 352 | b = append(b, packUi8(op.Result)...) 353 | 354 | return b 355 | } 356 | 357 | func (op *SubmitResp) String() string { 358 | var b bytes.Buffer 359 | b.WriteString(op.Header.String()) 360 | 361 | fmt.Fprintln(&b, "--- SubmitResp ---") 362 | fmt.Fprintln(&b, "MsgID: ", op.MsgId) 363 | fmt.Fprintln(&b, "Result: ", op.Result) 364 | 365 | return b.String() 366 | } 367 | 368 | func (op *SubmitResp) Ok() (err error) { 369 | 370 | switch op.Result { 371 | case 0: // 正确 372 | err = nil 373 | case 1: 374 | err = errors.New("消息结构错") 375 | case 2: 376 | err = errors.New("命令字错") 377 | case 3: 378 | err = errors.New("消息序号重复") 379 | case 4: 380 | err = errors.New("消息长度错") 381 | case 5: 382 | err = errors.New("资费代码错") 383 | case 6: 384 | err = errors.New("超过最大信息长") 385 | case 7: 386 | err = errors.New("业务代码错") 387 | case 8: 388 | err = errors.New("流量控制错") 389 | default: 390 | err = errors.New("其他错误: " + strconv.Itoa(int(op.Result))) 391 | } 392 | 393 | return err 394 | } 395 | -------------------------------------------------------------------------------- /cmpp/protocol/op_submit_test.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestSubmit(t *testing.T) { 8 | op, err := NewSubmit( 9 | 1, 10 | 1, 1, 1, 1, 11 | "", 2, "", 12 | 0, 0, 0, 13 | "", "01", "", "", "", 14 | "1069000000", []string{"17600000000", "16600000000"}, 15 | []byte("hello haha, test msg"), 16 | ) 17 | if err != nil { 18 | t.Error(err) 19 | } 20 | 21 | parsed, err := ParseOperation(op.Serialize()) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | 26 | submit := parsed.(*Submit) 27 | 28 | if submit.MsgId != op.MsgId || 29 | submit.PkTotal != op.PkTotal || 30 | submit.PkNumber != op.PkNumber || 31 | submit.MsgFmt != op.MsgFmt || 32 | submit.TP_pid != op.TP_pid || 33 | submit.TP_udhi != op.TP_udhi || 34 | submit.SrcId.String() != op.SrcId.String() || 35 | submit.ServiceId.String() != op.ServiceId.String() || 36 | submit.DestUsrTl != op.DestUsrTl || 37 | submit.MsgLength != op.MsgLength { 38 | 39 | t.Error("not equal") 40 | } 41 | } 42 | 43 | func TestSubmitResp(t *testing.T) { 44 | op, err := NewSubmitResp(1, 12345, 0) 45 | if err != nil { 46 | t.Error(err) 47 | } 48 | 49 | parsed, err := ParseOperation(op.Serialize()) 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | 54 | resp := parsed.(*SubmitResp) 55 | 56 | if resp.MsgId != op.MsgId || 57 | resp.Result != op.Result { 58 | t.Error("parsedLoginResp not equal") 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /cmpp/protocol/op_terminate.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | // SP或ISMG请求拆除连接(CMPP_TERMINATE)操作 4 | type Terminate struct { 5 | *Header 6 | } 7 | 8 | func NewTerminate(sequenceID uint32) (*Terminate, error) { 9 | op := &Terminate{} 10 | 11 | op.Header = &Header{} 12 | 13 | op.Total_Length = 4 + 4 + 4 14 | op.Command_Id = CMPP_TERMINATE 15 | op.Sequence_Id = sequenceID 16 | 17 | return op, nil 18 | } 19 | 20 | func ParseTermanite(hdr *Header, data []byte) (*Terminate, error) { 21 | op := &Terminate{} 22 | op.Header = hdr 23 | 24 | return op, nil 25 | } 26 | 27 | func (op *Terminate) Serialize() []byte { 28 | 29 | return op.Header.Serialize() 30 | } 31 | 32 | func (op *Terminate) String() string { 33 | 34 | return op.Header.String() 35 | } 36 | 37 | func (op *Terminate) Ok() error { 38 | return nil 39 | } 40 | 41 | type TerminateResp struct { 42 | *Header 43 | } 44 | 45 | func NewTerminateResp(sequenceID uint32) (*TerminateResp, error) { 46 | op := &TerminateResp{} 47 | 48 | op.Header = &Header{} 49 | 50 | op.Total_Length = 4 + 4 + 4 51 | op.Command_Id = CMPP_TERMINATE_RESP 52 | op.Sequence_Id = sequenceID 53 | 54 | return op, nil 55 | } 56 | 57 | func ParseTermaniteResp(hdr *Header, data []byte) (*TerminateResp, error) { 58 | op := &TerminateResp{} 59 | op.Header = hdr 60 | 61 | return op, nil 62 | } 63 | 64 | func (op *TerminateResp) Serialize() []byte { 65 | 66 | return op.Header.Serialize() 67 | } 68 | 69 | func (op *TerminateResp) String() string { 70 | 71 | return op.Header.String() 72 | } 73 | 74 | func (op *TerminateResp) Ok() error { 75 | return nil 76 | } 77 | -------------------------------------------------------------------------------- /cmpp/protocol/op_terminate_test.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestTerminate(t *testing.T) { 8 | op, err := NewTerminate(1) 9 | if err != nil { 10 | t.Error(err) 11 | } 12 | 13 | _, err = ParseOperation(op.Serialize()) 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | 18 | } 19 | 20 | func TestTerminateResp(t *testing.T) { 21 | op, err := NewTerminateResp(1) 22 | if err != nil { 23 | t.Error(err) 24 | } 25 | 26 | _, err = ParseOperation(op.Serialize()) 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /cmpp/protocol/operation.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | type Operation interface { 9 | //Header returns the Operation header, decoded. Header fields 10 | // can be updated before reserialzing . 11 | GetHeader() *Header 12 | 13 | // SerializeTo encodes Operation to it's binary form, 14 | // include the header and body 15 | Serialize() []byte 16 | 17 | // String 18 | String() string 19 | 20 | // if status/result not ok, return error 21 | Ok() error 22 | } 23 | 24 | func ParseOperation(data []byte) (Operation, error) { 25 | if len(data) < 12 { 26 | return nil, errors.New("Invalide data length") 27 | } 28 | 29 | header, err := ParseHeader(data) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | if int(header.Total_Length) != len(data) { 35 | return nil, errors.New("Invalide data length") 36 | } 37 | 38 | var op Operation 39 | 40 | switch header.Command_Id { 41 | case CMPP_CONNECT: 42 | op, err = ParseConnect(header, data[12:]) 43 | case CMPP_CONNECT_RESP: 44 | op, err = ParseConnectResp(header, data[12:]) 45 | 46 | case CMPP_SUBMIT: 47 | op, err = ParseSubmit(header, data[12:]) 48 | case CMPP_SUBMIT_RESP: 49 | op, err = ParseSubmitResp(header, data[12:]) 50 | 51 | case CMPP_DELIVER: 52 | op, err = ParseDeliver(header, data[12:]) 53 | case CMPP_DELIVER_RESP: 54 | op, err = ParseDeliverResp(header, data[12:]) 55 | 56 | case CMPP_ACTIVE_TEST: 57 | op, err = ParseActiveTest(header, data[12:]) 58 | case CMPP_ACTIVE_TEST_RESP: 59 | op, err = ParseActiveTestResp(header, data[12:]) 60 | 61 | case CMPP_CANCEL: 62 | op, err = ParseCancel(header, data[12:]) 63 | case CMPP_CANCEL_RESP: 64 | op, err = ParseCancelResp(header, data[12:]) 65 | 66 | case CMPP_TERMINATE: 67 | op, err = ParseTermanite(header, data[12:]) 68 | case CMPP_TERMINATE_RESP: 69 | op, err = ParseTermaniteResp(header, data[12:]) 70 | 71 | case CMPP_QUERY: 72 | op, err = ParseQuery(header, data[12:]) 73 | case CMPP_QUERY_RESP: 74 | op, err = ParseQueryResp(header, data[12:]) 75 | 76 | default: 77 | err = fmt.Errorf("Unknow Operation CmdId: 0x%x", header.Command_Id) 78 | } 79 | 80 | return op, err 81 | } 82 | -------------------------------------------------------------------------------- /cmpp/protocol/report.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | // 状态报告 8 | type Report struct { 9 | // SP提交短信(CMPP_SUBMIT)操作时, 10 | // 与SP相连的ISMG产生的Msg_Id 11 | MsgId uint64 12 | Stat *OctetString // 发送短信的应答结果 13 | SubmitTime *OctetString 14 | DoneTime *OctetString 15 | DestTerminalId *OctetString 16 | SMSCSequence uint32 17 | } 18 | 19 | func ParseReport(rawData []byte) (*Report, error) { 20 | if len(rawData) != 60 { 21 | return nil, errors.New("ParseReport: len error") 22 | } 23 | 24 | p := 0 25 | rpt := &Report{} 26 | 27 | rpt.MsgId = unpackUi64(rawData[p : p+8]) 28 | p = p + 8 29 | rpt.Stat = &OctetString{Data: rawData[p : p+7]} 30 | p = p + 7 31 | rpt.SubmitTime = &OctetString{Data: rawData[p : p+10]} 32 | p = p + 10 33 | rpt.DoneTime = &OctetString{Data: rawData[p : p+10]} 34 | p = p + 10 35 | rpt.DestTerminalId = &OctetString{Data: rawData[p : p+21]} 36 | p = p + 21 37 | rpt.SMSCSequence = unpackUi32(rawData[p : p+4]) 38 | p = p + 4 39 | 40 | return rpt, nil 41 | } 42 | 43 | func NewReport( 44 | msgId uint64, 45 | stat, submitTime, doneTime, destTerminalId string, 46 | smscSequence uint32, 47 | ) (*Report, error) { 48 | 49 | rpt := &Report{} 50 | rpt.MsgId = msgId 51 | rpt.Stat = &OctetString{Data: []byte(stat), FixedLen: 7} 52 | rpt.SubmitTime = &OctetString{Data: []byte(submitTime), FixedLen: 10} 53 | rpt.DoneTime = &OctetString{Data: []byte(doneTime), FixedLen: 10} 54 | rpt.DestTerminalId = &OctetString{Data: []byte(destTerminalId), FixedLen: 21} 55 | rpt.SMSCSequence = smscSequence 56 | 57 | return rpt, nil 58 | } 59 | 60 | func (r *Report) Serialize() []byte { 61 | 62 | b := packUi64(r.MsgId) 63 | b = append(b, r.Stat.Byte()...) 64 | b = append(b, r.SubmitTime.Byte()...) 65 | b = append(b, r.DoneTime.Byte()...) 66 | b = append(b, r.DestTerminalId.Byte()...) 67 | b = append(b, packUi32(r.SMSCSequence)...) 68 | 69 | return b 70 | } 71 | -------------------------------------------------------------------------------- /cmpp/protocol/report_test.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestParseReport(t *testing.T) { 8 | rawData := []byte{102, 66, 144, 128, 9, 217, 89, 254, 77, 75, 58, 48, 48, 49, 50, 49, 56, 48, 54, 49, 50, 49, 54, 52, 48, 49, 56, 48, 54, 49, 50, 49, 54, 52, 48, 56, 54, 49, 52, 55, 49, 52, 55, 54, 50, 56, 57, 52, 0, 0, 0, 0, 0, 0, 0, 0, 63, 138, 46, 192} 9 | 10 | // test parse raw 11 | _, err := ParseReport(rawData) 12 | if err != nil { 13 | t.Error(err) 14 | } 15 | 16 | // test parse new 17 | rpt, err := NewReport(1234, "DELIVRD", "", "", "17600000000", 0) 18 | if err != nil { 19 | t.Error(err) 20 | } 21 | 22 | parsed, err := ParseReport(rpt.Serialize()) 23 | if err != nil { 24 | t.Error(err) 25 | } 26 | 27 | if rpt.MsgId != parsed.MsgId || 28 | rpt.Stat.String() != parsed.Stat.String() || 29 | rpt.DestTerminalId.String() != parsed.DestTerminalId.String() { 30 | t.Error("parsed report not match") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /cmpp/protocol/types.go: -------------------------------------------------------------------------------- 1 | // 定义cmpp中的字段类型 2 | 3 | package protocol 4 | 5 | import ( 6 | "bytes" 7 | "encoding/binary" 8 | ) 9 | 10 | // 不强制以0x00结尾的定长字符串。 11 | // 当位数不足时,在不明确注明的 12 | // 情况下, 应左对齐,右补0x00 13 | type OctetString struct { 14 | Data []byte // 数据 未补零/已补零 15 | FixedLen int // 协议中该参数的固定长度 16 | } 17 | 18 | // 去除补零,转为字符串 19 | func (o *OctetString) String() string { 20 | 21 | end := bytes.IndexByte(o.Data, 0) 22 | if -1 == end { 23 | return string(o.Data) 24 | } 25 | 26 | return string(o.Data[:end]) 27 | } 28 | 29 | // 按需补零 30 | func (o *OctetString) Byte() []byte { 31 | if len(o.Data) < o.FixedLen { 32 | // fill 0x00 33 | tmp := make([]byte, o.FixedLen-len(o.Data)) 34 | o.Data = append(o.Data, tmp...) 35 | } 36 | 37 | return o.Data 38 | } 39 | 40 | func unpackUi64(b []byte) uint64 { 41 | return binary.BigEndian.Uint64(b) 42 | } 43 | 44 | func packUi64(n uint64) []byte { 45 | b := make([]byte, 8) 46 | binary.BigEndian.PutUint64(b, n) 47 | return b 48 | } 49 | 50 | func unpackUi32(b []byte) uint32 { 51 | return binary.BigEndian.Uint32(b) 52 | } 53 | 54 | func packUi32(n uint32) []byte { 55 | b := make([]byte, 4) 56 | binary.BigEndian.PutUint32(b, n) 57 | return b 58 | } 59 | 60 | func unpackUi16(b []byte) uint16 { 61 | return binary.BigEndian.Uint16(b) 62 | } 63 | 64 | func packUi16(n uint16) []byte { 65 | b := make([]byte, 2) 66 | binary.BigEndian.PutUint16(b, n) 67 | return b 68 | } 69 | 70 | func packUi8(n uint8) []byte { 71 | b := make([]byte, 2) 72 | binary.BigEndian.PutUint16(b, uint16(n)) 73 | return b[1:] 74 | } 75 | -------------------------------------------------------------------------------- /cmpp/protocol/types_test.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestOctetString(t *testing.T) { 8 | 9 | // serialize 10 | str_content := "1069009010053" 11 | o := &OctetString{ 12 | Data: []byte(str_content), 13 | FixedLen: 21, 14 | } 15 | 16 | if len(o.Byte()) != o.FixedLen { 17 | t.Error("len error") 18 | } 19 | 20 | if str_content != o.String() { 21 | t.Error("OctetString string error") 22 | } 23 | 24 | // decode 25 | bytes_content := []byte{49, 48, 54, 57, 48, 48, 57, 48, 49, 48, 48, 53, 51, 0, 0, 0, 0, 0, 0, 0, 0} 26 | 27 | o = &OctetString{ 28 | Data: bytes_content, 29 | FixedLen: 21, 30 | } 31 | 32 | if len(o.Byte()) != o.FixedLen { 33 | t.Error("len error") 34 | } 35 | 36 | if str_content != o.String() { 37 | t.Error("OctetString string error") 38 | } 39 | } 40 | 41 | func TestUi64(t *testing.T) { 42 | var x uint64 = 123456789 43 | 44 | if x != unpackUi64(packUi64(x)) { 45 | t.Error("uint64 pack unpack not equal") 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /cmpp/protocol/utils.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | import ( 4 | "bytes" 5 | "crypto/md5" 6 | "fmt" 7 | "time" 8 | ) 9 | 10 | func genTimestamp() uint32 { 11 | t := time.Now() 12 | return uint32(int(t.Month())*100000000 + t.Day()*1000000 + 13 | t.Hour()*10000 + t.Minute()*100 + t.Second()) 14 | } 15 | 16 | // AuthenticatorSource = 17 | // MD5(Source_Addr+9 字节的0 +shared secret+timestamp) 18 | // Shared secret 由中国移动与源地址实体事先商定, 19 | // timestamp格式为:MMDDHHMMSS,即月日时分秒,10位。 20 | func genAuthenticatorSource(sourceAddr, secret string, timestamp uint32) ([]byte, error) { 21 | buf := new(bytes.Buffer) 22 | 23 | buf.WriteString(sourceAddr) 24 | buf.Write([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0}) 25 | buf.WriteString(secret) 26 | buf.WriteString(fmt.Sprintf("%010d", timestamp)) 27 | 28 | h := md5.New() 29 | _, err := h.Write(buf.Bytes()) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | return h.Sum(nil), nil 35 | } 36 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/yedamao/go_cmpp 2 | 3 | go 1.12 4 | 5 | require github.com/yedamao/encoding v1.0.1 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/yedamao/encoding v1.0.1 h1:hZP3BleDxKVhHecj7NNZ7ztP6jxHJUTkbNtjwXeWES8= 2 | github.com/yedamao/encoding v1.0.1/go.mod h1:Xhs+ZpR5sceCUV4soK4K0BjTJxxaQpJ25T/6kDWcR28= 3 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 4 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 5 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 6 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 7 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 8 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 9 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 10 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 11 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 12 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 13 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 14 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 15 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 16 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 17 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 18 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 19 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 20 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 21 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 22 | golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= 23 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 24 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 25 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 26 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 27 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 28 | --------------------------------------------------------------------------------