Teach you how to implement Smgp protocol using Perl

This article is shared from the Huawei Cloud Community " Huawei Cloud SMS Service teaches you to use Perl to implement the Smgp protocol ", author: Zhang Jian.

Introduction & Protocol Overview

China Telecom Short Message Gateway Protocol (SMGP) is a communication protocol developed by China Netcom to implement SMS services. Its full name is Short Message Gateway Protocol. It is used between the Short Message Gateway (SMGW) and the Service Provider (SP). Communication between Short Message Gateway (SMGW) and Short Message Gateway (SMGW).

Perl is an old scripting language that is installed by default on many Linux systems. For example, in the base image of version 22.04 of Ubuntu, there is not even Python, but Perl is still installed. The popularity of Perl is evident. Perl's IO::Async module provides a concise asynchronous IO programming model.

The SMGP protocol works on a client/server model. The client (SMS application, such as mobile phone, application, etc.) first establishes a long TCP connection with the SMS Gateway (SMGW Short Message Gateway), and uses CNGP commands to interact with SMGW to send and receive SMS messages. In the CNGP protocol, the next command can be sent without waiting for a response synchronously. Implementers can implement two message transmission modes, synchronous and asynchronous, according to their own needs to meet performance requirements in different scenarios.

Timing diagram

Connection successful, send text message

 
 
 

Connection successful, SMS received from SMGW

 
 
 

Introduction to protocol frames

image.png

SMGP Header

Header contains the following fields, the size and length of which are all 4 bytes

  • Packet Length : The length of the entire PDU, including Header and Body.
  • Request ID : Used to identify the type of PDU (for example, Login, Submit, etc.).
  • Sequence Id : Sequence number, used to match requests and responses.

Use perl to establish connections in the SMGP protocol stack

├── Makefile.PL
├── examples
│   └── smgp_client_login_example.pl
└── lib
    └── Smgp
        ├── BoundAtomic.pm
        ├── Client.pm
        ├── Constant.pm
        └── Protocol.pm
Makefile.PL : used to generate Makefile

examples: stores sample code

  • smgp_client_login_example.pl: stores Smgp login example
lib/Smgp: Contains all Perl module files
  • BoundAtomic.pm: incremental tool class used to generate SequenceId
  • Client.pm: Smgp definition, responsible for communicating with Smgp services, such as establishing connections, sending text messages, etc.
  • Protocol.pm: stores PDU, codec, etc.

Implement sequence_id increment

sequence_id is a value from 1 to 0x7FFFFFFFF

package Smgp::BoundAtomic;

use strict;
use warnings FATAL => 'all';

sub new {
    my ($class, %args) = @_;
    my $self = {
        min     => $args{min},
        max     => $args{max},
        value   => $args{min},
    };
    bless $self, $class;
    return $self;
}

sub increment {
    my ($self) = @_;
    if ($self->{value} >= $self->{max}) {
        $self->{value} = $self->{min};
    } else {
        $self->{value}++;
    }
    return $self->{value};
}

sub get {
    my ($self) = @_;
    return $self->{value};
}

1;

Define SMGP PDU and encoding and decoding functions in Perl

package Smgp::Protocol;

use strict;
use warnings FATAL => 'all';

use Smgp::Constant;

sub new_login {
    my ($class, %args) = @_;
    my $self = {
        clientId            => $args{clientId},
        authenticatorClient => $args{authenticatorClient},
        loginMode           => $args{loginMode},
        timeStamp           => $args{timeStamp},
        version             => $args{version},
    };
    return bless $self, $class;
}

sub encode_login {
    my ($self) = @_;
    return pack("A8A16CNC", @{$self}{qw(clientId authenticatorClient loginMode timeStamp version)});
}

sub decode_login_resp {
    my ($class, $buffer) = @_;
    my ($status, $authenticatorServer, $version) = unpack("N4A16C", $buffer);
    return bless {
        status              => $status,
        authenticatorServer => $authenticatorServer,
        version             => $version,
    }, $class;
}

sub new_header {
    my ($class, %args) = @_;
    my $self = {
        total_length   => $args{total_length},
        request_id     => $args{request_id},
        command_status => $args{command_status},
        sequence_id    => $args{sequence_id},
    };
    return bless $self, $class;
}

sub encode_header {
    my ($self, $total_length) = @_;
    return pack("N3", $total_length, @{$self}{qw(request_id sequence_id)});
}

sub new_pdu {
    my ($class, %args) = @_;
    my $self = {
        header => $args{header},
        body   => $args{body},
    };
    return bless $self, $class;
}

sub encode_login_pdu {
    my ($self) = @_;
    my $encoded_body = $self->{body}->encode_login();
    return $self->{header}->encode_header(length($encoded_body) + 12) . $encoded_body;
}

sub decode_pdu {
    my ($class, $buffer) = @_;
    my ($request_id, $sequence_id) = unpack("N2", substr($buffer, 0, 8));
    my $body_buffer = substr($buffer, 8);

    my $header = $class->new_header(
        total_length   => 0,
        request_id     => $request_id,
        sequence_id    => $sequence_id,
    );

    my $body;
    if ($request_id == Smgp::Constant::LOGIN_RESP_ID) {
        $body = $class->decode_login_resp($body_buffer);
    } else {
        die "Unsupported request_id: $request_id";
    }

    return $class->new_pdu(
        header => $header,
        body   => $body,
    );
}

1;

constant.pm stores related requestId

package Smgp::Constant;

use strict;
use warnings FATAL => 'all';

use constant {
    LOGIN_ID               => 0x00000001,
    LOGIN_RESP_ID          => 0x80000001,
    SUBMIT_ID              => 0x00000002,
    SUBMIT_RESP_ID         => 0x80000002,
    DELIVER_ID             => 0x00000003,
    DELIVER_RESP_ID        => 0x80000003,
    ACTIVE_TEST_ID         => 0x00000004,
    ACTIVE_TEST_RESP_ID    => 0x80000004,
    FORWARD_ID             => 0x00000005,
    FORWARD_RESP_ID        => 0x80000005,
    EXIT_ID                => 0x00000006,
    EXIT_RESP_ID           => 0x80000006,
    QUERY_ID               => 0x00000007,
    QUERY_RESP_ID          => 0x80000007,
    MT_ROUTE_UPDATE_ID     => 0x00000008,
    MT_ROUTE_UPDATE_RESP_ID => 0x80000008,
};

1;

Implement client and login methods

package Smgp::Client;
use strict;
use warnings FATAL => 'all';
use IO::Socket::INET;

use Smgp::Protocol;
use Smgp::Constant;

sub new {
    my ($class, %args) = @_;
    my $self = {
        host => $args{host} // 'localhost',
        port => $args{port} // 9000,
        socket => undef,
        sequence_id => 1,
    };
    bless $self, $class;
    return $self;
}

sub connect {
    my ($self) = @_;
    $self->{socket} = IO::Socket::INET->new(
        PeerHost => $self->{host},
        PeerPort => $self->{port},
        Proto => 'tcp',
    ) or die "Cannot connect to $self->{host}:$self->{port} $!";
}

sub login {
    my ($self, $body) = @_;
    my $header = Smgp::Protocol->new_header(
        request_id     => Smgp::Constant::LOGIN_ID,
        sequence_id    => 1,
    );

    my $pdu = Smgp::Protocol->new_pdu(
        header => $header,
        body   => $body,
    );

    $self->{socket}->send($pdu->encode_login_pdu());

    $self->{socket}->recv(my $response_length_bytes, 4);

    my $total_length = unpack("N", $response_length_bytes);
    my $remain_length = $total_length - 4;
    $self->{socket}->recv(my $response_data, $remain_length);

    return Smgp::Protocol->decode_pdu($response_data)->{body};
}

sub disconnect {
    my ($self) = @_;
    close($self->{socket}) if $self->{socket};
}

1;

Run example to verify the connection is successful

package smgp_client_login_example;

use strict;
use warnings FATAL => 'all';

use Smgp::Client;
use Smgp::Protocol;
use Smgp::Constant;

sub main {
    my $client = Smgp::Client->new(
        host => 'localhost',
        port => 9000,
    );

    $client->connect();

    my $login = Smgp::Protocol->new_login(
        clientId            => '12345678',
        authenticatorClient => '1234567890123456',
        loginMode           => 1,
        timeStamp           => time(),
        version             => 0,
    );

    my $response = $client->login($login);

    if ($response->{status} == 0) {
        print "Login successful!\n";
    }
    else {
        print "Login failed! Status: ", (defined $response->{status} ? $response->{status} : 'undefined'), "\n";
    }

    $client->disconnect();
}

main() unless caller;

1;

image.png

Related open source projects

Summarize

This article briefly introduces the SMGP protocol and attempts to use perl to implement the protocol stack. However, actual commercial SMS sending is often more complicated and faces issues such as flow control, operator interoperability, and transport layer security. You can choose Huawei Cloud Message & SMS (Message & SMS) service is accessed through HTTP protocol . Huawei Cloud SMS service is a communication service provided by Huawei Cloud to enterprise users in cooperation with many high-quality operators and channels around the world. Enterprises can use verification code and notification SMS services by calling API or using group sending assistant.

Click to follow and learn about Huawei Cloud’s new technologies as soon as possible~

A programmer born in the 1990s developed a video porting software and made over 7 million in less than a year. The ending was very punishing! Google confirmed layoffs, involving the "35-year-old curse" of Chinese coders in the Flutter, Dart and Python teams . Daily | Microsoft is running against Chrome; a lucky toy for impotent middle-aged people; the mysterious AI capability is too strong and is suspected of GPT-4.5; Tongyi Qianwen open source 8 models Arc Browser for Windows 1.0 in 3 months officially GA Windows 10 market share reaches 70%, Windows 11 GitHub continues to decline. GitHub releases AI native development tool GitHub Copilot Workspace JAVA is the only strong type query that can handle OLTP+OLAP. This is the best ORM. We meet each other too late.
{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/4526289/blog/11090249