/* * (C) 2017-2018 by sysmocom - s.f.m.c. GmbH, Author: Max * (C) 2018 by Vadim Yanitskiy * (C) 2011-2012 by Luca Melette * * All Rights Reserved * * SPDX-License-Identifier: GPL-2.0+ * * 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 #include #include #include #include #include #include #include #include "l1ctl_proto.h" #include "rlcmac.h" #include "gsmtap.h" static struct gprs_tbf tbf_table[32 * 2]; static inline int too_old(uint32_t current_fn, uint32_t test_fn) { uint32_t delta = (current_fn - test_fn) & 0xffffffff; /* More and less 30 seconds from now */ return abs(delta) > OLD_TIME; } static inline int bsn_is_next(uint8_t first, uint8_t second) { return ((first + 1) % 128) == second; } void print_pkt(uint8_t *msg, size_t len) { size_t i; printf("MSG: "); for (i = 0; i < len; i++) printf("%.02x", msg[i]); printf("\n"); } void process_blocks(struct gprs_tbf *t, bool ul) { uint8_t llc_data[65536], llc_first_bsn, llc_last_bsn = 0; unsigned skip, llc_len = 0; uint8_t bsn, bsn2, li_off; uint32_t current_fn; struct gprs_frag *f; struct gprs_lime *l; /* Get current "time", oldest unreassembled frag */ bsn = t->start_bsn; while (t->frags[bsn].len == 0) { bsn = (bsn + 1) % 128; if (bsn == t->start_bsn) { printf("no valid blocks in current TBF!\n"); fflush(stdout); return; } } current_fn = t->frags[bsn].fn; t->start_bsn = bsn; /* Walk through fragments, mark reassembled/used blocks */ skip = 0; for (bsn = t->start_bsn; bsn != ((t->last_bsn + 1) % 128); bsn = (bsn + 1) % 128) { /* Get fragment descriptor */ f = &t->frags[bsn]; printf(" bsn %d ", bsn); fflush(stdout); /* Already processed or null */ if (!f->len) { printf("null\n"); fflush(stdout); llc_len = 0; skip = 1; continue; } /* Check fragment age */ if (too_old(current_fn, f->fn)) { printf("old segment\n"); fflush(stdout); llc_len = 0; skip = 1; continue; } /* Update "time" */ current_fn = f->fn; if (llc_len && !bsn_is_next(llc_last_bsn, bsn)) { printf("missing bsn, previous %d\n", llc_last_bsn); fflush(stdout); llc_len = 0; skip = 1; continue; } /* Check for multiple blocks/parts */ if (f->n_blocks == 0) { /* Check if first part of message */ if (!llc_len) llc_first_bsn = bsn; /* Append data to buffer */ memcpy(&llc_data[llc_len], f->data, f->len); llc_len += f->len; llc_last_bsn = bsn; /* Last TBF block? (very rare condition) */ if (f->last) { printf("end of TBF\n"); fflush(stdout); print_pkt(llc_data, llc_len); gsmtap_send_llc(llc_data, llc_len, ul); /* Reset all fragments */ for (bsn2 = 0; bsn2 < 128; bsn2++) { f = &t->frags[bsn2]; f->len = 0; f->n_blocks = 0; } /* Reset buffer state */ llc_len = 0; t->start_bsn = 0; } } else { /* Multiple data parts */ unsigned i; li_off = 0; for (i = 0; i < f->n_blocks; i++) { printf("\nlime %d\n", i); fflush(stdout); l = &f->blocks[i]; if (l->used) { if (llc_len) { printf("\nlime error!\n"); fflush(stdout); llc_len = 0; } } else { if (!llc_len) llc_first_bsn = bsn; /* Append data to buffer */ memcpy(&llc_data[llc_len], &f->data[li_off], l->li); llc_len += l->li; llc_last_bsn = bsn; if (!l->e || !l->m || (l->e && l->m)) { /* Message ends here */ printf("end of message reached\n"); fflush(stdout); print_pkt(llc_data, llc_len); gsmtap_send_llc(llc_data, llc_len, ul); /* Mark frags as used */ l->used = 1; if (llc_first_bsn != bsn) { } llc_len = 0; if (!skip) t->start_bsn = bsn; } } li_off += l->li; } /* Is spare data valid? */ if (l->m) { if (llc_len) { printf("spare and buffer not empty!\n"); print_pkt(llc_data, llc_len); fflush(stdout); } if ((f->len > li_off) && (f->len-li_off < 65536)) { memcpy(llc_data, &f->data[li_off], f->len-li_off); llc_len = f->len - li_off; llc_first_bsn = bsn; llc_last_bsn = bsn; t->start_bsn = bsn; } } } } /* Shift window if needed */ if (((t->last_bsn - t->start_bsn) % 128) > 64) { t->start_bsn = (t->last_bsn - 64) % 128; printf("shifting window\n"); fflush(stdout); } } void rlc_data_handler(struct gprs_message *gm) { int ul, off, d_bsn; uint8_t tfi, bsn, cv = 1, fbi = 0; uint32_t d_same_bsn, d_last_bsn; struct gprs_tbf *t, *t_prev; struct gprs_frag *f; struct gprs_lime *l; tfi = (gm->msg[1] & 0x3e) >> 1; bsn = (gm->msg[2] & 0xfe) >> 1; /* Get "end of TBF" according to direction */ ul = !!(gm->arfcn & GSMTAP_ARFCN_F_UPLINK); if (ul) { cv = (gm->msg[0] & 0x3c) >> 2; printf("TFI %d BSN %d CV %d ", tfi, bsn, cv); } else { fbi = (gm->msg[1] & 0x01); printf("TFI %d BSN %d FBI %d ", tfi, bsn, fbi); } /* Get TBF descriptor for TFI,UL couple */ t = &tbf_table[2 * tfi + ul]; d_same_bsn = (gm->fn - t->frags[bsn].fn) & 0xffffffff; d_last_bsn = (gm->fn - t->frags[t->last_bsn].fn) & 0xffffffff; d_bsn = (bsn - t->last_bsn) % 128; printf("\nfn_same_bsn %d fn_last_bsn %d delta_bsn %d old_len %d\n", d_same_bsn, d_last_bsn, d_bsn, t->frags[bsn].len); /* New / old fragment decision */ if (d_same_bsn > OLD_TIME) { if (d_last_bsn > OLD_TIME) { /* New TBF is starting, close old one... */ t_prev = &tbf_table[2 * ((tfi + 1) % 32) + ul]; printf("clearing TBF %d, first %d last %d\n", (tfi + 1) % 32, t_prev->start_bsn, t_prev->last_bsn); f = &t_prev->frags[t_prev->last_bsn]; /* ...only if data is present */ if (f->len) { f->last = 1; process_blocks(t_prev, ul); } printf("new TBF, starting from %d\n", bsn); t->start_bsn = 0; t->last_bsn = bsn; memset(t->frags, 0, 128 * sizeof(struct gprs_frag)); } else { /* Fresh frag, current TBF */ if ((d_bsn >= 0) || (d_bsn < -64)) { /* New frag */ t->last_bsn = bsn; } else { /* Out of sequence / duplicate */ t->frags[bsn].fn = gm->fn; printf("duplicate\n"); fflush(stdout); return; } } } else { if (d_last_bsn > OLD_TIME) { printf("fucking error last_bsn!\n"); fflush(stdout); return; } else { /* Fresh frag, current TBF */ if (d_bsn > 0) { printf("fucking error d_bsn!\n"); fflush(stdout); return; } else { if (d_bsn < -64) { /* New frag */ t->last_bsn = bsn; } else { /* Duplicate */ t->frags[bsn].fn = gm->fn; printf("duplicate2\n"); fflush(stdout); return; } } } } /* Get fragment struct for current BSN */ f = &t->frags[bsn]; /* Scan for LI_M_E entries */ off = 2; f->n_blocks = 0; while (!(gm->msg[off++] & 0x01)) { l = &f->blocks[f->n_blocks++]; l->li = (gm->msg[off] & 0xfc) >> 2; l->m = (gm->msg[off] & 0x02) >> 1; l->e = (gm->msg[off] & 0x01); l->used = 0; } /* End of TBF? */ f->last = (!cv || fbi) ? 1 : 0; /* Optional fields for uplink, indicated in TI and PI */ if (ul) { if (gm->msg[1] & 0x01) { printf("TLLI 0x%.02x%.02x%.02x%.02x ", gm->msg[off], gm->msg[off+1], gm->msg[off + 2], gm->msg[off + 3]); off += 4; } if (gm->msg[1] & 0x40) { printf("PFI %d ", gm->msg[off]); off += 1; } } /* Copy data part of message */ f->len = gm->len - off; f->fn = gm->fn; memcpy(f->data, &gm->msg[off], f->len); process_blocks(t, ul); } int rlc_type_handler(struct gprs_message *gm) { bool ul = !!(gm->arfcn & GSMTAP_ARFCN_F_UPLINK); uint8_t rlc_type = (gm->msg[0] & 0xc0) >> 6; int rc = 0; /* Determine the RLC type */ switch (rlc_type) { case 0: printf("TS %d ", gm->tn); switch(gm->len) { case 23: printf("CS1 "); break; case 33: printf("CS2 "); break; case 39: printf("CS3 "); break; case 53: printf("CS4 "); break; default: printf("unknown (M)CS "); } printf(ul ? "UL " : "DL "); gsmtap_send_rlcmac(gm->msg, gm->len, gm->tn, ul); printf("DATA "); rlc_data_handler(gm); printf("\n"); fflush(stdout); break; /* Control block */ case 1: case 2: gsmtap_send_rlcmac(gm->msg, gm->len, gm->tn, !!(gm->arfcn & GSMTAP_ARFCN_F_UPLINK)); rc = 0; break; /* Reserved */ case 3: printf("RLC type: reserved\n"); rc = 0; break; default: printf("Unrecognized RLC type: %d\n", rlc_type); return -EINVAL; } return rc; }