diff options
Diffstat (limited to 'src/gprs/gprs_llc.c')
-rw-r--r-- | src/gprs/gprs_llc.c | 852 |
1 files changed, 852 insertions, 0 deletions
diff --git a/src/gprs/gprs_llc.c b/src/gprs/gprs_llc.c new file mode 100644 index 000000000..7991f4c1e --- /dev/null +++ b/src/gprs/gprs_llc.c @@ -0,0 +1,852 @@ +/* GPRS LLC protocol implementation as per 3GPP TS 04.64 */ + +/* (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 Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <errno.h> +#include <stdint.h> + +#include <osmocore/msgb.h> +#include <osmocore/linuxlist.h> +#include <osmocore/timer.h> +#include <osmocore/talloc.h> + +#include <openbsc/gsm_data.h> +#include <openbsc/debug.h> +#include <openbsc/gprs_sgsn.h> +#include <openbsc/gprs_gmm.h> +#include <openbsc/gprs_bssgp.h> +#include <openbsc/gprs_llc.h> +#include <openbsc/crc24.h> + +/* Section 8.9.9 LLC layer parameter default values */ +static const struct gprs_llc_params llc_default_params[] = { + [1] = { + .t200_201 = 5, + .n200 = 3, + .n201_u = 400, + }, + [2] = { + .t200_201 = 5, + .n200 = 3, + .n201_u = 270, + }, + [3] = { + .iov_i_exp = 27, + .t200_201 = 5, + .n200 = 3, + .n201_u = 500, + .n201_i = 1503, + .mD = 1520, + .mU = 1520, + .kD = 16, + .kU = 16, + }, + [5] = { + .iov_i_exp = 27, + .t200_201 = 10, + .n200 = 3, + .n201_u = 500, + .n201_i = 1503, + .mD = 760, + .mU = 760, + .kD = 8, + .kU = 8, + }, + [7] = { + .t200_201 = 20, + .n200 = 3, + .n201_u = 270, + }, + [8] = { + .t200_201 = 20, + .n200 = 3, + .n201_u = 270, + }, + [9] = { + .iov_i_exp = 27, + .t200_201 = 20, + .n200 = 3, + .n201_u = 500, + .n201_i = 1503, + .mD = 380, + .mU = 380, + .kD = 4, + .kU = 4, + }, + [11] = { + .iov_i_exp = 27, + .t200_201 = 40, + .n200 = 3, + .n201_u = 500, + .n201_i = 1503, + .mD = 190, + .mU = 190, + .kD = 2, + .kU = 2, + }, +}; + +LLIST_HEAD(gprs_llc_llmes); +void *llc_tall_ctx; + +/* If the TLLI is foreign, return its local version */ +static inline uint32_t tlli_foreign2local(uint32_t tlli) +{ + uint32_t new_tlli; + + if (gprs_tlli_type(tlli) == TLLI_FOREIGN) { + new_tlli = tlli | 0x40000000; + DEBUGP(DLLC, "TLLI 0x%08x is foreign, converting to " + "local TLLI 0x%08x\n", tlli, new_tlli); + } else + new_tlli = tlli; + + return new_tlli; +} + +/* lookup LLC Entity based on DLCI (TLLI+SAPI tuple) */ +static struct gprs_llc_lle *lle_by_tlli_sapi(uint32_t tlli, uint8_t sapi) +{ + struct gprs_llc_llme *llme; + + tlli = tlli_foreign2local(tlli); + + llist_for_each_entry(llme, &gprs_llc_llmes, list) { + if (llme->tlli == tlli || llme->old_tlli == tlli) + return &llme->lle[sapi]; + } + return NULL; +} + +static void lle_init(struct gprs_llc_llme *llme, uint8_t sapi) +{ + struct gprs_llc_lle *lle = &llme->lle[sapi]; + + lle->llme = llme; + lle->sapi = sapi; + lle->state = GPRS_LLES_UNASSIGNED; + + /* Initialize according to parameters */ + memcpy(&lle->params, &llc_default_params[sapi], sizeof(lle->params)); +} + +static struct gprs_llc_llme *llme_alloc(uint32_t tlli) +{ + struct gprs_llc_llme *llme; + uint32_t i; + + llme = talloc_zero(llc_tall_ctx, struct gprs_llc_llme); + if (!llme) + return NULL; + + llme->tlli = tlli; + llme->old_tlli = 0xffffffff; + llme->state = GPRS_LLMS_UNASSIGNED; + + for (i = 0; i < ARRAY_SIZE(llme->lle); i++) + lle_init(llme, i); + + llist_add(&llme->list, &gprs_llc_llmes); + + return llme; +} + +static void llme_free(struct gprs_llc_llme *llme) +{ + llist_del(&llme->list); + talloc_free(llme); +} + +enum gprs_llc_cmd { + GPRS_LLC_NULL, + GPRS_LLC_RR, + GPRS_LLC_ACK, + GPRS_LLC_RNR, + GPRS_LLC_SACK, + GPRS_LLC_DM, + GPRS_LLC_DISC, + GPRS_LLC_UA, + GPRS_LLC_SABM, + GPRS_LLC_FRMR, + GPRS_LLC_XID, + GPRS_LLC_UI, +}; + +static const struct value_string llc_cmd_strs[] = { + { GPRS_LLC_NULL, "NULL" }, + { GPRS_LLC_RR, "RR" }, + { GPRS_LLC_ACK, "ACK" }, + { GPRS_LLC_RNR, "RNR" }, + { GPRS_LLC_SACK, "SACK" }, + { GPRS_LLC_DM, "DM" }, + { GPRS_LLC_DISC, "DISC" }, + { GPRS_LLC_UA, "UA" }, + { GPRS_LLC_SABM, "SABM" }, + { GPRS_LLC_FRMR, "FRMR" }, + { GPRS_LLC_XID, "XID" }, + { GPRS_LLC_UI, "UI" }, + { 0, NULL } +}; + +struct gprs_llc_hdr_parsed { + uint8_t sapi; + uint8_t is_cmd:1, + ack_req:1, + is_encrypted:1; + uint32_t seq_rx; + uint32_t seq_tx; + uint32_t fcs; + uint32_t fcs_calc; + uint8_t *data; + uint16_t data_len; + uint16_t crc_length; + enum gprs_llc_cmd cmd; +}; + +#define LLC_ALLOC_SIZE 16384 +#define UI_HDR_LEN 3 +#define N202 4 +#define CRC24_LENGTH 3 + +static int gprs_llc_fcs(uint8_t *data, unsigned int len) +{ + uint32_t fcs_calc; + + fcs_calc = crc24_calc(INIT_CRC24, data, len); + fcs_calc = ~fcs_calc; + fcs_calc &= 0xffffff; + + return fcs_calc; +} + +static void t200_expired(void *data) +{ + struct gprs_llc_lle *lle = data; + + /* 8.5.1.3: Expiry of T200 */ + + if (lle->retrans_ctr >= lle->params.n200) { + /* FIXME: LLGM-STATUS-IND, LL-RELEASE-IND/CNF */ + lle->state = GPRS_LLES_ASSIGNED_ADM; + } + + switch (lle->state) { + case GPRS_LLES_LOCAL_EST: + /* FIXME: retransmit SABM */ + /* FIXME: re-start T200 */ + lle->retrans_ctr++; + break; + case GPRS_LLES_LOCAL_REL: + /* FIXME: retransmit DISC */ + /* FIXME: re-start T200 */ + lle->retrans_ctr++; + break; + } + +} + +static void t201_expired(void *data) +{ + struct gprs_llc_lle *lle = data; + + if (lle->retrans_ctr < lle->params.n200) { + /* FIXME: transmit apropriate supervisory frame (8.6.4.1) */ + /* FIXME: set timer T201 */ + lle->retrans_ctr++; + } +} + +int gprs_llc_tx_u(struct msgb *msg, uint8_t sapi, int command, + enum gprs_llc_u_cmd u_cmd, int pf_bit) +{ + uint8_t *fcs, *llch; + uint8_t addr, ctrl; + uint32_t fcs_calc; + + /* Identifiers from UP: (TLLI, SAPI) + (BVCI, NSEI) */ + + /* Address Field */ + addr = sapi & 0xf; + if (command) + addr |= 0x40; + + /* 6.3 Figure 8 */ + ctrl = 0xe0 | u_cmd; + if (pf_bit) + ctrl |= 0x10; + + /* prepend LLC UI header */ + llch = msgb_push(msg, 2); + llch[0] = addr; + llch[1] = ctrl; + + /* append FCS to end of frame */ + fcs = msgb_put(msg, 3); + fcs_calc = gprs_llc_fcs(llch, fcs - llch); + fcs[0] = fcs_calc & 0xff; + fcs[1] = (fcs_calc >> 8) & 0xff; + fcs[2] = (fcs_calc >> 16) & 0xff; + + /* Identifiers passed down: (BVCI, NSEI) */ + + /* Send BSSGP-DL-UNITDATA.req */ + return gprs_bssgp_tx_dl_ud(msg, NULL); +} + +/* Send XID response to LLE */ +static int gprs_llc_tx_xid(struct gprs_llc_lle *lle, struct msgb *msg) +{ + /* copy identifiers from LLE to ensure lower layers can route */ + msgb_tlli(msg) = lle->llme->tlli; + msgb_bvci(msg) = lle->llme->bvci; + msgb_nsei(msg) = lle->llme->nsei; + + return gprs_llc_tx_u(msg, lle->sapi, 0, GPRS_LLC_U_XID, 1); +} + +/* Transmit a UI frame over the given SAPI */ +int gprs_llc_tx_ui(struct msgb *msg, uint8_t sapi, int command, + void *mmctx) +{ + struct gprs_llc_lle *lle; + uint8_t *fcs, *llch; + uint8_t addr, ctrl[2]; + uint32_t fcs_calc; + uint16_t nu = 0; + uint32_t oc; + + /* Identifiers from UP: (TLLI, SAPI) + (BVCI, NSEI) */ + + /* look-up or create the LL Entity for this (TLLI, SAPI) tuple */ + lle = lle_by_tlli_sapi(msgb_tlli(msg), sapi); + if (!lle) { + struct gprs_llc_llme *llme; + LOGP(DLLC, LOGL_ERROR, "LLC TX: unknown TLLI 0x%08x, " + "creating LLME on the fly\n", msgb_tlli(msg)); + llme = llme_alloc(msgb_tlli(msg)); + lle = &llme->lle[sapi]; + } + + if (msg->len > lle->params.n201_u) { + LOGP(DLLC, LOGL_ERROR, "Cannot Tx %u bytes (N201-U=%u)\n", + msg->len, lle->params.n201_u); + return -EFBIG; + } + + /* Update LLE's (BVCI, NSEI) tuple */ + lle->llme->bvci = msgb_bvci(msg); + lle->llme->nsei = msgb_nsei(msg); + + /* Obtain current values for N(u) and OC */ + nu = lle->vu_send; + oc = lle->oc_ui_send; + /* Increment V(U) */ + lle->vu_send = (lle->vu_send + 1) % 512; + /* Increment Overflow Counter, if needed */ + if ((lle->vu_send + 1) / 512) + lle->oc_ui_send += 512; + + /* Address Field */ + addr = sapi & 0xf; + if (command) + addr |= 0x40; + + /* Control Field */ + ctrl[0] = 0xc0; + ctrl[0] |= nu >> 6; + ctrl[1] = (nu << 2) & 0xfc; + ctrl[1] |= 0x01; /* Protected Mode */ + + /* prepend LLC UI header */ + llch = msgb_push(msg, 3); + llch[0] = addr; + llch[1] = ctrl[0]; + llch[2] = ctrl[1]; + + /* append FCS to end of frame */ + fcs = msgb_put(msg, 3); + fcs_calc = gprs_llc_fcs(llch, fcs - llch); + fcs[0] = fcs_calc & 0xff; + fcs[1] = (fcs_calc >> 8) & 0xff; + fcs[2] = (fcs_calc >> 16) & 0xff; + + /* encrypt information field + FCS, if needed! */ + if (lle->llme->algo != GPRS_ALGO_GEA0) { + uint32_t iov_ui = 0; /* FIXME: randomly select for TLLI */ + uint16_t crypt_len = (fcs + 3) - (llch + 3); + uint8_t cipher_out[GSM0464_CIPH_MAX_BLOCK]; + uint32_t iv; + int rc, i; + uint64_t kc = *(uint64_t *)&lle->llme->kc; + + /* Compute the 'Input' Paraemeter */ + iv = gprs_cipher_gen_input_ui(iov_ui, sapi, nu, oc); + + /* Compute the keystream that we need to XOR with the data */ + rc = gprs_cipher_run(cipher_out, crypt_len, lle->llme->algo, + kc, iv, GPRS_CIPH_SGSN2MS); + if (rc < 0) { + LOGP(DLLC, LOGL_ERROR, "Error crypting UI frame: %d\n", rc); + return rc; + } + + /* XOR the cipher output with the information field + FCS */ + for (i = 0; i < crypt_len; i++) + *(llch + 3 + i) ^= cipher_out[i]; + + /* Mark frame as encrypted */ + ctrl[1] |= 0x02; + } + + /* Identifiers passed down: (BVCI, NSEI) */ + + /* Send BSSGP-DL-UNITDATA.req */ + return gprs_bssgp_tx_dl_ud(msg, mmctx); +} + +static void gprs_llc_hdr_dump(struct gprs_llc_hdr_parsed *gph) +{ + DEBUGP(DLLC, "LLC SAPI=%u %c %c FCS=0x%06x", + gph->sapi, gph->is_cmd ? 'C' : 'R', gph->ack_req ? 'A' : ' ', + gph->fcs); + + if (gph->cmd) + DEBUGPC(DLLC, "CMD=%s ", get_value_string(llc_cmd_strs, gph->cmd)); + + if (gph->data) + DEBUGPC(DLLC, "DATA "); + + DEBUGPC(DLLC, "\n"); +} +static int gprs_llc_hdr_rx(struct gprs_llc_hdr_parsed *gph, + struct gprs_llc_lle *lle) +{ + switch (gph->cmd) { + case GPRS_LLC_SABM: /* Section 6.4.1.1 */ + lle->v_sent = lle->v_ack = lle->v_recv = 0; + if (lle->state == GPRS_LLES_ASSIGNED_ADM) { + /* start re-establishment (8.7.1) */ + } + lle->state = GPRS_LLES_REMOTE_EST; + /* FIXME: Send UA */ + lle->state = GPRS_LLES_ABM; + /* FIXME: process data */ + break; + case GPRS_LLC_DISC: /* Section 6.4.1.2 */ + /* FIXME: Send UA */ + /* terminate ABM */ + lle->state = GPRS_LLES_ASSIGNED_ADM; + break; + case GPRS_LLC_UA: /* Section 6.4.1.3 */ + if (lle->state == GPRS_LLES_LOCAL_EST) + lle->state = GPRS_LLES_ABM; + break; + case GPRS_LLC_DM: /* Section 6.4.1.4: ABM cannot be performed */ + if (lle->state == GPRS_LLES_LOCAL_EST) + lle->state = GPRS_LLES_ASSIGNED_ADM; + break; + case GPRS_LLC_FRMR: /* Section 6.4.1.5 */ + break; + case GPRS_LLC_XID: /* Section 6.4.1.6 */ + /* FIXME: implement XID negotiation using SNDCP */ + { + struct msgb *resp; + uint8_t *xid; + resp = msgb_alloc_headroom(4096, 1024, "LLC_XID"); + xid = msgb_put(resp, gph->data_len); + memcpy(xid, gph->data, gph->data_len); + gprs_llc_tx_xid(lle, resp); + } + break; + case GPRS_LLC_UI: + if (gph->seq_tx < lle->vu_recv) { + LOGP(DLLC, LOGL_NOTICE, "TLLI=%08x dropping UI, vurecv %u <= %u\n", + lle->llme ? lle->llme->tlli : -1, + gph->seq_tx, lle->vu_recv); + return -EIO; + } + /* Increment the sequence number that we expect in the next frame */ + lle->vu_recv = (gph->seq_tx + 1) % 512; + /* Increment Overflow Counter */ + if ((gph->seq_tx + 1) / 512) + lle->oc_ui_recv += 512; + break; + } + + return 0; +} + +/* parse a GPRS LLC header, also check for invalid frames */ +static int gprs_llc_hdr_parse(struct gprs_llc_hdr_parsed *ghp, + uint8_t *llc_hdr, int len) +{ + uint8_t *ctrl = llc_hdr+1; + int is_sack = 0; + + if (len <= CRC24_LENGTH) + return -EIO; + + ghp->crc_length = len - CRC24_LENGTH; + + ghp->ack_req = 0; + + /* Section 5.5: FCS */ + ghp->fcs = *(llc_hdr + len - 3); + ghp->fcs |= *(llc_hdr + len - 2) << 8; + ghp->fcs |= *(llc_hdr + len - 1) << 16; + + /* Section 6.2.1: invalid PD field */ + if (llc_hdr[0] & 0x80) + return -EIO; + + /* This only works for the MS->SGSN direction */ + if (llc_hdr[0] & 0x40) + ghp->is_cmd = 0; + else + ghp->is_cmd = 1; + + ghp->sapi = llc_hdr[0] & 0xf; + + /* Section 6.2.3: check for reserved SAPI */ + switch (ghp->sapi) { + case 0: + case 4: + case 6: + case 0xa: + case 0xc: + case 0xd: + case 0xf: + return -EINVAL; + } + + if ((ctrl[0] & 0x80) == 0) { + /* I (Information transfer + Supervisory) format */ + uint8_t k; + + ghp->data = ctrl + 3; + + if (ctrl[0] & 0x40) + ghp->ack_req = 1; + + ghp->seq_tx = (ctrl[0] & 0x1f) << 4; + ghp->seq_tx |= (ctrl[1] >> 4); + + ghp->seq_rx = (ctrl[1] & 0x7) << 6; + ghp->seq_rx |= (ctrl[2] >> 2); + + switch (ctrl[2] & 0x03) { + case 0: + ghp->cmd = GPRS_LLC_RR; + break; + case 1: + ghp->cmd = GPRS_LLC_ACK; + break; + case 2: + ghp->cmd = GPRS_LLC_RNR; + break; + case 3: + ghp->cmd = GPRS_LLC_SACK; + k = ctrl[3] & 0x1f; + ghp->data += 1 + k; + break; + } + ghp->data_len = (llc_hdr + len - 3) - ghp->data; + } else if ((ctrl[0] & 0xc0) == 0x80) { + /* S (Supervisory) format */ + ghp->data = NULL; + ghp->data_len = 0; + + if (ctrl[0] & 0x20) + ghp->ack_req = 1; + ghp->seq_rx = (ctrl[0] & 0x7) << 6; + ghp->seq_rx |= (ctrl[1] >> 2); + + switch (ctrl[1] & 0x03) { + case 0: + ghp->cmd = GPRS_LLC_RR; + break; + case 1: + ghp->cmd = GPRS_LLC_ACK; + break; + case 2: + ghp->cmd = GPRS_LLC_RNR; + break; + case 3: + ghp->cmd = GPRS_LLC_SACK; + break; + } + } else if ((ctrl[0] & 0xe0) == 0xc0) { + /* UI (Unconfirmed Inforamtion) format */ + ghp->cmd = GPRS_LLC_UI; + ghp->data = ctrl + 2; + ghp->data_len = (llc_hdr + len - 3) - ghp->data; + + ghp->seq_tx = (ctrl[0] & 0x7) << 6; + ghp->seq_tx |= (ctrl[1] >> 2); + if (ctrl[1] & 0x02) { + ghp->is_encrypted = 1; + /* FIXME: encryption */ + } + if (ctrl[1] & 0x01) { + /* FCS over hdr + all inf fields */ + } else { + /* FCS over hdr + N202 octets (4) */ + if (ghp->crc_length > UI_HDR_LEN + N202) + ghp->crc_length = UI_HDR_LEN + N202; + } + } else { + /* U (Unnumbered) format: 1 1 1 P/F M4 M3 M2 M1 */ + ghp->data = NULL; + ghp->data_len = 0; + + switch (ctrl[0] & 0xf) { + case GPRS_LLC_U_NULL_CMD: + ghp->cmd = GPRS_LLC_NULL; + break; + case GPRS_LLC_U_DM_RESP: + ghp->cmd = GPRS_LLC_DM; + break; + case GPRS_LLC_U_DISC_CMD: + ghp->cmd = GPRS_LLC_DISC; + break; + case GPRS_LLC_U_UA_RESP: + ghp->cmd = GPRS_LLC_UA; + break; + case GPRS_LLC_U_SABM_CMD: + ghp->cmd = GPRS_LLC_SABM; + break; + case GPRS_LLC_U_FRMR_RESP: + ghp->cmd = GPRS_LLC_FRMR; + break; + case GPRS_LLC_U_XID: + ghp->cmd = GPRS_LLC_XID; + ghp->data = ctrl + 1; + ghp->data_len = (llc_hdr + len - 3) - ghp->data; + break; + default: + return -EIO; + } + } + + /* FIXME: parse sack frame */ + if (ghp->cmd == GPRS_LLC_SACK) { + LOGP(DLLC, LOGL_NOTICE, "Unsupported SACK frame\n"); + return -EIO; + } + + return 0; +} + +/* receive an incoming LLC PDU (BSSGP-UL-UNITDATA-IND, 7.2.4.2) */ +int gprs_llc_rcvmsg(struct msgb *msg, struct tlv_parsed *tv) +{ + struct bssgp_ud_hdr *udh = (struct bssgp_ud_hdr *) msgb_bssgph(msg); + struct gprs_llc_hdr *lh = msgb_llch(msg); + struct gprs_llc_hdr_parsed llhp; + struct gprs_llc_lle *lle; + int rc = 0; + + /* Identifiers from DOWN: NSEI, BVCI, TLLI */ + + memset(&llhp, 0, sizeof(llhp)); + rc = gprs_llc_hdr_parse(&llhp, (uint8_t *) lh, TLVP_LEN(tv, BSSGP_IE_LLC_PDU)); + gprs_llc_hdr_dump(&llhp); + if (rc < 0) { + LOGP(DLLC, LOGL_NOTICE, "Error during LLC header parsing\n"); + return rc; + } + + switch (gprs_tlli_type(msgb_tlli(msg))) { + case TLLI_LOCAL: + case TLLI_FOREIGN: + case TLLI_RANDOM: + case TLLI_AUXILIARY: + break; + default: + LOGP(DLLC, LOGL_ERROR, + "Discarding frame with strange TLLI type\n"); + break; + } + + /* find the LLC Entity for this TLLI+SAPI tuple */ + lle = lle_by_tlli_sapi(msgb_tlli(msg), llhp.sapi); + + /* 7.2.1.1 LLC belonging to unassigned TLLI+SAPI shall be discarded, + * except UID and XID frames with SAPI=1 */ + if (!lle) { + if (llhp.sapi == GPRS_SAPI_GMM && + (llhp.cmd == GPRS_LLC_XID || llhp.cmd == GPRS_LLC_UI)) { + struct gprs_llc_llme *llme; + /* FIXME: don't use the TLLI but the 0xFFFF unassigned? */ + llme = llme_alloc(msgb_tlli(msg)); + LOGP(DLLC, LOGL_DEBUG, "LLC RX: unknown TLLI 0x08x, " + "creating LLME on the fly\n", msgb_tlli(msg)); + lle = &llme->lle[llhp.sapi]; + } else { + LOGP(DLLC, LOGL_NOTICE, + "unknown TLLI/SAPI: Silently dropping\n"); + return 0; + } + } + + /* decrypt information field + FCS, if needed! */ + if (llhp.is_encrypted) { + uint32_t iov_ui = 0; /* FIXME: randomly select for TLLI */ + uint16_t crypt_len = llhp.data_len + 3; + uint8_t cipher_out[GSM0464_CIPH_MAX_BLOCK]; + uint32_t iv; + uint64_t kc = *(uint64_t *)&lle->llme->kc; + int rc, i; + + if (lle->llme->algo == GPRS_ALGO_GEA0) { + LOGP(DLLC, LOGL_NOTICE, "encrypted frame for LLC that " + "has no KC/Algo! Dropping.\n"); + return 0; + } + + iv = gprs_cipher_gen_input_ui(iov_ui, lle->sapi, llhp.seq_tx, + lle->oc_ui_recv); + rc = gprs_cipher_run(cipher_out, crypt_len, lle->llme->algo, + kc, iv, GPRS_CIPH_MS2SGSN); + if (rc < 0) { + LOGP(DLLC, LOGL_ERROR, "Error decrypting frame: %d\n", + rc); + return rc; + } + + /* XOR the cipher output with the information field + FCS */ + for (i = 0; i < crypt_len; i++) + *(llhp.data + i) ^= cipher_out[i]; + } else { + if (lle->llme->algo != GPRS_ALGO_GEA0) { + LOGP(DLLC, LOGL_NOTICE, "unencrypted frame for LLC " + "that is supposed to be encrypted. Dropping.\n"); + return 0; + } + } + + /* We have to do the FCS check _after_ decryption */ + llhp.fcs_calc = gprs_llc_fcs((uint8_t *)lh, llhp.crc_length); + if (llhp.fcs != llhp.fcs_calc) { + LOGP(DLLC, LOGL_INFO, "Dropping frame with invalid FCS\n"); + return -EIO; + } + + /* Update LLE's (BVCI, NSEI) tuple */ + lle->llme->bvci = msgb_bvci(msg); + lle->llme->nsei = msgb_nsei(msg); + + /* Receive and Process the actual LLC frame */ + rc = gprs_llc_hdr_rx(&llhp, lle); + if (rc < 0) + return rc; + + /* llhp.data is only set when we need to send LL_[UNIT]DATA_IND up */ + if (llhp.data) { + msgb_gmmh(msg) = llhp.data; + switch (llhp.sapi) { + case GPRS_SAPI_GMM: + /* send LL_UNITDATA_IND to GMM */ + rc = gsm0408_gprs_rcvmsg(msg, lle->llme); + break; + case GPRS_SAPI_SNDCP3: + case GPRS_SAPI_SNDCP5: + case GPRS_SAPI_SNDCP9: + case GPRS_SAPI_SNDCP11: + /* send LL_DATA_IND/LL_UNITDATA_IND to SNDCP */ + rc = sndcp_llunitdata_ind(msg, lle, llhp.data, llhp.data_len); + break; + case GPRS_SAPI_SMS: + /* FIXME */ + case GPRS_SAPI_TOM2: + case GPRS_SAPI_TOM8: + /* FIXME: send LL_DATA_IND/LL_UNITDATA_IND to TOM */ + default: + LOGP(DLLC, LOGL_NOTICE, "Unsupported SAPI %u\n", llhp.sapi); + rc = -EINVAL; + break; + } + } + + return rc; +} + +/* 04.64 Chapter 7.2.1.1 LLGMM-ASSIGN */ +int gprs_llgmm_assign(struct gprs_llc_llme *llme, + uint32_t old_tlli, uint32_t new_tlli, + enum gprs_ciph_algo alg, const uint8_t *kc) +{ + unsigned int i; + + /* Update the crypto parameters */ + llme->algo = alg; + if (alg != GPRS_ALGO_GEA0) + memcpy(llme->kc, kc, sizeof(llme->kc)); + + if (old_tlli == 0xffffffff && new_tlli != 0xffffffff) { + /* TLLI Assignment 8.3.1 */ + /* New TLLI shall be assigned and used when (re)transmitting LLC frames */ + /* If old TLLI != 0xffffffff was assigned to LLME, then TLLI + * old is unassigned. Only TLLI new shall be accepted when + * received from peer. */ + if (llme->old_tlli != 0xffffffff) { + llme->old_tlli = 0xffffffff; + llme->tlli = new_tlli; + } else { + /* If TLLI old == 0xffffffff was assigned to LLME, then this is + * TLLI assignmemt according to 8.3.1 */ + llme->old_tlli = 0xffffffff; + llme->tlli = new_tlli; + llme->state = GPRS_LLMS_ASSIGNED; + /* 8.5.3.1 For all LLE's */ + for (i = 0; i < ARRAY_SIZE(llme->lle); i++) { + struct gprs_llc_lle *l = &llme->lle[i]; + l->vu_send = l->vu_recv = 0; + l->retrans_ctr = 0; + l->state = GPRS_LLES_ASSIGNED_ADM; + /* FIXME Set parameters according to table 9 */ + } + } + } else if (old_tlli != 0xffffffff && new_tlli != 0xffffffff) { + /* TLLI Change 8.3.2 */ + /* Both TLLI Old and TLLI New are assigned; use New when + * (re)transmitting. Accept toth Old and New on Rx */ + llme->old_tlli = llme->tlli; + llme->tlli = new_tlli; + llme->state = GPRS_LLMS_ASSIGNED; + } else if (old_tlli != 0xffffffff && new_tlli == 0xffffffff) { + /* TLLI Unassignment 8.3.3) */ + llme->tlli = llme->old_tlli = 0; + llme->state = GPRS_LLMS_UNASSIGNED; + for (i = 0; i < ARRAY_SIZE(llme->lle); i++) { + struct gprs_llc_lle *l = &llme->lle[i]; + l->state = GPRS_LLES_UNASSIGNED; + } + llme_free(llme); + } else + return -EINVAL; + + return 0; +} + +int gprs_llc_init(const char *cipher_plugin_path) +{ + return gprs_cipher_load(cipher_plugin_path); +} |