///////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2000-2023 Ericsson Telecom AB
//
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v2.0
// which accompanies this distribution, and is available at
// https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
///////////////////////////////////////////////////////////////////////////////
//
//  File:         M3UA_Emulation.ttcn
//  Reference:    M3UA Protocol Emulation
//  Rev:          R2A
//  Prodnr:       CNL 113 537
//  Updated:      2009-01-06
//  Contact:      http://ttcn.ericsson.se

module M3UA_Emulation
{

modulepar
{
  boolean tsp_logVerbose := false;
  float tsp_Timer := 2.0;                     // General timer used in M3UA emulation.
  float tsp_ASPUP_Resend_Timer := 2.0;
  float tsp_ASPAC_Resend_Timer := 2.0;
  float tsp_Assoc_Restart_Timer := 60.0;
  float tsp_Heartbeat_Timer := 30.0;
  integer tsp_SCTP_PayloadProtocolID := 3;    // 3 for M3UA.
  boolean tsp_Enable_M3UA_Heartbeat := false; // Send SCTP packets periodically.
  boolean tsp_SCTP_Server_Mode := false;
  boolean tsp_M3UA_Server_Mode := false;
}

import from General_Types all;
import from M3UA_Types all;
import from SCTPasp_Types all;
import from SCTPasp_PortType all;
import from MTP3asp_Types all;

type record of ASP_MTP3_TRANSFERreq TRANSFERreq_Buffer;

type record SCTP_Association_Address
{
  integer local_sctp_port,
  charstring local_ip_addr,
  integer remote_sctp_port,
  charstring remote_ip_addr
}

// Definition of M3UA_Entity which contains M3UA entity data.
type record M3UA_Entity
{
  M3UA_CommStatus commStatus optional,
  integer sCTP_Assoc_ID optional,
  M3UA_Routing_Context rctx optional,
  SCTP_Association_Address assoc
}

// Type for status of SCTP communication for an M3UA entity.
type enumerated M3UA_CommStatus
{
  aSP_Down_initial_State (0),
  aSP_Down_sCTP_Initialize_Done (1),
  aSP_Down_sCTP_Associate_done (2),
  aSP_Down_commUP_Received (3),
  aSP_Down_ASPUP_Sent (4),
  aSP_Inactive (5),
  aSP_Inact_ASPAC_Sent (6),
  aSP_Active (7) // aSPAC_Ack_Received
}

// We need an internal port to communicate with the MTP3 side.
// internal in name
type port MTP3asp_SP_PT_Int message
{
  in ASP_MTP3_TRANSFERreq;
  out ASP_MTP3_TRANSFERind;
  out ASP_MTP3_PAUSE;
  out ASP_MTP3_RESUME;
  out ASP_MTP3_STATUS;
} with {
  extension "internal"
}

// M3UA emulation component.
type component M3UA_CT
{
  var M3UA_Entity v_Entity;
  var TRANSFERreq_Buffer v_TRANSFERreq_Buffer := {};

  var ASP_SCTP v_ASP_SCTP;
  var ASP_SCTP_SEND_FAILED v_ASP_SCTP_SEND_FAILED;
  var ASP_SCTP_RESULT v_ASP_SCTP_RESULT;
  var ASP_SCTP_Connected v_ASP_SCTP_Connected;
  var ASP_SCTP_ASSOC_CHANGE v_ASP_SCTP_ASSOC_CHANGE;
  var ASP_SCTP_SHUTDOWN_EVENT v_ASP_SCTP_SHUTDOWN_EVENT;

  var PDU_M3UA v_PDU_M3UA;

  // Component timers.
  timer T_Timer := tsp_Timer;
  timer T_ASPUP_resend := tsp_ASPUP_Resend_Timer;
  timer T_ASPAC_resend := tsp_ASPAC_Resend_Timer;
  timer T_Heartbeat := tsp_Heartbeat_Timer;
  timer T_Assoc_restart := tsp_Assoc_Restart_Timer;

  // Port declarations.
  port MTP3asp_SP_PT_Int MTP3_SP_PORT; // Port towards MTP3/M3UA.
  port SCTPasp_PT SCTP_PORT;           // Port towards target through SCTP.
}

//********************************
// Start of SCTP related templates
//********************************
template ASP_SCTP t_S_SCTP_Send
  (in template integer pl_associationID,
   in template integer pl_streamID,
   in template octetstring pl_userData,
   in template integer pl_protocolID) :=
{
  client_id := pl_associationID,
  sinfo_stream := pl_streamID,
  sinfo_ppid := pl_protocolID,
  data := pl_userData
}

template ASP_SCTP_SEND_FAILED t_ASP_SCTP_SEND_FAILED
  (in template integer pl_streamID) :=
{
  client_id := pl_streamID
}

template ASP_SCTP_Listen t_ASP_SCTP_Listen
  (template charstring pl_local_hostname,
   template integer pl_local_portnumber) :=
{
  local_hostname := pl_local_hostname,
  local_portnumber := pl_local_portnumber
}

template ASP_SCTP_Connected tr_ASP_SCTP_Connected
  (template integer pl_client_id,
   template charstring pl_local_hostname,
   template integer pl_local_portnumber,
   template charstring pl_peer_hostname,
   template integer pl_peer_portnumber) :=
{
  client_id := pl_client_id,
  local_hostname := pl_local_hostname,
  local_portnumber := pl_local_portnumber,
  peer_hostname := pl_peer_hostname,
  peer_portnumber := pl_peer_portnumber
}

template ASP_SCTP_ConnectFrom t_ASP_SCTP_ConnectFrom
  (template charstring pl_local_hostname,
   template integer pl_local_portnumber,
   template charstring pl_peer_hostname,
   template integer pl_peer_portnumber) :=
{
  local_hostname := pl_local_hostname,
  local_portnumber := pl_local_portnumber,
  peer_hostname := pl_peer_hostname,
  peer_portnumber := pl_peer_portnumber
}

template ASP_SCTP_RESULT t_ASP_SCTP_RESULT
  (template integer pl_client_id,
   template boolean pl_error_status,
   template charstring pl_error_message) :=
{
  client_id := pl_client_id,
  error_status := pl_error_status,
  error_message := pl_error_message
}

template ASP_SCTP_ASSOC_CHANGE tr_S_SCTP_CommunicationUp
  (in template integer pl_associationID) :=
{
  client_id := pl_associationID,
  sac_state := SCTP_COMM_UP
}

template ASP_SCTP_ASSOC_CHANGE tr_S_SCTP_CommunicationLost
  (in template integer pl_associationID) :=
{
  client_id := pl_associationID,
  sac_state := SCTP_COMM_LOST
}

template ASP_SCTP_ASSOC_CHANGE tr_S_SCTP_ShutdownComplete
  (in template integer pl_associationID) :=
{
  client_id := pl_associationID,
  sac_state := SCTP_SHUTDOWN_COMP
}

template ASP_SCTP_SHUTDOWN_EVENT tr_S_SCTP_ShutdownEvent
  (in template integer pl_associationID) :=
{
  client_id := pl_associationID
}

template ASP_SCTP_ASSOC_CHANGE tr_S_SCTP_Restart
  (in template integer pl_associationID) :=
{
  client_id := pl_associationID,
  sac_state := SCTP_RESTART
}

template ASP_SCTP_ASSOC_CHANGE tr_S_SCTP_CANT_STR_ASSOC
  (in template integer pl_associationID) :=
{
  client_id := pl_associationID,
  sac_state := SCTP_CANT_STR_ASSOC
}

template ASP_SCTP tr_S_SCTP_DataArrive
  (in template integer pl_associationID,
   in template integer pl_streamID,
   in template integer pl_protocolID,
   in template PDU_SCTP pl_data) :=
{
  client_id := pl_associationID,
  sinfo_stream := pl_streamID,
  sinfo_ppid := pl_protocolID,
  data := pl_data
}

template ASP_SCTP_Close t_ASP_SCTP_Close
  (in template integer pl_associationID) :=
{
  client_id := pl_associationID
}
//******************************
// End of SCTP related templates
//******************************

//*****************************
// Start of M3UA PDU templates.
//*****************************
template PDU_M3UA t_PDU_M3UA_ASPUP
  (in template M3UA_ASP_Identifier pl_aSP_Identifier,
   in template M3UA_Info_String pl_info_String) :=
{
  m3UA_ASPUP := {
    version := '01'O,
    reserved := '00'O,
    messageClassAndType := '0301'O,
    messageLength := 0,
    messageParameters := {
      aSP_Identifier := pl_aSP_Identifier,
      info_String := pl_info_String
    }
  }
}

template PDU_M3UA t_PDU_M3UA_ASPUP_Ack :=
{
  m3UA_ASPUP_Ack := {
    version := '01'O,
    reserved := '00'O,
    messageClassAndType := '0304'O,
    messageLength := 0,
    messageParameters := {
      info_String := omit
    }
  }
}

template PDU_M3UA t_PDU_M3UA_ASPDN :=
{
  m3UA_ASPDN := {
    version := '01'O,
    reserved := '00'O,
    messageClassAndType := '0302'O,
    messageLength := 0,
    messageParameters := {
      info_String := omit
    }
  }
}

template PDU_M3UA t_PDU_M3UA_ASPDN_Ack :=
{
  m3UA_ASPDN_Ack := {
    version := '01'O,
    reserved := '00'O,
    messageClassAndType := '0305'O,
    messageLength := 0,
    messageParameters := {
      info_String := omit
    }
  }
}

template PDU_M3UA t_PDU_M3UA_ASPAC
  (in template M3UA_Traffic_Mode_Type pl_traffic_Mode_Type,
   in template M3UA_Routing_Context pl_routing_Context,
   in template M3UA_Info_String pl_info_String) :=
{
  m3UA_ASPAC := {
    version := '01'O,
    reserved := '00'O,
    messageClassAndType := '0401'O,
    messageLength := 0,
    messageParameters := {
      traffic_Mode_Type := pl_traffic_Mode_Type,
      routing_Context := pl_routing_Context,
      info_String := pl_info_String
    }
  }
}

template PDU_M3UA t_PDU_M3UA_ASPAC_Ack
  (in template M3UA_Traffic_Mode_Type pl_traffic_mode_type,
   in template M3UA_Routing_Context pl_routing_Context) :=
{
  m3UA_ASPAC_Ack := {
    version := '01'O,
    reserved := '00'O,
    messageClassAndType := '0403'O,
    messageLength := 0,
    messageParameters := {
      traffic_Mode_Type := pl_traffic_mode_type,
      routing_Context := pl_routing_Context,
      info_String := omit
    }
  }
}

template PDU_M3UA t_PDU_M3UA_ASPIA
  (in template M3UA_Routing_Context pl_routing_Context,
   in template M3UA_Info_String pl_info_String) :=
{
  m3UA_ASPIA := {
    version := '01'O,
    reserved := '00'O,
    messageClassAndType := '0402'O,
    messageLength := 0,
    messageParameters := {
      routing_Context := pl_routing_Context,
      info_String := pl_info_String
    }
  }
}

template PDU_M3UA t_PDU_M3UA_ASPIA_Ack
  (in template M3UA_Routing_Context pl_routing_Context) :=
{
  m3UA_ASPIA_Ack := {
    version := '01'O,
    reserved := '00'O,
    messageClassAndType := '0404'O,
    messageLength := 0,
    messageParameters := {
      routing_Context := pl_routing_Context,
      info_String := omit
    }
  }
}

template PDU_M3UA t_PDU_M3UA_Heartbeat
  (in template M3UA_Heartbeat_Data pl_heartbeat_Data) :=
{
  m3UA_BEAT := {
    version := '01'O,
    reserved := '00'O,
    messageClassAndType := '0303'O,
    messageLength := 0,
    messageParameters := {
      heartbeat_Data := pl_heartbeat_Data
    }
  }
}

template PDU_M3UA t_PDU_M3UA_Beat_Ack
  (in template M3UA_Heartbeat_Data pl_heartbeat_Data) :=
{
  m3UA_BEAT_Ack := {
    version := '01'O,
    reserved := '00'O,
    messageClassAndType := '0306'O,
    messageLength := 0,
    messageParameters := {
      heartbeat_Data := pl_heartbeat_Data
    }
  }
}

template PDU_M3UA t_PDU_M3UA_NOTIFY
  (in template M3UA_Status pl_status,
   in template M3UA_ASP_Identifier pl_aSP_Identifier,
   in template M3UA_Routing_Context pl_routing_Context,
   in template M3UA_Info_String pl_info_String) :=
{
  m3UA_NOTIFY := {
    version := '01'O,
    reserved := '00'O,
    messageClassAndType := '0001'O,
    messageLength := 0,
    messageParameters := {
      status := pl_status,
      aSP_Identifier := pl_aSP_Identifier,
      routing_Context := pl_routing_Context,
      info_String := pl_info_String
    }
  }
}

template PDU_M3UA t_PDU_M3UA_DATA
  (in template M3UA_Network_Appearance pl_network_Appearance,
   in template M3UA_Routing_Context pl_routing_Context,
   in template M3UA_Protocol_Data pl_protocol_Data,
   in template M3UA_Correlation_ID pl_correlation_ID) :=
{
  m3UA_DATA := {
    version := '01'O,
    reserved := '00'O,
    messageClassAndType := '0101'O,
    messageLength := 0,
    messageParameters := {
      network_Appearance := pl_network_Appearance,
      routing_Context := pl_routing_Context,
      protocol_Data := pl_protocol_Data,
      correlation_ID := pl_correlation_ID
    }
  }
}

template PDU_M3UA t_PDU_M3UA_DAVA
  (in template M3UA_Network_Appearance pl_network_Appearance,
   in template M3UA_Routing_Context pl_routing_Context,
   in template M3UA_Affected_Point_Codes pl_affected_Point_Codes,
   in template M3UA_Info_String pl_info_String) :=
{
  m3UA_DAVA := {
    version := '01'O,
    reserved := '00'O,
    messageClassAndType := '0202'O,
    messageLength := 0,
    messageParameters := {
      network_Appearance := pl_network_Appearance,
      routing_Context := pl_routing_Context,
      affected_Point_Codes := pl_affected_Point_Codes,
      info_String := pl_info_String
    }
  }
}
//**************************
// End of M3UA PDU templates
//**************************

//**********************************
// Start of M3UA parameter templates
//**********************************
template M3UA_Protocol_Data t_M3UA_Protocol_Data
  (template OCT4 pl_oPC,
   template OCT4 pl_dPC,
   template OCT1 pl_sI,
   template OCT1 pl_nI,
   template OCT1 pl_mP,
   template OCT1 pl_sLS,
   template octetstring pl_userProtocolData) :=
{
  tag := '0210'O,
  lengthInd := 0,
  oPC := pl_oPC,
  dPC := pl_dPC,
  sI := pl_sI,
  nI := pl_nI,
  mP := pl_mP,
  sLS := pl_sLS,
  userProtocolData := pl_userProtocolData
}
//********************************
// End of M3UA parameter templates
//********************************

//***********************************
// Dynamic part of the M3UA emulation
//***********************************

private template (value) M3UA_Routing_Context t_RoutingContext(OCT4 rctx) := {
	tag := '0006'O,
	lengthInd := 8,
	routingContext := rctx
}

private template (value) M3UA_Status t_Status(OCT2 status_type, OCT2 status_info) := {
	tag := '000d'O,
	lengthInd := 8,
	statusType := status_type,
	statusInfo := status_info
}

function f_M3UA_Emulation(SCTP_Association_Address pl_Boot,
			  template (omit) integer rctx := omit) runs on M3UA_CT
{
  // Initialize parameters from the test case.
  v_Entity.assoc := pl_Boot;
  v_Entity.commStatus := aSP_Down_initial_State;
  if (istemplatekind(rctx, "omit")) {
    v_Entity.rctx := omit;
  } else {
    v_Entity.rctx := valueof(t_RoutingContext(int2oct(valueof(rctx), 4)));
  }

  // At this point, we assume that the ports are already connected and mapped
  // properly by the user.
  log("*************************************************");
  log("M3UA emulation initiated, the test can be started");
  log("*************************************************");

  f_Initialize_SCTP();

  // Start the main function in an infinte loop.
  f_M3UA_ScanEvents();
}

// Initialize the SCTP layer with parameters read from the configuration file.
// We have only a single association.
function f_Initialize_SCTP() runs on M3UA_CT
{
  v_Entity.commStatus := aSP_Down_sCTP_Initialize_Done;
  if (tsp_SCTP_Server_Mode) {
    // Send out a LISTEN message.  The communication status doesn't change
    // here.
    SCTP_PORT.send
      (t_ASP_SCTP_Listen(v_Entity.assoc.local_ip_addr,
                         v_Entity.assoc.local_sctp_port));
  }
  else {
    // Send ConnectFrom sequentially, wait for RESULT messages.
    f_Associate();
    T_Assoc_restart.start;
  }

  if (tsp_SCTP_PayloadProtocolID == 3) {
    if (not tsp_M3UA_Server_Mode) {
      T_ASPUP_resend.start;
      T_ASPAC_resend.start;
    }
    if (tsp_Enable_M3UA_Heartbeat) {
      T_Heartbeat.start;
    }
  }
}

// Associate SCTP connection for a M3UA entity.
function f_Associate() runs on M3UA_CT
{
  SCTP_PORT.send(t_ASP_SCTP_ConnectFrom
    (v_Entity.assoc.local_ip_addr,
     v_Entity.assoc.local_sctp_port,
     v_Entity.assoc.remote_ip_addr,
     v_Entity.assoc.remote_sctp_port));

  T_Timer.start;
  alt {
    [] SCTP_PORT.receive(t_ASP_SCTP_RESULT(*, ?, *)) -> value v_ASP_SCTP_RESULT {
      if (v_ASP_SCTP_RESULT.error_status) {
        log("Connect failed: ", v_ASP_SCTP_RESULT.error_message);
      }
      else {
        v_Entity.sCTP_Assoc_ID := v_ASP_SCTP_RESULT.client_id;
        v_Entity.commStatus := aSP_Down_sCTP_Associate_done;
        log("SCTP_ConnectResult -> connection established from: ",
          v_Entity.assoc.local_ip_addr, ":", v_Entity.assoc.local_sctp_port,
          " to server: ", v_Entity.assoc.remote_ip_addr, ":",
          v_Entity.assoc.remote_sctp_port, " association #",
	  v_Entity.sCTP_Assoc_ID);
        if (tsp_logVerbose) {
          log("Association #", v_Entity.sCTP_Assoc_ID, " state changed to ",
            v_Entity.commStatus);
        }
      }
      T_Timer.stop;
    }
    [] T_Timer.timeout {
      log("----------------------------------------------");
      log("No response received to t_ASP_SCTP_ConnectFrom");
      log("----------------------------------------------");
      setverdict(fail);
      // mtc.stop;
    }
  }
}

// Starts M3UA emulation execution.
function f_M3UA_ScanEvents() runs on M3UA_CT
{
  var ASP_MTP3_TRANSFERreq vl_ASP_MTP3_TRANSFERreq;

  alt {
    [] MTP3_SP_PORT.receive(ASP_MTP3_TRANSFERreq : ?)
      -> value vl_ASP_MTP3_TRANSFERreq {
      f_Send_MTP3_TRANSFERreq(vl_ASP_MTP3_TRANSFERreq);
      repeat;
    }
    [] as_SCTP_CommunicationUp();
    [] as_SCTP_DataArrive();
    [] as_SCTP_Connected();
    [] as_Unexpected_SCTP_Events();
    [] as_handleM3UA_timers();
    [] as_handleSCTP_timers();
  }
}

function f_Send_MTP3_TRANSFERreq(ASP_MTP3_TRANSFERreq pl_ASP_MTP3_TRANSFERreq)
  runs on M3UA_CT
{
  if (v_Entity.commStatus == aSP_Active) {
    if (tsp_SCTP_PayloadProtocolID == 3) { // M3UA
      SCTP_PORT.send
        (t_S_SCTP_Send
         (v_Entity.sCTP_Assoc_ID,
          1,
          enc_PDU_M3UA
            (valueof
             (t_PDU_M3UA_DATA
              (omit,
               v_Entity.rctx,
               t_M3UA_Protocol_Data
                 (int2oct(pl_ASP_MTP3_TRANSFERreq.opc, 4),               // OPC
                  int2oct(pl_ASP_MTP3_TRANSFERreq.dpc, 4),               // DPC
                  bit2oct('0000'B & pl_ASP_MTP3_TRANSFERreq.sio.si),     // SIO
                  bit2oct('000000'B & pl_ASP_MTP3_TRANSFERreq.sio.ni),
                  bit2oct('000000'B & pl_ASP_MTP3_TRANSFERreq.sio.prio),
                  int2oct(pl_ASP_MTP3_TRANSFERreq.sls, 1),               // SLS
                  pl_ASP_MTP3_TRANSFERreq.data),
                omit))),
          tsp_SCTP_PayloadProtocolID));
    }
    else { // Non-M3UA
      SCTP_PORT.send
        (t_S_SCTP_Send
         (v_Entity.sCTP_Assoc_ID,
          1,
          pl_ASP_MTP3_TRANSFERreq.data,
          tsp_SCTP_PayloadProtocolID));
    }
    if (tsp_logVerbose) {
      log("MTP3_SP_PORT: ASP_MTP3_TRANSFERreq received -> message sent " &
          "via SCTP");
    }
  }
  else {
    // If the SCTP association is not yet running, we have to buffer the data
    // messages arrived from the MTP3 side.  Sending of buffered data messages
    // should occure when the SCTP association is up and before sending the
    // data message in reply for a new ASP_MTP3_TRANSFERreq data message.  The
    // buffer should be checked before sending.
    v_TRANSFERreq_Buffer[sizeof(v_TRANSFERreq_Buffer)] :=
      pl_ASP_MTP3_TRANSFERreq;
    if (tsp_logVerbose) {
      log("MTP3_SP_PORT: ASP_MTP3_TRANSFERreq received in an inactive state " &
          "-> message was buffered");
    }
  }
}

// Handle communication up messages of users which performed associate earlier.
// We have only one association.
altstep as_SCTP_CommunicationUp() runs on M3UA_CT
{
  [] SCTP_PORT.receive(tr_S_SCTP_CommunicationUp(?))
    -> value v_ASP_SCTP_ASSOC_CHANGE {
    if (v_Entity.sCTP_Assoc_ID == v_ASP_SCTP_ASSOC_CHANGE.client_id) {
      if (v_Entity.commStatus == aSP_Down_sCTP_Associate_done) {
        v_Entity.commStatus := aSP_Down_commUP_Received;
        if (tsp_SCTP_PayloadProtocolID != 3) { // Non-M3UA
          v_Entity.commStatus := aSP_Active;
          var integer v_i;
          for (v_i := 0; v_i < sizeof(v_TRANSFERreq_Buffer); v_i := v_i + 1) {
            log("Sending buffered message #", v_i);
            f_Send_MTP3_TRANSFERreq(v_TRANSFERreq_Buffer[v_i]);
          }
          v_TRANSFERreq_Buffer := {};
	  // MTP3_SP_PORT.send(ASP_MTP3_RESUME : {});
        }
        if (tsp_logVerbose) {
          log("Association #", v_Entity.sCTP_Assoc_ID, " state changed to: ",
	      v_Entity.commStatus);
	}
	if ((not tsp_M3UA_Server_Mode) and
	    (tsp_SCTP_PayloadProtocolID == 3)) { // M3UA
	  f_ASPUP_Sending();
	}
      }
      else {
        if (tsp_logVerbose) {
          log("SCTP_CommunicationUp received in wrong state (i.e. not after " &
	      "SCTP_Associate is done) in state: ", v_Entity.commStatus);
	}
      }
    }
    else {
      if (tsp_logVerbose) {
        log("Association does not exists, received in CommunicationUp");
      }
    }
    repeat;
  }
}

// This altstep handles the data received from SCTP.
altstep as_SCTP_DataArrive() runs on M3UA_CT
{
  [] SCTP_PORT.receive(tr_S_SCTP_DataArrive
                       (?, // associationID
                        ?, // streamID
                        ?, // protocolID
                        ?  // data
                      )) -> value v_ASP_SCTP {
    // Checking the identifier for the association is not necessary, because we
    // have only only one association.
    if (f_Assoc_Exists(v_ASP_SCTP.client_id)) {
      if (tsp_logVerbose) {
        log("Message received on association #", v_Entity.sCTP_Assoc_ID);
      }
      if (tsp_SCTP_PayloadProtocolID == 3) { // M3UA
        v_PDU_M3UA := dec_PDU_M3UA(v_ASP_SCTP.data);
        f_handle_M3UA_msg(v_PDU_M3UA);
      }
      else { // Non-M3UA
        f_handle_nonM3UA_msg(v_ASP_SCTP.data);
      }
    }
    else{
      log("Message received on unknown association #", v_Entity.sCTP_Assoc_ID,
          " -> closing connection");
      SCTP_PORT.send(t_ASP_SCTP_Close(v_Entity.sCTP_Assoc_ID));
      log("SCTP connection closed");
    }
    repeat;
  }
}

// Handle the SCTP connected messages.  It is sent from the SCTP side and it
// signals, that we're on the right track to create the association.  This is
// for server mode.
altstep as_SCTP_Connected() runs on M3UA_CT
{
  [tsp_SCTP_Server_Mode] SCTP_PORT.receive(tr_ASP_SCTP_Connected(?, ?, ?, ?, ?))
    -> value v_ASP_SCTP_Connected {
    // Message from the configured endpoint.
    if ((v_ASP_SCTP_Connected.local_portnumber ==
         v_Entity.assoc.local_sctp_port) and
        (v_ASP_SCTP_Connected.local_hostname ==
         v_Entity.assoc.local_ip_addr) and
        (v_ASP_SCTP_Connected.peer_portnumber ==
         v_Entity.assoc.remote_sctp_port) and
        (v_ASP_SCTP_Connected.peer_hostname ==
         v_Entity.assoc.remote_ip_addr)) {
      v_Entity.sCTP_Assoc_ID := v_ASP_SCTP_Connected.client_id;
      v_Entity.commStatus := aSP_Down_sCTP_Associate_done;
      log("ASP_SCTP_Connected -> accepted connection from client: ",
          v_ASP_SCTP_Connected.peer_hostname, ":",
          v_ASP_SCTP_Connected.peer_portnumber, " on server: ",
          v_ASP_SCTP_Connected.local_hostname, ":",
          v_ASP_SCTP_Connected.local_portnumber, " with association #",
          v_Entity.sCTP_Assoc_ID);
    }
    else {
      log("ASP_SCTP_Connected -> connection from unknown client: ",
          v_ASP_SCTP_Connected.peer_hostname, ":",
          v_ASP_SCTP_Connected.peer_portnumber);
    }
    repeat;
  }
}

// Handle error messages of users.
altstep as_Unexpected_SCTP_Events() runs on M3UA_CT
{
  // Handle communications lost message.  State of user with given index jumps
  // back to initial state and stays there.  That user will not be able to
  // communicate anymore.
  [] SCTP_PORT.receive(tr_S_SCTP_CommunicationLost(?))
    -> value v_ASP_SCTP_ASSOC_CHANGE {
    if (f_Assoc_Exists(v_ASP_SCTP_ASSOC_CHANGE.client_id)) {
      if (v_Entity.commStatus == aSP_Active) {
	// MTP3_SP_PORT.send(ASP_MTP3_PAUSE : {});
      }
      v_Entity.commStatus := aSP_Down_sCTP_Initialize_Done;
      v_Entity.sCTP_Assoc_ID := omit;
      if (tsp_logVerbose) {
        log("SCTP_CommunicationLost received");
        log("Association #", v_Entity.sCTP_Assoc_ID, " cleared, state " &
            "changed to: ", v_Entity.commStatus);
      }
    }
    else {
      if (tsp_logVerbose) {
        log("Association does not exist, received in CommunicationLost");
      }
    }
    repeat;
  }
  [] SCTP_PORT.receive(tr_S_SCTP_ShutdownComplete(?))
    -> value v_ASP_SCTP_ASSOC_CHANGE {
    if (f_Assoc_Exists(v_ASP_SCTP_ASSOC_CHANGE.client_id)) {
      if (v_Entity.commStatus == aSP_Active) {
        // MTP3_SP_PORT.send(ASP_MTP3_PAUSE : {});
      }
      v_Entity.commStatus := aSP_Down_sCTP_Initialize_Done;
      if (tsp_logVerbose) {
        log("SCTP_ShutdownComplete received");
	log("Association #", v_Entity.sCTP_Assoc_ID, " state changed to: ",
	    v_Entity.commStatus);
      }
    }
    else {
      if (tsp_logVerbose) {
        log("Association does not exist, received in ShutdownComplete");
      }
    }
    repeat;
  }
  [] SCTP_PORT.receive(tr_S_SCTP_ShutdownEvent(?))
    -> value v_ASP_SCTP_SHUTDOWN_EVENT {
    if (f_Assoc_Exists(v_ASP_SCTP_SHUTDOWN_EVENT.client_id)) {
      if (v_Entity.commStatus == aSP_Active) {
	// MTP3_SP_PORT.send(ASP_MTP3_PAUSE : {});
      }
      v_Entity.commStatus := aSP_Down_sCTP_Initialize_Done;
      if (tsp_logVerbose) {
        log("SCTP_ShutdownEvent received");
	log("Association #", v_Entity.sCTP_Assoc_ID, " state changed to: ",
	    v_Entity.commStatus);
      }
    }
    else {
      if (tsp_logVerbose) {
        log("Association does not exist, received in ShutdownEvent");
      }
    }
    repeat;
  }
  [] SCTP_PORT.receive(tr_ASP_SCTP_Connected(?, ?, ?, ?, ?))
    -> value v_ASP_SCTP_Connected {
    log("Unexpected ASP_SCTP_Connected");
    repeat;
  }
  [] SCTP_PORT.receive(tr_S_SCTP_Restart(?)) -> value v_ASP_SCTP_ASSOC_CHANGE {
    if (f_Assoc_Exists(v_ASP_SCTP_ASSOC_CHANGE.client_id)) {
      log("SCTP_Restart received");
      v_Entity.commStatus := aSP_Down_commUP_Received;
      if (tsp_logVerbose) {
        log("Association #", v_Entity.sCTP_Assoc_ID, " state changed to: ",
	    v_Entity.commStatus);
      }
    }
    else {
      if (tsp_logVerbose) {
        log("Association does not exist, received in SCTP_Restart");
      }
    }
    repeat;
  }
  [] SCTP_PORT.receive(t_ASP_SCTP_SEND_FAILED(?))
    -> value v_ASP_SCTP_SEND_FAILED {
    log("SCTP_Send failed for association #", v_Entity.sCTP_Assoc_ID);
    if (f_Assoc_Exists(v_ASP_SCTP_SEND_FAILED.client_id)) {
      // Daemon sends an error status message here.
      // MTP3_SP_PORT.send(ASP_MTP3_PAUSE : {});
    }
    else {
      log("Send error received for association that doesn't exist");
    }
    repeat;
  }
  [] SCTP_PORT.receive(tr_S_SCTP_CANT_STR_ASSOC(?)) {
    repeat;
  }
  [] SCTP_PORT.receive {
    repeat;
  }
}

// After reception of SCTP_CommunicationUp M3UA ASPUP/ASPAC is resent by the
// entity if it didn't receive ASPUP_Ack/ASPAC_Ack.
altstep as_handleM3UA_timers() runs on M3UA_CT
{
  [] T_ASPUP_resend.timeout {
    if ((v_Entity.commStatus == aSP_Down_commUP_Received) or
        (v_Entity.commStatus == aSP_Down_ASPUP_Sent)) {
      // Try to send ASPUP again.
      SCTP_PORT.send
        (t_S_SCTP_Send
         (v_Entity.sCTP_Assoc_ID,
          0,
          enc_PDU_M3UA(valueof(t_PDU_M3UA_ASPUP(omit, omit))),
          tsp_SCTP_PayloadProtocolID));
      v_Entity.commStatus := aSP_Down_ASPUP_Sent;
      if (tsp_logVerbose) {
        log("Association #", v_Entity.sCTP_Assoc_ID, " state changed to: ",
          v_Entity.commStatus);
      }
    }
    T_ASPUP_resend.start;
    repeat;
  }

  [] T_ASPAC_resend.timeout {
    if ((v_Entity.commStatus == aSP_Inactive) or
        (v_Entity.commStatus == aSP_Inact_ASPAC_Sent)) {
      SCTP_PORT.send
        (t_S_SCTP_Send
         (v_Entity.sCTP_Assoc_ID,
          0,
          enc_PDU_M3UA(valueof(t_PDU_M3UA_ASPAC(omit, v_Entity.rctx, omit))),
          tsp_SCTP_PayloadProtocolID));
      v_Entity.commStatus := aSP_Inact_ASPAC_Sent;
      if (tsp_logVerbose) {
        log("Association #", v_Entity.sCTP_Assoc_ID, " state changed to: ",
            v_Entity.commStatus);
      }
    }
    T_ASPAC_resend.start;
    repeat;
  }

  [tsp_Enable_M3UA_Heartbeat] T_Heartbeat.timeout {
    if (v_Entity.commStatus == aSP_Active) {
      SCTP_PORT.send
        (t_S_SCTP_Send
         (v_Entity.sCTP_Assoc_ID,
          0,
          enc_PDU_M3UA(valueof(t_PDU_M3UA_Heartbeat(omit))),
          tsp_SCTP_PayloadProtocolID));
      if (tsp_logVerbose) {
        log("Heartbeat sent to association #", v_Entity.sCTP_Assoc_ID);
      }
    }
    T_Heartbeat.start;
    repeat;
  }
}

// Handles SCTP timer events.  In server mode we don't associate.
altstep as_handleSCTP_timers() runs on M3UA_CT
{
  [not tsp_SCTP_Server_Mode] T_Assoc_restart.timeout {
    if (v_Entity.commStatus == aSP_Down_sCTP_Initialize_Done) {
      f_Associate();
    }
    T_Assoc_restart.start;
    repeat;
  }
}

// After reception of SCTP CommunicationUp messages M3UA ASPUP is sent by
// every entity and the M3UA ASPUP_Ack is received by every entity.
function f_ASPUP_Sending() runs on M3UA_CT
{
  SCTP_PORT.send
    (t_S_SCTP_Send
     (v_Entity.sCTP_Assoc_ID,
      0, // streamID
      enc_PDU_M3UA(valueof(t_PDU_M3UA_ASPUP(omit, omit))),
      tsp_SCTP_PayloadProtocolID));
  v_Entity.commStatus := aSP_Down_ASPUP_Sent;
  if (tsp_logVerbose) {
    log("M3UA_ASPUP sent");
    log("Association #", v_Entity.sCTP_Assoc_ID, " state changed to: ",
        v_Entity.commStatus);
  }
}


// Test if an association with assocID exists or not.  We have only one
// association at the moment, we just check if the given assocID is the same,
// that is associated with our single entity.  If we would have more entities
// in a table, the index of it should be returned instead of a boolean value.
function f_Assoc_Exists(integer pl_assocID) runs on M3UA_CT return boolean
{
  if (ispresent(v_Entity.sCTP_Assoc_ID) and v_Entity.sCTP_Assoc_ID == pl_assocID) {
    return true;
  }
  else {
    if (tsp_logVerbose) {
      log("Association #", v_Entity.sCTP_Assoc_ID, " not found");
    }
  }
  return false;
}

function f_handle_M3UA_msg(PDU_M3UA pl_PDU_M3UA) runs on M3UA_CT
{
  var M3UA_Point_Codes aff_pcs;

  if (ischosen(pl_PDU_M3UA.m3UA_DATA)) {
    if (v_Entity.commStatus == aSP_Active) {
      // Send ASP_MTP3_TRANSFERind message.
      MTP3_SP_PORT.send
        (valueof
         (tr_ASP_MTP3_TRANSFERind_sio
          (substr(oct2bit(pl_PDU_M3UA.m3UA_DATA.messageParameters.protocol_Data.nI), 6, 2),
           substr(oct2bit(pl_PDU_M3UA.m3UA_DATA.messageParameters.protocol_Data.mP), 6, 2),
           substr(oct2bit(pl_PDU_M3UA.m3UA_DATA.messageParameters.protocol_Data.sI), 4, 4),
           oct2int(pl_PDU_M3UA.m3UA_DATA.messageParameters.protocol_Data.oPC),
           oct2int(pl_PDU_M3UA.m3UA_DATA.messageParameters.protocol_Data.dPC),
           oct2int(pl_PDU_M3UA.m3UA_DATA.messageParameters.protocol_Data.sLS),
           pl_PDU_M3UA.m3UA_DATA.messageParameters.protocol_Data.userProtocolData)));
      if (tsp_logVerbose) {
        log("MTP3_SP_PORT: Data received -> TRANSFERind sent");
      }
    }
    else {
      // Buffering indication messages?
      if (tsp_logVerbose) {
        log("MTP3_SP_PORT: Data received, no user connected -> discard");
      }
    }
  }
  else if (ischosen(pl_PDU_M3UA.m3UA_BEAT)) {
    if (v_Entity.commStatus == aSP_Active) {
      SCTP_PORT.send
        (t_S_SCTP_Send
         (v_Entity.sCTP_Assoc_ID,
          0,
          enc_PDU_M3UA
	    (valueof
             (t_PDU_M3UA_Beat_Ack
              (pl_PDU_M3UA.m3UA_BEAT.messageParameters.heartbeat_Data))),
          tsp_SCTP_PayloadProtocolID));
      if (tsp_logVerbose) {
        log("M3UA_BEAT received -> M3UA_BEAT_Ack sent");
      }
    }
    else {
      if (tsp_logVerbose) {
        log("M3UA_BEAT received in wrong state");
      }
    }
  }
  else if (ischosen(pl_PDU_M3UA.m3UA_BEAT_Ack)) {
    if (tsp_logVerbose) {
      log("Received M3UA_BEAT_Ack -> discard");
    }
  }
  else if (ischosen(pl_PDU_M3UA.m3UA_ERR)) {
    if (tsp_logVerbose) {
      log("Received M3UA_ERR -> discard");
    }
  }
  else if (ischosen(pl_PDU_M3UA.m3UA_NOTIFY)) {
    if (tsp_logVerbose) {
      log("Received M3UA_NOTIFY -> discard");
    }
  }
  else if (ischosen(pl_PDU_M3UA.m3UA_DUNA)) {
    if (tsp_logVerbose) {
      log("Received M3UA_DUNA -> MTP-PAUSE.ind");
    }
    aff_pcs := pl_PDU_M3UA.m3UA_DUNA.messageParameters.affected_Point_Codes.pointCodes;
    for (var integer i := 0; i < lengthof(aff_pcs); i := i + 1) {
      if (aff_pcs[i].mask != '00'O) {
        /* TODO: Submit one MTP-PAUSE for each point code matching the mask. */
        log("Received M3UA_DUNA -> Affected Point Code ", aff_pcs[i], ", discarded due to unsupported mask");
        continue;
      }
      MTP3_SP_PORT.send(ts_ASP_MTP3_PAUSE(oct2int(aff_pcs[i].affected_Point_Code)));
    }
  }
  else if (ischosen(pl_PDU_M3UA.m3UA_DAVA)) {
    if (tsp_logVerbose) {
      log("Received M3UA_DAVA -> MTP-RESUME.ind");
    }
    aff_pcs := pl_PDU_M3UA.m3UA_DAVA.messageParameters.affected_Point_Codes.pointCodes;
    for (var integer i := 0; i < lengthof(aff_pcs); i := i + 1) {
      if (aff_pcs[i].mask != '00'O) {
        /* TODO: Submit one MTP-RESUME for each point code matching the mask. */
        log("Received M3UA_DAVA -> Affected Point Code ", aff_pcs[i], ", discarded due to unsupported mask");
        continue;
      }
      MTP3_SP_PORT.send(ts_ASP_MTP3_RESUME(oct2int(aff_pcs[i].affected_Point_Code)));
    }
  }
  // In server mode ASP_M3UA_DAUD messages can be received.  In response the
  // server must send ASP_M3UA_DAVA messages.  It's not checked if we're
  // servers or not.
  else if (ischosen(pl_PDU_M3UA.m3UA_DAUD)) {
    if ((v_Entity.commStatus == aSP_Inactive) or
        (v_Entity.commStatus == aSP_Inact_ASPAC_Sent) or
        (v_Entity.commStatus == aSP_Active)) {
      // Send ASP_M3UA_DAVA message.
      SCTP_PORT.send
        (t_S_SCTP_Send
         (v_Entity.sCTP_Assoc_ID,
          0,
          enc_PDU_M3UA
            (valueof
             (t_PDU_M3UA_DAVA
              (pl_PDU_M3UA.m3UA_DAUD.messageParameters.network_Appearance,
               pl_PDU_M3UA.m3UA_DAUD.messageParameters.routing_Context,
               pl_PDU_M3UA.m3UA_DAUD.messageParameters.affected_Point_Codes,
               pl_PDU_M3UA.m3UA_DAUD.messageParameters.info_String))),
          tsp_SCTP_PayloadProtocolID));
      if (tsp_logVerbose) {
        log("M3UA_DAUD received -> DAVA sent");
      }
    }
    else {
      if (tsp_logVerbose) {
        log("M3UA_DAUD received in wrong state");
      }
    }
  }
  else if (ischosen(pl_PDU_M3UA.m3UA_SCON)) {
    /* This field is not explicitly listed in Q.704 15.17.5, but it is
     * explicitly described as one of four options in:
     * Q.701 "TABLE 1" and 8.4
     * Q.704 2.4.2
     * Q.711 "Table 18" and 7.2.4
     * Q.714 "Figure D.4"
     * Hence, assign it next value available as per ITU Q.704 15.17.5: 3:
     */
    const integer mtp_status_cause := 3;
    var template (omit) integer congestion_level := omit;
    if (tsp_logVerbose) {
      log("Received M3UA_SCON -> MTP-STATUS.ind");
    }
    aff_pcs := pl_PDU_M3UA.m3UA_SCON.messageParameters.affected_Point_Codes.pointCodes;
    if (ispresent(pl_PDU_M3UA.m3UA_SCON.messageParameters.congestion_Indicators)) {
      congestion_level := oct2int(pl_PDU_M3UA.m3UA_SCON.messageParameters.congestion_Indicators.congestionLevel);
    }
    for (var integer i := 0; i < lengthof(aff_pcs); i := i + 1) {
      if (aff_pcs[i].mask != '00'O) {
        /* TODO: Submit one MTP-STATUS for each point code matching the mask. */
        log("Received M3UA_SCON -> Affected Point Code ", aff_pcs[i], ", discarded due to unsupported mask");
        continue;
      }
      MTP3_SP_PORT.send(ts_ASP_MTP3_STATUS(oct2int(aff_pcs[i].affected_Point_Code),
                                           mtp_status_cause,
                                           congestion_level := congestion_level));
    }
  }
  else if (ischosen(pl_PDU_M3UA.m3UA_DUPU)) {
    if (tsp_logVerbose) {
      log("Received M3UA_DUPU -> MTP-STATUS.ind");
    }
    aff_pcs := pl_PDU_M3UA.m3UA_DUPU.messageParameters.affected_Point_Codes.pointCodes;
    for (var integer i := 0; i < lengthof(aff_pcs); i := i + 1) {
      if (aff_pcs[i].mask != '00'O) {
        /* TODO: Submit one MTP-STATUS for each point code matching the mask. */
        log("Received M3UA_DUPU -> Affected Point Code ", aff_pcs[i], ", discarded due to unsupported mask");
        continue;
      }
      MTP3_SP_PORT.send(ts_ASP_MTP3_STATUS(oct2int(aff_pcs[i].affected_Point_Code),
                                           oct2int(pl_PDU_M3UA.m3UA_DUPU.messageParameters.user_Cause.cause),
                                           oct2int(pl_PDU_M3UA.m3UA_DUPU.messageParameters.user_Cause.user)));
    }
  }
  else if (ischosen(pl_PDU_M3UA.m3UA_DRST)) {
    if (tsp_logVerbose) {
      log("Received M3UA_DRST -> discard");
    }
  }
  // In server mode we can receive M3UA_ASPUP messages.  The answer will be a
  // M3UA_ASPUP_Ack message to the client, followed by a NTFY if AS state changed.
  else if (ischosen(pl_PDU_M3UA.m3UA_ASPUP)) {
    if (((v_Entity.commStatus == aSP_Down_commUP_Received) or
        (v_Entity.commStatus == aSP_Down_ASPUP_Sent)) and
	tsp_M3UA_Server_Mode) {
      v_Entity.commStatus := aSP_Inactive;
      SCTP_PORT.send
        (t_S_SCTP_Send
         (v_Entity.sCTP_Assoc_ID,
          0,
          enc_PDU_M3UA(valueof(t_PDU_M3UA_ASPUP_Ack)),
          tsp_SCTP_PayloadProtocolID));
      if (tsp_logVerbose) {
        log("M3UA_ASPUP received -> M3UA_ASPUP_Ack sent");
        log("Association #", v_Entity.sCTP_Assoc_ID, " state changed to: ", v_Entity.commStatus);
      }

      /* AS state change -> send NTFY (see RFC4666 4.3.4.5) */
      SCTP_PORT.send
        (t_S_SCTP_Send
          (v_Entity.sCTP_Assoc_ID,
            0,
            enc_PDU_M3UA
              (valueof
                (t_PDU_M3UA_NOTIFY
                  /* type AS-State_Change (1), Status Information AS-INACTIVE (2) */
                  (t_Status('0001'O, '0002'O), omit, v_Entity.rctx, omit))),
             tsp_SCTP_PayloadProtocolID));
      if (tsp_logVerbose) {
        log("AS state changed -> M3UA_NOTIFY sent");
      }
    }
    else {
      if (tsp_logVerbose) {
        log("M3UA_ASPUP received in wrong state or the emulation is not in " &
	    "M3UA server mode");
      }
    }
  }
  // Receives a M3UA_ASPDN message and sends a M3UA_ASPDN_Ack message in
  // response.
  else if (ischosen(pl_PDU_M3UA.m3UA_ASPDN)) {
    if ((v_Entity.commStatus == aSP_Inactive) or
        (v_Entity.commStatus == aSP_Inact_ASPAC_Sent) or
        (v_Entity.commStatus == aSP_Active)) {
      SCTP_PORT.send
        (t_S_SCTP_Send
         (v_Entity.sCTP_Assoc_ID,
          0,
          enc_PDU_M3UA(valueof(t_PDU_M3UA_ASPDN_Ack)),
          tsp_SCTP_PayloadProtocolID));
      if (v_Entity.commStatus == aSP_Active) {
        // MTP3_SP_PORT.send(ASP_MTP3_PAUSE : {});
      }
      v_Entity.commStatus := aSP_Down_commUP_Received;
      if (tsp_logVerbose) {
        log("M3UA_ASPDN received -> ASPDN_Ack sent");
        log("Association #", v_Entity.sCTP_Assoc_ID, " state changed to: ",
            v_Entity.commStatus);
      }
    }
    else {
      if (tsp_logVerbose) {
        log("ASPDN received in wrong state or the emulation is not in M3UA " &
	    "server mode");
      }
    }
  }
  // The M3UA client receives M3UA_ASPUP_Ack messages from the server.  In
  // response of a M3UA_ASPUP message sent by the client.
  else if (ischosen(pl_PDU_M3UA.m3UA_ASPUP_Ack)) {
    if (((v_Entity.commStatus == aSP_Down_ASPUP_Sent) or
        (v_Entity.commStatus == aSP_Inactive)) and
	not tsp_M3UA_Server_Mode) {
      v_Entity.commStatus := aSP_Inactive;
      if (tsp_logVerbose) {
        log("M3UA_ASPUP_Ack received -> send M3UA_ASPAC");
        log("Association #", v_Entity.sCTP_Assoc_ID, " state changed to: ",
            v_Entity.commStatus);
      }
      SCTP_PORT.send
        (t_S_SCTP_Send
         (v_Entity.sCTP_Assoc_ID,
          0,
          enc_PDU_M3UA(valueof(t_PDU_M3UA_ASPAC(omit, v_Entity.rctx, omit))),
          tsp_SCTP_PayloadProtocolID));
      // The state changes again after sending the M3UA_ASPAC message.
      v_Entity.commStatus := aSP_Inact_ASPAC_Sent;
      if (tsp_logVerbose) {
        log("Association #", v_Entity.sCTP_Assoc_ID, " state changed to: ",
            v_Entity.commStatus);
      }
    }
    else {
      if (tsp_logVerbose) {
        log("M3UA_ASPUP_Ack received in wrong state or the emulation is not " &
	    "in M3UA client mode");
      }
    }
  }
  else if (ischosen(pl_PDU_M3UA.m3UA_ASPDN_Ack)) {
    if (tsp_logVerbose) {
      log("Received M3UA_ASPDN_Ack -> discard");
    }
  }
  // M3UA_ASPAC messages are received on the server side.  The server sends a
  // M3UA_ASPAC_Ack message back to the client.  This step makes the
  // association active on both sides.
  else if (ischosen(pl_PDU_M3UA.m3UA_ASPAC)) {
    if (((v_Entity.commStatus == aSP_Inactive) or
        (v_Entity.commStatus == aSP_Inact_ASPAC_Sent)) and
	tsp_M3UA_Server_Mode) {
      v_Entity.commStatus := aSP_Active;
      if (tsp_logVerbose) {
        log("M3UA_ASPAC received -> M3UA_ASPAC_Ack sent");
	log("Association #", v_Entity.sCTP_Assoc_ID, " state changed to: ",
	    v_Entity.commStatus);
      }
      var integer v_i;
      for (v_i := 0; v_i < sizeof(v_TRANSFERreq_Buffer); v_i := v_i + 1) {
        log("Sending buffered message #", v_i);
        f_Send_MTP3_TRANSFERreq(v_TRANSFERreq_Buffer[v_i]);
      }
      v_TRANSFERreq_Buffer := {};
      // Send M3UA_ASPAC_Ack.
      SCTP_PORT.send
        (t_S_SCTP_Send
	 (v_Entity.sCTP_Assoc_ID,
	  0,
	  enc_PDU_M3UA
	    (valueof
	     (t_PDU_M3UA_ASPAC_Ack
	      (pl_PDU_M3UA.m3UA_ASPAC.messageParameters.traffic_Mode_Type,
	       pl_PDU_M3UA.m3UA_ASPAC.messageParameters.routing_Context))),
	  tsp_SCTP_PayloadProtocolID));
      // MTP3_SP_PORT.send(ASP_MTP3_RESUME : {});

      /* AS state change -> send NTFY (see RFC4666 4.3.4.5) */
      SCTP_PORT.send
        (t_S_SCTP_Send
          (v_Entity.sCTP_Assoc_ID,
            0,
            enc_PDU_M3UA
              (valueof
                (t_PDU_M3UA_NOTIFY
                  /* tag 0xd, len 8, type AS-State_Change (1), Status Information AS-ACTIVE (1) */
                  (t_Status('0001'O, '0001'O), omit, v_Entity.rctx, omit))),
             tsp_SCTP_PayloadProtocolID));
      if (tsp_logVerbose) {
        log("AS state changed -> M3UA_NOTIFY sent");
      }
    }
    else {
      if (tsp_logVerbose) {
        log("M3UA_ASPAC received in wrong state or the emulation is not in " &
	    "M3UA server mode");
      }
    }
  }
  // The client receives M3UA_ASPAC_Ack messages from the server.  The
  // association will be activated.  The buffered messages should be send here.
  else if (ischosen(pl_PDU_M3UA.m3UA_ASPAC_Ack)) {
    if (((v_Entity.commStatus == aSP_Inact_ASPAC_Sent) or
        (v_Entity.commStatus == aSP_Active)) and
	not tsp_M3UA_Server_Mode) {
      // MTP3_SP_PORT.send(ASP_MTP3_RESUME : {});
      v_Entity.commStatus := aSP_Active;
      if (tsp_logVerbose) {
        log("ASPAC_Ack received for association #", v_Entity.sCTP_Assoc_ID);
        log("Association #", v_Entity.sCTP_Assoc_ID, " state changed to: ",
            v_Entity.commStatus);
      }
      var integer v_i;
      for (v_i := 0; v_i < sizeof(v_TRANSFERreq_Buffer); v_i := v_i + 1) {
        log("Sending buffered message #", v_i);
        f_Send_MTP3_TRANSFERreq(v_TRANSFERreq_Buffer[v_i]);
      }
      v_TRANSFERreq_Buffer := {};
    }
    else {
      if (tsp_logVerbose) {
        log("M3UA_ASPAC_Ack received in wrong state on association #",
            v_Entity.sCTP_Assoc_ID, " or the emulation is not in M3UA " &
	    "client mode");
      }
    }
  }
  // Receives a M3UA_ASPIA message and sends back a M3UA_ASPIA_Ack message in
  // response.
  else if (ischosen(pl_PDU_M3UA.m3UA_ASPIA)) {
    if (v_Entity.commStatus == aSP_Active) {
      SCTP_PORT.send
        (t_S_SCTP_Send
         (v_Entity.sCTP_Assoc_ID,
          0,
          enc_PDU_M3UA
	    (valueof
             (t_PDU_M3UA_ASPIA_Ack
	      (pl_PDU_M3UA.m3UA_ASPIA.messageParameters.routing_Context))),
          tsp_SCTP_PayloadProtocolID));
      // MTP3_SP_PORT.send(ASP_MTP3_PAUSE : {});
      v_Entity.commStatus := aSP_Inactive;
      if (tsp_logVerbose) {
        log("M3UA_ASPIA received -> M3UA_ASPIA_Ack sent");
        log("Association #", v_Entity.sCTP_Assoc_ID, " state changed to: ",
          v_Entity.commStatus);
      }
    }
    else {
      if (tsp_logVerbose) {
        log("M3UA_ASPIA received in wrong state or the emulation is not " &
	    "running in M3UA server mode");
      }
    }
  }
  else if (ischosen(pl_PDU_M3UA.m3UA_ASPIA_Ack)) {
    if (tsp_logVerbose) {
      log("Received M3UA_ASPIA_Ack -> discard");
    }
  }
  else if (ischosen(pl_PDU_M3UA.m3UA_REG_REQ)) {
    if (tsp_logVerbose) {
      log("Received M3UA_REG_REQ -> discard");
    }
  }
  else if (ischosen(pl_PDU_M3UA.m3UA_REG_RSP)) {
    if (tsp_logVerbose) {
      log("Received M3UA_REG_RSP -> discard");
    }
  }
  else if (ischosen(pl_PDU_M3UA.m3UA_DEREG_REQ)) {
    if (tsp_logVerbose) {
      log("Received M3UA_DEREG_REQ -> discard");
    }
  }
  else if (ischosen(pl_PDU_M3UA.m3UA_DEREG_RSP)) {
    if (tsp_logVerbose) {
      log("Received M3UA_DEREG_RSP -> discard");
    }
  }
}

function f_handle_nonM3UA_msg(octetstring pl_PDU) runs on M3UA_CT
{
  if (v_Entity.commStatus == aSP_Active) {
    // Send TRANSFERind message.
    MTP3_SP_PORT.send(valueof
                      (tr_ASP_MTP3_TRANSFERind_sio
                       ('00'B,
                        '00'B,
                        '0000'B,
                        0,
                        0,
                        0,
                        pl_PDU)));
    if (tsp_logVerbose) {
      log("Non-M3UA DATA received -> TRANSFERind sent");
    }
  }
  else {
    if (tsp_logVerbose) {
      log("DATA received, but no user connected -> discard");
    }
  }
}

}
