diff --git a/SCCP/doc/nms_sccp_architecture.pdf b/SCCP/doc/nms_sccp_architecture.pdf new file mode 100644 index 0000000..241159e Binary files /dev/null and b/SCCP/doc/nms_sccp_architecture.pdf differ diff --git a/SCCP/ebin/sccp.app b/SCCP/ebin/sccp.app new file mode 100644 index 0000000..cbbd7b8 --- /dev/null +++ b/SCCP/ebin/sccp.app @@ -0,0 +1,6 @@ +{application, sccp, + [{description, "Signaling Connection Control Part"}, + {vsn, "1.2"}, + {modules, [sccp]}, + {registered, []}, + {applications, [kernel, stdlib, nms]}]}. diff --git a/SCCP/itu/include/sccp.hrl b/SCCP/itu/include/sccp.hrl new file mode 100644 index 0000000..f83bded --- /dev/null +++ b/SCCP/itu/include/sccp.hrl @@ -0,0 +1,85 @@ +%%%--------------------------------------------------------------------- +%%% @copyright 2004, 2005 Motivity Telecom +%%% @author Vance Shipley [http://www.motivity.ca] +%%% @end +%%% +%%% Copyright (c) 2004, 2005, Motivity Telecom +%%% +%%% All rights reserved. +%%% +%%% Redistribution and use in source and binary forms, with or without +%%% modification, are permitted provided that the following conditions +%%% are met: +%%% +%%% - Redistributions of source code must retain the above copyright +%%% notice, this list of conditions and the following disclaimer. +%%% - Redistributions in binary form must reproduce the above copyright +%%% notice, this list of conditions and the following disclaimer in +%%% the documentation and/or other materials provided with the +%%% distribution. +%%% - Neither the name of Motivity Telecom nor the names of its +%%% contributors may be used to endorse or promote products derived +%%% from this software without specific prior written permission. +%%% +%%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +%%% "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +%%% LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +%%% A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +%%% OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +%%% SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +%%% LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +%%% DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +%%% THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +%%% (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +%%% OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +%%% +%%%--------------------------------------------------------------------- +%%% +%%% ITU-T recommendation Q.711 Functional Decsription of +%%% the Signalling Connection Control Part describes the +%%% primitives and their parameters used in the N-Service +%%% interface. Each primitive has a record defined here +%%% containing it's parameters. Modules using this service +%%% utilize these records to format messages: +%%% +%%% {'N', 'N-CONNECT', Connect} when is_record(Connect, 'N-CONNECT') +%%% + +%% reference: Table 2/Q.711 - Parameters of the primitive N-CONNECT +-record('N-CONNECT', {calledAddress, callingAddress, respondAddress, + expeditedData, qos, userData, connectionID, importance}). + +%% reference: Table 3/Q.711 - Parameters of the primitive N-DATA +-record('N-DATA', {userData, connectionID, importance}). + +%% reference: Table 4/Q.711 - Parameters of the primitive N-EXPEDITED-DATA +-record('N-EXPEDITED-DATA', {userData, connectionID}). + +%% reference: Table 5/Q.711 - Parameters of the primitive N-RESET +-record('N-RESET', {originator, reason, connectionID}). + +%% reference: Table 6/Q.711 - Parameters of the primitive N-DISCONNECT +-record('N-DISCONNECT', {originator, respondAddress, reason, userData, + connectionID, importance}). + +%% reference: Table 8/Q.711 - Parameters of the primitive N-INFORM +-record('N-INFORM', {reason, connectionID, qos}). + +%% reference: Table 12/Q.711 - Parameters of the primitive N-UNITDATA +-record('N-UNITDATA', {calledAddress, callingAddress, sequenceControl, + returnOption, importance, userData}). + +%% reference: Table 13/Q.711 - Parameters of the primitive N-NOTICE +-record('N-NOTICE', {calledAddress, callingAddress, reason, userData, + importance}). + +%% reference: Table 15/Q.711 - Parameters of the primitive N-COORD +-record('N-COORD', {affectedSubsystem, multiplicity}). + +%% reference: Table 16/Q.711 - Parameters of the primitive N-STATE +-record('N-STATE', {affectedSubsystem, userStatus, multiplicity}). + +%% reference: Table 17/Q.711 - Parameters of the primitive N-PCSTATE +-record('N-PCSTATE', {affectedSignallingPoint, signallingPointStatus, + remoteSCCPStatus, restrictedImportanceLevel}). + diff --git a/SCCP/src/ITU/README b/SCCP/src/ITU/README new file mode 100644 index 0000000..6ec99cc --- /dev/null +++ b/SCCP/src/ITU/README @@ -0,0 +1,15 @@ + +This directory contains the erlang source modules for an implementation +of the ITU-T version of the Signalling Connection Control Part (SCCP). +See ITU-T recommendations Q.711-Q.715 for the specification this +implementation is derived from. + +The basic structure of the SCCP appears in Figure 1/Q.714 - SCCP overview. +It is divided into four functional blocks (ref: Q.714 clause 1.4): + + SCCP connection-oriented control (SCOC) + SCCP connectionless control (SCLC) + SCCP management (SCMG) + SCCP routing control (SCRC) + + diff --git a/SCCP/src/NMS/Makefile b/SCCP/src/NMS/Makefile new file mode 100644 index 0000000..9a19a0c --- /dev/null +++ b/SCCP/src/NMS/Makefile @@ -0,0 +1,50 @@ + +EBIN = ../../ebin +DOC = ../../doc/html + +NMSINC = ../../../nms_erldrv/include + +INCLUDES = $(NMSINC) + +ERLC = erlc +ERL = erl +ERLCFLAGS = -W -v -o $(EBIN) -I $(INCLUDES) +DEBUGFLAGS = +debug_info + +$(EBIN)/%.beam:%.erl + ${ERLC} $(ERLCFLAGS) $< + +$(DOC)/%.html:%.erl + ${ERL} -noshell -run edoc_run file '"$<"' '[{dir, "$(DOC)"}]' -s init stop + +BEAMS = $(EBIN)/sccp.beam + +DOCS = $(DOC)/sccp.html + +$(EBIN)/sccp.app: $(EBIN)/sccp.app.src + sed -e "s;%VERSION%;${VERSION};" $(EBIN)/sccp.app.src > $(EBIN)/sccp.app + +all: $(EBIN)/sccp.app beams docs + +.PHONY: beams +beams: $(BEAMS) + +.PHONY: docs +docs: $(DOCS) + +.PHONY: install +install: all + install -d $(ERL_TOP)/lib/sccp-${VERSION}/ebin + install -f $(ERL_TOP)/lib/sccp-${VERSION}/ebin $(EBIN)/sccp.app + install -f $(ERL_TOP)/lib/sccp-${VERSION}/ebin $(BEAMS) + install -d $(ERL_TOP)/lib/sccp-${VERSION}/doc/html + install -f $(ERL_TOP)/lib/sccp-${VERSION}/doc/html $(DOCS) + +.PHONY: clean +clean: + rm -rf $(EBIN)/sccp.app + rm -rf $(BEAMS) + rm -rf $(DOCS) + +sccp.erl: $(NMSINC)/nms_sccp.hrl + diff --git a/SCCP/src/NMS/sccp.erl b/SCCP/src/NMS/sccp.erl new file mode 100644 index 0000000..39cda97 --- /dev/null +++ b/SCCP/src/NMS/sccp.erl @@ -0,0 +1,863 @@ +%%% $Id: sccp.erl,v 1.26 2007/08/15 11:02:50 vances Exp $ +%%%--------------------------------------------------------------------- +%%% @copyright 2001-2005 Motivity Telecom +%%% @author Vance Shipley [http://www.motivity.ca] +%%% @end +%%% +%%% Copyright (c) 2001-2005, Motivity Telecom +%%% +%%% All rights reserved. +%%% +%%% Redistribution and use in source and binary forms, with or without +%%% modification, are permitted provided that the following conditions +%%% are met: +%%% +%%% - Redistributions of source code must retain the above copyright +%%% notice, this list of conditions and the following disclaimer. +%%% - Redistributions in binary form must reproduce the above copyright +%%% notice, this list of conditions and the following disclaimer in +%%% the documentation and/or other materials provided with the +%%% distribution. +%%% - Neither the name of Motivity Telecom nor the names of its +%%% contributors may be used to endorse or promote products derived +%%% from this software without specific prior written permission. +%%% +%%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +%%% "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +%%% LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +%%% A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +%%% OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +%%% SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +%%% LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +%%% DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +%%% THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +%%% (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +%%% OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +%%% +%%%--------------------------------------------------------------------- +%%% @doc Signaling Connection Control Part (SCCP) application using NMS. +%%%

Implements SCCP service access points (SAP) on top of the +%%% nms application.

+%%% +%%% @see //nms +%%% @reference +%%% +%%% NMS SCCP Developer's Reference Manual +%%% @reference ITU-T Q.771-Q.774 +%%% @reference ANSI T1.112 + +-module(sccp). +-copyright('Copyright (c) 2001-2007 Motivity Telecom Inc.'). +-author('vances@motivity.ca'). +-vsn('$Revision: 1.26 $'). + +-include("nms_sccp.hrl"). + +-behaviour(gen_server). + +%% call backs needed for gen_server behaviour +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +%% our published API functions +-export([start_link/2, start_link/3, start_link/7, stop/1]). +-export([address/1, address_itu/1, address_ansi/1, bcd_digits/1, btoi/1]). + +-record(state, {user, board, entityid, serviceuserid, sap, ssn, + queue, context, object, port}). + +%% TODO: find a way to have autoconf define these +-define(SW_INT, 1). +-define(SW_ANSI, 2). +-define(ENC_UNKNOWN, 0). +-define(ENC_BCD_ODD, 1). +-define(ENC_BCD_EVEN, 2). + +%%---------------------------------------------------------------------- +%% The sccp exported API (not the service primitives) +%%---------------------------------------------------------------------- + +%% @spec (USAP::pid(), SSN::integer()) -> {ok, NSAP} | {error, Reason} +%% NSAP = pid() +%% Reason = term() +%% +%% @doc Starts an sccp server. +%%

USAP is the pid of the SCCP-User.

+%%

SSN is the SCCP subsystem number for this SAP.

+%%

NSAP is a pid which the SCCP-User will use as the +%% service access point for the SCCP-Service.

+%% +start_link(User, SSN) -> + start_link(User, 0, SSN). +%% +%% @spec (USAP::pid(), SpID::integer(), SSN::integer()) -> {ok, NSAP} | {error, Reason} +%% NSAP = pid() +%% Reason = term() +%% +%% @doc Starts an sccp server. +%%

USAP is the pid of the SCCP-User.

+%%

SpID NMS SCCP service access point ID on which to bind. +%% This is SCCP SAP number which is defined in the TX board SCCP +%% configuration.

+%%

SSN is the SCCP subsystem number for this SAP.

+%%

NSAP is a pid which the SCCP-User will use as the +%% service access point for the SCCP-Service.

+%% +start_link(User, SpID, SSN) -> + start_link(User, 1, 16#20, 0, SpID, SSN, 128). +%% +%% @spec (USAP::pid(), Board::integer(), EntityID::integer(), SuID::integer(), +%% SpID::integer(), SSN::integer(), PoolSize::integer()) -> {ok, NSAP} | {error, Reason} +%% NSAP = pid() +%% Reason = term() +%% +%% @doc Starts an sccp server. +%%

USAP is the pid of the SCCP-User.

+%%

Board is the number of the TX board for this SAP.

+%%

EntityID is the Entity ID which identifies this application +%% to the NMS TX board.

+%%

SuID NMS SCCP calling application service user ID. +%% This is User SAP number which is defined in the TX board SCCP +%% configuration.

+%%

SpID NMS SCCP service access point ID on which to bind. +%% This is SCCP SAP number which is defined in the TX board SCCP +%% configuration.

+%%

SSN is the SCCP subsystem number for this SAP.

+%%

NSAP is a pid which the SCCP-User will use as the +%% service access point for the SCCP-Service.

+%%

PoolSize is the number of messages allowed to +%% be queued to the TX board.

+%% +start_link(User, Board, EntityID, SuID, SpID, SSN, PoolSize) -> + Ourname = list_to_atom("sccp_ssn" ++ integer_to_list(SSN)), + gen_server:start_link({local, Ourname}, ?MODULE, + [User, Board, EntityID, SuID, SpID, SSN, PoolSize], []). + +%% @spec (NSAP) -> ok +%% NSAP = pid() +%% +%% @doc Stop an sccp server. +%%

Closes an SCCP service access point (SAP).

+%%

NSAP is a pid returned from a previous call to +%% start_link/2,3,7.

+%% +stop(NSAP) -> + gen_server:call(NSAP, stop). + +%% @type party(). SCCP called/calling party address. +%%

A binary() or an #'SccpAddr'{} record.

+ +%% @spec (SccpAddress::party()) -> SccpAddress +%% SccpAddress = party() +%% +%% @doc Encodes/decodes SCCP called/calling party addresses. +%%

Naively, but convienently, assumes national addresses +%% are in ANSI format and international addresses are in +%% ITU format when decoding.

+%% @end +%% +% International Indicator +address(<<0:1, _:7, _Rest/binary>> = OrigBin) -> + address_itu(OrigBin); +% National Indicator +address(<<1:1, _:7, _Rest/binary>> = OrigBin) -> + address_ansi(OrigBin); +% encode ANSI address records +address(CP) when is_record(CP, 'SccpAddr'), CP#'SccpAddr'.swtype == ?SW_ANSI -> + address_ansi(CP); +% encode ITU address records +address(CP) when is_record(CP, 'SccpAddr'), CP#'SccpAddr'.swtype == ?SW_INT -> + address_itu(CP). + +%% @spec (SccpAddress::party()) -> SccpAddress +%% SccpAddress = party() +%% +%% @doc Encodes/decodes an ITU-T variant SCCP called/calling party address. +%%

Operates according to ITU-T Q.713 3.4.

+%% @end +%% +%% [note: ANSI reverse the order of the PC & SSN] +%% +address_itu(<>) -> + #'SccpAddr'{presind = 1, swtype = ?SW_INT, natintind = NatIntInd, + routingind = RoutingInd, gltitleind = GlobalTitleInd, + subsystemind = SubsystemInd, pointcodeind = PointCodeInd}; +address_itu(<>) -> + case PointCodeInd of + 1 -> + % ITU-T point codes are 14 bits (Q.713 clause 3.4.2.1) + <> = Addresses, + PointCode = (MSB bsl 8) bor LSB, + AddressWithPC = #'SccpAddr'{presind = 1, swtype = ?SW_INT, + natintind = NatIntInd, pointcodeind = 1, pointcode = PointCode}; + 0 -> + RestAddresses = Addresses, + AddressWithPC = #'SccpAddr'{presind = 1, swtype = ?SW_INT, + natintind = NatIntInd, pointcodeind = 0} + end, + case SubsystemInd of + 1 -> + <> = RestAddresses, + AddressWithSSN = AddressWithPC#'SccpAddr'{subsystemind = 1, + subsystem = SubSystemNumber}; + 0 -> + MoreAddresses = RestAddresses, + AddressWithSSN = AddressWithPC#'SccpAddr'{subsystemind = 0} + end, + case GlobalTitleInd of + % no global title included + 2#0000 -> + AddressWithGT = AddressWithSSN#'SccpAddr'{ + gltitleind = GlobalTitleInd}; + + % global title includes nature of address indicator only + 2#0001 -> + <> = MoreAddresses, + case OddEven of + 0 -> Encoding = ?ENC_BCD_EVEN; + 1 -> Encoding = ?ENC_BCD_ODD + end, + GlobalTitleLen = size(GlobalTitle), + GlobalTitleRestSize = (?MAX_GLT_SZ - GlobalTitleLen), + AddressWithGT = AddressWithSSN#'SccpAddr'{ + gltitleind = GlobalTitleInd, encoding = Encoding, + nataddrind = NatureOfAddressInd, + gltitle = <>/binary>>, + gltitlelen = GlobalTitleLen}; + + % global title includes translation type only + 2#0010 -> + <> = MoreAddresses, + GlobalTitleLen = size(GlobalTitle), + GlobalTitleRestSize = (?MAX_GLT_SZ - GlobalTitleLen), + AddressWithGT = AddressWithSSN#'SccpAddr'{ + gltitleind = GlobalTitleInd, + gltranstype = TranslationType, + gltitle = <>/binary>>, + gltitlelen = GlobalTitleLen}; + + % global title includes translation type, numbering plan + % and encoding scheme + 2#0011 -> + <> = MoreAddresses, + GlobalTitleLen = size(GlobalTitle), + GlobalTitleRestSize = (?MAX_GLT_SZ - GlobalTitleLen), + AddressWithGT = AddressWithSSN#'SccpAddr'{ + gltitleind = GlobalTitleInd, + gltranstype = TranslationType, numplan = NumberingPlan, + encoding = EncodingScheme, + gltitle = <>/binary>>, + gltitlelen = GlobalTitleLen}; + + % global title includes translation type, numbering plan, + % encoding scheme and nature of address indicator + 2#0100 -> + <> = MoreAddresses, + GlobalTitleLen = size(GlobalTitle), + GlobalTitleRestSize = (?MAX_GLT_SZ - GlobalTitleLen), + AddressWithGT = AddressWithSSN#'SccpAddr'{ + gltitleind = GlobalTitleInd, gltranstype = TranslationType, + numplan = NumberingPlan, encoding = EncodingScheme, + nataddrind = NatureOfAddressInd, + gltitle = <>/binary>>, + gltitlelen = GlobalTitleLen}; + % GlobalTitleInd 2#0101 to 2#0111 spare international + % 2#1000 to 2#1110 spare national + % 2#1111 reserved for extension + _ -> + <> = MoreAddresses, + GlobalTitleLen = size(GlobalTitle), + GlobalTitleRestSize = (?MAX_GLT_SZ - GlobalTitleLen), + AddressWithGT = AddressWithSSN#'SccpAddr'{ + gltitleind = GlobalTitleInd, + gltitle = <>/binary>>, + gltitlelen = GlobalTitleLen} + end, + AddressWithGT#'SccpAddr'{routingind = RoutingInd}; +address_itu(CP) when is_record(CP, 'SccpAddr') -> + Indicators = <<(CP#'SccpAddr'.natintind):1, + (CP#'SccpAddr'.routingind):1, + (CP#'SccpAddr'.gltitleind):4, + (CP#'SccpAddr'.subsystemind):1, + (CP#'SccpAddr'.pointcodeind):1>>, + case CP#'SccpAddr'.pointcodeind of + 0 -> + BinAfterPC = Indicators; + 1 -> + <<_:18, MSB:6, LSB:8>> = CP#'SccpAddr'.pointcode, + BinAfterPC = <> + end, + case CP#'SccpAddr'.subsystemind of + 0 -> + BinAfterSSN = BinAfterPC; + 1 -> + BinAfterSSN = <> + end, + GTLen = CP#'SccpAddr'.gltitlelen, + <> = CP#'SccpAddr'.gltitle, + case CP#'SccpAddr'.gltitleind of + %% no global title included + 2#0000 -> + BinAfterSSN; + %% global title includes nature of address indicator only + 2#0001 -> + case (CP#'SccpAddr'.encoding) of + %% BCD odd number of digits + 16#01 -> + OddEven = 1; + %% BCD even number of digits + 16#02 -> + OddEven = 0 + end, + <>; + %% global title includes translation type only + 2#0010 -> + <>; + %% global title includes translation type, numbering plan + %% and encoding scheme + 2#0011 -> + <>; + %% global title includes translation type, numbering plan, + %% encoding scheme and nature of address indicator + 2#0100 -> + <>; + %% spare/reserved + _ -> + <> + end. + + +%% @spec (SccpAddress::party()) -> SccpAddress +%% SccpAddress = party() +%% +%% @doc Encodes/decodes an ANSI variant SCCP called/calling party address. +%%

Operates according to ANSI T1.112.3.

+%% @end +%% +%% [note: ANSI reverse the order of the PC & SSN] +%% +address_ansi(<>) -> + #'SccpAddr'{presind = 1, swtype = ?SW_ANSI, natintind = NatIntInd, + routingind = RoutingInd, pointcodeind = PointCodeInd, + subsystemind = SubsystemInd, gltitleind = GlobalTitleInd}; +address_ansi(<>) -> + case SubsystemInd of + 1 -> + <> = Addresses, + AddressWithSSN = #'SccpAddr'{presind = 1, swtype = ?SW_ANSI, + natintind = NatIntInd, subsystemind = 1, subsystem = SubSystemNumber}; + 0 -> + MoreAddresses = Addresses, + AddressWithSSN = #'SccpAddr'{presind = 1, swtype = ?SW_ANSI, + natintind = NatIntInd, subsystemind = 0} + end, + case PointCodeInd of + 1 -> + % ANSI point codes are three octets with the first octet + % containing the network cluster member and the last + % containing the network identifier + <> = MoreAddresses, + PointCode = (Network bsl 16) bor (Cluster bsl 8) bor Member, + AddressWithPC = AddressWithSSN#'SccpAddr'{pointcodeind = 1, + pointcode = PointCode}; + 0 -> + RestAddresses = MoreAddresses, + AddressWithPC = AddressWithSSN#'SccpAddr'{pointcodeind = 0} + end, + case GlobalTitleInd of + % no global title included + 2#0000 -> + AddressWithGT = AddressWithPC#'SccpAddr'{ + gltitleind = GlobalTitleInd}; + % global title includes translation type, numbering plan + % and encoding scheme (ITU codes this format as 2#0011) + 2#0001 -> + <> = RestAddresses, + GlobalTitleLen = size(GlobalTitle), + GlobalTitleRestSize = (?MAX_GLT_SZ - GlobalTitleLen), + AddressWithGT = AddressWithPC#'SccpAddr'{ + gltitleind = GlobalTitleInd, + gltranstype = TranslationType, numplan = NumberingPlan, + encoding = EncodingScheme, + gltitle = <>/binary>>, + gltitlelen = GlobalTitleLen}; + % global title includes translation type only + 2#0010 -> + <> = RestAddresses, + GlobalTitleLen = size(GlobalTitle), + GlobalTitleRestSize = (?MAX_GLT_SZ - GlobalTitleLen), + AddressWithGT = AddressWithPC#'SccpAddr'{ + gltitleind = GlobalTitleInd, + gltranstype = TranslationType, + gltitle = <>/binary>>, + gltitlelen = GlobalTitleLen}; + % GlobalTitleInd 2#0011 to 2#0100 not assigned for US networks + % GlobalTitleInd 2#0101 to 2#0111 spare international + % 2#1000 to 2#1110 spare national + % 2#1111 reserved for extension + _ -> + <> = RestAddresses, + GlobalTitleLen = size(GlobalTitle), + GlobalTitleRestSize = (?MAX_GLT_SZ - GlobalTitleLen), + AddressWithGT = AddressWithPC#'SccpAddr'{ + gltitleind = GlobalTitleInd, + gltitle = <>/binary>>, + gltitlelen = GlobalTitleLen} + end, + AddressWithGT#'SccpAddr'{routingind = RoutingInd}; +address_ansi(CP) when is_record(CP, 'SccpAddr') -> + Indicators = <<(CP#'SccpAddr'.natintind):1, + (CP#'SccpAddr'.routingind):1, + (CP#'SccpAddr'.gltitleind):4, + (CP#'SccpAddr'.pointcodeind):1, + (CP#'SccpAddr'.subsystemind):1>>, + case CP#'SccpAddr'.subsystemind of + 0 -> + BinAfterSSN = Indicators; + 1 -> + BinAfterSSN = <> + end, + case CP#'SccpAddr'.pointcodeind of + 0 -> + BinAfterPC = BinAfterSSN; + 1 -> + Member = 16#FF band CP#'SccpAddr'.pointcode, + Cluster = 16#FF band (CP#'SccpAddr'.pointcode bsr 8), + Network = 16#FF band (CP#'SccpAddr'.pointcode bsr 16), + BinAfterPC = <> + end, + GTLen = CP#'SccpAddr'.gltitlelen, + <> = CP#'SccpAddr'.gltitle, + case CP#'SccpAddr'.gltitleind of + % no global title + 2#0000 -> + BinAfterPC; + % global title includes translation type, + % numbering plan and encoding scheme + 2#0001 -> + <>; + % global title includes translation type + 2#0010 -> + <>; + % spare/reserved + _ -> + <> + end. + + +%% @type protoclass(). SCCP protocol class. +%%

An #'SccpProtoClass'{} record.

+%% @type importance(). SCCP importance. +%%

An #'SccpImportance'{} record.

+ +%% @spec (ConnectionOriented, QosParams) -> {ProtoClass, Importance} +%% ProtoClass = protoclass() +%% Importance = importance() +%% +%% @doc Creates the records required for the NMS API out of the +%% qos parameter set in the service primitive. +%% +qos_parameters(ConnectionOriented, {SequenceControl, ReturnOption, + MessagePriority}) -> + if + not ConnectionOriented and not SequenceControl -> Class = 0; + not ConnectionOriented and SequenceControl -> Class = 1; + ConnectionOriented and not SequenceControl -> Class = 2; + ConnectionOriented and SequenceControl -> Class = 3 + end, + case ReturnOption of + false -> MessageHandling = 0; + true -> MessageHandling = 8 + end, + ProtoClass = #'SccpProtoClass'{classind = Class, + msghandling = MessageHandling}, + case MessagePriority of + none -> + Importance = #'SccpImportance'{}; + Priority -> + Importance = #'SccpImportance'{presind = 1, impvalue = Priority} + end, + {ProtoClass, Importance}. + +%% @spec (BCDDigits) -> Digits +%% BCDDigits = [integer()] +%% Digits = [char()] +%% +%% @doc Decode BCD encoded digit string. +%%

BCD encoding uses 4 bits for each digit, packing two digits +%% into each octet.

+%%

Returns a character string representation of the digits.

+%% +bcd_digits(BCDDigits) when is_binary(BCDDigits) -> + bcd_digits(BCDDigits, []); +bcd_digits(BCDDigits) when is_list(BCDDigits) -> + bcd_digits(list_to_binary(BCDDigits), []). +bcd_digits(<<>>, Result) -> Result; +bcd_digits(<>, Acc) -> + bcd_digits(Rest, Acc ++ integer_to_list(First) ++ integer_to_list(Second)). + +%% @spec (BCDDigits) -> integer() +%% BCDDigits = [integer()] +%% +%% @doc Decode BCD encoded digit string. +%%

BCD encoding uses 4 bits for each digit, packing two digits +%% into each octet.

+%%

Returns an integer representation of the digits.

+%% +btoi(BCDDigits) when is_list(BCDDigits) -> + list_to_integer(bcd_digits(BCDDigits)). + + +%%---------------------------------------------------------------------- +%% The gen_server call backs +%%---------------------------------------------------------------------- + +%% @private +%% +%% @spec (Args) -> {ok, State} +%% +%% @doc Initialize the sccp server. +%%

This callback is caled by the new process in response to +%% the start_link/2,3,7 functions.

+%% +init([User, Board, EntityID, ServiceUserID, SAP, SSN, Poolsize]) -> + Queue = na:ctaCreateQueue(), + Context = na:ctaCreateContext(Queue, SSN, []), + Service = "sccp", + ServiceManager = "sccpmgr", + ServiceName = {Service, ServiceManager}, + ServiceAddress = 0, + Arg = "", + Args = [Board, 0, EntityID, 0, SAP, ServiceUserID, SSN, 0, Poolsize], + ServiceArgs = {Arg, Args}, + MVIPAddress = {0,0,0,0,0}, + ServiceDecription = {ServiceName, ServiceAddress, ServiceArgs, MVIPAddress}, + Object = na:ctaOpenServices(Queue, Context, [ServiceDecription]), + State = #state{user = User, board = Board, entityid = EntityID, + serviceuserid = ServiceUserID, sap = SAP, ssn = SSN, + queue = Queue, context = Context, object = Object}, + % load the dynamicly linked device driver + PrivDir = code:priv_dir(nms), + LibDir = filename:join([PrivDir, "lib"]), + Name = nms_sccp_drv, + case erl_ddll:try_load(LibDir, Name, [{monitor, pending_driver}]) of + {error, permanent} -> + init1(State); + {error, ErrorDescriptor} -> + {stop, erl_ddll:format_error(ErrorDescriptor)}; + {ok, Loaded} when Loaded == loaded; Loaded == already_loaded -> + init1(State); + {ok, pending_driver, Ref} -> + receive + {'UP', Ref, driver, Name, loaded} -> + init1(State); + {'UP', Ref, driver, Name, permanent} -> + init1(State); + {'DOWN', Ref, driver, Name, load_cancelled} -> + {stop, load_cancelled}; + {'DOWN', Ref, driver, Name, {load_failure, Failure}} -> + {stop, erl_ddll:format_error(Failure)} + after 10 -> + {stop, timeout} + end + end. +init1(State) -> + Port = open_port({spawn, 'nms_sccp_drv'}, [stream, binary]), + process_flag(trap_exit, true), + {ok, State#state{port = Port}}. + +%% @private +%% +%% @spec (Request, From, State) -> {stop, shutdown, State} +%% +%% @doc Handle requests sent with gen_server:call/2,3. +%% @end +%% +% shutdown the sccp server +handle_call(stop, _From, State) -> + {stop, shutdown, State}. + +%% @private +%% +%% @spec (Request, State) -> {noreply, State} +%% +%% @doc Handle requests sent with gen_server:cast/2. +%% @end +% +% service primitives received from the SCCP-Users +% +% Connection-Oriented Primitives +handle_cast({'N', 'CONNECT', request, {_CalledAddress, _CallingAddress, + _RespondingAddress, _ExpeditedDataSelection, + _QualityOfServiceParameterSet, _UserData, _Importance, + _ConnectionID}}, State) -> + {noreply, State}; +handle_cast({'N', 'CONNECT', response, {_CalledAddress, _CallingAddress, + _RespondingAddress, _ExpeditedDataSelection, + _QualityOfServiceParameterSet, _UserData, _Importance, + _ConnectionID}}, State) -> + {noreply, State}; +handle_cast({'N', 'DATA', request, {_Importance, _UserData, + _ConnectionID}}, State) -> + {noreply, State}; +handle_cast({'N', 'EXPEDITEDDATA', request , {_UserData, _ConnectionID}}, + State) -> + {noreply, State}; +handle_cast({'N', 'DISCONNECT', request, {_Originator, _Reason, _UserData, + _RespondingAddress, _Importance, _ConnectionID}}, State) -> + {noreply, State}; +handle_cast({'N', 'RESET', request, {_Originator, _Reason, + _ConnectionID}}, State) -> + {noreply, State}; +handle_cast({'N', 'RESET', response, {_Originator, _Reason, + _ConnectionID}}, State) -> + {noreply, State}; +handle_cast({'N', 'INFORM', request, {_Reason, _ConnectionID, + _QualityOfServiceParameterSet}}, State) -> + {noreply, State}; +% Connectionless Primitives +handle_cast({'N', 'UNITDATA', request, {CalledAddress, CallingAddess, + QualityOfServiceParameterSet, UserData}}, State) -> + {ProtoClass, Importance} = qos_parameters(false, + QualityOfServiceParameterSet), + Data = #'SccpData'{presind = 1, + data = UserData, datalen = size(UserData)}, + SCCPUDataRqst = nms_sccp:'SccpUdataRqst'(#'SccpUdataRqst'{ + protoclass = ProtoClass, + calledpty = address(CalledAddress), + callingpty = address(CallingAddess), + % TODO: what about end-of-sequence? (eos = 0) + importance = Importance, + data = Data}), + erlang:port_command(State#state.port, <>), + {noreply, State}; +% Management Primitives +handle_cast({'N', 'COORD', request, {_AffectedSubsystem, + _SubsystemMultiplicityIndicator}}, State) -> + {noreply, State}; +handle_cast({'N', 'COORD', response, {_AffectedSubsystem, + _SubsystemMultiplicityIndicator}}, State) -> + {noreply, State}; +handle_cast({'N', 'STATE', request, {_AffectedSubsystem, _UserStatus, + _SubsystemMultiplicityIndicator}}, State) -> + {noreply, State}. + + +%% @private +%% +%% @spec (Info, State) -> {noreply, State} | {stop, Reason, State} +%% +%% @doc Handle system events and messages. +%% @end +%% +% +% service primitives received from the NMS SCCP service +% +handle_info({{sccpEventData, _Context, _Port, _UserId}, {SccpRcvInfoBlk, SccpAllMsgs}}, State) -> + I = nms_sccp:'SccpRcvInfoBlk'(SccpRcvInfoBlk), + MoreInfo = {I#'SccpRcvInfoBlk'.board, I#'SccpRcvInfoBlk'.evnttype, + I#'SccpRcvInfoBlk'.suid, I#'SccpRcvInfoBlk'.connid, + I#'SccpRcvInfoBlk'.opc}, + forward_primitive(State#state.user, I#'SccpRcvInfoBlk'.indtype, + MoreInfo, SccpAllMsgs), + {noreply, State}; +% trapped exit signals +handle_info({'EXIT', Port, Reason} = R, State) when is_port(Port) -> + error_logger:error_report([Port, Reason, "Port terminated"]), + {stop, R, State}; +handle_info({'EXIT', Pid, Reason} = R, State) when is_pid(Pid) -> + error_logger:error_report([Pid, Reason, "Linked Pid terminated"]), + {stop, R, State}. + +%% @private +%% +%% @spec (Reason, State) -> ok +%% +%% @doc Called when the system is being shutdown. +%% @end +%% +% someone wants us to shutdown and cleanup +terminate(_Reason, _State) -> ok. + +%% @private +%% +%% @spec (OldVsn, State, Extra) -> {ok, NewState} +%% +%% @doc Called during a release upgarde. +%% +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + + +%%---------------------------------------------------------------------- +%% internal functions +%%---------------------------------------------------------------------- + +%% @private +%% +%% @spec (User, Name, Params, SccpAllMsgs) -> ok +%% +%% @doc Handles incoming primitives from the NMS driver and sends standardized +%% SCCP-service primitives to the service user. +%% @end +%% +%% TODO: all parameters should be encoded in standardized format +%% +forward_primitive(User, ?SCCPUDATIND, + {_Board, _EvntType, _Suid, _Connid, OPC}, SccpAllMsgs) -> + UD = nms_sccp:'SccpUdataRqst'(SccpAllMsgs), + CL = nms_sccp:'SccpProtoClass'(UD#'SccpUdataRqst'.protoclass), + CD = nms_sccp:'SccpAddr'(UD#'SccpUdataRqst'.calledpty), + CG = nms_sccp:'SccpAddr'(UD#'SccpUdataRqst'.callingpty), + IM = nms_sccp:'SccpImportance'(UD#'SccpUdataRqst'.importance), + SD = nms_sccp:'SccpData'(UD#'SccpUdataRqst'.data), + % if there is no point code in the calling party address we + % insert the originating point code from the routing label + if + (CG#'SccpAddr'.presind == 0) -> + NewCG = #'SccpAddr'{presind = 1, + swtype = CD#'SccpAddr'.swtype, % cheating? + pointcodeind = 1, + pointcode = OPC}; + (CG#'SccpAddr'.presind == 1) + and (CG#'SccpAddr'.pointcodeind == 0) -> + NewCG = CG#'SccpAddr'{pointcodeind = 1, pointcode = OPC}; + (CG#'SccpAddr'.presind == 1) + and (CG#'SccpAddr'.pointcodeind == 1) -> + NewCG = CG + end, + CallingParty = address(NewCG), + CalledParty = address(CD), + case CL#'SccpProtoClass'.classind of + 0 -> SequenceControl = false; + 1 -> SequenceControl = true + end, + case CL#'SccpProtoClass'.msghandling of + 0 -> ReturnOption = false; + 8 -> ReturnOption = true + end, + case IM#'SccpImportance'.presind of + 0 -> Importance = none; + 1 -> Importance = IM#'SccpImportance'.impvalue + end, + PDULen = SD#'SccpData'.datalen, + case SD#'SccpData'.presind of + 1 -> <> = SD#'SccpData'.data; + 0 -> PDU = <<>> + end, + gen_server:cast(User, {'N', 'UNITDATA', indication, {CalledParty, + CallingParty, {SequenceControl, ReturnOption, Importance}, PDU}}); +forward_primitive(User, ?SCCPSTAIND, {_, EventType, _, _, _}, SccpAllMsgs) -> + UD = nms_sccp:'SccpUdataRqst'(SccpAllMsgs), + gen_server:cast(User, {'N', 'NOTICE', indication, + {UD#'SccpUdataRqst'.calledpty, + UD#'SccpUdataRqst'.callingpty, UD#'SccpUdataRqst'.eos, + EventType, UD#'SccpUdataRqst'.importance, + UD#'SccpUdataRqst'.data}}); +forward_primitive(User, ?SCCPCOORDIND, _MoreInfo, SccpAllMsgs) -> + CO = nms_sccp:'SccpCoordRqst'(SccpAllMsgs), + gen_server:cast(User, {'N', 'COORD', indication, + {CO#'SccpCoordRqst'.assn, CO#'SccpCoordRqst'.smi}}); +forward_primitive(User, ?SCCPCOORDCFM, _MoreInfo, SccpAllMsgs) -> + CO = nms_sccp:'SccpCoordRqst'(SccpAllMsgs), + gen_server:cast(User, {'N', 'COORD', confirm, + {CO#'SccpCoordRqst'.assn, CO#'SccpCoordRqst'.smi}}); +forward_primitive(User, ?SCCPSTATEIND, _MoreInfo, SccpAllMsgs) -> + CO = nms_sccp:'SccpCoordRqst'(SccpAllMsgs), + gen_server:cast(User, {'N', 'STATE', indication, + {CO#'SccpCoordRqst'.assn, CO#'SccpCoordRqst'.status, + CO#'SccpCoordRqst'.smi}}); +forward_primitive(User, ?SCCPPCSTIND, {_, _, _, _, Opc}, SccpAllMsgs) -> + CO = nms_sccp:'SccpCoordRqst'(SccpAllMsgs), + gen_server:cast(User, {'N', 'PCSTATE', indication, + {Opc, CO#'SccpCoordRqst'.status, none, none}}); +forward_primitive(User, ?SCCPCONNIND, {_, _, _, Connid, _}, SccpAllMsgs) -> + CN = nms_sccp:'SccpConnRqst'(SccpAllMsgs), + gen_server:cast(User, {'N', 'CONNECT', indication, + {CN#'SccpConnRqst'.calledpty, CN#'SccpConnRqst'.callingpty, + none, CN#'SccpConnRqst'.eds, none, CN#'SccpConnRqst'.data, + CN#'SccpConnRqst'.importance, Connid}}); +%forward_primitive(User, ?SCCPCONNCFM, {_, _, _, Connid, _}, SccpAllMsgs) -> +% CN = nms_sccp:'SccpConnRqst'(SccpAllMsgs), +% gen_server:cast(User, {'N', 'CONNECT', confirm, +% {CN#'SccpConnRqst'.calledpty, CN#'SccpConnRqst'.callingpty, +% none, CN#'SccpConnRqst'.eds, none, CN#'SccpConnRqst'.data, +% CN#'SccpConnRqst'.importance, Connid}}); +forward_primitive(User, ?SCCPDATIND, {_, _, _, Connid, _}, SccpAllMsgs) -> + DA = nms_sccp:'SccpDataRqst'(SccpAllMsgs), + gen_server:cast(User, {'N', 'DATA', indication, + {none, DA#'SccpDataRqst'.data, Connid}}); +%forward_primitive(User, ?SCCPEDATIND, {_, _, _, Connid, _}, SccpAllMsgs) -> +% DA = nms_sccp:'SccpDataRqst'(SccpAllMsgs), +% gen_server:cast(User, {'N', 'EXPEDITEDDATA', indication, +% {none, DA#'SccpDataRqst'.data, Connid}}); +forward_primitive(User, ?SCCPRESETIND, {_, _, _, Connid, _}, SccpAllMsgs) -> + RS = nms_sccp:'SccpResetRqst'(SccpAllMsgs), + gen_server:cast(User, {'N', 'RESET', indication, + {RS#'SccpResetRqst'.orig, RS#'SccpResetRqst'.cause, Connid}}); +forward_primitive(User, ?SCCPRESETCFM, {_, _, _, Connid, _}, SccpAllMsgs) -> + RS = nms_sccp:'SccpResetRqst'(SccpAllMsgs), + gen_server:cast(User, {'N', 'RESET', confirm, + {RS#'SccpResetRqst'.orig, RS#'SccpResetRqst'.cause, Connid}}); +forward_primitive(User, ?SCCPRELIND, {_, _, _, Connid, _}, SccpAllMsgs) -> + DC = nms_sccp:'SccpRelease'(SccpAllMsgs), + gen_server:cast(User, {'N', 'DISCONNECT', indication, + {DC#'SccpRelease'.orig, DC#'SccpRelease'.cause, + DC#'SccpRelease'.data, DC#'SccpRelease'.rsppty, + DC#'SccpRelease'.importance, Connid}}); +forward_primitive(_User, ?SCCPDACKIND, _MoreInfo, _SccpAllMsgs) -> + ok; +forward_primitive(_User, ?SCCPCONNAUDCFM, _MoreInfo, _SccpAllMsgs) -> + ok; +forward_primitive(_User, ?SCCPRUNSTATEIND, {_,?SPRS_STANDALONE,SuID,_,OPC}, + _SccpAllMsgs) -> + <<_, N, C, M>> = <>, + error_logger:info_msg("SCCP Point code ~w.~w.~w SuID ~w " + "run state is standalone~n", [N, M, C, SuID]), + ok; +forward_primitive(_User, ?SCCPRUNSTATEIND, {_,?SPRS_PRIMARY,SuID,_,OPC}, + _SccpAllMsgs) -> + <<_, N, C, M>> = <>, + error_logger:info_msg("SCCP Point code ~w.~w.~w SuID ~w " + "run state is primary~n", [N, M, C, SuID]), + ok; +forward_primitive(_User, ?SCCPRUNSTATEIND, {_,?SPRS_BACKUP,SuID,_,OPC}, + _SccpAllMsgs) -> + <<_, N, C, M>> = <>, + error_logger:info_msg("SCCP Point code ~w.~w.~w SuID ~w " + "run state is backup~n", [N, M, C, SuID]), + ok; +forward_primitive(_User, ?SCCPCONGIND, _MoreInfo, _SccpAllMsgs) -> + ok.