How to Send and Receive SMS: Implementing a GSM Protocol in Go
When developers add an SMS component in their app either for verification or notification purposes, they usually do it via RESTful API like the ones provided by Twilio. But what really happens behind the scenes?
In this post, you’ll learn what Universal Computer Protocol (UCP) is and how you can use it to directly communicate with a Short Message Service Centre (SMSC) to send and receive SMS using Go.
Terminology
Mobile Terminating Message
Messages from telco to subscriber. For example, weather update messages.
Mobile Originating Message
Messages from subscriber to telco. For example, texting a keyword to an accesscode for balance inquiry.
Multi-part MTs and MOs
SMS greater than 160 characters are considered multi-part SMS. To send a multi-part mobile terminating messages, we need to split it into message parts. Each message part will contain a message part number, total message parts number, and a reference number.
The multi-part mobile originating message also contains a message part number, total message parts number, and a reference number. We need to concatenate those message parts in order to interpret the mobile originating message that the user sent.
Universal Computer Protocol
Universal Computer Protocol or UCP is primarily used to connect to Short Message Service Centres (SMSC) to send and receive SMS.
session management operation
Allows us the send login credentials to the SMSC.
alert operation
Allows us to send pings to the SMSC.
submit short message operation
Allows us to send mobile terminating SMS.
delivery notification operation
Sent by the SMSC to the client as a delivery status receipt indicating if the SMS message was successfully sent or not.
delivery short message operation
Sent by the SMSC to the client on behalf of a subscriber’s mobile originating SMS message.
Implementation
We can treat UCP like a traditional client-server protocol. After establishing a TCP connection, we send UCP requests with a sequence number (called “transaction reference number” in the protocol specification) from 00 to 99 and the SMSC will respond with a UCP response message synchronously. However, the SMSC can also send UCP requests, just like in the case of “delivery notification operation” and “delivery short message operation”. We also need to periodically send keepalive pings to the SMSC so that it won’t treat the connection as stale and disconnect us.
Lets start with a Client
struct containing the login credentials to the SMSC. The login credentials are provided by the telco but for testing purposes, we can use an SMSC simulator.
|
|
Transaction Reference Number
To generate valid transaction reference numbers ranging from 00 to 99, we can use the ring package from the standard library.
|
|
|
|
Establishing TCP Connection
We can use the net package to establish a TCP connection with the SMSC, then create a buffered reader and writer using the bufio package.
After establishing the TCP connection, we can now send a session management operation
request to the SMSC. This request contains our credentials to the SMSC.
|
|
|
|
createLoginReq
creates a session management operation
request packet containing our credentials.
parseSessionResp
parses the session management operation
response packet from the SMSC. If our credentials are invalid, it will return an error
otherwise it will return nil
.
Channels and Goroutines
We can treat the different UCP operations as separate goroutines and channels.
|
|
Read UCP packets
To read packets from the UCP connection, we start the readLoop
goroutine. A valid UCP packet is delimited by an End-of-Text indicator (ETX), that is the byte 03
.
readLoop
will read up to etx
, parse the packet and send it to the appropriate channel.
|
|
Send Keepalive
To send periodic pings to the SMSC, we start the sendAlert
goroutine.
We use time.NewTicker to create a ticker that will fire periodically.
createAlertReq
creates a valid alert operation
request packet with the appropriate transaction reference number.
|
|
Read Delivery Notification
To read SMS delivery notification status, we start the readDeliveryNotif
goroutine.
Once a delivery notification operation
message is read, it sends an acknowledgement
response packet to the SMSC.
|
|
Read Delivery Short Message
To read incoming mobile originating messages, we start the readDeliveryMsg
goroutine.
|
|
deliverMsgPart
is a struct that contains the neccessary parts to concatenate and decode the partial incoming mobile originating message.
|
|
To handle multi-part mobile originating SMS, we send partial mobile originating messages to deliverMsgPartCh
channel and complete mobile originating messages to deliverMsgCompleteCh
channel.
|
|
The goroutine spawned in readPartialDeliveryMsg
will read from deliverMsgPartCh
channel and concatenate the incoming mobile originating message parts. The goroutine spawned in readCompleteDeliveryMsg
will receive from deliverMsgCompleteCh
channel and execute the callback for mobile originating messages.
Send SMS
To send an SMS, we call the Send
method.
|
|
getMessageType
determines whether the message contains plain GSM 7-bit characters or Unicode characters.
getMessageParts
splits the message into multiple parts in case it’s a multi-part message.
encodeMessage
takes care of creating a valid submit short message orperation
request packet with the appropriate reference number. It handles text encoding to UCS2 for unicode messages as well as masking the sender name.
To get the response from the SMSC, we use select
statement, that blocks until the data from the submitSmRespCh
channel can be read or a given timeout occurred.
Send
returns a list of message identifiers indicating that the SMSC received the submit short message operation
request. This response is synchronous. For example, if we send a multi-part SMS consisting of five message parts, Send
will return a list of five strings.
|
|
Each identifier has the form recipient:timestamp
. The timestamp
can be parsed with the layout 020106150405
using time.Parse. If you’re more familiar with strftime, you can use the format %d%m%y%H%M%S
.
Demo
I’ve written a CLI to demonstrate this library. We’ll use an SMSC simulator and view the UCP packets on Wireshark.
First, go get
the CLI and simulator and make sure that redis is running on localhost:6379
:
1 2 |
$ go get github.com/go-gsm/ucp-cli $ go get github.com/jcaberio/ucp-smsc-sim |
Export the following environment variables:
1 2 3 4 5 |
$ export SMSC_HOST=127.0.0.1 $ export SMSC_PORT=16004 $ export SMSC_USER=emi_client $ export SMSC_PASSWORD=password $ export SMSC_ACCESSCODE=2929 |
Run the simulator and visit localhost:16003
on your browser.
1
|
$ ucp-smsc-sim |
Run the CLI
1
|
$ ucp-cli |
Let send Hello, 世界
to 09191234567
with a sender mask of Gopher
.
The simulator responded with a message id of [09191234567:021218201629]
.
We can also see the delivery notification message from the simulator.
We can look at the UCP packets in detail via Wireshark.
We can view the SMS in the browser.
To simulate a mobile-originating message from a subscriber, we can send the following curl
request:
1
|
curl -H "Content-Type: application/json" -d '{"sender":"09191234567", "receiver":"2929", "message":"This is a mobile-originating message"}' http://localhost:16003/mo |
Here we simulate the subscriber with a mobile number of 09191234567
sending the message
This is a mobile-originating message
to accesscode 2929
.
We can see that the CLI received the mobile-originating message and verify using Wireshark.
Conclusion
Go’s built-in features such as goroutines and channels enabled us to implement the UCP protocol. We used Go’s message-passing style for concurrently processing different types of UCP messages. We treat the independent operations as goroutines and communicate with them via channels. We also relied heavily on the standard library to implement the protocol operations. If you work on the telco field and have an access to an SMSC, feel free to try the ucp package. It has additional features such as rate limiting and tariff charging. Suggestions and recommendations are welcome.
Thanks!