diff options
author | Harald Welte <laforge@gnumonks.org> | 2010-03-22 22:25:13 +0800 |
---|---|---|
committer | Harald Welte <laforge@gnumonks.org> | 2010-03-22 22:25:13 +0800 |
commit | 351ec2a1784fbdb2ce688f50a9134532f3bf0723 (patch) | |
tree | d599030f3ffb4e563b73f5ede9a8e80fc1bc048f /libosmocore/src | |
parent | e164d29e7f6c86d1f6f047bbf58159e46a11425f (diff) | |
parent | 045cc22baeb7f12dcb0022b79a1984d86e922870 (diff) |
Add 'libosmocore/' from commit '045cc22baeb7f12dcb0022b79a1984d86e922870'openbsc/0.9.0
git-subtree-dir: libosmocore
git-subtree-mainline: e164d29e7f6c86d1f6f047bbf58159e46a11425f
git-subtree-split: 045cc22baeb7f12dcb0022b79a1984d86e922870
Diffstat (limited to 'libosmocore/src')
-rw-r--r-- | libosmocore/src/Makefile.am | 16 | ||||
-rw-r--r-- | libosmocore/src/bitvec.c | 170 | ||||
-rw-r--r-- | libosmocore/src/comp128.c | 230 | ||||
-rw-r--r-- | libosmocore/src/gsm48.c | 283 | ||||
-rw-r--r-- | libosmocore/src/gsm48_ie.c | 659 | ||||
-rw-r--r-- | libosmocore/src/gsm_utils.c | 361 | ||||
-rw-r--r-- | libosmocore/src/msgb.c | 89 | ||||
-rw-r--r-- | libosmocore/src/rsl.c | 327 | ||||
-rw-r--r-- | libosmocore/src/rxlev_stat.c | 94 | ||||
-rw-r--r-- | libosmocore/src/select.c | 130 | ||||
-rw-r--r-- | libosmocore/src/signal.c | 84 | ||||
-rw-r--r-- | libosmocore/src/statistics.c | 66 | ||||
-rw-r--r-- | libosmocore/src/talloc.c | 1805 | ||||
-rw-r--r-- | libosmocore/src/timer.c | 185 | ||||
-rw-r--r-- | libosmocore/src/tlv_parser.c | 171 | ||||
-rw-r--r-- | libosmocore/src/utils.c | 46 | ||||
-rw-r--r-- | libosmocore/src/write_queue.c | 74 |
17 files changed, 4790 insertions, 0 deletions
diff --git a/libosmocore/src/Makefile.am b/libosmocore/src/Makefile.am new file mode 100644 index 000000000..f0effa2ea --- /dev/null +++ b/libosmocore/src/Makefile.am @@ -0,0 +1,16 @@ +# This is _NOT_ the library release version, it's an API version. +# Please read Chapter 6 "Library interface versions" of the libtool documentation before making any modification +LIBVERSION=0:0:0 + +INCLUDES = $(all_includes) -I$(top_srcdir)/include +AM_CFLAGS = -fPIC -Wall + +lib_LTLIBRARIES = libosmocore.la + +libosmocore_la_SOURCES = timer.c select.c signal.c msgb.c rxlev_stat.c \ + tlv_parser.c bitvec.c comp128.c gsm_utils.c statistics.c \ + write_queue.c utils.c rsl.c gsm48.c gsm48_ie.c + +if ENABLE_TALLOC +libosmocore_la_SOURCES += talloc.c +endif diff --git a/libosmocore/src/bitvec.c b/libosmocore/src/bitvec.c new file mode 100644 index 000000000..eb83ac667 --- /dev/null +++ b/libosmocore/src/bitvec.c @@ -0,0 +1,170 @@ +/* bit vector utility routines */ + +/* (C) 2009 by Harald Welte <laforge@gnumonks.org> + * + * 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 <errno.h> +#include <stdint.h> + +#include <osmocore/bitvec.h> + +#define BITNUM_FROM_COMP(byte, bit) ((byte*8)+bit) + +static inline unsigned int bytenum_from_bitnum(unsigned int bitnum) +{ + unsigned int bytenum = bitnum / 8; + + return bytenum; +} + +/* convert ZERO/ONE/L/H to a bitmask at given pos in a byte */ +static uint8_t bitval2mask(enum bit_value bit, uint8_t bitnum) +{ + int bitval; + + switch (bit) { + case ZERO: + bitval = (0 << bitnum); + break; + case ONE: + bitval = (1 << bitnum); + break; + case L: + bitval = ((0x2b ^ (0 << bitnum)) & (1 << bitnum)); + break; + case H: + bitval = ((0x2b ^ (1 << bitnum)) & (1 << bitnum)); + break; + default: + return 0; + } + return bitval; +} + +/* check if the bit is 0 or 1 for a given position inside a bitvec */ +enum bit_value bitvec_get_bit_pos(const struct bitvec *bv, unsigned int bitnr) +{ + unsigned int bytenum = bytenum_from_bitnum(bitnr); + unsigned int bitnum = 7 - (bitnr % 8); + uint8_t bitval; + + if (bytenum >= bv->data_len) + return -EINVAL; + + bitval = bitval2mask(ONE, bitnum); + + if (bv->data[bytenum] & bitval) + return ONE; + + return ZERO; +} + +/* get the Nth set bit inside the bit vector */ +unsigned int bitvec_get_nth_set_bit(const struct bitvec *bv, unsigned int n) +{ + unsigned int i, k = 0; + + for (i = 0; i < bv->data_len*8; i++) { + if (bitvec_get_bit_pos(bv, i) == ONE) { + k++; + if (k == n) + return i; + } + } + + return 0; +} + +/* set the bit at a given position inside a bitvec */ +int bitvec_set_bit_pos(struct bitvec *bv, unsigned int bitnr, + enum bit_value bit) +{ + unsigned int bytenum = bytenum_from_bitnum(bitnr); + unsigned int bitnum = 7 - (bitnr % 8); + uint8_t bitval; + + if (bytenum >= bv->data_len) + return -EINVAL; + + /* first clear the bit */ + bitval = bitval2mask(ONE, bitnum); + bv->data[bytenum] &= ~bitval; + + /* then set it to desired value */ + bitval = bitval2mask(bit, bitnum); + bv->data[bytenum] |= bitval; + + return 0; +} + +/* set the next bit inside a bitvec */ +int bitvec_set_bit(struct bitvec *bv, enum bit_value bit) +{ + int rc; + + rc = bitvec_set_bit_pos(bv, bv->cur_bit, bit); + if (!rc) + bv->cur_bit++; + + return rc; +} + +/* set multiple bits (based on array of bitvals) at current pos */ +int bitvec_set_bits(struct bitvec *bv, enum bit_value *bits, int count) +{ + int i, rc; + + for (i = 0; i < count; i++) { + rc = bitvec_set_bit(bv, bits[i]); + if (rc) + return rc; + } + + return 0; +} + +/* set multiple bits (based on numeric value) at current pos */ +int bitvec_set_uint(struct bitvec *bv, unsigned int ui, int num_bits) +{ + int i, rc; + + for (i = 0; i < num_bits; i++) { + int bit = 0; + if (ui & (1 << (num_bits - i - 1))) + bit = 1; + rc = bitvec_set_bit(bv, bit); + if (rc) + return rc; + } + + return 0; +} + +/* pad all remaining bits up to num_bits */ +int bitvec_spare_padding(struct bitvec *bv, unsigned int up_to_bit) +{ + unsigned int i; + + for (i = bv->cur_bit; i <= up_to_bit; i++) + bitvec_set_bit(bv, L); + + return 0; +} diff --git a/libosmocore/src/comp128.c b/libosmocore/src/comp128.c new file mode 100644 index 000000000..5d5680c72 --- /dev/null +++ b/libosmocore/src/comp128.c @@ -0,0 +1,230 @@ +/* + * COMP128 implementation + * + * + * This code is inspired by original code from : + * Marc Briceno <marc@scard.org>, Ian Goldberg <iang@cs.berkeley.edu>, + * and David Wagner <daw@cs.berkeley.edu> + * + * But it has been fully rewritten from various PDFs found online describing + * the algorithm because the licence of the code referenced above was unclear. + * A comment snippet from the original code is included below, it describes + * where the doc came from and how the algorithm was reverse engineered. + * + * + * (C) 2009 by Sylvain Munaut <tnt@246tNt.com> + * + * 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. + * + */ + +/* + * --- SNIP --- + * + * This code derived from a leaked document from the GSM standards. + * Some missing pieces were filled in by reverse-engineering a working SIM. + * We have verified that this is the correct COMP128 algorithm. + * + * The first page of the document identifies it as + * _Technical Information: GSM System Security Study_. + * 10-1617-01, 10th June 1988. + * The bottom of the title page is marked + * Racal Research Ltd. + * Worton Drive, Worton Grange Industrial Estate, + * Reading, Berks. RG2 0SB, England. + * Telephone: Reading (0734) 868601 Telex: 847152 + * The relevant bits are in Part I, Section 20 (pages 66--67). Enjoy! + * + * Note: There are three typos in the spec (discovered by + * reverse-engineering). + * First, "z = (2 * x[n] + x[n]) mod 2^(9-j)" should clearly read + * "z = (2 * x[m] + x[n]) mod 2^(9-j)". + * Second, the "k" loop in the "Form bits from bytes" section is severely + * botched: the k index should run only from 0 to 3, and clearly the range + * on "the (8-k)th bit of byte j" is also off (should be 0..7, not 1..8, + * to be consistent with the subsequent section). + * Third, SRES is taken from the first 8 nibbles of x[], not the last 8 as + * claimed in the document. (And the document doesn't specify how Kc is + * derived, but that was also easily discovered with reverse engineering.) + * All of these typos have been corrected in the following code. + * + * --- /SNIP --- + */ + +#include <string.h> +#include <stdint.h> + +/* The compression tables (just copied ...) */ +static const uint8_t table_0[512] = { + 102, 177, 186, 162, 2, 156, 112, 75, 55, 25, 8, 12, 251, 193, 246, 188, + 109, 213, 151, 53, 42, 79, 191, 115, 233, 242, 164, 223, 209, 148, 108, 161, + 252, 37, 244, 47, 64, 211, 6, 237, 185, 160, 139, 113, 76, 138, 59, 70, + 67, 26, 13, 157, 63, 179, 221, 30, 214, 36, 166, 69, 152, 124, 207, 116, + 247, 194, 41, 84, 71, 1, 49, 14, 95, 35, 169, 21, 96, 78, 215, 225, + 182, 243, 28, 92, 201, 118, 4, 74, 248, 128, 17, 11, 146, 132, 245, 48, + 149, 90, 120, 39, 87, 230, 106, 232, 175, 19, 126, 190, 202, 141, 137, 176, + 250, 27, 101, 40, 219, 227, 58, 20, 51, 178, 98, 216, 140, 22, 32, 121, + 61, 103, 203, 72, 29, 110, 85, 212, 180, 204, 150, 183, 15, 66, 172, 196, + 56, 197, 158, 0, 100, 45, 153, 7, 144, 222, 163, 167, 60, 135, 210, 231, + 174, 165, 38, 249, 224, 34, 220, 229, 217, 208, 241, 68, 206, 189, 125, 255, + 239, 54, 168, 89, 123, 122, 73, 145, 117, 234, 143, 99, 129, 200, 192, 82, + 104, 170, 136, 235, 93, 81, 205, 173, 236, 94, 105, 52, 46, 228, 198, 5, + 57, 254, 97, 155, 142, 133, 199, 171, 187, 50, 65, 181, 127, 107, 147, 226, + 184, 218, 131, 33, 77, 86, 31, 44, 88, 62, 238, 18, 24, 43, 154, 23, + 80, 159, 134, 111, 9, 114, 3, 91, 16, 130, 83, 10, 195, 240, 253, 119, + 177, 102, 162, 186, 156, 2, 75, 112, 25, 55, 12, 8, 193, 251, 188, 246, + 213, 109, 53, 151, 79, 42, 115, 191, 242, 233, 223, 164, 148, 209, 161, 108, + 37, 252, 47, 244, 211, 64, 237, 6, 160, 185, 113, 139, 138, 76, 70, 59, + 26, 67, 157, 13, 179, 63, 30, 221, 36, 214, 69, 166, 124, 152, 116, 207, + 194, 247, 84, 41, 1, 71, 14, 49, 35, 95, 21, 169, 78, 96, 225, 215, + 243, 182, 92, 28, 118, 201, 74, 4, 128, 248, 11, 17, 132, 146, 48, 245, + 90, 149, 39, 120, 230, 87, 232, 106, 19, 175, 190, 126, 141, 202, 176, 137, + 27, 250, 40, 101, 227, 219, 20, 58, 178, 51, 216, 98, 22, 140, 121, 32, + 103, 61, 72, 203, 110, 29, 212, 85, 204, 180, 183, 150, 66, 15, 196, 172, + 197, 56, 0, 158, 45, 100, 7, 153, 222, 144, 167, 163, 135, 60, 231, 210, + 165, 174, 249, 38, 34, 224, 229, 220, 208, 217, 68, 241, 189, 206, 255, 125, + 54, 239, 89, 168, 122, 123, 145, 73, 234, 117, 99, 143, 200, 129, 82, 192, + 170, 104, 235, 136, 81, 93, 173, 205, 94, 236, 52, 105, 228, 46, 5, 198, + 254, 57, 155, 97, 133, 142, 171, 199, 50, 187, 181, 65, 107, 127, 226, 147, + 218, 184, 33, 131, 86, 77, 44, 31, 62, 88, 18, 238, 43, 24, 23, 154, + 159, 80, 111, 134, 114, 9, 91, 3, 130, 16, 10, 83, 240, 195, 119, 253, +}, table_1[256] = { + 19, 11, 80, 114, 43, 1, 69, 94, 39, 18, 127, 117, 97, 3, 85, 43, + 27, 124, 70, 83, 47, 71, 63, 10, 47, 89, 79, 4, 14, 59, 11, 5, + 35, 107, 103, 68, 21, 86, 36, 91, 85, 126, 32, 50, 109, 94, 120, 6, + 53, 79, 28, 45, 99, 95, 41, 34, 88, 68, 93, 55, 110, 125, 105, 20, + 90, 80, 76, 96, 23, 60, 89, 64, 121, 56, 14, 74, 101, 8, 19, 78, + 76, 66, 104, 46, 111, 50, 32, 3, 39, 0, 58, 25, 92, 22, 18, 51, + 57, 65, 119, 116, 22, 109, 7, 86, 59, 93, 62, 110, 78, 99, 77, 67, + 12, 113, 87, 98, 102, 5, 88, 33, 38, 56, 23, 8, 75, 45, 13, 75, + 95, 63, 28, 49, 123, 120, 20, 112, 44, 30, 15, 98, 106, 2, 103, 29, + 82, 107, 42, 124, 24, 30, 41, 16, 108, 100, 117, 40, 73, 40, 7, 114, + 82, 115, 36, 112, 12, 102, 100, 84, 92, 48, 72, 97, 9, 54, 55, 74, + 113, 123, 17, 26, 53, 58, 4, 9, 69, 122, 21, 118, 42, 60, 27, 73, + 118, 125, 34, 15, 65, 115, 84, 64, 62, 81, 70, 1, 24, 111, 121, 83, + 104, 81, 49, 127, 48, 105, 31, 10, 6, 91, 87, 37, 16, 54, 116, 126, + 31, 38, 13, 0, 72, 106, 77, 61, 26, 67, 46, 29, 96, 37, 61, 52, + 101, 17, 44, 108, 71, 52, 66, 57, 33, 51, 25, 90, 2, 119, 122, 35, +}, table_2[128] = { + 52, 50, 44, 6, 21, 49, 41, 59, 39, 51, 25, 32, 51, 47, 52, 43, + 37, 4, 40, 34, 61, 12, 28, 4, 58, 23, 8, 15, 12, 22, 9, 18, + 55, 10, 33, 35, 50, 1, 43, 3, 57, 13, 62, 14, 7, 42, 44, 59, + 62, 57, 27, 6, 8, 31, 26, 54, 41, 22, 45, 20, 39, 3, 16, 56, + 48, 2, 21, 28, 36, 42, 60, 33, 34, 18, 0, 11, 24, 10, 17, 61, + 29, 14, 45, 26, 55, 46, 11, 17, 54, 46, 9, 24, 30, 60, 32, 0, + 20, 38, 2, 30, 58, 35, 1, 16, 56, 40, 23, 48, 13, 19, 19, 27, + 31, 53, 47, 38, 63, 15, 49, 5, 37, 53, 25, 36, 63, 29, 5, 7, +}, table_3[64] = { + 1, 5, 29, 6, 25, 1, 18, 23, 17, 19, 0, 9, 24, 25, 6, 31, + 28, 20, 24, 30, 4, 27, 3, 13, 15, 16, 14, 18, 4, 3, 8, 9, + 20, 0, 12, 26, 21, 8, 28, 2, 29, 2, 15, 7, 11, 22, 14, 10, + 17, 21, 12, 30, 26, 27, 16, 31, 11, 7, 13, 23, 10, 5, 22, 19, +}, table_4[32] = { + 15, 12, 10, 4, 1, 14, 11, 7, 5, 0, 14, 7, 1, 2, 13, 8, + 10, 3, 4, 9, 6, 0, 3, 2, 5, 6, 8, 9, 11, 13, 15, 12, +}; + +static const uint8_t *_comp128_table[5] = { table_0, table_1, table_2, table_3, table_4 }; + + +static inline void +_comp128_compression_round(uint8_t *x, int n, const uint8_t *tbl) +{ + int i, j, m, a, b, y, z; + m = 4 - n; + for (i=0; i<(1<<n); i++) + for (j=0; j<(1<<m); j++) { + a = j + i * (2<<m); + b = a + (1<<m); + y = (x[a] + (x[b]<<1)) & ((32<<m)-1); + z = ((x[a]<<1) + x[b]) & ((32<<m)-1); + x[a] = tbl[y]; + x[b] = tbl[z]; + } +} + +static inline void +_comp128_compression(uint8_t *x) +{ + int n; + for (n=0; n<5; n++) + _comp128_compression_round(x, n, _comp128_table[n]); +} + +static inline void +_comp128_bitsfrombytes(uint8_t *x, uint8_t *bits) +{ + int i; + memset(bits, 0x00, 128); + for (i=0; i<128; i++) + if (x[i>>2] & (1<<(3-(i&3)))) + bits[i] = 1; +} + +static inline void +_comp128_permutation(uint8_t *x, uint8_t *bits) +{ + int i; + memset(&x[16], 0x00, 16); + for (i=0; i<128; i++) + x[(i>>3)+16] |= bits[(i*17) & 127] << (7-(i&7)); +} + +void +comp128(uint8_t *ki, uint8_t *rand, uint8_t *sres, uint8_t *kc) +{ + int i; + uint8_t x[32], bits[128]; + + /* x[16-31] = RAND */ + memcpy(&x[16], rand, 16); + + /* Round 1-7 */ + for (i=0; i<7; i++) { + /* x[0-15] = Ki */ + memcpy(x, ki, 16); + + /* Compression */ + _comp128_compression(x); + + /* FormBitFromBytes */ + _comp128_bitsfrombytes(x, bits); + + /* Permutation */ + _comp128_permutation(x, bits); + } + + /* Round 8 (final) */ + /* x[0-15] = Ki */ + memcpy(x, ki, 16); + + /* Compression */ + _comp128_compression(x); + + /* Output stage */ + for (i=0; i<8; i+=2) + sres[i>>1] = x[i]<<4 | x[i+1]; + + for (i=0; i<12; i+=2) + kc[i>>1] = (x[i + 18] << 6) | + (x[i + 19] << 2) | + (x[i + 20] >> 2); + + kc[6] = (x[30]<<6) | (x[31]<<2); + kc[7] = 0; +} + diff --git a/libosmocore/src/gsm48.c b/libosmocore/src/gsm48.c new file mode 100644 index 000000000..ff989eaf6 --- /dev/null +++ b/libosmocore/src/gsm48.c @@ -0,0 +1,283 @@ +/* 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-2009 by Harald Welte <laforge@gnumonks.org> + * (C) 2008, 2009 by Holger Hans Peter Freyther <zecke@selfish.org> + * + * 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 <stdio.h> +#include <string.h> + +#include <arpa/inet.h> + +#include <osmocore/utils.h> +#include <osmocore/tlv.h> +#include <osmocore/gsm48.h> + +#include <osmocore/protocol/gsm_04_08.h> + +const struct tlv_definition gsm48_att_tlvdef = { + .def = { + [GSM48_IE_MOBILE_ID] = { TLV_TYPE_TLV }, + [GSM48_IE_NAME_LONG] = { TLV_TYPE_TLV }, + [GSM48_IE_NAME_SHORT] = { TLV_TYPE_TLV }, + [GSM48_IE_UTC] = { TLV_TYPE_TV }, + [GSM48_IE_NET_TIME_TZ] = { TLV_TYPE_FIXED, 7 }, + [GSM48_IE_LSA_IDENT] = { TLV_TYPE_TLV }, + + [GSM48_IE_BEARER_CAP] = { TLV_TYPE_TLV }, + [GSM48_IE_CAUSE] = { TLV_TYPE_TLV }, + [GSM48_IE_CC_CAP] = { TLV_TYPE_TLV }, + [GSM48_IE_ALERT] = { TLV_TYPE_TLV }, + [GSM48_IE_FACILITY] = { TLV_TYPE_TLV }, + [GSM48_IE_PROGR_IND] = { TLV_TYPE_TLV }, + [GSM48_IE_AUX_STATUS] = { TLV_TYPE_TLV }, + [GSM48_IE_NOTIFY] = { TLV_TYPE_TV }, + [GSM48_IE_KPD_FACILITY] = { TLV_TYPE_TV }, + [GSM48_IE_SIGNAL] = { TLV_TYPE_TV }, + [GSM48_IE_CONN_BCD] = { TLV_TYPE_TLV }, + [GSM48_IE_CONN_SUB] = { TLV_TYPE_TLV }, + [GSM48_IE_CALLING_BCD] = { TLV_TYPE_TLV }, + [GSM48_IE_CALLING_SUB] = { TLV_TYPE_TLV }, + [GSM48_IE_CALLED_BCD] = { TLV_TYPE_TLV }, + [GSM48_IE_CALLED_SUB] = { TLV_TYPE_TLV }, + [GSM48_IE_REDIR_BCD] = { TLV_TYPE_TLV }, + [GSM48_IE_REDIR_SUB] = { TLV_TYPE_TLV }, + [GSM48_IE_LOWL_COMPAT] = { TLV_TYPE_TLV }, + [GSM48_IE_HIGHL_COMPAT] = { TLV_TYPE_TLV }, + [GSM48_IE_USER_USER] = { TLV_TYPE_TLV }, + [GSM48_IE_SS_VERS] = { TLV_TYPE_TLV }, + [GSM48_IE_MORE_DATA] = { TLV_TYPE_T }, + [GSM48_IE_CLIR_SUPP] = { TLV_TYPE_T }, + [GSM48_IE_CLIR_INVOC] = { TLV_TYPE_T }, + [GSM48_IE_REV_C_SETUP] = { TLV_TYPE_T }, + [GSM48_IE_REPEAT_CIR] = { TLV_TYPE_T }, + [GSM48_IE_REPEAT_SEQ] = { TLV_TYPE_T }, + /* FIXME: more elements */ + }, +}; + +static const char *rr_cause_names[] = { + [GSM48_RR_CAUSE_NORMAL] = "Normal event", + [GSM48_RR_CAUSE_ABNORMAL_UNSPEC] = "Abnormal release, unspecified", + [GSM48_RR_CAUSE_ABNORMAL_UNACCT] = "Abnormal release, channel unacceptable", + [GSM48_RR_CAUSE_ABNORMAL_TIMER] = "Abnormal release, timer expired", + [GSM48_RR_CAUSE_ABNORMAL_NOACT] = "Abnormal release, no activity on radio path", + [GSM48_RR_CAUSE_PREMPTIVE_REL] = "Preemptive release", + [GSM48_RR_CAUSE_HNDOVER_IMP] = "Handover impossible, timing advance out of range", + [GSM48_RR_CAUSE_CHAN_MODE_UNACCT] = "Channel mode unacceptable", + [GSM48_RR_CAUSE_FREQ_NOT_IMPL] = "Frequency not implemented", + [GSM48_RR_CAUSE_CALL_CLEARED] = "Call already cleared", + [GSM48_RR_CAUSE_SEMANT_INCORR] = "Semantically incorrect message", + [GSM48_RR_CAUSE_INVALID_MAND_INF] = "Invalid mandatory information", + [GSM48_RR_CAUSE_MSG_TYPE_N] = "Message type non-existant or not implemented", + [GSM48_RR_CAUSE_MSG_TYPE_N_COMPAT] = "Message type not compatible with protocol state", + [GSM48_RR_CAUSE_COND_IE_ERROR] = "Conditional IE error", + [GSM48_RR_CAUSE_NO_CELL_ALLOC_A] = "No cell allocation available", + [GSM48_RR_CAUSE_PROT_ERROR_UNSPC] = "Protocol error unspecified", +}; + +const char *cc_state_names[32] = { + "NULL", + "INITIATED", + "illegal state 2", + "MO_CALL_PROC", + "CALL_DELIVERED", + "illegal state 5", + "CALL_PRESENT", + "CALL_RECEIVED", + "CONNECT_REQUEST", + "MO_TERM_CALL_CONF", + "ACTIVE", + "DISCONNECT_REQ", + "DISCONNECT_IND", + "illegal state 13", + "illegal state 14", + "illegal state 15", + "illegal state 16", + "illegal state 17", + "illegal state 18", + "RELEASE_REQ", + "illegal state 20", + "illegal state 21", + "illegal state 22", + "illegal state 23", + "illegal state 24", + "illegal state 25", + "MO_ORIG_MODIFY", + "MO_TERM_MODIFY", + "CONNECT_IND", + "illegal state 29", + "illegal state 30", + "illegal state 31", +}; + +const char *gsm48_cc_msg_names[0x40] = { + "unknown 0x00", + "ALERTING", + "CALL_PROC", + "PROGRESS", + "ESTAB", + "SETUP", + "ESTAB_CONF", + "CONNECT", + "CALL_CONF", + "START_CC", + "unknown 0x0a", + "RECALL", + "unknown 0x0c", + "unknown 0x0d", + "EMERG_SETUP", + "CONNECT_ACK", + "USER_INFO", + "unknown 0x11", + "unknown 0x12", + "MODIFY_REJECT", + "unknown 0x14", + "unknown 0x15", + "unknown 0x16", + "MODIFY", + "HOLD", + "HOLD_ACK", + "HOLD_REJ", + "unknown 0x1b", + "RETR", + "RETR_ACK", + "RETR_REJ", + "MODIFY_COMPL", + "unknown 0x20", + "unknown 0x21", + "unknown 0x22", + "unknown 0x23", + "unknown 0x24", + "DISCONNECT", + "unknown 0x26", + "unknown 0x27", + "unknown 0x28", + "unknown 0x29", + "RELEASE_COMPL", + "unknown 0x2b", + "unknown 0x2c", + "RELEASE", + "unknown 0x2e", + "unknown 0x2f", + "unknown 0x30", + "STOP_DTMF", + "STOP_DTMF_ACK", + "unknown 0x33", + "STATUS_ENQ", + "START_DTMF", + "START_DTMF_ACK", + "START_DTMF_REJ", + "unknown 0x38", + "CONG_CTRL", + "FACILITY", + "unknown 0x3b", + "STATUS", + "unknown 0x3d", + "NOTIFY", + "unknown 0x3f", +}; + +static char strbuf[64]; + +const char *rr_cause_name(uint8_t cause) +{ + if (cause < ARRAY_SIZE(rr_cause_names) && + rr_cause_names[cause]) + return rr_cause_names[cause]; + + snprintf(strbuf, sizeof(strbuf), "0x%02x", cause); + return strbuf; +} + +static void to_bcd(uint8_t *bcd, uint16_t val) +{ + bcd[2] = val % 10; + val = val / 10; + bcd[1] = val % 10; + val = val / 10; + bcd[0] = val % 10; + val = val / 10; +} + +void gsm48_generate_lai(struct gsm48_loc_area_id *lai48, uint16_t mcc, + uint16_t mnc, uint16_t lac) +{ + uint8_t bcd[3]; + + to_bcd(bcd, mcc); + lai48->digits[0] = bcd[0] | (bcd[1] << 4); + lai48->digits[1] = bcd[2]; + + to_bcd(bcd, mnc); + /* FIXME: do we need three-digit MNC? See Table 10.5.3 */ +#if 0 + lai48->digits[1] |= bcd[2] << 4; + lai48->digits[2] = bcd[0] | (bcd[1] << 4); +#else + lai48->digits[1] |= 0xf << 4; + lai48->digits[2] = bcd[1] | (bcd[2] << 4); +#endif + + lai48->lac = htons(lac); +} + +int gsm48_generate_mid_from_tmsi(uint8_t *buf, uint32_t tmsi) +{ + uint32_t *tptr = (uint32_t *) &buf[3]; + + buf[0] = GSM48_IE_MOBILE_ID; + buf[1] = GSM48_TMSI_LEN; + buf[2] = 0xf0 | GSM_MI_TYPE_TMSI; + *tptr = htonl(tmsi); + + return 7; +} + +int gsm48_generate_mid_from_imsi(uint8_t *buf, const char *imsi) +{ + unsigned int length = strlen(imsi), i, off = 0; + uint8_t odd = (length & 0x1) == 1; + + buf[0] = GSM48_IE_MOBILE_ID; + buf[2] = char2bcd(imsi[0]) << 4 | GSM_MI_TYPE_IMSI | (odd << 3); + + /* if the length is even we will fill half of the last octet */ + if (odd) + buf[1] = (length + 1) >> 1; + else + buf[1] = (length + 2) >> 1; + + for (i = 1; i < buf[1]; ++i) { + uint8_t lower, upper; + + lower = char2bcd(imsi[++off]); + if (!odd && off + 1 == length) + upper = 0x0f; + else + upper = char2bcd(imsi[++off]) & 0x0f; + + buf[2 + i] = (upper << 4) | lower; + } + + return 2 + buf[1]; +} diff --git a/libosmocore/src/gsm48_ie.c b/libosmocore/src/gsm48_ie.c new file mode 100644 index 000000000..4ca5fb800 --- /dev/null +++ b/libosmocore/src/gsm48_ie.c @@ -0,0 +1,659 @@ +/* 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) 2008-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> + +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] = 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; +} + diff --git a/libosmocore/src/gsm_utils.c b/libosmocore/src/gsm_utils.c new file mode 100644 index 000000000..593dd5c94 --- /dev/null +++ b/libosmocore/src/gsm_utils.c @@ -0,0 +1,361 @@ +/* + * (C) 2008 by Daniel Willmann <daniel@totalueberwachung.de> + * (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org> + * (C) 2009-2010 by Harald Welte <laforge@gnumonks.org> + * + * 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 <openbsc/gsm_data.h> +#include <osmocore/utils.h> +#include <osmocore/gsm_utils.h> + +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <ctype.h> + +#include "../config.h" + +/* GSM 03.38 6.2.1 Charachter packing */ +int gsm_7bit_decode(char *text, const uint8_t *user_data, uint8_t length) +{ + int i = 0; + int l = 0; + + /* FIXME: We need to account for user data headers here */ + i += l; + for (; i < length; i ++) + *(text ++) = + ((user_data[(i * 7 + 7) >> 3] << + (7 - ((i * 7 + 7) & 7))) | + (user_data[(i * 7) >> 3] >> + ((i * 7) & 7))) & 0x7f; + *text = '\0'; + + return i - l; +} + + +/* GSM 03.38 6.2.1 Charachter packing */ +int gsm_7bit_encode(uint8_t *result, const char *data) +{ + int i,j = 0; + unsigned char ch1, ch2; + int shift = 0; + + for ( i=0; i<strlen(data); i++ ) { + + ch1 = data[i] & 0x7F; + ch1 = ch1 >> shift; + ch2 = data[(i+1)] & 0x7F; + ch2 = ch2 << (7-shift); + + ch1 = ch1 | ch2; + + result[j++] = ch1; + + shift++; + + if ((shift == 7) && (i+1<strlen(data))) { + shift = 0; + i++; + } + } + + return i; +} + +/* determine power control level for given dBm value, as indicated + * by the tables in chapter 4.1.1 of GSM TS 05.05 */ +int ms_pwr_ctl_lvl(enum gsm_band band, unsigned int dbm) +{ + switch (band) { + case GSM_BAND_450: + case GSM_BAND_480: + case GSM_BAND_750: + case GSM_BAND_900: + case GSM_BAND_810: + case GSM_BAND_850: + if (dbm >= 39) + return 0; + else if (dbm < 5) + return 19; + else { + /* we are guaranteed to have (5 <= dbm < 39) */ + return 2 + ((39 - dbm) / 2); + } + break; + case GSM_BAND_1800: + if (dbm >= 36) + return 29; + else if (dbm >= 34) + return 30; + else if (dbm >= 32) + return 31; + else if (dbm == 31) + return 0; + else { + /* we are guaranteed to have (0 <= dbm < 31) */ + return (30 - dbm) / 2; + } + break; + case GSM_BAND_1900: + if (dbm >= 33) + return 30; + else if (dbm >= 32) + return 31; + else if (dbm == 31) + return 0; + else { + /* we are guaranteed to have (0 <= dbm < 31) */ + return (30 - dbm) / 2; + } + break; + } + return -EINVAL; +} + +int ms_pwr_dbm(enum gsm_band band, uint8_t lvl) +{ + lvl &= 0x1f; + + switch (band) { + case GSM_BAND_450: + case GSM_BAND_480: + case GSM_BAND_750: + case GSM_BAND_900: + case GSM_BAND_810: + case GSM_BAND_850: + if (lvl < 2) + return 39; + else if (lvl < 20) + return 39 - ((lvl - 2) * 2) ; + else + return 5; + break; + case GSM_BAND_1800: + if (lvl < 16) + return 30 - (lvl * 2); + else if (lvl < 29) + return 0; + else + return 36 - ((lvl - 29) * 2); + break; + case GSM_BAND_1900: + if (lvl < 16) + return 30 - (lvl * 2); + else if (lvl < 30) + return -EINVAL; + else + return 33 - (lvl - 30); + break; + } + return -EINVAL; +} + +/* According to TS 08.05 Chapter 8.1.4 */ +int rxlev2dbm(uint8_t rxlev) +{ + if (rxlev > 63) + rxlev = 63; + + return -110 + rxlev; +} + +/* According to TS 08.05 Chapter 8.1.4 */ +uint8_t dbm2rxlev(int dbm) +{ + int rxlev = dbm + 110; + + if (rxlev > 63) + rxlev = 63; + else if (rxlev < 0) + rxlev = 0; + + return rxlev; +} + +const char *gsm_band_name(enum gsm_band band) +{ + switch (band) { + case GSM_BAND_450: + return "GSM450"; + case GSM_BAND_480: + return "GSM450"; + case GSM_BAND_750: + return "GSM750"; + case GSM_BAND_810: + return "GSM810"; + case GSM_BAND_850: + return "GSM850"; + case GSM_BAND_900: + return "GSM900"; + case GSM_BAND_1800: + return "DCS1800"; + case GSM_BAND_1900: + return "PCS1900"; + } + return "invalid"; +} + +enum gsm_band gsm_band_parse(const char* mhz) +{ + while (*mhz && !isdigit(*mhz)) + mhz++; + + if (*mhz == '\0') + return -EINVAL; + + switch (strtol(mhz, NULL, 10)) { + case 450: + return GSM_BAND_450; + case 480: + return GSM_BAND_480; + case 750: + return GSM_BAND_750; + case 810: + return GSM_BAND_810; + case 850: + return GSM_BAND_850; + case 900: + return GSM_BAND_900; + case 1800: + return GSM_BAND_1800; + case 1900: + return GSM_BAND_1900; + default: + return -EINVAL; + } +} + + +#ifdef HAVE_EXECINFO_H +#include <execinfo.h> +void generate_backtrace() +{ + int i, nptrs; + void *buffer[100]; + char **strings; + + nptrs = backtrace(buffer, ARRAY_SIZE(buffer)); + printf("backtrace() returned %d addresses\n", nptrs); + + strings = backtrace_symbols(buffer, nptrs); + if (!strings) + return; + + for (i = 1; i < nptrs; i++) + printf("%s\n", strings[i]); + + free(strings); +} +#endif + +enum gsm_band gsm_arfcn2band(uint16_t arfcn) +{ + if (arfcn & ARFCN_PCS) + return GSM_BAND_1900; + else if (arfcn <= 124) + return GSM_BAND_900; + else if (arfcn >= 955 && arfcn <= 1023) + return GSM_BAND_900; + else if (arfcn >= 128 && arfcn <= 251) + return GSM_BAND_850; + else if (arfcn >= 512 && arfcn <= 885) + return GSM_BAND_1800; + else if (arfcn >= 259 && arfcn <= 293) + return GSM_BAND_450; + else if (arfcn >= 306 && arfcn <= 340) + return GSM_BAND_480; + else if (arfcn >= 350 && arfcn <= 425) + return GSM_BAND_810; + else if (arfcn >= 438 && arfcn <= 511) + return GSM_BAND_750; + else + return GSM_BAND_1800; +} + +/* Convert an ARFCN to the frequency in MHz * 10 */ +uint16_t gsm_arfcn2freq10(uint16_t arfcn, int uplink) +{ + uint16_t freq10_ul; + uint16_t freq10_dl; + + if (arfcn & ARFCN_PCS) { + /* DCS 1900 */ + arfcn &= ~ARFCN_PCS; + freq10_ul = 18502 + 2 * (arfcn-512); + freq10_dl = freq10_ul + 800; + } else if (arfcn <= 124) { + /* Primary GSM + ARFCN 0 of E-GSM */ + freq10_ul = 8900 + 2 * arfcn; + freq10_dl = freq10_ul + 450; + } else if (arfcn >= 955 && arfcn <= 1023) { + /* E-GSM and R-GSM */ + freq10_ul = 8900 + 2 * (arfcn - 1024); + freq10_dl = freq10_ul + 450; + } else if (arfcn >= 128 && arfcn <= 251) { + /* GSM 850 */ + freq10_ul = 8242 + 2 * (arfcn - 128); + freq10_dl = freq10_ul + 450; + } else if (arfcn >= 512 && arfcn <= 885) { + /* DCS 1800 */ + freq10_ul = 17102 + 2 * (arfcn - 512); + freq10_dl = freq10_ul + 950; + } else if (arfcn >= 259 && arfcn <= 293) { + /* GSM 450 */ + freq10_ul = 4506 + 2 * (arfcn - 259); + freq10_dl = freq10_ul + 100; + } else if (arfcn >= 306 && arfcn <= 340) { + /* GSM 480 */ + freq10_ul = 4790 + 2 * (arfcn - 306); + freq10_dl = freq10_ul + 100; + } else if (arfcn >= 350 && arfcn <= 425) { + /* GSM 810 */ + freq10_ul = 8060 + 2 * (arfcn - 350); + freq10_dl = freq10_ul + 450; + } else if (arfcn >= 438 && arfcn <= 511) { + /* GSM 750 */ + freq10_ul = 7472 + 2 * (arfcn - 438); + freq10_dl = freq10_ul + 300; + } else + return 0xffff; + + if (uplink) + return freq10_ul; + else + return freq10_dl; +} + +void gsm_fn2gsmtime(struct gsm_time *time, uint32_t fn) +{ + time->fn = fn; + time->t1 = time->fn / (26*51); + time->t2 = time->fn % 26; + time->t3 = time->fn % 51; + time->tc = (time->fn / 51) % 8; +} + +uint32_t gsm_gsmtime2fn(struct gsm_time *time) +{ + /* TS 05.02 Chapter 4.3.3 TDMA frame number */ + return (51 * ((time->t3 - time->t2 + 26) % 26) + time->t3 + (26 * 51 * time->t1)); +} diff --git a/libosmocore/src/msgb.c b/libosmocore/src/msgb.c new file mode 100644 index 000000000..60af373eb --- /dev/null +++ b/libosmocore/src/msgb.c @@ -0,0 +1,89 @@ +/* (C) 2008 by Harald Welte <laforge@gnumonks.org> + * 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 <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <sys/types.h> + +#include <osmocore/msgb.h> +//#include <openbsc/gsm_data.h> +#include <osmocore/talloc.h> +//#include <openbsc/debug.h> + +void *tall_msgb_ctx; + +struct msgb *msgb_alloc(uint16_t size, const char *name) +{ + struct msgb *msg; + + msg = _talloc_zero(tall_msgb_ctx, sizeof(*msg) + size, name); + + if (!msg) { + //LOGP(DRSL, LOGL_FATAL, "unable to allocate msgb\n"); + return NULL; + } + + msg->data_len = size; + msg->len = 0; + msg->data = msg->_data; + msg->head = msg->_data; + msg->tail = msg->_data; + + return msg; +} + +void msgb_free(struct msgb *m) +{ + talloc_free(m); +} + +void msgb_enqueue(struct llist_head *queue, struct msgb *msg) +{ + llist_add_tail(&msg->list, queue); +} + +struct msgb *msgb_dequeue(struct llist_head *queue) +{ + struct llist_head *lh; + + if (llist_empty(queue)) + return NULL; + + lh = queue->next; + llist_del(lh); + + return llist_entry(lh, struct msgb, list); +} + +void msgb_reset(struct msgb *msg) +{ + msg->len = 0; + msg->data = msg->_data; + msg->head = msg->_data; + msg->tail = msg->_data; + + msg->bts_link = NULL; + msg->trx = NULL; + msg->lchan = NULL; + msg->l2h = NULL; + msg->l3h = NULL; + msg->smsh = NULL; +} diff --git a/libosmocore/src/rsl.c b/libosmocore/src/rsl.c new file mode 100644 index 000000000..c864b12f7 --- /dev/null +++ b/libosmocore/src/rsl.c @@ -0,0 +1,327 @@ +/* GSM Radio Signalling Link messages on the A-bis interface + * 3GPP TS 08.58 version 8.6.0 Release 1999 / ETSI TS 100 596 V8.6.0 */ + +/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org> + * + * 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 <errno.h> + +#include <osmocore/tlv.h> +#include <osmocore/rsl.h> + +#define RSL_ALLOC_SIZE 200 +#define RSL_ALLOC_HEADROOM 56 + +void rsl_init_rll_hdr(struct abis_rsl_rll_hdr *dh, uint8_t msg_type) +{ + dh->c.msg_discr = ABIS_RSL_MDISC_RLL; + dh->c.msg_type = msg_type; + dh->ie_chan = RSL_IE_CHAN_NR; + dh->ie_link_id = RSL_IE_LINK_IDENT; +} + +const struct tlv_definition rsl_att_tlvdef = { + .def = { + [RSL_IE_CHAN_NR] = { TLV_TYPE_TV }, + [RSL_IE_LINK_IDENT] = { TLV_TYPE_TV }, + [RSL_IE_ACT_TYPE] = { TLV_TYPE_TV }, + [RSL_IE_BS_POWER] = { TLV_TYPE_TV }, + [RSL_IE_CHAN_IDENT] = { TLV_TYPE_TLV }, + [RSL_IE_CHAN_MODE] = { TLV_TYPE_TLV }, + [RSL_IE_ENCR_INFO] = { TLV_TYPE_TLV }, + [RSL_IE_FRAME_NUMBER] = { TLV_TYPE_FIXED, 2 }, + [RSL_IE_HANDO_REF] = { TLV_TYPE_TV }, + [RSL_IE_L1_INFO] = { TLV_TYPE_FIXED, 2 }, + [RSL_IE_L3_INFO] = { TLV_TYPE_TL16V }, + [RSL_IE_MS_IDENTITY] = { TLV_TYPE_TLV }, + [RSL_IE_MS_POWER] = { TLV_TYPE_TV }, + [RSL_IE_PAGING_GROUP] = { TLV_TYPE_TV }, + [RSL_IE_PAGING_LOAD] = { TLV_TYPE_FIXED, 2 }, + [RSL_IE_PYHS_CONTEXT] = { TLV_TYPE_TLV }, + [RSL_IE_ACCESS_DELAY] = { TLV_TYPE_TV }, + [RSL_IE_RACH_LOAD] = { TLV_TYPE_TLV }, + [RSL_IE_REQ_REFERENCE] = { TLV_TYPE_FIXED, 3 }, + [RSL_IE_RELEASE_MODE] = { TLV_TYPE_TV }, + [RSL_IE_RESOURCE_INFO] = { TLV_TYPE_TLV }, + [RSL_IE_RLM_CAUSE] = { TLV_TYPE_TLV }, + [RSL_IE_STARTNG_TIME] = { TLV_TYPE_FIXED, 2 }, + [RSL_IE_TIMING_ADVANCE] = { TLV_TYPE_TV }, + [RSL_IE_UPLINK_MEAS] = { TLV_TYPE_TLV }, + [RSL_IE_CAUSE] = { TLV_TYPE_TLV }, + [RSL_IE_MEAS_RES_NR] = { TLV_TYPE_TV }, + [RSL_IE_MSG_ID] = { TLV_TYPE_TV }, + [RSL_IE_SYSINFO_TYPE] = { TLV_TYPE_TV }, + [RSL_IE_MS_POWER_PARAM] = { TLV_TYPE_TLV }, + [RSL_IE_BS_POWER_PARAM] = { TLV_TYPE_TLV }, + [RSL_IE_PREPROC_PARAM] = { TLV_TYPE_TLV }, + [RSL_IE_PREPROC_MEAS] = { TLV_TYPE_TLV }, + [RSL_IE_IMM_ASS_INFO] = { TLV_TYPE_TLV }, + [RSL_IE_SMSCB_INFO] = { TLV_TYPE_FIXED, 23 }, + [RSL_IE_MS_TIMING_OFFSET] = { TLV_TYPE_TV }, + [RSL_IE_ERR_MSG] = { TLV_TYPE_TLV }, + [RSL_IE_FULL_BCCH_INFO] = { TLV_TYPE_TLV }, + [RSL_IE_CHAN_NEEDED] = { TLV_TYPE_TV }, + [RSL_IE_CB_CMD_TYPE] = { TLV_TYPE_TV }, + [RSL_IE_SMSCB_MSG] = { TLV_TYPE_TLV }, + [RSL_IE_FULL_IMM_ASS_INFO] = { TLV_TYPE_TLV }, + [RSL_IE_SACCH_INFO] = { TLV_TYPE_TLV }, + [RSL_IE_CBCH_LOAD_INFO] = { TLV_TYPE_TV }, + [RSL_IE_SMSCB_CHAN_INDICATOR] = { TLV_TYPE_TV }, + [RSL_IE_GROUP_CALL_REF] = { TLV_TYPE_TLV }, + [RSL_IE_CHAN_DESC] = { TLV_TYPE_TLV }, + [RSL_IE_NCH_DRX_INFO] = { TLV_TYPE_TLV }, + [RSL_IE_CMD_INDICATOR] = { TLV_TYPE_TLV }, + [RSL_IE_EMLPP_PRIO] = { TLV_TYPE_TV }, + [RSL_IE_UIC] = { TLV_TYPE_TLV }, + [RSL_IE_MAIN_CHAN_REF] = { TLV_TYPE_TV }, + [RSL_IE_MR_CONFIG] = { TLV_TYPE_TLV }, + [RSL_IE_MR_CONTROL] = { TLV_TYPE_TV }, + [RSL_IE_SUP_CODEC_TYPES] = { TLV_TYPE_TLV }, + [RSL_IE_CODEC_CONFIG] = { TLV_TYPE_TLV }, + [RSL_IE_RTD] = { TLV_TYPE_TV }, + [RSL_IE_TFO_STATUS] = { TLV_TYPE_TV }, + [RSL_IE_LLP_APDU] = { TLV_TYPE_TLV }, + [RSL_IE_SIEMENS_MRPCI] = { TLV_TYPE_TV }, + [RSL_IE_IPAC_PROXY_UDP] = { TLV_TYPE_FIXED, 2 }, + [RSL_IE_IPAC_BSCMPL_TOUT] = { TLV_TYPE_TV }, + [RSL_IE_IPAC_REMOTE_IP] = { TLV_TYPE_FIXED, 4 }, + [RSL_IE_IPAC_REMOTE_PORT] = { TLV_TYPE_FIXED, 2 }, + [RSL_IE_IPAC_RTP_PAYLOAD] = { TLV_TYPE_TV }, + [RSL_IE_IPAC_LOCAL_PORT] = { TLV_TYPE_FIXED, 2 }, + [RSL_IE_IPAC_SPEECH_MODE] = { TLV_TYPE_TV }, + [RSL_IE_IPAC_LOCAL_IP] = { TLV_TYPE_FIXED, 4 }, + [RSL_IE_IPAC_CONN_ID] = { TLV_TYPE_FIXED, 2 }, + [RSL_IE_IPAC_RTP_CSD_FMT] = { TLV_TYPE_TV }, + [RSL_IE_IPAC_RTP_JIT_BUF] = { TLV_TYPE_FIXED, 2 }, + [RSL_IE_IPAC_RTP_COMPR] = { TLV_TYPE_TV }, + [RSL_IE_IPAC_RTP_PAYLOAD2] = { TLV_TYPE_TV }, + [RSL_IE_IPAC_RTP_MPLEX] = { TLV_TYPE_FIXED, 8 }, + [RSL_IE_IPAC_RTP_MPLEX_ID] = { TLV_TYPE_TV }, + }, +}; + +/* encode channel number as per Section 9.3.1 */ +uint8_t rsl_enc_chan_nr(uint8_t type, uint8_t subch, uint8_t timeslot) +{ + uint8_t ret; + + ret = (timeslot & 0x07) | type; + + switch (type) { + case RSL_CHAN_Lm_ACCHs: + subch &= 0x01; + break; + case RSL_CHAN_SDCCH4_ACCH: + subch &= 0x03; + break; + case RSL_CHAN_SDCCH8_ACCH: + subch &= 0x07; + break; + default: + /* no subchannels allowed */ + subch = 0x00; + break; + } + ret |= (subch << 3); + + return ret; +} + +int rsl_dec_chan_nr(uint8_t chan_nr, uint8_t *type, uint8_t *subch, uint8_t *timeslot) +{ + *timeslot = chan_nr & 0x7; + + if ((chan_nr & 0xf8) == RSL_CHAN_Bm_ACCHs) { + *type = RSL_CHAN_Bm_ACCHs; + *subch = 0; + } else if ((chan_nr & 0xf0) == RSL_CHAN_Lm_ACCHs) { + *type = RSL_CHAN_Lm_ACCHs; + *subch = (chan_nr >> 3) & 0x1; + } else if ((chan_nr & 0xe0) == RSL_CHAN_SDCCH4_ACCH) { + *type = RSL_CHAN_SDCCH4_ACCH; + *subch = (chan_nr >> 3) & 0x3; + } else if ((chan_nr & 0xc0) == RSL_CHAN_SDCCH8_ACCH) { + *type = RSL_CHAN_SDCCH8_ACCH; + *subch = (chan_nr >> 3) & 0x7; + } else if ((chan_nr & 0xf8) == RSL_CHAN_BCCH) { + *type = RSL_CHAN_BCCH; + *subch = 0; + } else if ((chan_nr & 0xf8) == RSL_CHAN_RACH) { + *type = RSL_CHAN_RACH; + *subch = 0; + } else if ((chan_nr & 0xf8) == RSL_CHAN_PCH_AGCH) { + *type = RSL_CHAN_PCH_AGCH; + *subch = 0; + } else + return -EINVAL; + + return 0; +} + +/* FIXME: convert to value_string */ +static const char *rsl_err_vals[0xff] = { + [RSL_ERR_RADIO_IF_FAIL] = "Radio Interface Failure", + [RSL_ERR_RADIO_LINK_FAIL] = "Radio Link Failure", + [RSL_ERR_HANDOVER_ACC_FAIL] = "Handover Access Failure", + [RSL_ERR_TALKER_ACC_FAIL] = "Talker Access Failure", + [RSL_ERR_OM_INTERVENTION] = "O&M Intervention", + [RSL_ERR_NORMAL_UNSPEC] = "Normal event, unspecified", + [RSL_ERR_T_MSRFPCI_EXP] = "Siemens: T_MSRFPCI Expired", + [RSL_ERR_EQUIPMENT_FAIL] = "Equipment Failure", + [RSL_ERR_RR_UNAVAIL] = "Radio Resource not available", + [RSL_ERR_TERR_CH_FAIL] = "Terrestrial Channel Failure", + [RSL_ERR_CCCH_OVERLOAD] = "CCCH Overload", + [RSL_ERR_ACCH_OVERLOAD] = "ACCH Overload", + [RSL_ERR_PROCESSOR_OVERLOAD] = "Processor Overload", + [RSL_ERR_RES_UNAVAIL] = "Resource not available, unspecified", + [RSL_ERR_TRANSC_UNAVAIL] = "Transcoding not available", + [RSL_ERR_SERV_OPT_UNAVAIL] = "Service or Option not available", + [RSL_ERR_ENCR_UNIMPL] = "Encryption algorithm not implemented", + [RSL_ERR_SERV_OPT_UNIMPL] = "Service or Option not implemented", + [RSL_ERR_RCH_ALR_ACTV_ALLOC] = "Radio channel already activated", + [RSL_ERR_INVALID_MESSAGE] = "Invalid Message, unspecified", + [RSL_ERR_MSG_DISCR] = "Message Discriminator Error", + [RSL_ERR_MSG_TYPE] = "Message Type Error", + [RSL_ERR_MSG_SEQ] = "Message Sequence Error", + [RSL_ERR_IE_ERROR] = "General IE error", + [RSL_ERR_MAND_IE_ERROR] = "Mandatory IE error", + [RSL_ERR_OPT_IE_ERROR] = "Optional IE error", + [RSL_ERR_IE_NONEXIST] = "IE non-existent", + [RSL_ERR_IE_LENGTH] = "IE length error", + [RSL_ERR_IE_CONTENT] = "IE content error", + [RSL_ERR_PROTO] = "Protocol error, unspecified", + [RSL_ERR_INTERWORKING] = "Interworking error, unspecified", +}; + +const struct value_string rsl_rlm_cause_strs[] = { + { RLL_CAUSE_T200_EXPIRED, "Timer T200 expired (N200+1) times" }, + { RLL_CAUSE_REEST_REQ, "Re-establishment request" }, + { RLL_CAUSE_UNSOL_UA_RESP, "Unsolicited UA response" }, + { RLL_CAUSE_UNSOL_DM_RESP, "Unsolicited DM response" }, + { RLL_CAUSE_UNSOL_DM_RESP_MF, "Unsolicited DM response, multiple frame" }, + { RLL_CAUSE_UNSOL_SPRV_RESP, "Unsolicited supervisory response" }, + { RLL_CAUSE_SEQ_ERR, "Sequence Error" }, + { RLL_CAUSE_UFRM_INC_PARAM, "U-Frame with incorrect parameters" }, + { RLL_CAUSE_SFRM_INC_PARAM, "S-Frame with incorrect parameters" }, + { RLL_CAUSE_IFRM_INC_MBITS, "I-Frame with incorrect use of M bit" }, + { RLL_CAUSE_IFRM_INC_LEN, "I-Frame with incorrect length" }, + { RLL_CAUSE_FRM_UNIMPL, "Fraeme not implemented" }, + { RLL_CAUSE_SABM_MF, "SABM command, multiple frame established state" }, + { RLL_CAUSE_SABM_INFO_NOTALL, "SABM frame with information not allowed in this state" }, + { 0, NULL }, +}; + +const char *rsl_err_name(uint8_t err) +{ + if (rsl_err_vals[err]) + return rsl_err_vals[err]; + else + return "unknown"; +} + +/* Section 3.3.2.3 TS 05.02. I think this looks like a table */ +int rsl_ccch_conf_to_bs_cc_chans(int ccch_conf) +{ + switch (ccch_conf) { + case RSL_BCCH_CCCH_CONF_1_NC: + return 1; + case RSL_BCCH_CCCH_CONF_1_C: + return 1; + case RSL_BCCH_CCCH_CONF_2_NC: + return 2; + case RSL_BCCH_CCCH_CONF_3_NC: + return 3; + case RSL_BCCH_CCCH_CONF_4_NC: + return 4; + default: + return -1; + } +} + +/* Section 3.3.2.3 TS 05.02 */ +int rsl_ccch_conf_to_bs_ccch_sdcch_comb(int ccch_conf) +{ + switch (ccch_conf) { + case RSL_BCCH_CCCH_CONF_1_NC: + return 0; + case RSL_BCCH_CCCH_CONF_1_C: + return 1; + case RSL_BCCH_CCCH_CONF_2_NC: + return 0; + case RSL_BCCH_CCCH_CONF_3_NC: + return 0; + case RSL_BCCH_CCCH_CONF_4_NC: + return 0; + default: + return -1; + } +} + +/* Push a RSL RLL header with L3_INFO IE */ +void rsl_rll_push_l3(struct msgb *msg, uint8_t msg_type, uint8_t chan_nr, + uint8_t link_id, int transparent) +{ + uint8_t l3_len = msg->tail - (uint8_t *)msgb_l3(msg); + struct abis_rsl_rll_hdr *rh; + + /* construct a RSLms RLL message (DATA INDICATION, UNIT DATA + * INDICATION) and send it off via RSLms */ + + /* Push the L3 IE tag and lengh */ + msgb_tv16_push(msg, RSL_IE_L3_INFO, l3_len); + + /* Then push the RSL header */ + rh = (struct abis_rsl_rll_hdr *) msgb_push(msg, sizeof(*rh)); + rsl_init_rll_hdr(rh, msg_type); + if (transparent) + rh->c.msg_discr |= ABIS_RSL_MDISC_TRANSP; + rh->chan_nr = chan_nr; + rh->link_id = link_id; + + /* set the l2 header pointer */ + msg->l2h = (uint8_t *)rh; +} + +struct msgb *rsl_rll_simple(uint8_t msg_type, uint8_t chan_nr, + uint8_t link_id, int transparent) +{ + struct abis_rsl_rll_hdr *rh; + struct msgb *msg; + + msg = msgb_alloc_headroom(RSL_ALLOC_SIZE+RSL_ALLOC_HEADROOM, + RSL_ALLOC_HEADROOM, "rsl_rll_simple"); + + if (!msg) + return NULL; + + /* put the RSL header */ + rh = (struct abis_rsl_rll_hdr *) msgb_put(msg, sizeof(*rh)); + rsl_init_rll_hdr(rh, msg_type); + if (transparent) + rh->c.msg_discr |= ABIS_RSL_MDISC_TRANSP; + rh->chan_nr = chan_nr; + rh->link_id = link_id; + + /* set the l2 header pointer */ + msg->l2h = (uint8_t *)rh; + + return msg; +} diff --git a/libosmocore/src/rxlev_stat.c b/libosmocore/src/rxlev_stat.c new file mode 100644 index 000000000..1bfd6795a --- /dev/null +++ b/libosmocore/src/rxlev_stat.c @@ -0,0 +1,94 @@ +/* Rx Level statistics */ + +/* (C) 2010 by Harald Welte <laforge@gnumonks.org> + * + * 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 <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <stdint.h> + +#include <osmocore/bitvec.h> +#include <osmocore/rxlev_stat.h> + +int bitvec_find_bit_pos(const struct bitvec *bv, unsigned int n, enum bit_value val) +{ + unsigned int i; + + for (i = n; i < bv->data_len*8; i++) { + if (bitvec_get_bit_pos(bv, i) == val) + return i; + } + + return -1; +} + +void rxlev_stat_input(struct rxlev_stats *st, uint16_t arfcn, uint8_t rxlev) +{ + struct bitvec bv; + + if (rxlev >= NUM_RXLEVS) + rxlev = NUM_RXLEVS-1; + + bv.data_len = NUM_ARFCNS/8; + bv.data = st->rxlev_buckets[rxlev]; + + bitvec_set_bit_pos(&bv, arfcn, ONE); +} + +/* get the next ARFCN that has the specified Rxlev */ +int16_t rxlev_stat_get_next(const struct rxlev_stats *st, uint8_t rxlev, int16_t arfcn) +{ + struct bitvec bv; + + if (rxlev >= NUM_RXLEVS) + rxlev = NUM_RXLEVS-1; + + bv.data_len = NUM_ARFCNS/8; + + if (arfcn < 0) + arfcn = -1; + + bv.data = st->rxlev_buckets[rxlev]; + + return bitvec_find_bit_pos(&bv, arfcn+1, ONE); +} + +void rxlev_stat_reset(struct rxlev_stats *st) +{ + memset(st, 0, sizeof(*st)); +} + +void rxlev_stat_dump(const struct rxlev_stats *st) +{ + int i; + + for (i = NUM_RXLEVS-1; i >= 0; i--) { + int16_t arfcn = -1; + + printf("ARFCN with RxLev %u: ", i); + while ((arfcn = rxlev_stat_get_next(st, i, arfcn)) >= 0) { + printf("%u ", arfcn); + } + printf("\n"); + } +} diff --git a/libosmocore/src/select.c b/libosmocore/src/select.c new file mode 100644 index 000000000..9517778ce --- /dev/null +++ b/libosmocore/src/select.c @@ -0,0 +1,130 @@ +/* select filedescriptor handling, taken from: + * userspace logging daemon for the iptables ULOG target + * of the linux 2.4 netfilter subsystem. + * + * (C) 2000-2009 by Harald Welte <laforge@gnumonks.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <fcntl.h> +#include <osmocore/select.h> +#include <osmocore/linuxlist.h> +#include <osmocore/timer.h> + +#include "../config.h" + +#ifdef HAVE_SYS_SELECT_H + +static int maxfd = 0; +static LLIST_HEAD(bsc_fds); +static int unregistered_count; + +int bsc_register_fd(struct bsc_fd *fd) +{ + int flags; + + /* make FD nonblocking */ + flags = fcntl(fd->fd, F_GETFL); + if (flags < 0) + return flags; + flags |= O_NONBLOCK; + flags = fcntl(fd->fd, F_SETFL, flags); + if (flags < 0) + return flags; + + /* Register FD */ + if (fd->fd > maxfd) + maxfd = fd->fd; + + llist_add_tail(&fd->list, &bsc_fds); + + return 0; +} + +void bsc_unregister_fd(struct bsc_fd *fd) +{ + unregistered_count++; + llist_del(&fd->list); +} + +int bsc_select_main(int polling) +{ + struct bsc_fd *ufd, *tmp; + fd_set readset, writeset, exceptset; + int work = 0, rc; + struct timeval no_time = {0, 0}; + + FD_ZERO(&readset); + FD_ZERO(&writeset); + FD_ZERO(&exceptset); + + /* prepare read and write fdsets */ + llist_for_each_entry(ufd, &bsc_fds, list) { + if (ufd->when & BSC_FD_READ) + FD_SET(ufd->fd, &readset); + + if (ufd->when & BSC_FD_WRITE) + FD_SET(ufd->fd, &writeset); + + if (ufd->when & BSC_FD_EXCEPT) + FD_SET(ufd->fd, &exceptset); + } + + bsc_timer_check(); + + if (!polling) + bsc_prepare_timers(); + rc = select(maxfd+1, &readset, &writeset, &exceptset, polling ? &no_time : bsc_nearest_timer()); + if (rc < 0) + return 0; + + /* fire timers */ + bsc_update_timers(); + + /* call registered callback functions */ +restart: + unregistered_count = 0; + llist_for_each_entry_safe(ufd, tmp, &bsc_fds, list) { + int flags = 0; + + if (FD_ISSET(ufd->fd, &readset)) { + flags |= BSC_FD_READ; + FD_CLR(ufd->fd, &readset); + } + + if (FD_ISSET(ufd->fd, &writeset)) { + flags |= BSC_FD_WRITE; + FD_CLR(ufd->fd, &writeset); + } + + if (FD_ISSET(ufd->fd, &exceptset)) { + flags |= BSC_FD_EXCEPT; + FD_CLR(ufd->fd, &exceptset); + } + + if (flags) { + work = 1; + ufd->cb(ufd, flags); + } + /* ugly, ugly hack. If more than one filedescriptors were + * unregistered, they might have been consecutive and + * llist_for_each_entry_safe() is no longer safe */ + if (unregistered_count > 1) + goto restart; + } + return work; +} + +#endif /* _HAVE_SYS_SELECT_H */ diff --git a/libosmocore/src/signal.c b/libosmocore/src/signal.c new file mode 100644 index 000000000..c7ca86c48 --- /dev/null +++ b/libosmocore/src/signal.c @@ -0,0 +1,84 @@ +/* Generic signalling/notification infrastructure */ +/* (C) 2009 by Holger Hans Peter Freyther <zecke@selfish.org> + * 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/signal.h> +#include <osmocore/talloc.h> +#include <osmocore/linuxlist.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +void *tall_sigh_ctx; +static LLIST_HEAD(signal_handler_list); + +struct signal_handler { + struct llist_head entry; + unsigned int subsys; + signal_cbfn *cbfn; + void *data; +}; + + +int register_signal_handler(unsigned int subsys, signal_cbfn *cbfn, void *data) +{ + struct signal_handler *sig_data; + + sig_data = talloc(tall_sigh_ctx, struct signal_handler); + if (!sig_data) + return -ENOMEM; + + memset(sig_data, 0, sizeof(*sig_data)); + + sig_data->subsys = subsys; + sig_data->data = data; + sig_data->cbfn = cbfn; + + /* FIXME: check if we already have a handler for this subsys/cbfn/data */ + + llist_add_tail(&sig_data->entry, &signal_handler_list); + + return 0; +} + +void unregister_signal_handler(unsigned int subsys, signal_cbfn *cbfn, void *data) +{ + struct signal_handler *handler; + + llist_for_each_entry(handler, &signal_handler_list, entry) { + if (handler->cbfn == cbfn && handler->data == data + && subsys == handler->subsys) { + llist_del(&handler->entry); + talloc_free(handler); + break; + } + } +} + + +void dispatch_signal(unsigned int subsys, unsigned int signal, void *signal_data) +{ + struct signal_handler *handler; + + llist_for_each_entry(handler, &signal_handler_list, entry) { + if (handler->subsys != subsys) + continue; + (*handler->cbfn)(subsys, signal, handler->data, signal_data); + } +} diff --git a/libosmocore/src/statistics.c b/libosmocore/src/statistics.c new file mode 100644 index 000000000..34e6a408e --- /dev/null +++ b/libosmocore/src/statistics.c @@ -0,0 +1,66 @@ +/* utility routines for keeping some statistics */ + +/* (C) 2009 by Harald Welte <laforge@gnumonks.org> + * + * 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 <sys/types.h> + +#include <osmocore/linuxlist.h> +#include <osmocore/talloc.h> +#include <osmocore/statistics.h> + +static LLIST_HEAD(counters); + +void *tall_ctr_ctx; + +struct counter *counter_alloc(const char *name) +{ + struct counter *ctr = talloc_zero(tall_ctr_ctx, struct counter); + + if (!ctr) + return NULL; + + ctr->name = name; + llist_add_tail(&ctr->list, &counters); + + return ctr; +} + +void counter_free(struct counter *ctr) +{ + llist_del(&ctr->list); + talloc_free(ctr); +} + +int counters_for_each(int (*handle_counter)(struct counter *, void *), void *data) +{ + struct counter *ctr; + int rc = 0; + + llist_for_each_entry(ctr, &counters, list) { + rc = handle_counter(ctr, data); + if (rc < 0) + return rc; + } + + return rc; +} + diff --git a/libosmocore/src/talloc.c b/libosmocore/src/talloc.c new file mode 100644 index 000000000..98c2ee097 --- /dev/null +++ b/libosmocore/src/talloc.c @@ -0,0 +1,1805 @@ +/* + Samba Unix SMB/CIFS implementation. + + Samba trivial allocation library - new interface + + NOTE: Please read talloc_guide.txt for full documentation + + Copyright (C) Andrew Tridgell 2004 + Copyright (C) Stefan Metzmacher 2006 + + ** NOTE! The following LGPL license applies to the talloc + ** library. This does NOT imply that all of Samba is released + ** under the LGPL + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, see <http://www.gnu.org/licenses/>. +*/ + +/* + inspired by http://swapped.cc/halloc/ +*/ + +#ifdef _SAMBA_BUILD_ +#include "version.h" +#if (SAMBA_VERSION_MAJOR<4) +#include "includes.h" +/* This is to circumvent SAMBA3's paranoid malloc checker. Here in this file + * we trust ourselves... */ +#ifdef malloc +#undef malloc +#endif +#ifdef realloc +#undef realloc +#endif +#define _TALLOC_SAMBA3 +#endif /* (SAMBA_VERSION_MAJOR<4) */ +#endif /* _SAMBA_BUILD_ */ + +#ifndef _TALLOC_SAMBA3 +//#include "replace.h" +#include <sys/types.h> +#include <unistd.h> +#include <stdio.h> +#include <stdbool.h> +#define __USE_GNU +#include <string.h> +#undef __USE_GNU +#include <osmocore/talloc.h> +#define MIN(x,y) ((x) < (y) ? (x) : (y)) +#endif /* not _TALLOC_SAMBA3 */ + +/* use this to force every realloc to change the pointer, to stress test + code that might not cope */ +#define ALWAYS_REALLOC 0 + + +#define MAX_TALLOC_SIZE 0x10000000 +#define TALLOC_MAGIC 0xe814ec70 +#define TALLOC_FLAG_FREE 0x01 +#define TALLOC_FLAG_LOOP 0x02 +#define TALLOC_FLAG_POOL 0x04 /* This is a talloc pool */ +#define TALLOC_FLAG_POOLMEM 0x08 /* This is allocated in a pool */ +#define TALLOC_MAGIC_REFERENCE ((const char *)1) + +/* by default we abort when given a bad pointer (such as when talloc_free() is called + on a pointer that came from malloc() */ +#ifndef TALLOC_ABORT +#define TALLOC_ABORT(reason) abort() +#endif + +#ifndef discard_const_p +#if defined(__intptr_t_defined) || defined(HAVE_INTPTR_T) +# define discard_const_p(type, ptr) ((type *)((intptr_t)(ptr))) +#else +# define discard_const_p(type, ptr) ((type *)(ptr)) +#endif +#endif + +/* these macros gain us a few percent of speed on gcc */ +#if (__GNUC__ >= 3) +/* the strange !! is to ensure that __builtin_expect() takes either 0 or 1 + as its first argument */ +#ifndef likely +#define likely(x) __builtin_expect(!!(x), 1) +#endif +#ifndef unlikely +#define unlikely(x) __builtin_expect(!!(x), 0) +#endif +#else +#ifndef likely +#define likely(x) (x) +#endif +#ifndef unlikely +#define unlikely(x) (x) +#endif +#endif + +#ifdef __APPLE__ +/* taken from http://insanecoding.blogspot.com/2007/03/methods-for-safe-string-handling.html */ +size_t strnlen(const char *s, size_t n) +{ + const char *p = (const char *)memchr(s, 0, n); + return(p ? p-s : n); +} +#endif + +/* this null_context is only used if talloc_enable_leak_report() or + talloc_enable_leak_report_full() is called, otherwise it remains + NULL +*/ +static void *null_context; +static void *autofree_context; + +struct talloc_reference_handle { + struct talloc_reference_handle *next, *prev; + void *ptr; +}; + +typedef int (*talloc_destructor_t)(void *); + +struct talloc_chunk { + struct talloc_chunk *next, *prev; + struct talloc_chunk *parent, *child; + struct talloc_reference_handle *refs; + talloc_destructor_t destructor; + const char *name; + size_t size; + unsigned flags; + + /* + * "pool" has dual use: + * + * For the talloc pool itself (i.e. TALLOC_FLAG_POOL is set), "pool" + * marks the end of the currently allocated area. + * + * For members of the pool (i.e. TALLOC_FLAG_POOLMEM is set), "pool" + * is a pointer to the struct talloc_chunk of the pool that it was + * allocated from. This way children can quickly find the pool to chew + * from. + */ + void *pool; +}; + +/* 16 byte alignment seems to keep everyone happy */ +#define TC_HDR_SIZE ((sizeof(struct talloc_chunk)+15)&~15) +#define TC_PTR_FROM_CHUNK(tc) ((void *)(TC_HDR_SIZE + (char*)tc)) + +static void (*talloc_abort_fn)(const char *reason); + +void talloc_set_abort_fn(void (*abort_fn)(const char *reason)) +{ + talloc_abort_fn = abort_fn; +} + +static void talloc_abort(const char *reason) +{ + if (!talloc_abort_fn) { + TALLOC_ABORT(reason); + } + + talloc_abort_fn(reason); +} + +static void talloc_abort_double_free(void) +{ + talloc_abort("Bad talloc magic value - double free"); +} + +static void talloc_abort_unknown_value(void) +{ + talloc_abort("Bad talloc magic value - unknown value"); +} + +/* panic if we get a bad magic value */ +static inline struct talloc_chunk *talloc_chunk_from_ptr(const void *ptr) +{ + const char *pp = (const char *)ptr; + struct talloc_chunk *tc = discard_const_p(struct talloc_chunk, pp - TC_HDR_SIZE); + if (unlikely((tc->flags & (TALLOC_FLAG_FREE | ~0xF)) != TALLOC_MAGIC)) { + if (tc->flags & TALLOC_FLAG_FREE) { + talloc_abort_double_free(); + } else { + talloc_abort_unknown_value(); + } + } + return tc; +} + +/* hook into the front of the list */ +#define _TLIST_ADD(list, p) \ +do { \ + if (!(list)) { \ + (list) = (p); \ + (p)->next = (p)->prev = NULL; \ + } else { \ + (list)->prev = (p); \ + (p)->next = (list); \ + (p)->prev = NULL; \ + (list) = (p); \ + }\ +} while (0) + +/* remove an element from a list - element doesn't have to be in list. */ +#define _TLIST_REMOVE(list, p) \ +do { \ + if ((p) == (list)) { \ + (list) = (p)->next; \ + if (list) (list)->prev = NULL; \ + } else { \ + if ((p)->prev) (p)->prev->next = (p)->next; \ + if ((p)->next) (p)->next->prev = (p)->prev; \ + } \ + if ((p) && ((p) != (list))) (p)->next = (p)->prev = NULL; \ +} while (0) + + +/* + return the parent chunk of a pointer +*/ +static inline struct talloc_chunk *talloc_parent_chunk(const void *ptr) +{ + struct talloc_chunk *tc; + + if (unlikely(ptr == NULL)) { + return NULL; + } + + tc = talloc_chunk_from_ptr(ptr); + while (tc->prev) tc=tc->prev; + + return tc->parent; +} + +void *talloc_parent(const void *ptr) +{ + struct talloc_chunk *tc = talloc_parent_chunk(ptr); + return tc? TC_PTR_FROM_CHUNK(tc) : NULL; +} + +/* + find parents name +*/ +const char *talloc_parent_name(const void *ptr) +{ + struct talloc_chunk *tc = talloc_parent_chunk(ptr); + return tc? tc->name : NULL; +} + +/* + A pool carries an in-pool object count count in the first 16 bytes. + bytes. This is done to support talloc_steal() to a parent outside of the + pool. The count includes the pool itself, so a talloc_free() on a pool will + only destroy the pool if the count has dropped to zero. A talloc_free() of a + pool member will reduce the count, and eventually also call free(3) on the + pool memory. + + The object count is not put into "struct talloc_chunk" because it is only + relevant for talloc pools and the alignment to 16 bytes would increase the + memory footprint of each talloc chunk by those 16 bytes. +*/ + +#define TALLOC_POOL_HDR_SIZE 16 + +static unsigned int *talloc_pool_objectcount(struct talloc_chunk *tc) +{ + return (unsigned int *)((char *)tc + sizeof(struct talloc_chunk)); +} + +/* + Allocate from a pool +*/ + +static struct talloc_chunk *talloc_alloc_pool(struct talloc_chunk *parent, + size_t size) +{ + struct talloc_chunk *pool_ctx = NULL; + size_t space_left; + struct talloc_chunk *result; + size_t chunk_size; + + if (parent == NULL) { + return NULL; + } + + if (parent->flags & TALLOC_FLAG_POOL) { + pool_ctx = parent; + } + else if (parent->flags & TALLOC_FLAG_POOLMEM) { + pool_ctx = (struct talloc_chunk *)parent->pool; + } + + if (pool_ctx == NULL) { + return NULL; + } + + space_left = ((char *)pool_ctx + TC_HDR_SIZE + pool_ctx->size) + - ((char *)pool_ctx->pool); + + /* + * Align size to 16 bytes + */ + chunk_size = ((size + 15) & ~15); + + if (space_left < chunk_size) { + return NULL; + } + + result = (struct talloc_chunk *)pool_ctx->pool; + +#if defined(DEVELOPER) && defined(VALGRIND_MAKE_MEM_UNDEFINED) + VALGRIND_MAKE_MEM_UNDEFINED(result, size); +#endif + + pool_ctx->pool = (void *)((char *)result + chunk_size); + + result->flags = TALLOC_MAGIC | TALLOC_FLAG_POOLMEM; + result->pool = pool_ctx; + + *talloc_pool_objectcount(pool_ctx) += 1; + + return result; +} + +/* + Allocate a bit of memory as a child of an existing pointer +*/ +static inline void *__talloc(const void *context, size_t size) +{ + struct talloc_chunk *tc = NULL; + + if (unlikely(context == NULL)) { + context = null_context; + } + + if (unlikely(size >= MAX_TALLOC_SIZE)) { + return NULL; + } + + if (context != NULL) { + tc = talloc_alloc_pool(talloc_chunk_from_ptr(context), + TC_HDR_SIZE+size); + } + + if (tc == NULL) { + tc = (struct talloc_chunk *)malloc(TC_HDR_SIZE+size); + if (unlikely(tc == NULL)) return NULL; + tc->flags = TALLOC_MAGIC; + tc->pool = NULL; + } + + tc->size = size; + tc->destructor = NULL; + tc->child = NULL; + tc->name = NULL; + tc->refs = NULL; + + if (likely(context)) { + struct talloc_chunk *parent = talloc_chunk_from_ptr(context); + + if (parent->child) { + parent->child->parent = NULL; + tc->next = parent->child; + tc->next->prev = tc; + } else { + tc->next = NULL; + } + tc->parent = parent; + tc->prev = NULL; + parent->child = tc; + } else { + tc->next = tc->prev = tc->parent = NULL; + } + + return TC_PTR_FROM_CHUNK(tc); +} + +/* + * Create a talloc pool + */ + +void *talloc_pool(const void *context, size_t size) +{ + void *result = __talloc(context, size + TALLOC_POOL_HDR_SIZE); + struct talloc_chunk *tc; + + if (unlikely(result == NULL)) { + return NULL; + } + + tc = talloc_chunk_from_ptr(result); + + tc->flags |= TALLOC_FLAG_POOL; + tc->pool = (char *)result + TALLOC_POOL_HDR_SIZE; + + *talloc_pool_objectcount(tc) = 1; + +#if defined(DEVELOPER) && defined(VALGRIND_MAKE_MEM_NOACCESS) + VALGRIND_MAKE_MEM_NOACCESS(tc->pool, size); +#endif + + return result; +} + +/* + setup a destructor to be called on free of a pointer + the destructor should return 0 on success, or -1 on failure. + if the destructor fails then the free is failed, and the memory can + be continued to be used +*/ +void _talloc_set_destructor(const void *ptr, int (*destructor)(void *)) +{ + struct talloc_chunk *tc = talloc_chunk_from_ptr(ptr); + tc->destructor = destructor; +} + +/* + increase the reference count on a piece of memory. +*/ +int talloc_increase_ref_count(const void *ptr) +{ + if (unlikely(!talloc_reference(null_context, ptr))) { + return -1; + } + return 0; +} + +/* + helper for talloc_reference() + + this is referenced by a function pointer and should not be inline +*/ +static int talloc_reference_destructor(struct talloc_reference_handle *handle) +{ + struct talloc_chunk *ptr_tc = talloc_chunk_from_ptr(handle->ptr); + _TLIST_REMOVE(ptr_tc->refs, handle); + return 0; +} + +/* + more efficient way to add a name to a pointer - the name must point to a + true string constant +*/ +static inline void _talloc_set_name_const(const void *ptr, const char *name) +{ + struct talloc_chunk *tc = talloc_chunk_from_ptr(ptr); + tc->name = name; +} + +/* + internal talloc_named_const() +*/ +static inline void *_talloc_named_const(const void *context, size_t size, const char *name) +{ + void *ptr; + + ptr = __talloc(context, size); + if (unlikely(ptr == NULL)) { + return NULL; + } + + _talloc_set_name_const(ptr, name); + + return ptr; +} + +/* + make a secondary reference to a pointer, hanging off the given context. + the pointer remains valid until both the original caller and this given + context are freed. + + the major use for this is when two different structures need to reference the + same underlying data, and you want to be able to free the two instances separately, + and in either order +*/ +void *_talloc_reference(const void *context, const void *ptr) +{ + struct talloc_chunk *tc; + struct talloc_reference_handle *handle; + if (unlikely(ptr == NULL)) return NULL; + + tc = talloc_chunk_from_ptr(ptr); + handle = (struct talloc_reference_handle *)_talloc_named_const(context, + sizeof(struct talloc_reference_handle), + TALLOC_MAGIC_REFERENCE); + if (unlikely(handle == NULL)) return NULL; + + /* note that we hang the destructor off the handle, not the + main context as that allows the caller to still setup their + own destructor on the context if they want to */ + talloc_set_destructor(handle, talloc_reference_destructor); + handle->ptr = discard_const_p(void, ptr); + _TLIST_ADD(tc->refs, handle); + return handle->ptr; +} + + +/* + internal talloc_free call +*/ +static inline int _talloc_free(void *ptr) +{ + struct talloc_chunk *tc; + + if (unlikely(ptr == NULL)) { + return -1; + } + + tc = talloc_chunk_from_ptr(ptr); + + if (unlikely(tc->refs)) { + int is_child; + /* check this is a reference from a child or grantchild + * back to it's parent or grantparent + * + * in that case we need to remove the reference and + * call another instance of talloc_free() on the current + * pointer. + */ + is_child = talloc_is_parent(tc->refs, ptr); + _talloc_free(tc->refs); + if (is_child) { + return _talloc_free(ptr); + } + return -1; + } + + if (unlikely(tc->flags & TALLOC_FLAG_LOOP)) { + /* we have a free loop - stop looping */ + return 0; + } + + if (unlikely(tc->destructor)) { + talloc_destructor_t d = tc->destructor; + if (d == (talloc_destructor_t)-1) { + return -1; + } + tc->destructor = (talloc_destructor_t)-1; + if (d(ptr) == -1) { + tc->destructor = d; + return -1; + } + tc->destructor = NULL; + } + + if (tc->parent) { + _TLIST_REMOVE(tc->parent->child, tc); + if (tc->parent->child) { + tc->parent->child->parent = tc->parent; + } + } else { + if (tc->prev) tc->prev->next = tc->next; + if (tc->next) tc->next->prev = tc->prev; + } + + tc->flags |= TALLOC_FLAG_LOOP; + + while (tc->child) { + /* we need to work out who will own an abandoned child + if it cannot be freed. In priority order, the first + choice is owner of any remaining reference to this + pointer, the second choice is our parent, and the + final choice is the null context. */ + void *child = TC_PTR_FROM_CHUNK(tc->child); + const void *new_parent = null_context; + if (unlikely(tc->child->refs)) { + struct talloc_chunk *p = talloc_parent_chunk(tc->child->refs); + if (p) new_parent = TC_PTR_FROM_CHUNK(p); + } + if (unlikely(_talloc_free(child) == -1)) { + if (new_parent == null_context) { + struct talloc_chunk *p = talloc_parent_chunk(ptr); + if (p) new_parent = TC_PTR_FROM_CHUNK(p); + } + talloc_steal(new_parent, child); + } + } + + tc->flags |= TALLOC_FLAG_FREE; + + if (tc->flags & (TALLOC_FLAG_POOL|TALLOC_FLAG_POOLMEM)) { + struct talloc_chunk *pool; + unsigned int *pool_object_count; + + pool = (tc->flags & TALLOC_FLAG_POOL) + ? tc : (struct talloc_chunk *)tc->pool; + + pool_object_count = talloc_pool_objectcount(pool); + + if (*pool_object_count == 0) { + talloc_abort("Pool object count zero!"); + } + + *pool_object_count -= 1; + + if (*pool_object_count == 0) { + free(pool); + } + } + else { + free(tc); + } + return 0; +} + +/* + move a lump of memory from one talloc context to another return the + ptr on success, or NULL if it could not be transferred. + passing NULL as ptr will always return NULL with no side effects. +*/ +void *_talloc_steal(const void *new_ctx, const void *ptr) +{ + struct talloc_chunk *tc, *new_tc; + + if (unlikely(!ptr)) { + return NULL; + } + + if (unlikely(new_ctx == NULL)) { + new_ctx = null_context; + } + + tc = talloc_chunk_from_ptr(ptr); + + if (unlikely(new_ctx == NULL)) { + if (tc->parent) { + _TLIST_REMOVE(tc->parent->child, tc); + if (tc->parent->child) { + tc->parent->child->parent = tc->parent; + } + } else { + if (tc->prev) tc->prev->next = tc->next; + if (tc->next) tc->next->prev = tc->prev; + } + + tc->parent = tc->next = tc->prev = NULL; + return discard_const_p(void, ptr); + } + + new_tc = talloc_chunk_from_ptr(new_ctx); + + if (unlikely(tc == new_tc || tc->parent == new_tc)) { + return discard_const_p(void, ptr); + } + + if (tc->parent) { + _TLIST_REMOVE(tc->parent->child, tc); + if (tc->parent->child) { + tc->parent->child->parent = tc->parent; + } + } else { + if (tc->prev) tc->prev->next = tc->next; + if (tc->next) tc->next->prev = tc->prev; + } + + tc->parent = new_tc; + if (new_tc->child) new_tc->child->parent = NULL; + _TLIST_ADD(new_tc->child, tc); + + return discard_const_p(void, ptr); +} + + + +/* + remove a secondary reference to a pointer. This undo's what + talloc_reference() has done. The context and pointer arguments + must match those given to a talloc_reference() +*/ +static inline int talloc_unreference(const void *context, const void *ptr) +{ + struct talloc_chunk *tc = talloc_chunk_from_ptr(ptr); + struct talloc_reference_handle *h; + + if (unlikely(context == NULL)) { + context = null_context; + } + + for (h=tc->refs;h;h=h->next) { + struct talloc_chunk *p = talloc_parent_chunk(h); + if (p == NULL) { + if (context == NULL) break; + } else if (TC_PTR_FROM_CHUNK(p) == context) { + break; + } + } + if (h == NULL) { + return -1; + } + + return _talloc_free(h); +} + +/* + remove a specific parent context from a pointer. This is a more + controlled varient of talloc_free() +*/ +int talloc_unlink(const void *context, void *ptr) +{ + struct talloc_chunk *tc_p, *new_p; + void *new_parent; + + if (ptr == NULL) { + return -1; + } + + if (context == NULL) { + context = null_context; + } + + if (talloc_unreference(context, ptr) == 0) { + return 0; + } + + if (context == NULL) { + if (talloc_parent_chunk(ptr) != NULL) { + return -1; + } + } else { + if (talloc_chunk_from_ptr(context) != talloc_parent_chunk(ptr)) { + return -1; + } + } + + tc_p = talloc_chunk_from_ptr(ptr); + + if (tc_p->refs == NULL) { + return _talloc_free(ptr); + } + + new_p = talloc_parent_chunk(tc_p->refs); + if (new_p) { + new_parent = TC_PTR_FROM_CHUNK(new_p); + } else { + new_parent = NULL; + } + + if (talloc_unreference(new_parent, ptr) != 0) { + return -1; + } + + talloc_steal(new_parent, ptr); + + return 0; +} + +/* + add a name to an existing pointer - va_list version +*/ +static inline const char *talloc_set_name_v(const void *ptr, const char *fmt, va_list ap) PRINTF_ATTRIBUTE(2,0); + +static inline const char *talloc_set_name_v(const void *ptr, const char *fmt, va_list ap) +{ + struct talloc_chunk *tc = talloc_chunk_from_ptr(ptr); + tc->name = talloc_vasprintf(ptr, fmt, ap); + if (likely(tc->name)) { + _talloc_set_name_const(tc->name, ".name"); + } + return tc->name; +} + +/* + add a name to an existing pointer +*/ +const char *talloc_set_name(const void *ptr, const char *fmt, ...) +{ + const char *name; + va_list ap; + va_start(ap, fmt); + name = talloc_set_name_v(ptr, fmt, ap); + va_end(ap); + return name; +} + + +/* + create a named talloc pointer. Any talloc pointer can be named, and + talloc_named() operates just like talloc() except that it allows you + to name the pointer. +*/ +void *talloc_named(const void *context, size_t size, const char *fmt, ...) +{ + va_list ap; + void *ptr; + const char *name; + + ptr = __talloc(context, size); + if (unlikely(ptr == NULL)) return NULL; + + va_start(ap, fmt); + name = talloc_set_name_v(ptr, fmt, ap); + va_end(ap); + + if (unlikely(name == NULL)) { + _talloc_free(ptr); + return NULL; + } + + return ptr; +} + +/* + return the name of a talloc ptr, or "UNNAMED" +*/ +const char *talloc_get_name(const void *ptr) +{ + struct talloc_chunk *tc = talloc_chunk_from_ptr(ptr); + if (unlikely(tc->name == TALLOC_MAGIC_REFERENCE)) { + return ".reference"; + } + if (likely(tc->name)) { + return tc->name; + } + return "UNNAMED"; +} + + +/* + check if a pointer has the given name. If it does, return the pointer, + otherwise return NULL +*/ +void *talloc_check_name(const void *ptr, const char *name) +{ + const char *pname; + if (unlikely(ptr == NULL)) return NULL; + pname = talloc_get_name(ptr); + if (likely(pname == name || strcmp(pname, name) == 0)) { + return discard_const_p(void, ptr); + } + return NULL; +} + +static void talloc_abort_type_missmatch(const char *location, + const char *name, + const char *expected) +{ + const char *reason; + + reason = talloc_asprintf(NULL, + "%s: Type mismatch: name[%s] expected[%s]", + location, + name?name:"NULL", + expected); + if (!reason) { + reason = "Type mismatch"; + } + + talloc_abort(reason); +} + +void *_talloc_get_type_abort(const void *ptr, const char *name, const char *location) +{ + const char *pname; + + if (unlikely(ptr == NULL)) { + talloc_abort_type_missmatch(location, NULL, name); + return NULL; + } + + pname = talloc_get_name(ptr); + if (likely(pname == name || strcmp(pname, name) == 0)) { + return discard_const_p(void, ptr); + } + + talloc_abort_type_missmatch(location, pname, name); + return NULL; +} + +/* + this is for compatibility with older versions of talloc +*/ +void *talloc_init(const char *fmt, ...) +{ + va_list ap; + void *ptr; + const char *name; + + /* + * samba3 expects talloc_report_depth_cb(NULL, ...) + * reports all talloc'ed memory, so we need to enable + * null_tracking + */ + talloc_enable_null_tracking(); + + ptr = __talloc(NULL, 0); + if (unlikely(ptr == NULL)) return NULL; + + va_start(ap, fmt); + name = talloc_set_name_v(ptr, fmt, ap); + va_end(ap); + + if (unlikely(name == NULL)) { + _talloc_free(ptr); + return NULL; + } + + return ptr; +} + +/* + this is a replacement for the Samba3 talloc_destroy_pool functionality. It + should probably not be used in new code. It's in here to keep the talloc + code consistent across Samba 3 and 4. +*/ +void talloc_free_children(void *ptr) +{ + struct talloc_chunk *tc; + + if (unlikely(ptr == NULL)) { + return; + } + + tc = talloc_chunk_from_ptr(ptr); + + while (tc->child) { + /* we need to work out who will own an abandoned child + if it cannot be freed. In priority order, the first + choice is owner of any remaining reference to this + pointer, the second choice is our parent, and the + final choice is the null context. */ + void *child = TC_PTR_FROM_CHUNK(tc->child); + const void *new_parent = null_context; + if (unlikely(tc->child->refs)) { + struct talloc_chunk *p = talloc_parent_chunk(tc->child->refs); + if (p) new_parent = TC_PTR_FROM_CHUNK(p); + } + if (unlikely(_talloc_free(child) == -1)) { + if (new_parent == null_context) { + struct talloc_chunk *p = talloc_parent_chunk(ptr); + if (p) new_parent = TC_PTR_FROM_CHUNK(p); + } + talloc_steal(new_parent, child); + } + } + + if ((tc->flags & TALLOC_FLAG_POOL) + && (*talloc_pool_objectcount(tc) == 1)) { + tc->pool = ((char *)tc + TC_HDR_SIZE + TALLOC_POOL_HDR_SIZE); +#if defined(DEVELOPER) && defined(VALGRIND_MAKE_MEM_NOACCESS) + VALGRIND_MAKE_MEM_NOACCESS( + tc->pool, tc->size - TALLOC_POOL_HDR_SIZE); +#endif + } +} + +/* + Allocate a bit of memory as a child of an existing pointer +*/ +void *_talloc(const void *context, size_t size) +{ + return __talloc(context, size); +} + +/* + externally callable talloc_set_name_const() +*/ +void talloc_set_name_const(const void *ptr, const char *name) +{ + _talloc_set_name_const(ptr, name); +} + +/* + create a named talloc pointer. Any talloc pointer can be named, and + talloc_named() operates just like talloc() except that it allows you + to name the pointer. +*/ +void *talloc_named_const(const void *context, size_t size, const char *name) +{ + return _talloc_named_const(context, size, name); +} + +/* + free a talloc pointer. This also frees all child pointers of this + pointer recursively + + return 0 if the memory is actually freed, otherwise -1. The memory + will not be freed if the ref_count is > 1 or the destructor (if + any) returns non-zero +*/ +int talloc_free(void *ptr) +{ + return _talloc_free(ptr); +} + + + +/* + A talloc version of realloc. The context argument is only used if + ptr is NULL +*/ +void *_talloc_realloc(const void *context, void *ptr, size_t size, const char *name) +{ + struct talloc_chunk *tc; + void *new_ptr; + bool malloced = false; + + /* size zero is equivalent to free() */ + if (unlikely(size == 0)) { + _talloc_free(ptr); + return NULL; + } + + if (unlikely(size >= MAX_TALLOC_SIZE)) { + return NULL; + } + + /* realloc(NULL) is equivalent to malloc() */ + if (ptr == NULL) { + return _talloc_named_const(context, size, name); + } + + tc = talloc_chunk_from_ptr(ptr); + + /* don't allow realloc on referenced pointers */ + if (unlikely(tc->refs)) { + return NULL; + } + + /* don't let anybody try to realloc a talloc_pool */ + if (unlikely(tc->flags & TALLOC_FLAG_POOL)) { + return NULL; + } + + /* don't shrink if we have less than 1k to gain */ + if ((size < tc->size) && ((tc->size - size) < 1024)) { + tc->size = size; + return ptr; + } + + /* by resetting magic we catch users of the old memory */ + tc->flags |= TALLOC_FLAG_FREE; + +#if ALWAYS_REALLOC + new_ptr = malloc(size + TC_HDR_SIZE); + if (new_ptr) { + memcpy(new_ptr, tc, tc->size + TC_HDR_SIZE); + free(tc); + } +#else + if (tc->flags & TALLOC_FLAG_POOLMEM) { + + new_ptr = talloc_alloc_pool(tc, size + TC_HDR_SIZE); + *talloc_pool_objectcount((struct talloc_chunk *) + (tc->pool)) -= 1; + + if (new_ptr == NULL) { + new_ptr = malloc(TC_HDR_SIZE+size); + malloced = true; + } + + if (new_ptr) { + memcpy(new_ptr, tc, MIN(tc->size,size) + TC_HDR_SIZE); + } + } + else { + new_ptr = realloc(tc, size + TC_HDR_SIZE); + } +#endif + if (unlikely(!new_ptr)) { + tc->flags &= ~TALLOC_FLAG_FREE; + return NULL; + } + + tc = (struct talloc_chunk *)new_ptr; + tc->flags &= ~TALLOC_FLAG_FREE; + if (malloced) { + tc->flags &= ~TALLOC_FLAG_POOLMEM; + } + if (tc->parent) { + tc->parent->child = tc; + } + if (tc->child) { + tc->child->parent = tc; + } + + if (tc->prev) { + tc->prev->next = tc; + } + if (tc->next) { + tc->next->prev = tc; + } + + tc->size = size; + _talloc_set_name_const(TC_PTR_FROM_CHUNK(tc), name); + + return TC_PTR_FROM_CHUNK(tc); +} + +/* + a wrapper around talloc_steal() for situations where you are moving a pointer + between two structures, and want the old pointer to be set to NULL +*/ +void *_talloc_move(const void *new_ctx, const void *_pptr) +{ + const void **pptr = discard_const_p(const void *,_pptr); + void *ret = _talloc_steal(new_ctx, *pptr); + (*pptr) = NULL; + return ret; +} + +/* + return the total size of a talloc pool (subtree) +*/ +size_t talloc_total_size(const void *ptr) +{ + size_t total = 0; + struct talloc_chunk *c, *tc; + + if (ptr == NULL) { + ptr = null_context; + } + if (ptr == NULL) { + return 0; + } + + tc = talloc_chunk_from_ptr(ptr); + + if (tc->flags & TALLOC_FLAG_LOOP) { + return 0; + } + + tc->flags |= TALLOC_FLAG_LOOP; + + total = tc->size; + for (c=tc->child;c;c=c->next) { + total += talloc_total_size(TC_PTR_FROM_CHUNK(c)); + } + + tc->flags &= ~TALLOC_FLAG_LOOP; + + return total; +} + +/* + return the total number of blocks in a talloc pool (subtree) +*/ +size_t talloc_total_blocks(const void *ptr) +{ + size_t total = 0; + struct talloc_chunk *c, *tc = talloc_chunk_from_ptr(ptr); + + if (tc->flags & TALLOC_FLAG_LOOP) { + return 0; + } + + tc->flags |= TALLOC_FLAG_LOOP; + + total++; + for (c=tc->child;c;c=c->next) { + total += talloc_total_blocks(TC_PTR_FROM_CHUNK(c)); + } + + tc->flags &= ~TALLOC_FLAG_LOOP; + + return total; +} + +/* + return the number of external references to a pointer +*/ +size_t talloc_reference_count(const void *ptr) +{ + struct talloc_chunk *tc = talloc_chunk_from_ptr(ptr); + struct talloc_reference_handle *h; + size_t ret = 0; + + for (h=tc->refs;h;h=h->next) { + ret++; + } + return ret; +} + +/* + report on memory usage by all children of a pointer, giving a full tree view +*/ +void talloc_report_depth_cb(const void *ptr, int depth, int max_depth, + void (*callback)(const void *ptr, + int depth, int max_depth, + int is_ref, + void *private_data), + void *private_data) +{ + struct talloc_chunk *c, *tc; + + if (ptr == NULL) { + ptr = null_context; + } + if (ptr == NULL) return; + + tc = talloc_chunk_from_ptr(ptr); + + if (tc->flags & TALLOC_FLAG_LOOP) { + return; + } + + callback(ptr, depth, max_depth, 0, private_data); + + if (max_depth >= 0 && depth >= max_depth) { + return; + } + + tc->flags |= TALLOC_FLAG_LOOP; + for (c=tc->child;c;c=c->next) { + if (c->name == TALLOC_MAGIC_REFERENCE) { + struct talloc_reference_handle *h = (struct talloc_reference_handle *)TC_PTR_FROM_CHUNK(c); + callback(h->ptr, depth + 1, max_depth, 1, private_data); + } else { + talloc_report_depth_cb(TC_PTR_FROM_CHUNK(c), depth + 1, max_depth, callback, private_data); + } + } + tc->flags &= ~TALLOC_FLAG_LOOP; +} + +static void talloc_report_depth_FILE_helper(const void *ptr, int depth, int max_depth, int is_ref, void *_f) +{ + const char *name = talloc_get_name(ptr); + FILE *f = (FILE *)_f; + + if (is_ref) { + fprintf(f, "%*sreference to: %s\n", depth*4, "", name); + return; + } + + if (depth == 0) { + fprintf(f,"%stalloc report on '%s' (total %6lu bytes in %3lu blocks)\n", + (max_depth < 0 ? "full " :""), name, + (unsigned long)talloc_total_size(ptr), + (unsigned long)talloc_total_blocks(ptr)); + return; + } + + fprintf(f, "%*s%-30s contains %6lu bytes in %3lu blocks (ref %d) %p\n", + depth*4, "", + name, + (unsigned long)talloc_total_size(ptr), + (unsigned long)talloc_total_blocks(ptr), + (int)talloc_reference_count(ptr), ptr); + +#if 0 + fprintf(f, "content: "); + if (talloc_total_size(ptr)) { + int tot = talloc_total_size(ptr); + int i; + + for (i = 0; i < tot; i++) { + if ((((char *)ptr)[i] > 31) && (((char *)ptr)[i] < 126)) { + fprintf(f, "%c", ((char *)ptr)[i]); + } else { + fprintf(f, "~%02x", ((char *)ptr)[i]); + } + } + } + fprintf(f, "\n"); +#endif +} + +/* + report on memory usage by all children of a pointer, giving a full tree view +*/ +void talloc_report_depth_file(const void *ptr, int depth, int max_depth, FILE *f) +{ + talloc_report_depth_cb(ptr, depth, max_depth, talloc_report_depth_FILE_helper, f); + fflush(f); +} + +/* + report on memory usage by all children of a pointer, giving a full tree view +*/ +void talloc_report_full(const void *ptr, FILE *f) +{ + talloc_report_depth_file(ptr, 0, -1, f); +} + +/* + report on memory usage by all children of a pointer +*/ +void talloc_report(const void *ptr, FILE *f) +{ + talloc_report_depth_file(ptr, 0, 1, f); +} + +/* + report on any memory hanging off the null context +*/ +static void talloc_report_null(void) +{ + if (talloc_total_size(null_context) != 0) { + talloc_report(null_context, stderr); + } +} + +/* + report on any memory hanging off the null context +*/ +static void talloc_report_null_full(void) +{ + if (talloc_total_size(null_context) != 0) { + talloc_report_full(null_context, stderr); + } +} + +/* + enable tracking of the NULL context +*/ +void talloc_enable_null_tracking(void) +{ + if (null_context == NULL) { + null_context = _talloc_named_const(NULL, 0, "null_context"); + } +} + +/* + disable tracking of the NULL context +*/ +void talloc_disable_null_tracking(void) +{ + _talloc_free(null_context); + null_context = NULL; +} + +/* + enable leak reporting on exit +*/ +void talloc_enable_leak_report(void) +{ + talloc_enable_null_tracking(); + atexit(talloc_report_null); +} + +/* + enable full leak reporting on exit +*/ +void talloc_enable_leak_report_full(void) +{ + talloc_enable_null_tracking(); + atexit(talloc_report_null_full); +} + +/* + talloc and zero memory. +*/ +void *_talloc_zero(const void *ctx, size_t size, const char *name) +{ + void *p = _talloc_named_const(ctx, size, name); + + if (p) { + memset(p, '\0', size); + } + + return p; +} + +/* + memdup with a talloc. +*/ +void *_talloc_memdup(const void *t, const void *p, size_t size, const char *name) +{ + void *newp = _talloc_named_const(t, size, name); + + if (likely(newp)) { + memcpy(newp, p, size); + } + + return newp; +} + +static inline char *__talloc_strlendup(const void *t, const char *p, size_t len) +{ + char *ret; + + ret = (char *)__talloc(t, len + 1); + if (unlikely(!ret)) return NULL; + + memcpy(ret, p, len); + ret[len] = 0; + + _talloc_set_name_const(ret, ret); + return ret; +} + +/* + strdup with a talloc +*/ +char *talloc_strdup(const void *t, const char *p) +{ + if (unlikely(!p)) return NULL; + return __talloc_strlendup(t, p, strlen(p)); +} + +/* + strndup with a talloc +*/ +char *talloc_strndup(const void *t, const char *p, size_t n) +{ + if (unlikely(!p)) return NULL; + return __talloc_strlendup(t, p, strnlen(p, n)); +} + +static inline char *__talloc_strlendup_append(char *s, size_t slen, + const char *a, size_t alen) +{ + char *ret; + + ret = talloc_realloc(NULL, s, char, slen + alen + 1); + if (unlikely(!ret)) return NULL; + + /* append the string and the trailing \0 */ + memcpy(&ret[slen], a, alen); + ret[slen+alen] = 0; + + _talloc_set_name_const(ret, ret); + return ret; +} + +/* + * Appends at the end of the string. + */ +char *talloc_strdup_append(char *s, const char *a) +{ + if (unlikely(!s)) { + return talloc_strdup(NULL, a); + } + + if (unlikely(!a)) { + return s; + } + + return __talloc_strlendup_append(s, strlen(s), a, strlen(a)); +} + +/* + * Appends at the end of the talloc'ed buffer, + * not the end of the string. + */ +char *talloc_strdup_append_buffer(char *s, const char *a) +{ + size_t slen; + + if (unlikely(!s)) { + return talloc_strdup(NULL, a); + } + + if (unlikely(!a)) { + return s; + } + + slen = talloc_get_size(s); + if (likely(slen > 0)) { + slen--; + } + + return __talloc_strlendup_append(s, slen, a, strlen(a)); +} + +/* + * Appends at the end of the string. + */ +char *talloc_strndup_append(char *s, const char *a, size_t n) +{ + if (unlikely(!s)) { + return talloc_strdup(NULL, a); + } + + if (unlikely(!a)) { + return s; + } + + return __talloc_strlendup_append(s, strlen(s), a, strnlen(a, n)); +} + +/* + * Appends at the end of the talloc'ed buffer, + * not the end of the string. + */ +char *talloc_strndup_append_buffer(char *s, const char *a, size_t n) +{ + size_t slen; + + if (unlikely(!s)) { + return talloc_strdup(NULL, a); + } + + if (unlikely(!a)) { + return s; + } + + slen = talloc_get_size(s); + if (likely(slen > 0)) { + slen--; + } + + return __talloc_strlendup_append(s, slen, a, strnlen(a, n)); +} + +#ifndef HAVE_VA_COPY +#ifdef HAVE___VA_COPY +#define va_copy(dest, src) __va_copy(dest, src) +#else +#define va_copy(dest, src) (dest) = (src) +#endif +#endif + +char *talloc_vasprintf(const void *t, const char *fmt, va_list ap) +{ + int len; + char *ret; + va_list ap2; + char c; + + /* this call looks strange, but it makes it work on older solaris boxes */ + va_copy(ap2, ap); + len = vsnprintf(&c, 1, fmt, ap2); + va_end(ap2); + if (unlikely(len < 0)) { + return NULL; + } + + ret = (char *)__talloc(t, len+1); + if (unlikely(!ret)) return NULL; + + va_copy(ap2, ap); + vsnprintf(ret, len+1, fmt, ap2); + va_end(ap2); + + _talloc_set_name_const(ret, ret); + return ret; +} + + +/* + Perform string formatting, and return a pointer to newly allocated + memory holding the result, inside a memory pool. + */ +char *talloc_asprintf(const void *t, const char *fmt, ...) +{ + va_list ap; + char *ret; + + va_start(ap, fmt); + ret = talloc_vasprintf(t, fmt, ap); + va_end(ap); + return ret; +} + +static inline char *__talloc_vaslenprintf_append(char *s, size_t slen, + const char *fmt, va_list ap) + PRINTF_ATTRIBUTE(3,0); + +static inline char *__talloc_vaslenprintf_append(char *s, size_t slen, + const char *fmt, va_list ap) +{ + ssize_t alen; + va_list ap2; + char c; + + va_copy(ap2, ap); + alen = vsnprintf(&c, 1, fmt, ap2); + va_end(ap2); + + if (alen <= 0) { + /* Either the vsnprintf failed or the format resulted in + * no characters being formatted. In the former case, we + * ought to return NULL, in the latter we ought to return + * the original string. Most current callers of this + * function expect it to never return NULL. + */ + return s; + } + + s = talloc_realloc(NULL, s, char, slen + alen + 1); + if (!s) return NULL; + + va_copy(ap2, ap); + vsnprintf(s + slen, alen + 1, fmt, ap2); + va_end(ap2); + + _talloc_set_name_const(s, s); + return s; +} + +/** + * Realloc @p s to append the formatted result of @p fmt and @p ap, + * and return @p s, which may have moved. Good for gradually + * accumulating output into a string buffer. Appends at the end + * of the string. + **/ +char *talloc_vasprintf_append(char *s, const char *fmt, va_list ap) +{ + if (unlikely(!s)) { + return talloc_vasprintf(NULL, fmt, ap); + } + + return __talloc_vaslenprintf_append(s, strlen(s), fmt, ap); +} + +/** + * Realloc @p s to append the formatted result of @p fmt and @p ap, + * and return @p s, which may have moved. Always appends at the + * end of the talloc'ed buffer, not the end of the string. + **/ +char *talloc_vasprintf_append_buffer(char *s, const char *fmt, va_list ap) +{ + size_t slen; + + if (unlikely(!s)) { + return talloc_vasprintf(NULL, fmt, ap); + } + + slen = talloc_get_size(s); + if (likely(slen > 0)) { + slen--; + } + + return __talloc_vaslenprintf_append(s, slen, fmt, ap); +} + +/* + Realloc @p s to append the formatted result of @p fmt and return @p + s, which may have moved. Good for gradually accumulating output + into a string buffer. + */ +char *talloc_asprintf_append(char *s, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + s = talloc_vasprintf_append(s, fmt, ap); + va_end(ap); + return s; +} + +/* + Realloc @p s to append the formatted result of @p fmt and return @p + s, which may have moved. Good for gradually accumulating output + into a buffer. + */ +char *talloc_asprintf_append_buffer(char *s, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + s = talloc_vasprintf_append_buffer(s, fmt, ap); + va_end(ap); + return s; +} + +/* + alloc an array, checking for integer overflow in the array size +*/ +void *_talloc_array(const void *ctx, size_t el_size, unsigned count, const char *name) +{ + if (count >= MAX_TALLOC_SIZE/el_size) { + return NULL; + } + return _talloc_named_const(ctx, el_size * count, name); +} + +/* + alloc an zero array, checking for integer overflow in the array size +*/ +void *_talloc_zero_array(const void *ctx, size_t el_size, unsigned count, const char *name) +{ + if (count >= MAX_TALLOC_SIZE/el_size) { + return NULL; + } + return _talloc_zero(ctx, el_size * count, name); +} + +/* + realloc an array, checking for integer overflow in the array size +*/ +void *_talloc_realloc_array(const void *ctx, void *ptr, size_t el_size, unsigned count, const char *name) +{ + if (count >= MAX_TALLOC_SIZE/el_size) { + return NULL; + } + return _talloc_realloc(ctx, ptr, el_size * count, name); +} + +/* + a function version of talloc_realloc(), so it can be passed as a function pointer + to libraries that want a realloc function (a realloc function encapsulates + all the basic capabilities of an allocation library, which is why this is useful) +*/ +void *talloc_realloc_fn(const void *context, void *ptr, size_t size) +{ + return _talloc_realloc(context, ptr, size, NULL); +} + + +static int talloc_autofree_destructor(void *ptr) +{ + autofree_context = NULL; + return 0; +} + +static void talloc_autofree(void) +{ + _talloc_free(autofree_context); +} + +/* + return a context which will be auto-freed on exit + this is useful for reducing the noise in leak reports +*/ +void *talloc_autofree_context(void) +{ + if (autofree_context == NULL) { + autofree_context = _talloc_named_const(NULL, 0, "autofree_context"); + talloc_set_destructor(autofree_context, talloc_autofree_destructor); + atexit(talloc_autofree); + } + return autofree_context; +} + +size_t talloc_get_size(const void *context) +{ + struct talloc_chunk *tc; + + if (context == NULL) + return 0; + + tc = talloc_chunk_from_ptr(context); + + return tc->size; +} + +/* + find a parent of this context that has the given name, if any +*/ +void *talloc_find_parent_byname(const void *context, const char *name) +{ + struct talloc_chunk *tc; + + if (context == NULL) { + return NULL; + } + + tc = talloc_chunk_from_ptr(context); + while (tc) { + if (tc->name && strcmp(tc->name, name) == 0) { + return TC_PTR_FROM_CHUNK(tc); + } + while (tc && tc->prev) tc = tc->prev; + if (tc) { + tc = tc->parent; + } + } + return NULL; +} + +/* + show the parentage of a context +*/ +void talloc_show_parents(const void *context, FILE *file) +{ + struct talloc_chunk *tc; + + if (context == NULL) { + fprintf(file, "talloc no parents for NULL\n"); + return; + } + + tc = talloc_chunk_from_ptr(context); + fprintf(file, "talloc parents of '%s'\n", talloc_get_name(context)); + while (tc) { + fprintf(file, "\t'%s'\n", talloc_get_name(TC_PTR_FROM_CHUNK(tc))); + while (tc && tc->prev) tc = tc->prev; + if (tc) { + tc = tc->parent; + } + } + fflush(file); +} + +/* + return 1 if ptr is a parent of context +*/ +int talloc_is_parent(const void *context, const void *ptr) +{ + struct talloc_chunk *tc; + + if (context == NULL) { + return 0; + } + + tc = talloc_chunk_from_ptr(context); + while (tc) { + if (TC_PTR_FROM_CHUNK(tc) == ptr) return 1; + while (tc && tc->prev) tc = tc->prev; + if (tc) { + tc = tc->parent; + } + } + return 0; +} diff --git a/libosmocore/src/timer.c b/libosmocore/src/timer.c new file mode 100644 index 000000000..37d7d166b --- /dev/null +++ b/libosmocore/src/timer.c @@ -0,0 +1,185 @@ +/* + * (C) 2008,2009 by Holger Hans Peter Freyther <zecke@selfish.org> + * 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 <assert.h> +#include <string.h> +#include <osmocore/timer.h> + +static LLIST_HEAD(timer_list); +static struct timeval s_nearest_time; +static struct timeval s_select_time; + +#define MICRO_SECONDS 1000000LL + +#define TIME_SMALLER(left, right) \ + (left.tv_sec*MICRO_SECONDS+left.tv_usec) <= (right.tv_sec*MICRO_SECONDS+right.tv_usec) + +void bsc_add_timer(struct timer_list *timer) +{ + struct timer_list *list_timer; + + /* TODO: Optimize and remember the closest item... */ + timer->active = 1; + + /* this might be called from within update_timers */ + llist_for_each_entry(list_timer, &timer_list, entry) + if (timer == list_timer) + return; + + timer->in_list = 1; + llist_add(&timer->entry, &timer_list); +} + +void bsc_schedule_timer(struct timer_list *timer, int seconds, int microseconds) +{ + struct timeval current_time; + + gettimeofday(¤t_time, NULL); + unsigned long long currentTime = current_time.tv_sec * MICRO_SECONDS + current_time.tv_usec; + currentTime += seconds * MICRO_SECONDS + microseconds; + timer->timeout.tv_sec = currentTime / MICRO_SECONDS; + timer->timeout.tv_usec = currentTime % MICRO_SECONDS; + bsc_add_timer(timer); +} + +void bsc_del_timer(struct timer_list *timer) +{ + if (timer->in_list) { + timer->active = 0; + timer->in_list = 0; + llist_del(&timer->entry); + } +} + +int bsc_timer_pending(struct timer_list *timer) +{ + return timer->active; +} + +/* + * if we have a nearest time return the delta between the current + * time and the time of the nearest timer. + * If the nearest timer timed out return NULL and then we will + * dispatch everything after the select + */ +struct timeval *bsc_nearest_timer() +{ + struct timeval current_time; + + if (s_nearest_time.tv_sec == 0 && s_nearest_time.tv_usec == 0) + return NULL; + + if (gettimeofday(¤t_time, NULL) == -1) + return NULL; + + unsigned long long nearestTime = s_nearest_time.tv_sec * MICRO_SECONDS + s_nearest_time.tv_usec; + unsigned long long currentTime = current_time.tv_sec * MICRO_SECONDS + current_time.tv_usec; + + if (nearestTime < currentTime) { + s_select_time.tv_sec = 0; + s_select_time.tv_usec = 0; + } else { + s_select_time.tv_sec = (nearestTime - currentTime) / MICRO_SECONDS; + s_select_time.tv_usec = (nearestTime - currentTime) % MICRO_SECONDS; + } + + return &s_select_time; +} + +/* + * Find the nearest time and update s_nearest_time + */ +void bsc_prepare_timers() +{ + struct timer_list *timer, *nearest_timer = NULL; + llist_for_each_entry(timer, &timer_list, entry) { + if (!nearest_timer || TIME_SMALLER(timer->timeout, nearest_timer->timeout)) { + nearest_timer = timer; + } + } + + if (nearest_timer) { + s_nearest_time = nearest_timer->timeout; + } else { + memset(&s_nearest_time, 0, sizeof(struct timeval)); + } +} + +/* + * fire all timers... and remove them + */ +int bsc_update_timers() +{ + struct timeval current_time; + struct timer_list *timer, *tmp; + int work = 0; + + gettimeofday(¤t_time, NULL); + + /* + * The callbacks might mess with our list and in this case + * even llist_for_each_entry_safe is not safe to use. To allow + * del_timer, add_timer, schedule_timer to be called from within + * the callback we jump through some loops. + * + * First we set the handled flag of each active timer to zero, + * then we iterate over the list and execute the callbacks. As the + * list might have been changed (specially the next) from within + * the callback we have to start over again. Once every callback + * is dispatched we will remove the non-active from the list. + * + * TODO: If this is a performance issue we can poison a global + * variable in add_timer and del_timer and only then restart. + */ + llist_for_each_entry(timer, &timer_list, entry) { + timer->handled = 0; + } + +restart: + llist_for_each_entry(timer, &timer_list, entry) { + if (!timer->handled && TIME_SMALLER(timer->timeout, current_time)) { + timer->handled = 1; + timer->active = 0; + (*timer->cb)(timer->data); + work = 1; + goto restart; + } + } + + llist_for_each_entry_safe(timer, tmp, &timer_list, entry) { + timer->handled = 0; + if (!timer->active) { + bsc_del_timer(timer); + } + } + + return work; +} + +int bsc_timer_check(void) +{ + struct timer_list *timer; + int i = 0; + + llist_for_each_entry(timer, &timer_list, entry) { + i++; + } + return i; +} diff --git a/libosmocore/src/tlv_parser.c b/libosmocore/src/tlv_parser.c new file mode 100644 index 000000000..407e57aa2 --- /dev/null +++ b/libosmocore/src/tlv_parser.c @@ -0,0 +1,171 @@ +#include <stdio.h> +#include <stdint.h> +#include <osmocore/utils.h> +#include <osmocore/tlv.h> + +struct tlv_definition tvlv_att_def; + +int tlv_dump(struct tlv_parsed *dec) +{ + int i; + + for (i = 0; i <= 0xff; i++) { + if (!dec->lv[i].val) + continue; + printf("T=%02x L=%d\n", i, dec->lv[i].len); + } + return 0; +} + +/* o_tag: output: tag found + * o_len: output: length of the data + * o_val: output: pointer to the data + * def: input: a structure defining the valid TLV tags / configurations + * buf: input: the input data buffer to be parsed + * buf_len: input: the length of the input data buffer + * + * Also, returns the number of bytes consumed by the TLV entry + */ +int tlv_parse_one(uint8_t *o_tag, uint16_t *o_len, const uint8_t **o_val, + const struct tlv_definition *def, + const uint8_t *buf, int buf_len) +{ + uint8_t tag; + int len; + + tag = *buf; + *o_tag = tag; + + /* FIXME: use tables for knwon IEI */ + switch (def->def[tag].type) { + case TLV_TYPE_T: + /* GSM TS 04.07 11.2.4: Type 1 TV or Type 2 T */ + *o_val = buf; + *o_len = 0; + len = 1; + break; + case TLV_TYPE_TV: + *o_val = buf+1; + *o_len = 1; + len = 2; + break; + case TLV_TYPE_FIXED: + *o_val = buf+1; + *o_len = def->def[tag].fixed_len; + len = def->def[tag].fixed_len + 1; + break; + case TLV_TYPE_TLV: + /* GSM TS 04.07 11.2.4: Type 4 TLV */ + if (buf + 1 > buf + buf_len) + return -1; + *o_val = buf+2; + *o_len = *(buf+1); + len = *o_len + 2; + if (len > buf_len) + return -2; + break; + case TLV_TYPE_TvLV: + if (*(buf+1) & 0x80) { + /* like TLV, but without highest bit of len */ + if (buf + 1 > buf + buf_len) + return -1; + *o_val = buf+2; + *o_len = *(buf+1) & 0x7f; + len = *o_len + 2; + if (len > buf_len) + return -2; + break; + } + /* like TL16V, fallthrough */ + case TLV_TYPE_TL16V: + if (2 > buf_len) + return -1; + *o_val = buf+3; + *o_len = *(buf+1) << 8 | *(buf+2); + len = *o_len + 3; + if (len > buf_len) + return -2; + break; + default: + return -3; + } + + return len; +} + +/* dec: output: a caller-allocated pointer to a struct tlv_parsed, + * def: input: a structure defining the valid TLV tags / configurations + * buf: input: the input data buffer to be parsed + * buf_len: input: the length of the input data buffer + * lv_tag: input: an initial LV tag at the start of the buffer + * lv_tag2: input: a second initial LV tag following lv_tag + */ +int tlv_parse(struct tlv_parsed *dec, const struct tlv_definition *def, + const uint8_t *buf, int buf_len, uint8_t lv_tag, + uint8_t lv_tag2) +{ + int ofs = 0, num_parsed = 0; + uint16_t len; + + memset(dec, 0, sizeof(*dec)); + + if (lv_tag) { + if (ofs > buf_len) + return -1; + dec->lv[lv_tag].val = &buf[ofs+1]; + dec->lv[lv_tag].len = buf[ofs]; + len = dec->lv[lv_tag].len + 1; + if (ofs + len > buf_len) + return -2; + num_parsed++; + ofs += len; + } + if (lv_tag2) { + if (ofs > buf_len) + return -1; + dec->lv[lv_tag2].val = &buf[ofs+1]; + dec->lv[lv_tag2].len = buf[ofs]; + len = dec->lv[lv_tag2].len + 1; + if (ofs + len > buf_len) + return -2; + num_parsed++; + ofs += len; + } + + while (ofs < buf_len) { + int rv; + uint8_t tag; + const uint8_t *val; + + rv = tlv_parse_one(&tag, &len, &val, def, + &buf[ofs], buf_len-ofs); + if (rv < 0) + return rv; + dec->lv[tag].val = val; + dec->lv[tag].len = len; + ofs += rv; + num_parsed++; + } + //tlv_dump(dec); + return num_parsed; +} + +/* take a master (src) tlvdev and fill up all empty slots in 'dst' */ +void tlv_def_patch(struct tlv_definition *dst, const struct tlv_definition *src) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dst->def); i++) { + if (src->def[i].type == TLV_TYPE_NONE) + continue; + if (dst->def[i].type == TLV_TYPE_NONE) + dst->def[i] = src->def[i]; + } +} + +static __attribute__((constructor)) void on_dso_load_tlv(void) +{ + int i; + for (i = 0; i < ARRAY_SIZE(tvlv_att_def.def); i++) + tvlv_att_def.def[i].type = TLV_TYPE_TvLV; +} diff --git a/libosmocore/src/utils.c b/libosmocore/src/utils.c new file mode 100644 index 000000000..2a73d397e --- /dev/null +++ b/libosmocore/src/utils.c @@ -0,0 +1,46 @@ + +#include <string.h> +#include <stdint.h> +#include <errno.h> + +#include <osmocore/utils.h> + +const char *get_value_string(const struct value_string *vs, uint32_t val) +{ + int i; + + for (i = 0;; i++) { + if (vs[i].value == 0 && vs[i].str == NULL) + break; + if (vs[i].value == val) + return vs[i].str; + } + return "unknown"; +} + +int get_string_value(const struct value_string *vs, const char *str) +{ + int i; + + for (i = 0;; i++) { + if (vs[i].value == 0 && vs[i].str == NULL) + break; + if (!strcasecmp(vs[i].str, str)) + return vs[i].value; + } + return -EINVAL; +} + +char bcd2char(uint8_t bcd) +{ + if (bcd < 0xa) + return '0' + bcd; + else + return 'A' + (bcd - 0xa); +} + +/* only works for numbers in ascci */ +uint8_t char2bcd(char c) +{ + return c - 0x30; +} diff --git a/libosmocore/src/write_queue.c b/libosmocore/src/write_queue.c new file mode 100644 index 000000000..7d908b4ca --- /dev/null +++ b/libosmocore/src/write_queue.c @@ -0,0 +1,74 @@ +/* Generic write queue implementation */ +/* + * (C) 2010 by Holger Hans Peter Freyther + * (C) 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/write_queue.h> + +int write_queue_bfd_cb(struct bsc_fd *fd, unsigned int what) +{ + struct write_queue *queue; + + queue = container_of(fd, struct write_queue, bfd); + + if (what & BSC_FD_READ) + queue->read_cb(fd); + + if (what & BSC_FD_WRITE) { + struct msgb *msg; + + fd->when &= ~BSC_FD_WRITE; + msg = msgb_dequeue(&queue->msg_queue); + if (!msg) + return -1; + + --queue->current_length; + queue->write_cb(fd, msg); + msgb_free(msg); + + if (!llist_empty(&queue->msg_queue)) + fd->when |= BSC_FD_WRITE; + } + + return 0; +} + +void write_queue_init(struct write_queue *queue, int max_length) +{ + queue->max_length = max_length; + queue->current_length = 0; + queue->read_cb = NULL; + queue->write_cb = NULL; + queue->bfd.cb = write_queue_bfd_cb; + INIT_LLIST_HEAD(&queue->msg_queue); +} + +int write_queue_enqueue(struct write_queue *queue, struct msgb *data) +{ +// if (queue->current_length + 1 >= queue->max_length) +// LOGP(DMSC, LOGL_ERROR, "The queue is full. Dropping not yet implemented.\n"); + + ++queue->current_length; + msgb_enqueue(&queue->msg_queue, data); + queue->bfd.when |= BSC_FD_WRITE; + + return 0; +} |