diff src/g23m-gprs/sm/sm_debug.c @ 1:fa8dc04885d8

src/g23m-*: import from Magnetite
author Mychaela Falconia <falcon@freecalypso.org>
date Fri, 16 Oct 2020 06:25:50 +0000
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/g23m-gprs/sm/sm_debug.c	Fri Oct 16 06:25:50 2020 +0000
@@ -0,0 +1,557 @@
+/*----------------------------------------------------------------------------
+|  Project :  3G PS
+|  Module  :  SM
++-----------------------------------------------------------------------------
+|             Copyright 2003 Texas Instruments.
+|             All rights reserved. 
+| 
+|             This file is confidential and a trade secret of Texas 
+|             Instruments .
+|             The receipt of or possession of this file does not convey 
+|             any rights to reproduce or disclose its contents or to 
+|             manufacture, use, or sell anything it may describe, in 
+|             whole, or in part, without the specific written consent of 
+|             Texas Instruments. 
++-----------------------------------------------------------------------------
+| Purpose:    Debug functions implementation in the SM entity.
+|             For design details, see:
+|             8010.908 SM Detailed Specification
++---------------------------------------------------------------------------*/
+
+/*==== DECLARATION CONTROL =================================================*/
+
+/*==== INCLUDES =============================================================*/
+
+#include <stdio.h>
+#include "sm.h"
+
+#include "sm_timer_handler.h"
+
+/*==== CONSTS ===============================================================*/
+
+/*==== TYPES ================================================================*/
+
+/*==== LOCALS ===============================================================*/
+
+/*==== PRIVATE FUNCTIONS ====================================================*/
+
+/*==== PUBLIC FUNCTIONS =====================================================*/
+
+#ifdef DEBUG
+/*
+ * Debug function for dumping the contents of an QoS structure.
+ */
+static void sm_qos_dump_r97_qos(T_PS_qos_r97 *qos_r97)
+{
+  /*@observer@*/const char *indent = "          - ";
+
+  /*@observer@*/const char *peak_text[16] = {
+    /* NAS_PEAK_SUB  */ "SUBSCRIBED",
+    /* NAS_PEAK_1K   */ "Up to 1000 octet/s",
+    /* NAS_PEAK_2K   */ "Up to 2000 octet/s",
+    /* NAS_PEAK_4K   */ "Up to 4000 octet/s",
+    /* NAS_PEAK_8K   */ "Up to 8000 octet/s",
+    /* NAS_PEAK_16K  */ "Up to 16000 octet/s",
+    /* NAS_PEAK_32K  */ "Up to 32000 octet/s",
+    /* NAS_PEAK_64K  */ "Up to 64000 octet/s",
+    /* NAS_PEAK_128K */ "Up to 128000 octet/s",
+    /* NAS_PEAK_256K */ "Up to 256000 octet/s",
+    /* 10            */ "RESERVED - 1000 octets/s",
+    /* 11            */ "RESERVED - 1000 octets/s",
+    /* 12            */ "RESERVED - 1000 octets/s",
+    /* 13            */ "RESERVED - 1000 octets/s",
+    /* 14            */ "RESERVED - 1000 octets/s",
+    /* 15            */ "RESERVED",
+  };
+
+  /*@observer@*/const char *mean_text[32] = {
+    /* NAS_MEAN_SUB  */ "SUBSCRIBED",
+    /* NAS_MEAN_100  */ "100 octets/h",
+    /* NAS_MEAN_200  */ "200 octets/h",
+    /* NAS_MEAN_500  */ "500 octets/h",
+    /* NAS_MEAN_1K   */ "1000 octets/h",
+    /* NAS_MEAN_2K   */ "2000 octets/h",
+    /* NAS_MEAN_5K   */ "5000 octets/h",
+    /* NAS_MEAN_10K  */ "10000 octets/h",
+    /* NAS_MEAN_20K  */ "20000 octets/h",
+    /* NAS_MEAN_50K  */ "50000 octets/h",
+    /* NAS_MEAN_100K */ "100000 octets/h", 
+    /* NAS_MEAN_200K */ "200000 octets/h",
+    /* NAS_MEAN_500K */ "500000 octets/h",
+    /* NAS_MEAN_1M   */ "1000000 octets/h",
+    /* NAS_MEAN_2M   */ "2000000 octets/h",
+    /* NAS_MEAN_5M   */ "5000000 octets/h",
+    /* NAS_MEAN_10M  */ "10000000 octets/h",
+    /* NAS_MEAN_20M  */ "20000000 octets/h",
+    /* NAS_MEAN_50M  */ "50000000 octets/h",
+    /* 19            */ "RESERVED",
+    /* 20            */ "RESERVED",
+    /* 21            */ "RESERVED",
+    /* 22            */ "RESERVED",
+    /* 23            */ "RESERVED",
+    /* 24            */ "RESERVED",
+    /* 25            */ "RESERVED",
+    /* 26            */ "RESERVED",
+    /* 27            */ "RESERVED",
+    /* 28            */ "RESERVED",
+    /* 29            */ "RESERVED",
+    /* 30            */ "RESERVED",
+    /* NAS_MEAN_BEST */"BEST EFFORT"
+  };
+  /*@observer@*/const char *rel_text[8] = {
+    /* NAS_RELCLASS_SUB     */ "Subscribed reliability class",
+    /* NAS_GTP_LLC_RLC_PROT */ "Ack'ed GTP, LLC, and RLC; Protected data",
+    /* NAS_LLC_RLC_PROT     */ "Unack'ed GTP; Ack'ed LLC and RLC, Protected data",
+    /* NAS_RLC_PROT         */ "Unack'ed GTP and LLC; Ack'ed RLC, Protected data",
+    /* NAS_PROT             */ "Unack'ed GTP, LLC, and RLC, Protected data",
+    /* NAS_NO_PROT          */ "Unack'ed GTP, LLC, and RLC, Unprotected data",
+    /* 6                    */ "UNKNOWN VALUE",
+    /* 7                    */ "RESERVED"
+  };
+
+  (void)TRACE_EVENT_P3("%sDelay class:             0x%02x (Class %d)",
+        indent, qos_r97->delay, qos_r97->delay);
+  (void)TRACE_EVENT_P3("%sReliability class:       0x%02x (%s)",
+        indent, qos_r97->relclass, rel_text[(U16)qos_r97->relclass]);
+  (void)TRACE_EVENT_P3("%sPeak bitrate             0x%02x (%s)",
+        indent, qos_r97->peak, peak_text[(U16)qos_r97->peak]);
+  (void)TRACE_EVENT_P2("%sPrecedence class:        0x%02x",
+        indent, qos_r97->preced);
+  (void)TRACE_EVENT_P3("%sMean bitrate             0x%02x (%s)",
+        indent, qos_r97->mean, mean_text[(U16)qos_r97->mean]);
+}
+
+static void sm_qos_dump_r99_qos(T_PS_qos_r99 *qos_r99)
+{
+  /*@observer@*/const char *indent = "          - ";
+
+  (void)TRACE_EVENT_P3("%sTraffic class:           0x%02x   (%s)",
+        indent, qos_r99->tc,
+        (qos_r99->tc == (U8)PS_TC_CONV      ? "CONVERSATIONAL" :
+        (qos_r99->tc == (U8)PS_TC_STREAM    ? "STREAMING" :
+        (qos_r99->tc == (U8)PS_TC_INTER     ? "INTERACTIVE" :
+        (qos_r99->tc == (U8)PS_TC_BG        ? "BACKGROUND" :
+        (qos_r99->tc == (U8)PS_TC_SUB       ? "SUBSCRIBED" :
+        "UNKNOWN"))))));
+  (void)TRACE_EVENT_P3("%sDelivery order:          0x%02x   (%s)",
+        indent, qos_r99->order,
+        (qos_r99->order == (U8)PS_ORDER_YES ? "YES" :
+        (qos_r99->order == (U8)PS_ORDER_NO  ? "NO" :
+        (qos_r99->order == (U8)PS_ORDER_SUB ? "SUBSCRIBED" :
+        "UNKNOWN"))));
+  (void)TRACE_EVENT_P3("%sDeliver erroneous SDUs:  0x%02x   (%s)",
+        indent, qos_r99->del_err_sdu,
+        (qos_r99->del_err_sdu == (U8)PS_DEL_ERR_YES      ? "YES" :
+        (qos_r99->del_err_sdu == (U8)PS_DEL_ERR_NO       ? "NO" :
+        (qos_r99->del_err_sdu == (U8)PS_DEL_ERR_NODETECT ? "NODETECT" :
+        (qos_r99->del_err_sdu == (U8)PS_DEL_ERR_SUB      ? "SUBSCRIBED" :
+        "UNKNOWN")))));
+  (void)TRACE_EVENT_P3("%sMax SDU size:            0x%04x (%d octets)",
+        indent, qos_r99->max_sdu, qos_r99->max_sdu);
+  (void)TRACE_EVENT_P3("%sMax bit-rate uplink:     0x%04x (%dkbps)",
+        indent, qos_r99->max_rate_ul, qos_r99->max_rate_ul);
+  (void)TRACE_EVENT_P3("%sMax bit-rate downlink:   0x%04x (%dkbps)",
+        indent, qos_r99->max_rate_dl, qos_r99->max_rate_dl);
+  (void)TRACE_EVENT_P3("%sMax residual BER:        %dE-%d",
+        indent, qos_r99->ber.ratio_mant, qos_r99->ber.ratio_exp);
+  (void)TRACE_EVENT_P3("%sMax SDU error ratio:     %dE-%d",
+        indent, qos_r99->sdu_err_ratio.ratio_mant, 
+        qos_r99->sdu_err_ratio.ratio_exp);
+  (void)TRACE_EVENT_P3("%sTransfer delay:          0x%04x (%dms)",
+        indent, qos_r99->xfer_delay, qos_r99->xfer_delay);
+  (void)TRACE_EVENT_P3("%sTraffic handling prio:   0x%02x   (%d)",
+        indent, qos_r99->handling_pri, qos_r99->handling_pri);
+  (void)TRACE_EVENT_P3("%sGuar. bit-rate uplink:   0x%04x (%dkbps)",
+        indent, qos_r99->guar_br_ul, qos_r99->guar_br_ul);
+  (void)TRACE_EVENT_P3("%sGuar. bit-rate downlink: 0x%04x (%dkbps)",
+        indent, qos_r99->guar_br_dl, qos_r99->guar_br_dl);
+}
+
+static void sm_qos_dump_qos(T_SM_qos *qos, const char *type)
+{
+  if (qos->ctrl_qos == PS_is_R97)
+  {
+    (void)TRACE_EVENT_P1(   "          R97 %s QoS:", type);
+    sm_qos_dump_r97_qos(&qos->qos.qos_r97);
+  } else if (qos->ctrl_qos == PS_is_R99) {
+    (void)TRACE_EVENT_P1(   "          R99 %s QoS:", type);
+    sm_qos_dump_r99_qos(&qos->qos.qos_r99);
+  } else {
+    (void)TRACE_EVENT_P2("ERROR! Invalid union controller == %d in %s QoS!",
+                         qos->ctrl_qos, type);
+  }
+}
+
+static BOOL sm_debug_is_port_range(U16 low_limit, U16 high_limit)
+{
+  return (high_limit != 0 && low_limit < high_limit);
+}
+
+static /*@observer@*/char *
+sm_debug_dump_port_range(U16 low_limit, U16 high_limit)
+{
+  static char range[sizeof("65535-65535")];
+
+  /*@-bufferoverflowhigh@*/
+  if (sm_debug_is_port_range(low_limit, high_limit))
+  {
+    sprintf(range, "%5hu-%5hu", low_limit, high_limit);
+  } else {
+    sprintf(range, "%5hu", low_limit);
+  }
+  /*@=bufferoverflowhigh@*/
+  return range;
+}
+
+static U32 sm_debug_octet_as_bits(U8 octet) /*@*/
+{
+  const U32 bit_masks[16] = {
+    0000UL, 0001UL, 0010UL, 0011UL, 0100UL, 0101UL, 0110UL, 0111UL,
+    1000UL, 1001UL, 1010UL, 1011UL, 1100UL, 1101UL, 1110UL, 1111UL
+  };
+  return (bit_masks[(U16)octet >> 4] * 10000UL + bit_masks[(U16)octet & 15]);
+}
+
+static void sm_debug_dump_ipv4_tft(T_NAS_tft_pf_ipv4 *pf, U8 valid_bits)
+{
+  if ( (valid_bits & NAS_TFT_ID_PROTOCOL_OR_NEXT_HDR) != (U8)0)
+  {
+    (void)TRACE_EVENT_P1("          + IPv4 protocol number  = %hu",
+                         (U16)pf->tft_protocol);
+  }
+  if ( (valid_bits & NAS_TFT_ID_TOS_AND_MASK) != (U8)0)
+  {
+    (void)TRACE_EVENT_P2("          + IPv4 ToS and mask     = 0x%02x/0x%02x",
+                         pf->tft_tos_and_mask.tos_value,
+                         pf->tft_tos_and_mask.tos_mask);
+  }
+  if ( (valid_bits & NAS_TFT_ID_DEST_PORT_RANGE) != (U8)0)
+  {
+    (void)TRACE_EVENT_P1("          + Dest port (range)     = %s",
+                         sm_debug_dump_port_range(pf->tft_dest_port_range.low_limit, pf->tft_dest_port_range.high_limit));
+  }
+  if ( (valid_bits & NAS_TFT_ID_SRC_PORT_RANGE) != (U8)0)
+  {
+    (void)TRACE_EVENT_P1("          + Source port (range)   = %s",
+                         sm_debug_dump_port_range(pf->tft_src_port_range.low_limit, pf->tft_src_port_range.high_limit));
+  }
+  if ( (valid_bits & NAS_TFT_ID_IPSEC_SPI) != (U8)0)
+  {
+    (void)TRACE_EVENT_P1("          + IPv4 IPSEC SPI        = %08x",
+                         pf->tft_ipsec_spi);
+  }
+  if ( (valid_bits & NAS_TFT_ID_IPv4_SRC_ADDR_MASK) != (U8)0)
+  {
+    U8 *a4 = pf->tft_ipv4_src_addr_mask.tft_ipv4_addr;
+    (void)TRACE_EVENT_P8("          + IPv4 src address/mask = %hu.%hu.%hu.%hu"
+                         "/%hu.%hu.%hu.%hu",
+                         (U16)a4[0], (U16)a4[1], (U16)a4[2], (U16)a4[3],
+                         (U16)a4[0], (U16)a4[1], (U16)a4[2], (U16)a4[3]);
+  }
+}
+
+static void sm_debug_dump_ipv6_tft(T_NAS_tft_pf_ipv6 *pf, U8 valid_bits)
+{
+  if ( (valid_bits & NAS_TFT_ID_PROTOCOL_OR_NEXT_HDR) != (U8)0)
+  {
+    (void)TRACE_EVENT_P1("          + IPv6 next header      = %hu",
+                         (U16)pf->tft_next_hdr);
+  }
+  if ( (valid_bits & NAS_TFT_ID_TOS_AND_MASK) != (U8)0)
+  {
+    (void)TRACE_EVENT_P2("          + IPv6 traffic class/mask= 0x%02x/0x%02x",
+                         pf->tft_tos_and_mask.tos_value,
+                         pf->tft_tos_and_mask.tos_mask);
+  }
+  if ( (valid_bits & NAS_TFT_ID_DEST_PORT_RANGE) != (U8)0)
+  {
+    (void)TRACE_EVENT_P1("          + Dest port (range)     = %s",
+                         sm_debug_dump_port_range(pf->tft_dest_port_range.low_limit, pf->tft_dest_port_range.high_limit));
+  }
+  if ( (valid_bits & NAS_TFT_ID_SRC_PORT_RANGE) != (U8)0)
+  {
+    (void)TRACE_EVENT_P1("          + Source port (range)   = %s",
+                         sm_debug_dump_port_range(pf->tft_src_port_range.low_limit, pf->tft_src_port_range.high_limit));
+  }
+  if ( (valid_bits & NAS_TFT_ID_IPSEC_SPI) != (U8)0)
+  {
+    (void)TRACE_EVENT_P1("          + IPv6 IPSEC SPI        = %08x",
+                         pf->tft_ipsec_spi);
+  }
+  if ( (valid_bits & NAS_TFT_ID_FLOW_LABEL) != (U8)0)
+  {
+    (void)TRACE_EVENT_P1("          + IPv6 flow label       = %06x",
+                         pf->tft_flow_label);
+  }
+  if ( (valid_bits & NAS_TFT_ID_IPv6_SRC_ADDR_MASK) != (U8)0)
+  {
+    char src_addr [SM_SIZE_FORMATTED_IPv6_ADDR],
+         addr_mask[SM_SIZE_FORMATTED_IPv6_ADDR];
+    (void)sm_format_ipv6_addr(pf->tft_ipv6_src_addr_mask.tft_ipv6_addr, src_addr);
+    (void)sm_format_ipv6_addr(pf->tft_ipv6_src_addr_mask.tft_ipv6_mask, addr_mask);
+    (void)TRACE_EVENT_P2("          + IPv6 src address/mask = %s/%s",
+                         src_addr, addr_mask);
+  }
+}
+
+static void sm_debug_dump_tft_pf(T_NAS_tft_pf *tft_pf, U16 index)
+{
+  (void)TRACE_EVENT_P3("          #%-2u: ID=%u, precedence=%3u, valid_mask=%08ul",
+                       index, tft_pf->tft_pf_precedence,
+                       sm_debug_octet_as_bits(tft_pf->tft_pf_valid_bits));
+      if (tft_pf->ctrl_tft_pf_entry == NAS_is_tft_pf_ipv4) {
+        sm_debug_dump_ipv4_tft(&tft_pf->tft_pf_entry.tft_pf_ipv4,
+                               tft_pf->tft_pf_valid_bits);
+      } else if (tft_pf->ctrl_tft_pf_entry == NAS_is_tft_pf_ipv6) {
+        sm_debug_dump_ipv6_tft(&tft_pf->tft_pf_entry.tft_pf_ipv6,
+                               tft_pf->tft_pf_valid_bits);
+      } else {
+	(void)TRACE_EVENT_P1("  ERROR!   Wrong union controller (%d) "
+			     "for tft_pf_entry; discarded...",
+			     tft_pf->ctrl_tft_pf_entry);
+      }
+}
+
+static void sm_debug_dump_tft(T_SM_tft *tft)
+{
+  if (tft->ptr_tft_pf != NULL && tft->c_tft_pf > (U8)0)
+  {
+    U16 index;
+
+    (void)TRACE_EVENT_P3("          TFT [%08x] with %d filters (mask 0b%08ul)",
+                         tft->ptr_tft_pf, tft->c_tft_pf,
+                         sm_debug_octet_as_bits(tft->tft_precence_mask));
+    for (index = 0; index < (U16)NAS_SIZE_TFT_FILTER; index++) {
+      if ( (tft->tft_precence_mask & (1UL << index)) != 0)
+      {
+        sm_debug_dump_tft_pf(&tft->ptr_tft_pf[index], index);
+      }
+    }
+  } else {
+    (void)TRACE_EVENT   ("          TFT [  NULL  ]");
+  }
+}
+
+/*@observer@*/char *
+sm_format_ipv6_addr(U8 *addr, /*@out@*/ /*@returned@*/ char *dest)
+{
+  /*@-bufferoverflowhigh@*/
+  (void)sprintf(dest, "%04hx:%04hx:%04hx:%04hx:%04hx:%04hx:%04hx:%04hx",
+        ((U16)addr[ 0] << 8) | (U16)addr[ 1], ((U16)addr[ 2] << 8) | (U16)addr[ 3],
+        ((U16)addr[ 4] << 8) | (U16)addr[ 5], ((U16)addr[ 6] << 8) | (U16)addr[ 7],
+        ((U16)addr[ 8] << 8) | (U16)addr[ 9], ((U16)addr[10] << 8) | (U16)addr[11],
+        ((U16)addr[12] << 8) | (U16)addr[13], ((U16)addr[14] << 8) | (U16)addr[15]);
+  /*@=bufferoverflowhigh@*/
+  return dest;
+}
+
+static void sm_format_ip_address(T_NAS_ip *ip_addr, /*@out@*/char *dest)
+{
+  if (ip_addr->ctrl_ip_address == NAS_is_ip_not_present)
+  {
+    strcpy(dest, "NOT_PRESENT");
+  } else if (ip_addr->ctrl_ip_address == NAS_is_ipv4) {
+    U8  *ptr_addr = ip_addr->ip_address.ipv4_addr.a4;
+  /*@-bufferoverflowhigh@*/
+    (void)sprintf(dest, "%hu.%hu.%hu.%hu",
+		   (U16)ptr_addr[0], (U16)ptr_addr[1],
+		   (U16)ptr_addr[2], (U16)ptr_addr[3]);
+  /*@=bufferoverflowhigh@*/
+  } else if (ip_addr->ctrl_ip_address == NAS_is_ipv6) {
+    (void)sm_format_ipv6_addr(ip_addr->ip_address.ipv6_addr.a6, dest);
+  } else {
+    strcpy(dest, "INVALID_CTRL");
+  }
+}
+
+struct T_SM_FLAG_STRING {
+  U16             flag;
+  /*@null@*/ /*@observer@*/const char *name;
+};
+
+static const struct T_SM_FLAG_STRING sm_context_flags[7] = {
+  {(U16)SM_CONTEXT_FLAG_COMP_PARAMS,            "COMP_PARAMS"},
+  {(U16)SM_CONTEXT_FLAG_STARTED_DURING_SUSPEND, "STARTED_DURING_SUSPEND"},
+  {(U16)SM_CONTEXT_FLAG_SECONDARY_CONTEXT,      "SECONDARY_CONTEXT"},
+  {(U16)SM_CONTEXT_FLAG_PENDING_DEALLOCATION,   "PENDING_DEALLOCATION"},
+  {(U16)SM_CONTEXT_FLAG_PENDING_REACTIVATION,   "PENDING_REACTIVATION"},
+  {(U16)SM_CONTEXT_FLAG_PFI_PRESENT,            "PFI_PRESENT"},
+  {(U16)0, NULL}
+};
+
+static void sm_flags_to_string(const struct T_SM_FLAG_STRING *flag_string,
+			       /*@out@*/ char *dest,
+			       U16 flags)
+{
+  U16                      index, flag_count;
+
+  flag_count = 0;
+
+  for (index = 0; index < (U16)16 && flag_string->flag != 0; index++)
+  {
+    if ((flags & flag_string->flag) != 0 && flag_string->name != NULL)
+    {
+      if (flag_count != 0)
+      {
+	*dest++ = ','; 	*dest++ = ' ';
+      } else {
+	flag_count++;
+      }
+      strcpy(dest, flag_string->name);
+      dest = &dest[strlen(flag_string->name)];
+    } /* if */
+    flag_string++;
+  } /* for */
+  *dest = '\0';
+}
+
+/*@observer@*/static const char *sm_pdp_type_name(U8 pdp_type)
+{
+  switch ((T_SMREG_pdp_type) pdp_type) {
+  case SMREG_PDP_PPP:             return "PPP";
+  case SMREG_PDP_IPV4:            return "IPv4";
+  case SMREG_PDP_IPV6:            return "IPv6";
+  case SMREG_PDP_EMPTY:           return "DYNAMIC";
+  default:                        return "INVALID!";
+  }
+}
+
+/*@observer@*/const char *sm_timer_name(U8 timer)
+{
+  switch ((T_SM_TIMER_TYPE) timer) {
+  case SM_TIMER_NONE:            return "NONE";
+  case SM_TIMER_T3380:           return "T3380";
+  case SM_TIMER_T3381:           return "T3381";
+  case SM_TIMER_T3390:           return "T3390";
+  default:                       return "UNKNOWN";
+  }
+}
+
+/*@observer@*/static const char *sm_pfi_name(U8 pfi)
+{
+  switch ((T_PS_pkt_flow_id) pfi) {
+  case PS_PFI_BEST_EFFORT:      return "Best Effort";
+  case PS_PFI_SIGNALING:        return "Signalling";
+  case PS_PFI_SMS:              return "SMS";
+  case PS_PKT_FLOW_ID_NOT_PRES: return "NONE";
+  default:                       return "UNKNOWN";
+  }
+}
+
+static void sm_format_apn(T_SMREG_apn *apn, /*@out@*/char *dest)
+{
+  U16  index;
+
+  assert(apn != NULL && apn->c_apn_buf > (U8)0);
+
+  /* First, copy (all) APN text skipping first length byte. */
+  if (apn == NULL) { 
+    return; /*Fix for Lint warning*/
+  }
+
+  memcpy(dest, &apn->apn_buf[1], (size_t)apn->c_apn_buf - 1);
+
+  index   = (U16)apn->apn_buf[0];
+  while (index < (U16)apn->c_apn_buf) {
+    dest[index] = '.';
+    index += (U16)apn->apn_buf[index];
+  }
+  dest[(U16)apn->c_apn_buf - 1] = '\0';
+}
+
+/*@observer@*/char *sm_context_bitfield(/*@out@*/ /*@returned@*/char *status,
+					U16 status_bits)
+{
+  U16 index;
+
+  for (index = 0; index < (U16)SM_MAX_NSAPI_OFFSET; index++)
+  {
+    U16 nsapi = sm_index_to_nsapi(index);
+    status[index] = (sm_is_nsapi_in_nsapi_set(nsapi, status_bits) ? '1' : '0');
+  }
+  status[SM_MAX_NSAPI_OFFSET] = '\0';
+
+  return status;
+}
+
+void sm_dump_state(void)
+{
+  int  nsapi;
+  char req_addr[SM_SIZE_FORMATTED_IPv6_ADDR],
+       neg_addr[SM_SIZE_FORMATTED_IPv6_ADDR];
+  char context_status[SM_MAX_NSAPI_OFFSET + 1];
+
+  (void)TRACE_FUNCTION("sm_dump_state");
+
+  (void)TRACE_EVENT_P3("SM is active in a(n) %s network in %s RAT; SM is%s suspended",
+		       (sm_get_current_nw_release() == PS_SGSN_98_OLDER ? "pre-R99" :
+			(sm_get_current_nw_release() == PS_SGSN_99_ONWARDS ? "R99" : "UNKNOWN")),
+		       (sm_get_current_rat() == PS_RAT_GSM       ? "GSM" :
+			(sm_get_current_rat() == PS_RAT_UMTS_FDD ? "UMTS" : "NONE")),
+		       (sm_is_suspended() ? "" : " not"));
+  (void)TRACE_EVENT_P1("Context activation status: %s",
+		       sm_context_bitfield(context_status, sm_data.sm_context_activation_status));
+
+  for (nsapi = (int)NAS_NSAPI_5; nsapi < NAS_SIZE_NSAPI; nsapi++)
+  {
+    struct T_SM_CONTEXT_DATA *context;
+
+    context = sm_get_context_data_from_nsapi(nsapi);
+    if (context != NULL)
+    {
+      char flags[256];
+
+      (void)TRACE_EVENT_P7("NSAPI%3d: [%08x] nsapi=%d, ti=%d, linked_ti=%d, "
+			   "active_timer=%s, timeouts=%d",
+			   nsapi, context, context->nsapi, context->ti,
+			   context->linked_ti,
+			   sm_timer_name(context->active_timer),
+			   context->timeouts);
+      (void)TRACE_EVENT_P4("          sapi=%d, radio_prio=%d, pfi=%d (%s)",
+			   context->sapi, context->radio_prio, context->pfi,
+                           sm_pfi_name(context->pfi));
+
+      sm_flags_to_string(sm_context_flags, flags, (U16)context->flags);
+      (void)TRACE_EVENT_P2("          flags=0x%02x (%s)",
+                           context->flags, flags);
+      (void)TRACE_EVENT_P1("          Network Control state:            %s",
+			   sm_network_control_state(context));
+      (void)TRACE_EVENT_P1("          Context Control state:            %s",
+			   sm_context_control_state(context));
+      (void)TRACE_EVENT_P1("          Context Deactivate Control state: %s",
+			   sm_context_deactivate_control_state(context));
+      (void)TRACE_EVENT_P1("          User Plane Control state:         %s",
+			   sm_user_plane_control_state(context));
+      if (!sm_is_secondary(context))
+      {
+      sm_format_ip_address(&context->requested_address,  req_addr);
+      sm_format_ip_address(&context->negotiated_address, neg_addr);
+      (void)TRACE_EVENT_P4("          PDP type=0x%02x (%s), "
+			   "requested_address=%s, negotiated_address=%s",
+			   context->pdp_type, 
+			   sm_pdp_type_name(context->pdp_type),
+			   req_addr, neg_addr);
+      }
+      sm_qos_dump_qos(&context->minimum_qos, "minimum");
+      sm_qos_dump_qos(&context->requested_qos, "requested");
+      sm_qos_dump_qos(&context->accepted_qos, "negotiated");
+      if (!sm_is_secondary(context))
+      {
+        if (context->apn == NULL)
+        {
+          (void)TRACE_EVENT   ("          APN [  NULL  ]");
+        } else {
+          char apn[103];
+          sm_format_apn(context->apn, apn);
+          (void)TRACE_EVENT_P2("          APN [%08x]: %s", context->apn, apn);
+        }
+      }
+      sm_debug_dump_tft(&context->active_tft);
+    } else {
+      (void)TRACE_EVENT_P1("NSAPI%3d: [  NULL  ]", nsapi);
+    }
+  }
+}
+#endif /* DEBUG */
+
+/*==== END OF FILE ==========================================================*/