Merge commit '47ee693170d589f760c4a9c7a5c4ad0b289aa65d'
authorSylvain Munaut <tnt@246tNt.com>
Mon, 20 Sep 2010 19:02:28 +0000 (21:02 +0200)
committerSylvain Munaut <tnt@246tNt.com>
Mon, 20 Sep 2010 19:02:28 +0000 (21:02 +0200)
1  2 
src/shared/libosmocore/src/gsm0808.c
src/shared/libosmocore/src/gsm48_ie.c

index 42a73b9,0000000..636c211
mode 100644,000000..100644
--- /dev/null
@@@ -1,313 -1,0 +1,313 @@@
-               [GSM0808_IE_CIRCUIT_IDENTITY_CODE]  = { TLV_TYPE_TV },
 +/* (C) 2009,2010 by Holger Hans Peter Freyther <zecke@selfish.org>
 + * (C) 2009,2010 by On-Waves
 + * All Rights Reserved
 + *
 + * This program is free software; you can redistribute it and/or modify
 + * it under the terms of the GNU General Public License as published by
 + * the Free Software Foundation; either version 2 of the License, or
 + * (at your option) any later version.
 + *
 + * This program is distributed in the hope that it will be useful,
 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 + * GNU General Public License for more details.
 + *
 + * You should have received a copy of the GNU General Public License along
 + * with this program; if not, write to the Free Software Foundation, Inc.,
 + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 + *
 + */
 +
 +#include <osmocore/gsm0808.h>
 +#include <osmocore/protocol/gsm_08_08.h>
 +#include <osmocore/gsm48.h>
 +
 +#include <arpa/inet.h>
 +
 +#define BSSMAP_MSG_SIZE 512
 +#define BSSMAP_MSG_HEADROOM 128
 +
 +static void put_data_16(uint8_t *data, const uint16_t val)
 +{
 +      memcpy(data, &val, sizeof(val));
 +}
 +
 +struct msgb *gsm0808_create_layer3(struct msgb *msg_l3, uint16_t nc, uint16_t cc, int lac, uint16_t _ci)
 +{
 +      uint8_t *data;
 +      uint8_t *ci;
 +      struct msgb* msg;
 +      struct gsm48_loc_area_id *lai;
 +
 +      msg  = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
 +                                 "bssmap cmpl l3");
 +      if (!msg)
 +              return NULL;
 +
 +      /* create the bssmap header */
 +      msg->l3h = msgb_put(msg, 2);
 +      msg->l3h[0] = 0x0;
 +
 +      /* create layer 3 header */
 +      data = msgb_put(msg, 1);
 +      data[0] = BSS_MAP_MSG_COMPLETE_LAYER_3;
 +
 +      /* create the cell header */
 +      data = msgb_put(msg, 3);
 +      data[0] = GSM0808_IE_CELL_IDENTIFIER;
 +      data[1] = 1 + sizeof(*lai) + 2;
 +      data[2] = CELL_IDENT_WHOLE_GLOBAL;
 +
 +      lai = (struct gsm48_loc_area_id *) msgb_put(msg, sizeof(*lai));
 +      gsm48_generate_lai(lai, cc, nc, lac);
 +
 +      ci = msgb_put(msg, 2);
 +      put_data_16(ci, htons(_ci));
 +
 +      /* copy the layer3 data */
 +      data = msgb_put(msg, msgb_l3len(msg_l3) + 2);
 +      data[0] = GSM0808_IE_LAYER_3_INFORMATION;
 +      data[1] = msgb_l3len(msg_l3);
 +      memcpy(&data[2], msg_l3->l3h, data[1]);
 +
 +      /* update the size */
 +      msg->l3h[1] = msgb_l3len(msg) - 2;
 +
 +      return msg;
 +}
 +
 +struct msgb *gsm0808_create_reset(void)
 +{
 +      struct msgb *msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
 +                                             "bssmap: reset");
 +      if (!msg)
 +              return NULL;
 +
 +      msg->l3h = msgb_put(msg, 6);
 +      msg->l3h[0] = BSSAP_MSG_BSS_MANAGEMENT;
 +      msg->l3h[1] = 0x04;
 +      msg->l3h[2] = 0x30;
 +      msg->l3h[3] = 0x04;
 +      msg->l3h[4] = 0x01;
 +      msg->l3h[5] = 0x20;
 +      return msg;
 +}
 +
 +struct msgb *gsm0808_create_clear_complete(void)
 +{
 +      struct msgb *msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
 +                                             "bssmap: clear complete");
 +      if (!msg)
 +              return NULL;
 +
 +      msg->l3h = msgb_put(msg, 3);
 +      msg->l3h[0] = BSSAP_MSG_BSS_MANAGEMENT;
 +      msg->l3h[1] = 1;
 +      msg->l3h[2] = BSS_MAP_MSG_CLEAR_COMPLETE;
 +
 +      return msg;
 +}
 +
 +struct msgb *gsm0808_create_cipher_complete(struct msgb *layer3, uint8_t alg_id)
 +{
 +      struct msgb *msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
 +                                             "cipher-complete");
 +      if (!msg)
 +              return NULL;
 +
 +        /* send response with BSS override for A5/1... cheating */
 +      msg->l3h = msgb_put(msg, 3);
 +      msg->l3h[0] = BSSAP_MSG_BSS_MANAGEMENT;
 +      msg->l3h[1] = 0xff;
 +      msg->l3h[2] = BSS_MAP_MSG_CIPHER_MODE_COMPLETE;
 +
 +      /* include layer3 in case we have at least two octets */
 +      if (layer3 && msgb_l3len(layer3) > 2) {
 +              msg->l4h = msgb_put(msg, msgb_l3len(layer3) + 2);
 +              msg->l4h[0] = GSM0808_IE_LAYER_3_MESSAGE_CONTENTS;
 +              msg->l4h[1] = msgb_l3len(layer3);
 +              memcpy(&msg->l4h[2], layer3->l3h, msgb_l3len(layer3));
 +      }
 +
 +      /* and the optional BSS message */
 +      msg->l4h = msgb_put(msg, 2);
 +      msg->l4h[0] = GSM0808_IE_CHOSEN_ENCR_ALG;
 +      msg->l4h[1] = alg_id;
 +
 +      /* update the size */
 +      msg->l3h[1] = msgb_l3len(msg) - 2;
 +      return msg;
 +}
 +
 +struct msgb *gsm0808_create_cipher_reject(uint8_t cause)
 +{
 +      struct msgb *msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
 +                                             "bssmap: clear complete");
 +      if (!msg)
 +              return NULL;
 +
 +      msg->l3h = msgb_put(msg, 3);
 +      msg->l3h[0] = BSSAP_MSG_BSS_MANAGEMENT;
 +      msg->l3h[1] = 2;
 +      msg->l3h[2] = BSS_MAP_MSG_CIPHER_MODE_REJECT;
 +      msg->l3h[3] = cause;
 +
 +      return msg;
 +}
 +
 +struct msgb *gsm0808_create_classmark_update(const uint8_t *classmark_data, uint8_t length)
 +{
 +      struct msgb *msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
 +                                             "classmark-update");
 +      if (!msg)
 +              return NULL;
 +
 +      msg->l3h = msgb_put(msg, 3);
 +      msg->l3h[0] = BSSAP_MSG_BSS_MANAGEMENT;
 +      msg->l3h[1] = 0xff;
 +      msg->l3h[2] = BSS_MAP_MSG_CLASSMARK_UPDATE;
 +
 +      msg->l4h = msgb_put(msg, length);
 +      memcpy(msg->l4h, classmark_data, length);
 +
 +      /* update the size */
 +      msg->l3h[1] = msgb_l3len(msg) - 2;
 +      return msg;
 +}
 +
 +struct msgb *gsm0808_create_sapi_reject(uint8_t link_id)
 +{
 +      struct msgb *msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
 +                                             "bssmap: sapi 'n' reject");
 +      if (!msg)
 +              return NULL;
 +
 +      msg->l3h = msgb_put(msg, 5);
 +      msg->l3h[0] = BSSAP_MSG_BSS_MANAGEMENT;
 +      msg->l3h[1] = 3;
 +      msg->l3h[2] = BSS_MAP_MSG_SAPI_N_REJECT;
 +      msg->l3h[3] = link_id;
 +      msg->l3h[4] = GSM0808_CAUSE_BSS_NOT_EQUIPPED;
 +
 +      return msg;
 +}
 +
 +struct msgb *gsm0808_create_assignment_completed(uint8_t rr_cause,
 +                                               uint8_t chosen_channel, uint8_t encr_alg_id,
 +                                               uint8_t speech_mode)
 +{
 +      uint8_t *data;
 +
 +      struct msgb *msg = msgb_alloc(35, "bssmap: ass compl");
 +      if (!msg)
 +              return NULL;
 +
 +      msg->l3h = msgb_put(msg, 3);
 +      msg->l3h[0] = BSSAP_MSG_BSS_MANAGEMENT;
 +      msg->l3h[1] = 0xff;
 +      msg->l3h[2] = BSS_MAP_MSG_ASSIGMENT_COMPLETE;
 +
 +      /* write 3.2.2.22 */
 +      data = msgb_put(msg, 2);
 +      data[0] = GSM0808_IE_RR_CAUSE;
 +      data[1] = rr_cause;
 +
 +      /* write cirtcuit identity  code 3.2.2.2 */
 +      /* write cell identifier 3.2.2.17 */
 +      /* write chosen channel 3.2.2.33 when BTS picked it */
 +      data = msgb_put(msg, 2);
 +      data[0] = GSM0808_IE_CHOSEN_CHANNEL;
 +      data[1] = chosen_channel;
 +
 +      /* write chosen encryption algorithm 3.2.2.44 */
 +      data = msgb_put(msg, 2);
 +      data[0] = GSM0808_IE_CHOSEN_ENCR_ALG;
 +      data[1] = encr_alg_id;
 +
 +      /* write circuit pool 3.2.2.45 */
 +      /* write speech version chosen: 3.2.2.51 when BTS picked it */
 +      if (speech_mode != 0) {
 +              data = msgb_put(msg, 2);
 +              data[0] = GSM0808_IE_SPEECH_VERSION;
 +              data[1] = speech_mode;
 +      }
 +
 +      /* write LSA identifier 3.2.2.15 */
 +
 +
 +      /* update the size */
 +      msg->l3h[1] = msgb_l3len(msg) - 2;
 +      return msg;
 +}
 +
 +struct msgb *gsm0808_create_assignment_failure(uint8_t cause, uint8_t *rr_cause)
 +{
 +      uint8_t *data;
 +      struct msgb *msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
 +                                             "bssmap: ass fail");
 +      if (!msg)
 +              return NULL;
 +
 +      msg->l3h = msgb_put(msg, 6);
 +      msg->l3h[0] = BSSAP_MSG_BSS_MANAGEMENT;
 +      msg->l3h[1] = 0xff;
 +      msg->l3h[2] = BSS_MAP_MSG_ASSIGMENT_FAILURE;
 +      msg->l3h[3] = GSM0808_IE_CAUSE;
 +      msg->l3h[4] = 1;
 +      msg->l3h[5] = cause;
 +
 +      /* RR cause 3.2.2.22 */
 +      if (rr_cause) {
 +              data = msgb_put(msg, 2);
 +              data[0] = GSM0808_IE_RR_CAUSE;
 +              data[1] = *rr_cause;
 +      }
 +
 +      /* Circuit pool 3.22.45 */
 +      /* Circuit pool list 3.2.2.46 */
 +
 +      /* update the size */
 +      msg->l3h[1] = msgb_l3len(msg) - 2;
 +      return msg;
 +}
 +
 +void gsm0808_prepend_dtap_header(struct msgb *msg, uint8_t link_id)
 +{
 +      uint8_t *hh = msgb_push(msg, 3);
 +      hh[0] = BSSAP_MSG_DTAP;
 +      hh[1] = link_id;
 +      hh[2] = msg->len - 3;
 +}
 +
 +static const struct tlv_definition bss_att_tlvdef = {
 +      .def = {
 +              [GSM0808_IE_IMSI]                   = { TLV_TYPE_TLV },
 +              [GSM0808_IE_TMSI]                   = { TLV_TYPE_TLV },
 +              [GSM0808_IE_CELL_IDENTIFIER_LIST]   = { TLV_TYPE_TLV },
 +              [GSM0808_IE_CHANNEL_NEEDED]         = { TLV_TYPE_TV },
 +              [GSM0808_IE_EMLPP_PRIORITY]         = { TLV_TYPE_TV },
 +              [GSM0808_IE_CHANNEL_TYPE]           = { TLV_TYPE_TLV },
 +              [GSM0808_IE_PRIORITY]               = { TLV_TYPE_TLV },
-               [GSM0808_IE_SERVICE_HANDOVER]       = { TLV_TYPE_TV},
++              [GSM0808_IE_CIRCUIT_IDENTITY_CODE]  = { TLV_TYPE_FIXED, 2 },
 +              [GSM0808_IE_DOWNLINK_DTX_FLAG]      = { TLV_TYPE_TV },
 +              [GSM0808_IE_INTERFERENCE_BAND_TO_USE] = { TLV_TYPE_TV },
 +              [GSM0808_IE_CLASSMARK_INFORMATION_T2] = { TLV_TYPE_TLV },
 +              [GSM0808_IE_GROUP_CALL_REFERENCE]   = { TLV_TYPE_TLV },
 +              [GSM0808_IE_TALKER_FLAG]            = { TLV_TYPE_T },
 +              [GSM0808_IE_CONFIG_EVO_INDI]        = { TLV_TYPE_TV },
 +              [GSM0808_IE_LSA_ACCESS_CTRL_SUPPR]  = { TLV_TYPE_TV },
++              [GSM0808_IE_SERVICE_HANDOVER]       = { TLV_TYPE_TLV },
 +              [GSM0808_IE_ENCRYPTION_INFORMATION] = { TLV_TYPE_TLV },
 +              [GSM0808_IE_CIPHER_RESPONSE_MODE]   = { TLV_TYPE_TV },
 +              [GSM0808_IE_CELL_IDENTIFIER]        = { TLV_TYPE_TLV },
 +              [GSM0808_IE_CHOSEN_CHANNEL]         = { TLV_TYPE_TV },
 +              [GSM0808_IE_LAYER_3_INFORMATION]    = { TLV_TYPE_TLV },
 +              [GSM0808_IE_SPEECH_VERSION]         = { TLV_TYPE_TV },
 +              [GSM0808_IE_CHOSEN_ENCR_ALG]        = { TLV_TYPE_TV },
 +      },
 +};
 +
 +const struct tlv_definition *gsm0808_att_tlvdef()
 +{
 +      return &bss_att_tlvdef;
 +}
index 71910c6,0000000..0e27088
mode 100644,000000..100644
--- /dev/null
@@@ -1,1094 -1,0 +1,1095 @@@
-       lv[1] = called->plan;
 +/* GSM Mobile Radio Interface Layer 3 messages
 + * 3GPP TS 04.08 version 7.21.0 Release 1998 / ETSI TS 100 940 V7.21.0 */
 +
 +/* (C) 2008 by Harald Welte <laforge@gnumonks.org>
 + * (C) 2009-2010 by Andreas Eversberg
 + *
 + * All Rights Reserved
 + *
 + * This program is free software; you can redistribute it and/or modify
 + * it under the terms of the GNU General Public License as published by
 + * the Free Software Foundation; either version 2 of the License, or
 + * (at your option) any later version.
 + *
 + * This program is distributed in the hope that it will be useful,
 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 + * GNU General Public License for more details.
 + *
 + * You should have received a copy of the GNU General Public License along
 + * with this program; if not, write to the Free Software Foundation, Inc.,
 + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 + *
 + */
 +
 +
 +#include <stdint.h>
 +#include <string.h>
 +#include <errno.h>
 +
 +#include <osmocore/utils.h>
 +#include <osmocore/msgb.h>
 +#include <osmocore/tlv.h>
 +#include <osmocore/mncc.h>
 +#include <osmocore/protocol/gsm_04_08.h>
 +#include <osmocore/gsm48_ie.h>
 +
 +static const char bcd_num_digits[] = {
 +      '0', '1', '2', '3', '4', '5', '6', '7',
 +      '8', '9', '*', '#', 'a', 'b', 'c', '\0'
 +};
 +
 +/* decode a 'called/calling/connect party BCD number' as in 10.5.4.7 */
 +int gsm48_decode_bcd_number(char *output, int output_len,
 +                          const uint8_t *bcd_lv, int h_len)
 +{
 +      uint8_t in_len = bcd_lv[0];
 +      int i;
 +
 +      for (i = 1 + h_len; i <= in_len; i++) {
 +              /* lower nibble */
 +              output_len--;
 +              if (output_len <= 1)
 +                      break;
 +              *output++ = bcd_num_digits[bcd_lv[i] & 0xf];
 +
 +              /* higher nibble */
 +              output_len--;
 +              if (output_len <= 1)
 +                      break;
 +              *output++ = bcd_num_digits[bcd_lv[i] >> 4];
 +      }
 +      if (output_len >= 1)
 +              *output++ = '\0';
 +
 +      return 0;
 +}
 +
 +/* convert a single ASCII character to call-control BCD */
 +static int asc_to_bcd(const char asc)
 +{
 +      int i;
 +
 +      for (i = 0; i < ARRAY_SIZE(bcd_num_digits); i++) {
 +              if (bcd_num_digits[i] == asc)
 +                      return i;
 +      }
 +      return -EINVAL;
 +}
 +
 +/* convert a ASCII phone number to 'called/calling/connect party BCD number' */
 +int gsm48_encode_bcd_number(uint8_t *bcd_lv, uint8_t max_len,
 +                    int h_len, const char *input)
 +{
 +      int in_len = strlen(input);
 +      int i;
 +      uint8_t *bcd_cur = bcd_lv + 1 + h_len;
 +
 +      /* two digits per byte, plus type byte */
 +      bcd_lv[0] = in_len/2 + h_len;
 +      if (in_len % 2)
 +              bcd_lv[0]++;
 +
 +      if (bcd_lv[0] > max_len)
 +              return -EIO;
 +
 +      for (i = 0; i < in_len; i++) {
 +              int rc = asc_to_bcd(input[i]);
 +              if (rc < 0)
 +                      return rc;
 +              if (i % 2 == 0)
 +                      *bcd_cur = rc;
 +              else
 +                      *bcd_cur++ |= (rc << 4);
 +      }
 +      /* append padding nibble in case of odd length */
 +      if (i % 2)
 +              *bcd_cur++ |= 0xf0;
 +
 +      /* return how many bytes we used */
 +      return (bcd_cur - bcd_lv);
 +}
 +
 +/* decode 'bearer capability' */
 +int gsm48_decode_bearer_cap(struct gsm_mncc_bearer_cap *bcap,
 +                           const uint8_t *lv)
 +{
 +      uint8_t in_len = lv[0];
 +      int i, s;
 +
 +      if (in_len < 1)
 +              return -EINVAL;
 +
 +      bcap->speech_ver[0] = -1; /* end of list, of maximum 7 values */
 +
 +      /* octet 3 */
 +      bcap->transfer = lv[1] & 0x07;
 +      bcap->mode = (lv[1] & 0x08) >> 3;
 +      bcap->coding = (lv[1] & 0x10) >> 4;
 +      bcap->radio = (lv[1] & 0x60) >> 5;
 +
 +      if (bcap->transfer == GSM_MNCC_BCAP_SPEECH) {
 +              i = 1;
 +              s = 0;
 +              while(!(lv[i] & 0x80)) {
 +                      i++; /* octet 3a etc */
 +                      if (in_len < i)
 +                              return 0;
 +                      bcap->speech_ver[s++] = lv[i] & 0x0f;
 +                      bcap->speech_ver[s] = -1; /* end of list */
 +                      if (i == 2) /* octet 3a */
 +                              bcap->speech_ctm = (lv[i] & 0x20) >> 5;
 +                      if (s == 7) /* maximum speech versions + end of list */
 +                              return 0;
 +              }
 +      } else {
 +              i = 1;
 +              while (!(lv[i] & 0x80)) {
 +                      i++; /* octet 3a etc */
 +                      if (in_len < i)
 +                              return 0;
 +                      /* ignore them */
 +              }
 +              /* FIXME: implement OCTET 4+ parsing */
 +      }
 +
 +      return 0;
 +}
 +
 +/* encode 'bearer capability' */
 +int gsm48_encode_bearer_cap(struct msgb *msg, int lv_only,
 +                           const struct gsm_mncc_bearer_cap *bcap)
 +{
 +      uint8_t lv[32 + 1];
 +      int i = 1, s;
 +
 +      lv[1] = bcap->transfer;
 +      lv[1] |= bcap->mode << 3;
 +      lv[1] |= bcap->coding << 4;
 +      lv[1] |= bcap->radio << 5;
 +
 +      if (bcap->transfer == GSM_MNCC_BCAP_SPEECH) {
 +              for (s = 0; bcap->speech_ver[s] >= 0; s++) {
 +                      i++; /* octet 3a etc */
 +                      lv[i] = bcap->speech_ver[s];
 +                      if (i == 2) /* octet 3a */
 +                              lv[i] |= bcap->speech_ctm << 5;
 +              }
 +              lv[i] |= 0x80; /* last IE of octet 3 etc */
 +      } else {
 +              /* FIXME: implement OCTET 4+ encoding */
 +      }
 +
 +      lv[0] = i;
 +      if (lv_only)
 +              msgb_lv_put(msg, lv[0], lv+1);
 +      else
 +              msgb_tlv_put(msg, GSM48_IE_BEARER_CAP, lv[0], lv+1);
 +
 +      return 0;
 +}
 +
 +/* decode 'call control cap' */
 +int gsm48_decode_cccap(struct gsm_mncc_cccap *ccap, const uint8_t *lv)
 +{
 +      uint8_t in_len = lv[0];
 +
 +      if (in_len < 1)
 +              return -EINVAL;
 +
 +      /* octet 3 */
 +      ccap->dtmf = lv[1] & 0x01;
 +      ccap->pcp = (lv[1] & 0x02) >> 1;
 +
 +      return 0;
 +}
 +
 +/* encode 'call control cap' */
 +int gsm48_encode_cccap(struct msgb *msg,
 +                      const struct gsm_mncc_cccap *ccap)
 +{
 +      uint8_t lv[2];
 +
 +      lv[0] = 1;
 +      lv[1] = 0;
 +      if (ccap->dtmf)
 +              lv [1] |= 0x01;
 +      if (ccap->pcp)
 +              lv [1] |= 0x02;
 +
 +      msgb_tlv_put(msg, GSM48_IE_CC_CAP, lv[0], lv+1);
 +
 +      return 0;
 +}
 +
 +/* decode 'called party BCD number' */
 +int gsm48_decode_called(struct gsm_mncc_number *called,
 +                       const uint8_t *lv)
 +{
 +      uint8_t in_len = lv[0];
 +
 +      if (in_len < 1)
 +              return -EINVAL;
 +
 +      /* octet 3 */
 +      called->plan = lv[1] & 0x0f;
 +      called->type = (lv[1] & 0x70) >> 4;
 +
 +      /* octet 4..N */
 +      gsm48_decode_bcd_number(called->number, sizeof(called->number), lv, 1);
 +
 +      return 0;
 +}
 +
 +/* encode 'called party BCD number' */
 +int gsm48_encode_called(struct msgb *msg,
 +                       const struct gsm_mncc_number *called)
 +{
 +      uint8_t lv[18];
 +      int ret;
 +
 +      /* octet 3 */
++      lv[1] = 0x80; /* no extension */
++      lv[1] |= called->plan;
 +      lv[1] |= called->type << 4;
 +
 +      /* octet 4..N, octet 2 */
 +      ret = gsm48_encode_bcd_number(lv, sizeof(lv), 1, called->number);
 +      if (ret < 0)
 +              return ret;
 +
 +      msgb_tlv_put(msg, GSM48_IE_CALLED_BCD, lv[0], lv+1);
 +
 +      return 0;
 +}
 +
 +/* decode callerid of various IEs */
 +int gsm48_decode_callerid(struct gsm_mncc_number *callerid,
 +                       const uint8_t *lv)
 +{
 +      uint8_t in_len = lv[0];
 +      int i = 1;
 +
 +      if (in_len < 1)
 +              return -EINVAL;
 +
 +      /* octet 3 */
 +      callerid->plan = lv[1] & 0x0f;
 +      callerid->type = (lv[1] & 0x70) >> 4;
 +
 +      /* octet 3a */
 +      if (!(lv[1] & 0x80)) {
 +              callerid->screen = lv[2] & 0x03;
 +              callerid->present = (lv[2] & 0x60) >> 5;
 +              i = 2;
 +      }
 +
 +      /* octet 4..N */
 +      gsm48_decode_bcd_number(callerid->number, sizeof(callerid->number), lv, i);
 +
 +      return 0;
 +}
 +
 +/* encode callerid of various IEs */
 +int gsm48_encode_callerid(struct msgb *msg, int ie, int max_len,
 +                         const struct gsm_mncc_number *callerid)
 +{
 +      uint8_t lv[max_len - 1];
 +      int h_len = 1;
 +      int ret;
 +
 +      /* octet 3 */
 +      lv[1] = callerid->plan;
 +      lv[1] |= callerid->type << 4;
 +
 +      if (callerid->present || callerid->screen) {
 +              /* octet 3a */
 +              lv[2] = callerid->screen;
 +              lv[2] |= callerid->present << 5;
 +              lv[2] |= 0x80;
 +              h_len++;
 +      } else
 +              lv[1] |= 0x80;
 +
 +      /* octet 4..N, octet 2 */
 +      ret = gsm48_encode_bcd_number(lv, sizeof(lv), h_len, callerid->number);
 +      if (ret < 0)
 +              return ret;
 +
 +      msgb_tlv_put(msg, ie, lv[0], lv+1);
 +
 +      return 0;
 +}
 +
 +/* decode 'cause' */
 +int gsm48_decode_cause(struct gsm_mncc_cause *cause,
 +                      const uint8_t *lv)
 +{
 +      uint8_t in_len = lv[0];
 +      int i;
 +
 +      if (in_len < 2)
 +              return -EINVAL;
 +
 +      cause->diag_len = 0;
 +
 +      /* octet 3 */
 +      cause->location = lv[1] & 0x0f;
 +      cause->coding = (lv[1] & 0x60) >> 5;
 +
 +      i = 1;
 +      if (!(lv[i] & 0x80)) {
 +              i++; /* octet 3a */
 +              if (in_len < i+1)
 +                      return 0;
 +              cause->rec = 1;
 +              cause->rec_val = lv[i] & 0x7f;
 +      }
 +      i++;
 +
 +      /* octet 4 */
 +      cause->value = lv[i] & 0x7f;
 +      i++;
 +
 +      if (in_len < i) /* no diag */
 +              return 0;
 +
 +      if (in_len - (i-1) > 32) /* maximum 32 octets */
 +              return 0;
 +
 +      /* octet 5-N */
 +      memcpy(cause->diag, lv + i, in_len - (i-1));
 +      cause->diag_len = in_len - (i-1);
 +
 +      return 0;
 +}
 +
 +/* encode 'cause' */
 +int gsm48_encode_cause(struct msgb *msg, int lv_only,
 +                      const struct gsm_mncc_cause *cause)
 +{
 +      uint8_t lv[32+4];
 +      int i;
 +
 +      if (cause->diag_len > 32)
 +              return -EINVAL;
 +
 +      /* octet 3 */
 +      lv[1] = cause->location;
 +      lv[1] |= cause->coding << 5;
 +
 +      i = 1;
 +      if (cause->rec) {
 +              i++; /* octet 3a */
 +              lv[i] = cause->rec_val;
 +      }
 +      lv[i] |= 0x80; /* end of octet 3 */
 +
 +      /* octet 4 */
 +      i++;
 +      lv[i] = 0x80 | cause->value;
 +
 +      /* octet 5-N */
 +      if (cause->diag_len) {
 +              memcpy(lv + i, cause->diag, cause->diag_len);
 +              i += cause->diag_len;
 +      }
 +
 +      lv[0] = i;
 +      if (lv_only)
 +              msgb_lv_put(msg, lv[0], lv+1);
 +      else
 +              msgb_tlv_put(msg, GSM48_IE_CAUSE, lv[0], lv+1);
 +
 +      return 0;
 +}
 +
 +/* decode 'calling number' */
 +int gsm48_decode_calling(struct gsm_mncc_number *calling,
 +                       const uint8_t *lv)
 +{
 +      return gsm48_decode_callerid(calling, lv);
 +}
 +
 +/* encode 'calling number' */
 +int gsm48_encode_calling(struct msgb *msg, 
 +                        const struct gsm_mncc_number *calling)
 +{
 +      return gsm48_encode_callerid(msg, GSM48_IE_CALLING_BCD, 14, calling);
 +}
 +
 +/* decode 'connected number' */
 +int gsm48_decode_connected(struct gsm_mncc_number *connected,
 +                       const uint8_t *lv)
 +{
 +      return gsm48_decode_callerid(connected, lv);
 +}
 +
 +/* encode 'connected number' */
 +int gsm48_encode_connected(struct msgb *msg,
 +                          const struct gsm_mncc_number *connected)
 +{
 +      return gsm48_encode_callerid(msg, GSM48_IE_CONN_BCD, 14, connected);
 +}
 +
 +/* decode 'redirecting number' */
 +int gsm48_decode_redirecting(struct gsm_mncc_number *redirecting,
 +                       const uint8_t *lv)
 +{
 +      return gsm48_decode_callerid(redirecting, lv);
 +}
 +
 +/* encode 'redirecting number' */
 +int gsm48_encode_redirecting(struct msgb *msg,
 +                            const struct gsm_mncc_number *redirecting)
 +{
 +      return gsm48_encode_callerid(msg, GSM48_IE_REDIR_BCD, 19, redirecting);
 +}
 +
 +/* decode 'facility' */
 +int gsm48_decode_facility(struct gsm_mncc_facility *facility,
 +                         const uint8_t *lv)
 +{
 +      uint8_t in_len = lv[0];
 +
 +      if (in_len < 1)
 +              return -EINVAL;
 +
 +      if (in_len > sizeof(facility->info))
 +              return -EINVAL;
 +
 +      memcpy(facility->info, lv+1, in_len);
 +      facility->len = in_len;
 +
 +      return 0;
 +}
 +
 +/* encode 'facility' */
 +int gsm48_encode_facility(struct msgb *msg, int lv_only,
 +                         const struct gsm_mncc_facility *facility)
 +{
 +      uint8_t lv[GSM_MAX_FACILITY + 1];
 +
 +      if (facility->len < 1 || facility->len > GSM_MAX_FACILITY)
 +              return -EINVAL;
 +
 +      memcpy(lv+1, facility->info, facility->len);
 +      lv[0] = facility->len;
 +      if (lv_only)
 +              msgb_lv_put(msg, lv[0], lv+1);
 +      else
 +              msgb_tlv_put(msg, GSM48_IE_FACILITY, lv[0], lv+1);
 +
 +      return 0;
 +}
 +
 +/* decode 'notify' */
 +int gsm48_decode_notify(int *notify, const uint8_t *v)
 +{
 +      *notify = v[0] & 0x7f;
 +
 +      return 0;
 +}
 +
 +/* encode 'notify' */
 +int gsm48_encode_notify(struct msgb *msg, int notify)
 +{
 +      msgb_v_put(msg, notify | 0x80);
 +
 +      return 0;
 +}
 +
 +/* decode 'signal' */
 +int gsm48_decode_signal(int *signal, const uint8_t *v)
 +{
 +      *signal = v[0];
 +
 +      return 0;
 +}
 +
 +/* encode 'signal' */
 +int gsm48_encode_signal(struct msgb *msg, int signal)
 +{
 +      msgb_tv_put(msg, GSM48_IE_SIGNAL, signal);
 +
 +      return 0;
 +}
 +
 +/* decode 'keypad' */
 +int gsm48_decode_keypad(int *keypad, const uint8_t *lv)
 +{
 +      uint8_t in_len = lv[0];
 +
 +      if (in_len < 1)
 +              return -EINVAL;
 +
 +      *keypad = lv[1] & 0x7f;
 +
 +      return 0;
 +}
 +
 +/* encode 'keypad' */
 +int gsm48_encode_keypad(struct msgb *msg, int keypad)
 +{
 +      msgb_tv_put(msg, GSM48_IE_KPD_FACILITY, keypad);
 +
 +      return 0;
 +}
 +
 +/* decode 'progress' */
 +int gsm48_decode_progress(struct gsm_mncc_progress *progress,
 +                         const uint8_t *lv)
 +{
 +      uint8_t in_len = lv[0];
 +
 +      if (in_len < 2)
 +              return -EINVAL;
 +
 +      progress->coding = (lv[1] & 0x60) >> 5;
 +      progress->location = lv[1] & 0x0f;
 +      progress->descr = lv[2] & 0x7f;
 +
 +      return 0;
 +}
 +
 +/* encode 'progress' */
 +int gsm48_encode_progress(struct msgb *msg, int lv_only,
 +                         const struct gsm_mncc_progress *p)
 +{
 +      uint8_t lv[3];
 +
 +      lv[0] = 2;
 +      lv[1] = 0x80 | ((p->coding & 0x3) << 5) | (p->location & 0xf);
 +      lv[2] = 0x80 | (p->descr & 0x7f);
 +      if (lv_only)
 +              msgb_lv_put(msg, lv[0], lv+1);
 +      else
 +              msgb_tlv_put(msg, GSM48_IE_PROGR_IND, lv[0], lv+1);
 +
 +      return 0;
 +}
 +
 +/* decode 'user-user' */
 +int gsm48_decode_useruser(struct gsm_mncc_useruser *uu,
 +                         const uint8_t *lv)
 +{
 +      uint8_t in_len = lv[0];
 +      char *info = uu->info;
 +      int info_len = sizeof(uu->info);
 +      int i;
 +
 +      if (in_len < 1)
 +              return -EINVAL;
 +
 +      uu->proto = lv[1];
 +
 +      for (i = 2; i <= in_len; i++) {
 +              info_len--;
 +              if (info_len <= 1)
 +                      break;
 +              *info++ = lv[i];
 +      }
 +      if (info_len >= 1)
 +              *info++ = '\0';
 +
 +      return 0;
 +}
 +
 +/* encode 'useruser' */
 +int gsm48_encode_useruser(struct msgb *msg, int lv_only,
 +                         const struct gsm_mncc_useruser *uu)
 +{
 +      uint8_t lv[GSM_MAX_USERUSER + 2];
 +
 +      if (strlen(uu->info) > GSM_MAX_USERUSER)
 +              return -EINVAL;
 +
 +      lv[0] = 1 + strlen(uu->info);
 +      lv[1] = uu->proto;
 +      memcpy(lv + 2, uu->info, strlen(uu->info));
 +      if (lv_only)
 +              msgb_lv_put(msg, lv[0], lv+1);
 +      else
 +              msgb_tlv_put(msg, GSM48_IE_USER_USER, lv[0], lv+1);
 +
 +      return 0;
 +}
 +
 +/* decode 'ss version' */
 +int gsm48_decode_ssversion(struct gsm_mncc_ssversion *ssv,
 +                          const uint8_t *lv)
 +{
 +      uint8_t in_len = lv[0];
 +
 +      if (in_len < 1 || in_len < sizeof(ssv->info))
 +              return -EINVAL;
 +
 +      memcpy(ssv->info, lv + 1, in_len);
 +      ssv->len = in_len;
 +
 +      return 0;
 +}
 +
 +/* encode 'ss version' */
 +int gsm48_encode_ssversion(struct msgb *msg,
 +                         const struct gsm_mncc_ssversion *ssv)
 +{
 +      uint8_t lv[GSM_MAX_SSVERSION + 1];
 +
 +      if (ssv->len > GSM_MAX_SSVERSION)
 +              return -EINVAL;
 +
 +      lv[0] = ssv->len;
 +      memcpy(lv + 1, ssv->info, ssv->len);
 +      msgb_tlv_put(msg, GSM48_IE_SS_VERS, lv[0], lv+1);
 +
 +      return 0;
 +}
 +
 +/* decode 'more data' does not require a function, because it has no value */
 +
 +/* encode 'more data' */
 +int gsm48_encode_more(struct msgb *msg)
 +{
 +      uint8_t *ie;
 +
 +      ie = msgb_put(msg, 1);
 +      ie[0] = GSM48_IE_MORE_DATA;
 +
 +      return 0;
 +}
 +
 +/* decode "Cell Channel Description" (10.5.2.1b) and other frequency lists */
 +int gsm48_decode_freq_list(struct gsm_sysinfo_freq *f, uint8_t *cd,
 +                         uint8_t len, uint8_t mask, uint8_t frqt)
 +{
 +      int i;
 +
 +      /* NOTES:
 +       *
 +       * The Range format uses "SMOD" computation.
 +       * e.g. "n SMOD m" equals "((n - 1) % m) + 1"
 +       * A cascade of multiple SMOD computations is simpified:
 +       * "(n SMOD m) SMOD o" equals "(((n - 1) % m) % o) + 1"
 +       *
 +       * The Range format uses 16 octets of data in SYSTEM INFORMATION.
 +       * When used in dedicated messages, the length can be less.
 +       * In this case the ranges are decoded for all frequencies that
 +       * fit in the block of given length.
 +       */
 +
 +      /* tabula rasa */
 +      for (i = 0; i < 1024; i++)
 +              f[i].mask &= ~frqt;
 +
 +      /* 00..XXX. */
 +      if ((cd[0] & 0xc0 & mask) == 0x00) {
 +              /* Bit map 0 format */
 +              if (len < 16)
 +                      return -EINVAL;
 +              for (i = 1; i <= 124; i++)
 +                      if ((cd[15 - ((i-1) >> 3)] & (1 << ((i-1) & 7))))
 +                              f[i].mask |= frqt;
 +
 +              return 0;
 +      }
 +
 +      /* 10..0XX. */
 +      if ((cd[0] & 0xc8 & mask) == 0x80) {
 +              /* Range 1024 format */
 +              uint16_t w[17]; /* 1..16 */
 +              struct gsm48_range_1024 *r = (struct gsm48_range_1024 *)cd;
 +
 +              if (len < 2)
 +                      return -EINVAL;
 +              memset(w, 0, sizeof(w));
 +              if (r->f0)
 +                      f[0].mask |= frqt;
 +              w[1] = (r->w1_hi << 8) | r->w1_lo;
 +              if (len >= 4)
 +                      w[2] = (r->w2_hi << 1) | r->w2_lo;
 +              if (len >= 5)
 +                      w[3] = (r->w3_hi << 2) | r->w3_lo;
 +              if (len >= 6)
 +                      w[4] = (r->w4_hi << 2) | r->w4_lo;
 +              if (len >= 7)
 +                      w[5] = (r->w5_hi << 2) | r->w5_lo;
 +              if (len >= 8)
 +                      w[6] = (r->w6_hi << 2) | r->w6_lo;
 +              if (len >= 9)
 +                      w[7] = (r->w7_hi << 2) | r->w7_lo;
 +              if (len >= 10)
 +                      w[8] = (r->w8_hi << 1) | r->w8_lo;
 +              if (len >= 10)
 +                      w[9] = r->w9;
 +              if (len >= 11)
 +                      w[10] = r->w10;
 +              if (len >= 12)
 +                      w[11] = (r->w11_hi << 6) | r->w11_lo;
 +              if (len >= 13)
 +                      w[12] = (r->w12_hi << 5) | r->w12_lo;
 +              if (len >= 14)
 +                      w[13] = (r->w13_hi << 4) | r->w13_lo;
 +              if (len >= 15)
 +                      w[14] = (r->w14_hi << 3) | r->w14_lo;
 +              if (len >= 16)
 +                      w[15] = (r->w15_hi << 2) | r->w15_lo;
 +              if (len >= 16)
 +                      w[16] = r->w16;
 +              if (w[1])
 +                      f[w[1]].mask |= frqt;
 +              if (w[2])
 +                      f[((w[1] - 512 + w[2] - 1) % 1023) + 1].mask |= frqt;
 +              if (w[3])
 +                      f[((w[1]       + w[3] - 1) % 1023) + 1].mask |= frqt;
 +              if (w[4])
 +                      f[((w[1] - 512 + ((w[2] - 256 + w[4] - 1) % 511)) % 1023) + 1].mask |= frqt;
 +              if (w[5])
 +                      f[((w[1]       + ((w[3] - 256 - w[5] - 1) % 511)) % 1023) + 1].mask |= frqt;
 +              if (w[6])
 +                      f[((w[1] - 512 + ((w[2]       + w[6] - 1) % 511)) % 1023) + 1].mask |= frqt;
 +              if (w[7])
 +                      f[((w[1]       + ((w[3]       + w[7] - 1) % 511)) % 1023) + 1].mask |= frqt;
 +              if (w[8])
 +                      f[((w[1] - 512 + ((w[2] - 256 + ((w[4] - 128 + w[8] - 1) % 255)) % 511)) % 1023) + 1].mask |= frqt;
 +              if (w[9])
 +                      f[((w[1]       + ((w[3] - 256 + ((w[5] - 128 + w[9] - 1) % 255)) % 511)) % 1023) + 1].mask |= frqt;
 +              if (w[10])
 +                      f[((w[1] - 512 + ((w[2]       + ((w[6] - 128 + w[10] - 1) % 255)) % 511)) % 1023) + 1].mask |= frqt;
 +              if (w[11])
 +                      f[((w[1]       + ((w[3]       + ((w[7] - 128 + w[11] - 1) % 255)) % 511)) % 1023) + 1].mask |= frqt;
 +              if (w[12])
 +                      f[((w[1] - 512 + ((w[2] - 256 + ((w[4]       + w[12] - 1) % 255)) % 511)) % 1023) + 1].mask |= frqt;
 +              if (w[13])
 +                      f[((w[1]       + ((w[3] - 256 + ((w[5]       + w[13] - 1) % 255)) % 511)) % 1023) + 1].mask |= frqt;
 +              if (w[14])
 +                      f[((w[1] - 512 + ((w[2]       + ((w[6]       + w[14] - 1) % 255)) % 511)) % 1023) + 1].mask |= frqt;
 +              if (w[15])
 +                      f[((w[1]       + ((w[3]       + ((w[7]       + w[15] - 1) % 255)) % 511)) % 1023) + 1].mask |= frqt;
 +              if (w[16])
 +                      f[((w[1] - 512 + ((w[2] - 256 + ((w[4] - 128 + ((w[8] - 64 + w[16] - 1) % 127)) % 255)) % 511)) % 1023) + 1].mask |= frqt;
 +
 +              return 0;
 +      }
 +      /* 10..100. */
 +      if ((cd[0] & 0xce & mask) == 0x88) {
 +              /* Range 512 format */
 +              uint16_t w[18]; /* 1..17 */
 +              struct gsm48_range_512 *r = (struct gsm48_range_512 *)cd;
 +
 +              if (len < 4)
 +                      return -EINVAL;
 +              memset(w, 0, sizeof(w));
 +              w[0] = (r->orig_arfcn_hi << 9) | (r->orig_arfcn_mid << 1) | r->orig_arfcn_lo;
 +              w[1] = (r->w1_hi << 2) | r->w1_lo;
 +              if (len >= 5)
 +                      w[2] = (r->w2_hi << 2) | r->w2_lo;
 +              if (len >= 6)
 +                      w[3] = (r->w3_hi << 2) | r->w3_lo;
 +              if (len >= 7)
 +                      w[4] = (r->w4_hi << 1) | r->w4_lo;
 +              if (len >= 7)
 +                      w[5] = r->w5;
 +              if (len >= 8)
 +                      w[6] = r->w6;
 +              if (len >= 9)
 +                      w[7] = (r->w7_hi << 6) | r->w7_lo;
 +              if (len >= 10)
 +                      w[8] = (r->w8_hi << 4) | r->w8_lo;
 +              if (len >= 11)
 +                      w[9] = (r->w9_hi << 2) | r->w9_lo;
 +              if (len >= 11)
 +                      w[10] = r->w10;
 +              if (len >= 12)
 +                      w[11] = r->w11;
 +              if (len >= 13)
 +                      w[12] = (r->w12_hi << 4) | r->w12_lo;
 +              if (len >= 14)
 +                      w[13] = (r->w13_hi << 2) | r->w13_lo;
 +              if (len >= 14)
 +                      w[14] = r->w14;
 +              if (len >= 15)
 +                      w[15] = r->w15;
 +              if (len >= 16)
 +                      w[16] = (r->w16_hi << 3) | r->w16_lo;
 +              if (len >= 16)
 +                      w[17] = r->w17;
 +              f[w[0]].mask |= frqt;
 +              if (w[1])
 +                      f[(w[0] + w[1]) % 1024].mask |= frqt;
 +              if (w[2])
 +                      f[(w[0] + ((w[1] - 256 + w[2] - 1) % 511) + 1) % 1024].mask |= frqt;
 +              if (w[3])
 +                      f[(w[0] + ((w[1]       + w[3] - 1) % 511) + 1) % 1024].mask |= frqt;
 +              if (w[4])
 +                      f[(w[0] + ((w[1] - 256 + ((w[2] - 128 + w[4] - 1) % 255)) % 511) + 1) % 1024].mask |= frqt;
 +              if (w[5])
 +                      f[(w[0] + ((w[1]       + ((w[3] - 128 + w[5] - 1) % 255)) % 511) + 1) % 1024].mask |= frqt;
 +              if (w[6])
 +                      f[(w[0] + ((w[1] - 256 + ((w[2]       + w[6] - 1) % 255)) % 511) + 1) % 1024].mask |= frqt;
 +              if (w[7])
 +                      f[(w[0] + ((w[1]       + ((w[3]       + w[7] - 1) % 255)) % 511) + 1) % 1024].mask |= frqt;
 +              if (w[8])
 +                      f[(w[0] + ((w[1] - 256 + ((w[2] - 128 + ((w[4] - 64 + w[8] - 1) % 127)) % 255)) % 511) + 1) % 1024].mask |= frqt;
 +              if (w[9])
 +                      f[(w[0] + ((w[1]       + ((w[3] - 128 + ((w[5] - 64 + w[9] - 1) % 127)) % 255)) % 511) + 1) % 1024].mask |= frqt;
 +              if (w[10])
 +                      f[(w[0] + ((w[1] - 256 + ((w[2]       + ((w[6] - 64 + w[10] - 1) % 127)) % 255)) % 511) + 1) % 1024].mask |= frqt;
 +              if (w[11])
 +                      f[(w[0] + ((w[1]       + ((w[3]       + ((w[7] - 64 + w[11] - 1) % 127)) % 255)) % 511) + 1) % 1024].mask |= frqt;
 +              if (w[12])
 +                      f[(w[0] + ((w[1] - 256 + ((w[2] - 128 + ((w[4]      + w[12] - 1) % 127)) % 255)) % 511) + 1) % 1024].mask |= frqt;
 +              if (w[13])
 +                      f[(w[0] + ((w[1]       + ((w[3] - 128 + ((w[5]      + w[13] - 1) % 127)) % 255)) % 511) + 1) % 1024].mask |= frqt;
 +              if (w[14])
 +                      f[(w[0] + ((w[1] - 256 + ((w[2]       + ((w[6]      + w[14] - 1) % 127)) % 255)) % 511) + 1) % 1024].mask |= frqt;
 +              if (w[15])
 +                      f[(w[0] + ((w[1]       + ((w[3]       + ((w[7]      + w[15] - 1) % 127)) % 255)) % 511) + 1) % 1024].mask |= frqt;
 +              if (w[16])
 +                      f[(w[0] + ((w[1] - 256 + ((w[2] - 128 + ((w[4] - 64 + ((w[8] - 32 + w[16] - 1) % 63)) % 127)) % 255)) % 511) + 1) % 1024].mask |= frqt;
 +              if (w[17])
 +                      f[(w[0] + ((w[1]       + ((w[3] - 128 + ((w[5] - 64 + ((w[9] - 32 + w[17] - 1) % 63)) % 127)) % 255)) % 511) + 1) % 1024].mask |= frqt;
 +
 +              return 0;
 +      }
 +      /* 10..101. */
 +      if ((cd[0] & 0xce & mask) == 0x8a) {
 +              /* Range 256 format */
 +              uint16_t w[22]; /* 1..21 */
 +              struct gsm48_range_256 *r = (struct gsm48_range_256 *)cd;
 +
 +              if (len < 4)
 +                      return -EINVAL;
 +              memset(w, 0, sizeof(w));
 +              w[0] = (r->orig_arfcn_hi << 9) | (r->orig_arfcn_mid << 1) | r->orig_arfcn_lo;
 +              w[1] = (r->w1_hi << 1) | r->w1_lo;
 +              if (len >= 4)
 +                      w[2] = r->w2;
 +              if (len >= 5)
 +                      w[3] = r->w3;
 +              if (len >= 6)
 +                      w[4] = (r->w4_hi << 5) | r->w4_lo;
 +              if (len >= 7)
 +                      w[5] = (r->w5_hi << 3) | r->w5_lo;
 +              if (len >= 8)
 +                      w[6] = (r->w6_hi << 1) | r->w6_lo;
 +              if (len >= 8)
 +                      w[7] = r->w7;
 +              if (len >= 9)
 +                      w[8] = (r->w8_hi << 4) | r->w8_lo;
 +              if (len >= 10)
 +                      w[9] = (r->w9_hi << 1) | r->w9_lo;
 +              if (len >= 10)
 +                      w[10] = r->w10;
 +              if (len >= 11)
 +                      w[11] = (r->w11_hi << 3) | r->w11_lo;
 +              if (len >= 11)
 +                      w[12] = r->w12;
 +              if (len >= 12)
 +                      w[13] = r->w13;
 +              if (len >= 13)
 +                      w[14] = r->w15;
 +              if (len >= 13)
 +                      w[15] = (r->w14_hi << 2) | r->w14_lo;
 +              if (len >= 14)
 +                      w[16] = (r->w16_hi << 3) | r->w16_lo;
 +              if (len >= 14)
 +                      w[17] = r->w17;
 +              if (len >= 15)
 +                      w[18] = r->w19;
 +              if (len >= 15)
 +                      w[19] = (r->w18_hi << 3) | r->w18_lo;
 +              if (len >= 16)
 +                      w[20] = (r->w20_hi << 3) | r->w20_lo;
 +              if (len >= 16)
 +                      w[21] = r->w21;
 +              f[w[0]].mask |= frqt;
 +              if (w[1])
 +                      f[(w[0] + w[1]) % 1024].mask |= frqt;
 +              if (w[2])
 +                      f[(w[0] + ((w[1] - 128 + w[2] - 1) % 255) + 1) % 1024].mask |= frqt;
 +              if (w[3])
 +                      f[(w[0] + ((w[1]       + w[3] - 1) % 255) + 1) % 1024].mask |= frqt;
 +              if (w[4])
 +                      f[(w[0] + ((w[1] - 128 + ((w[2] - 64 + w[4] - 1) % 127)) % 255) + 1) % 1024].mask |= frqt;
 +              if (w[5])
 +                      f[(w[0] + ((w[1]       + ((w[3] - 64 + w[5] - 1) % 127)) % 255) + 1) % 1024].mask |= frqt;
 +              if (w[6])
 +                      f[(w[0] + ((w[1] - 128 + ((w[2]      + w[6] - 1) % 127)) % 255) + 1) % 1024].mask |= frqt;
 +              if (w[7])
 +                      f[(w[0] + ((w[1]       + ((w[3]      + w[7] - 1) % 127)) % 255) + 1) % 1024].mask |= frqt;
 +              if (w[8])
 +                      f[(w[0] + ((w[1] - 128 + ((w[2] - 64 + ((w[4] - 32 + w[8] - 1) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt;
 +              if (w[9])
 +                      f[(w[0] + ((w[1]       + ((w[3] - 64 + ((w[5] - 32 + w[9] - 1) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt;
 +              if (w[10])
 +                      f[(w[0] + ((w[1] - 128 + ((w[2]      + ((w[6] - 32 + w[10] - 1) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt;
 +              if (w[11])
 +                      f[(w[0] + ((w[1]       + ((w[3]      + ((w[7] - 32 + w[11] - 1) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt;
 +              if (w[12])
 +                      f[(w[0] + ((w[1] - 128 + ((w[2] - 64 + ((w[4]      + w[12] - 1) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt;
 +              if (w[13])
 +                      f[(w[0] + ((w[1]       + ((w[3] - 64 + ((w[5]      + w[13] - 1) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt;
 +              if (w[14])
 +                      f[(w[0] + ((w[1] - 128 + ((w[2]      + ((w[6]      + w[14] - 1) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt;
 +              if (w[15])
 +                      f[(w[0] + ((w[1]       + ((w[3]      + ((w[7]      + w[15] - 1) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt;
 +              if (w[16])
 +                      f[(w[0] + ((w[1] - 128 + ((w[2] - 64 + ((w[4] - 32 + ((w[8] - 16 + w[16] - 1) % 31)) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt;
 +              if (w[17])
 +                      f[(w[0] + ((w[1]       + ((w[3] - 64 + ((w[5] - 32 + ((w[9] - 16 + w[17] - 1) % 31)) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt;
 +              if (w[18])
 +                      f[(w[0] + ((w[1] - 128 + ((w[2]      + ((w[6] - 32 + ((w[10] - 16 + w[18] - 1) % 31)) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt;
 +              if (w[19])
 +                      f[(w[0] + ((w[1]       + ((w[3]      + ((w[7] - 32 + ((w[11] - 16 + w[19] - 1) % 31)) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt;
 +              if (w[20])
 +                      f[(w[0] + ((w[1] - 128 + ((w[2] - 64 + ((w[4]      + ((w[12] - 16 + w[20] - 1) % 31)) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt;
 +              if (w[21])
 +                      f[(w[0] + ((w[1]       + ((w[3] - 64 + ((w[5]      + ((w[13] - 16 + w[21] - 1) % 31)) % 63)) % 127)) % 255) + 1) % 1024].mask |= frqt;
 +
 +              return 0;
 +      }
 +      /* 10..110. */
 +      if ((cd[0] & 0xce & mask) == 0x8c) {
 +              /* Range 128 format */
 +              uint16_t w[29]; /* 1..28 */
 +              struct gsm48_range_128 *r = (struct gsm48_range_128 *)cd;
 +
 +              if (len < 3)
 +                      return -EINVAL;
 +              memset(w, 0, sizeof(w));
 +              w[0] = (r->orig_arfcn_hi << 9) | (r->orig_arfcn_mid << 1) | r->orig_arfcn_lo;
 +              w[1] = r->w1;
 +              if (len >= 4)
 +                      w[2] = r->w2;
 +              if (len >= 5)
 +                      w[3] = (r->w3_hi << 4) | r->w3_lo;
 +              if (len >= 6)
 +                      w[4] = (r->w4_hi << 1) | r->w4_lo;
 +              if (len >= 6)
 +                      w[5] = r->w5;
 +              if (len >= 7)
 +                      w[6] = (r->w6_hi << 3) | r->w6_lo;
 +              if (len >= 7)
 +                      w[7] = r->w7;
 +              if (len >= 8)
 +                      w[8] = r->w8;
 +              if (len >= 8)
 +                      w[9] = r->w9;
 +              if (len >= 9)
 +                      w[10] = r->w10;
 +              if (len >= 9)
 +                      w[11] = r->w11;
 +              if (len >= 10)
 +                      w[12] = r->w12;
 +              if (len >= 10)
 +                      w[13] = r->w13;
 +              if (len >= 11)
 +                      w[14] = r->w14;
 +              if (len >= 11)
 +                      w[15] = r->w15;
 +              if (len >= 12)
 +                      w[16] = r->w16;
 +              if (len >= 12)
 +                      w[17] = r->w17;
 +              if (len >= 13)
 +                      w[18] = (r->w18_hi << 1) | r->w18_lo;
 +              if (len >= 13)
 +                      w[19] = r->w19;
 +              if (len >= 13)
 +                      w[20] = r->w20;
 +              if (len >= 14)
 +                      w[21] = (r->w21_hi << 2) | r->w21_lo;
 +              if (len >= 14)
 +                      w[22] = r->w22;
 +              if (len >= 14)
 +                      w[23] = r->w23;
 +              if (len >= 15)
 +                      w[24] = r->w24;
 +              if (len >= 15)
 +                      w[25] = r->w25;
 +              if (len >= 16)
 +                      w[26] = (r->w26_hi << 1) | r->w26_lo;
 +              if (len >= 16)
 +                      w[27] = r->w27;
 +              if (len >= 16)
 +                      w[28] = r->w28;
 +              f[w[0]].mask |= frqt;
 +              if (w[1])
 +                      f[(w[0] + w[1]) % 1024].mask |= frqt;
 +              if (w[2])
 +                      f[(w[0] + ((w[1] - 64 + w[2] - 1) % 127) + 1) % 1024].mask |= frqt;
 +              if (w[3])
 +                      f[(w[0] + ((w[1]      + w[3] - 1) % 127) + 1) % 1024].mask |= frqt;
 +              if (w[4])
 +                      f[(w[0] + ((w[1] - 64 + ((w[2] - 32 + w[4] - 1) % 63)) % 127) + 1) % 1024].mask |= frqt;
 +              if (w[5])
 +                      f[(w[0] + ((w[1]      + ((w[3] - 32 + w[5] - 1) % 63)) % 127) + 1) % 1024].mask |= frqt;
 +              if (w[6])
 +                      f[(w[0] + ((w[1] - 64 + ((w[2]      + w[6] - 1) % 63)) % 127) + 1) % 1024].mask |= frqt;
 +              if (w[7])
 +                      f[(w[0] + ((w[1]      + ((w[3]      + w[7] - 1) % 63)) % 127) + 1) % 1024].mask |= frqt;
 +              if (w[8])
 +                      f[(w[0] + ((w[1] - 64 + ((w[2] - 32 + ((w[4] - 16 + w[8] - 1) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
 +              if (w[9])
 +                      f[(w[0] + ((w[1]      + ((w[3] - 32 + ((w[5] - 16 + w[9] - 1) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
 +              if (w[10])
 +                      f[(w[0] + ((w[1] - 64 + ((w[2]      + ((w[6] - 16 + w[10] - 1) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
 +              if (w[11])
 +                      f[(w[0] + ((w[1]      + ((w[3]      + ((w[7] - 16 + w[11] - 1) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
 +              if (w[12])
 +                      f[(w[0] + ((w[1] - 64 + ((w[2] - 32 + ((w[4]      + w[12] - 1) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
 +              if (w[13])
 +                      f[(w[0] + ((w[1]      + ((w[3] - 32 + ((w[5]      + w[13] - 1) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
 +              if (w[14])
 +                      f[(w[0] + ((w[1] - 64 + ((w[2]      + ((w[6]      + w[14] - 1) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
 +              if (w[15])
 +                      f[(w[0] + ((w[1]      + ((w[3]      + ((w[7]      + w[15] - 1) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
 +              if (w[16])
 +                      f[(w[0] + ((w[1] - 64 + ((w[2] - 32 + ((w[4] - 16 + ((w[8] - 8 + w[16] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
 +              if (w[17])
 +                      f[(w[0] + ((w[1]      + ((w[3] - 32 + ((w[5] - 16 + ((w[9] - 8 + w[17] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
 +              if (w[18])
 +                      f[(w[0] + ((w[1] - 64 + ((w[2]      + ((w[6] - 16 + ((w[10] - 8 + w[18] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
 +              if (w[19])
 +                      f[(w[0] + ((w[1]      + ((w[3]      + ((w[7] - 16 + ((w[11] - 8 + w[19] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
 +              if (w[20])
 +                      f[(w[0] + ((w[1] - 64 + ((w[2] - 32 + ((w[4]      + ((w[12] - 8 + w[20] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
 +              if (w[21])
 +                      f[(w[0] + ((w[1]      + ((w[3] - 32 + ((w[5]      + ((w[13] - 8 + w[21] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
 +              if (w[22])
 +                      f[(w[0] + ((w[1] - 64 + ((w[2]      + ((w[6]      + ((w[14] - 8 + w[22] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
 +              if (w[23])
 +                      f[(w[0] + ((w[1]      + ((w[3]      + ((w[7]      + ((w[15] - 8 + w[23] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
 +              if (w[24])
 +                      f[(w[0] + ((w[1] - 64 + ((w[2] - 32 + ((w[4] - 16 + ((w[8]     + w[24] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
 +              if (w[25])
 +                      f[(w[0] + ((w[1]      + ((w[3] - 32 + ((w[5] - 16 + ((w[9]     + w[25] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
 +              if (w[26])
 +                      f[(w[0] + ((w[1] - 64 + ((w[2]      + ((w[6] - 16 + ((w[10]     + w[26] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
 +              if (w[27])
 +                      f[(w[0] + ((w[1]      + ((w[3]      + ((w[7] - 16 + ((w[11]     + w[27] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
 +              if (w[28])
 +                      f[(w[0] + ((w[1] - 64 + ((w[2] - 32 + ((w[4]      + ((w[12]     + w[28] - 1) % 15)) % 31)) % 63)) % 127) + 1) % 1024].mask |= frqt;
 +
 +              return 0;
 +      }
 +      /* 10..111. */
 +      if ((cd[0] & 0xce & mask) == 0x8e) {
 +              /* Variable bitmap format (can be any length >= 3) */
 +              uint16_t orig = 0;
 +              struct gsm48_var_bit *r = (struct gsm48_var_bit *)cd;
 +
 +              if (len < 3)
 +                      return -EINVAL;
 +              orig = (r->orig_arfcn_hi << 9) | (r->orig_arfcn_mid << 1) | r->orig_arfcn_lo;
 +              f[orig].mask |= frqt;
 +              for (i = 1; 2 + (i >> 3) < len; i++)
 +                      if ((cd[2 + (i >> 3)] & (0x80 >> (i & 7))))
 +                              f[(orig + i) % 1024].mask |= frqt;
 +
 +              return 0;
 +      }
 +
 +      return 0;
 +}