libosmo-gprs/src/sndcp/sndcp.c

1186 lines
35 KiB
C

/* GPRS SNDCP as per 3GPP TS 44.065 */
/*
* (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
*
* 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 <stdint.h>
#include <errno.h>
#include <arpa/inet.h>
#include <osmocom/core/talloc.h>
#include <osmocom/gprs/sndcp/sndcp.h>
#include <osmocom/gprs/sndcp/dcomp.h>
#include <osmocom/gprs/sndcp/pcomp.h>
#include <osmocom/gprs/sndcp/sndcp_prim.h>
#include <osmocom/gprs/sndcp/sndcp_private.h>
#include <osmocom/gprs/llc/llc_prim.h>
struct gprs_sndcp_ctx *g_sndcp_ctx;
struct gprs_sndcp_llc_params {
uint16_t n201_u;
uint16_t n201_i;
};
/* Section 8.9.9 LLC layer parameter default values */
static const struct gprs_sndcp_llc_params llc_default_params[GPRS_SNDCP_NUM_NSAPIS] = {
[1] = { .n201_u = 400, },
[2] = { .n201_u = 270, },
[3] = { .n201_u = 500, .n201_i = 1503, },
[5] = { .n201_u = 500, .n201_i = 1503, },
[7] = { .n201_u = 270, },
[8] = { .n201_u = 270, },
[9] = { .n201_u = 500, .n201_i = 1503, },
[11] = { .n201_u = 500, .n201_i = 1503, },
};
int osmo_gprs_sndcp_init(enum osmo_gprs_sndcp_location location)
{
if (g_sndcp_ctx)
talloc_free(g_sndcp_ctx);
g_sndcp_ctx = talloc_zero(NULL, struct gprs_sndcp_ctx);
g_sndcp_ctx->location = location;
INIT_LLIST_HEAD(&g_sndcp_ctx->snme_list);
return 0;
}
struct gprs_sndcp_mgmt_entity *gprs_sndcp_snme_alloc(uint32_t tlli)
{
struct gprs_sndcp_mgmt_entity *snme;
snme = talloc_zero(g_sndcp_ctx, struct gprs_sndcp_mgmt_entity);
if (!snme)
return NULL;
snme->tlli = tlli;
snme->comp.proto = gprs_sndcp_comp_alloc(snme);
snme->comp.data = gprs_sndcp_comp_alloc(snme);
llist_add(&snme->list, &g_sndcp_ctx->snme_list);
return snme;
}
static void gprs_sndcp_snme_free(struct gprs_sndcp_mgmt_entity *snme)
{
if (!snme)
return;
LOGSNME(snme, LOGL_DEBUG, "free()\n");
llist_del(&snme->list);
talloc_free(snme);
}
/* lookup SNDCP Management Entity based on TLLI */
struct gprs_sndcp_mgmt_entity *gprs_sndcp_snme_find_by_tlli(uint32_t tlli)
{
struct gprs_sndcp_mgmt_entity *snme;
llist_for_each_entry(snme, &g_sndcp_ctx->snme_list, list) {
if (snme->tlli == tlli)
return snme;
}
return NULL;
}
static void gprs_sndcp_snme_attach_sne(struct gprs_sndcp_mgmt_entity *snme, struct gprs_sndcp_entity *sne)
{
if (snme->sne[sne->nsapi])
osmo_panic("Trying to attach already existing SNDCP entity!\n");
snme->sne[sne->nsapi] = sne;
}
/* snme may become freed upon return (returns bool freed) */
static bool gprs_sndcp_snme_detach_sne(struct gprs_sndcp_mgmt_entity *snme, struct gprs_sndcp_entity *sne)
{
unsigned int i;
if (!snme->sne[sne->nsapi])
osmo_panic("Trying to detach already non-existing SNDCP entity!\n");
OSMO_ASSERT(sne->snme->sne[sne->nsapi] == sne);
snme->sne[sne->nsapi] = NULL;
for (i = 0; i < ARRAY_SIZE(snme->sne); i++) {
if (snme->sne[i])
return false;
}
LOGSNME(snme, LOGL_DEBUG, "No SNDCP Entities left activate, freeing SNME\n");
gprs_sndcp_snme_free(snme);
return true;
}
struct gprs_sndcp_entity *gprs_sndcp_sne_alloc(struct gprs_sndcp_mgmt_entity *snme, uint8_t llc_sapi, uint8_t nsapi)
{
struct gprs_sndcp_entity *sne;
sne = talloc_zero(g_sndcp_ctx, struct gprs_sndcp_entity);
if (!sne)
return NULL;
sne->llc_sapi = llc_sapi;
sne->nsapi = nsapi;
sne->defrag.timer.data = sne;
//sne->fqueue.timer.cb = FIXME;
sne->rx_state = GPRS_SNDCP_RX_S_FIRST;
INIT_LLIST_HEAD(&sne->defrag.frag_list);
sne->n201_u = llc_default_params[llc_sapi].n201_u;
sne->n201_i = llc_default_params[llc_sapi].n201_i;
sne->snme = snme;
gprs_sndcp_snme_attach_sne(snme, sne);
return sne;
}
void gprs_sndcp_sne_free(struct gprs_sndcp_entity *sne)
{
if (!sne)
return;
LOGSNE(sne, LOGL_DEBUG, "free()\n");
gprs_sndcp_snme_detach_sne(sne->snme, sne);
sne->snme = NULL;
talloc_free(sne);
}
struct gprs_sndcp_entity *gprs_sndcp_sne_by_dlci(uint32_t tlli, uint8_t llc_sapi)
{
struct gprs_sndcp_mgmt_entity *snme = gprs_sndcp_snme_find_by_tlli(tlli);
struct gprs_sndcp_entity *sne;
unsigned int i;
if (!snme)
return NULL;
for (i = 0; i < ARRAY_SIZE(snme->sne); i++) {
sne = snme->sne[i];
if (!sne)
continue;
if (sne->llc_sapi != llc_sapi)
continue;
return sne;
}
return NULL;
}
struct gprs_sndcp_entity *gprs_sndcp_sne_by_dlci_nsapi(uint32_t tlli, uint8_t llc_sapi, uint8_t nsapi)
{
struct gprs_sndcp_mgmt_entity *snme = gprs_sndcp_snme_find_by_tlli(tlli);
struct gprs_sndcp_entity *sne;
if (!snme)
return NULL;
sne = gprs_sndcp_snme_get_sne(snme, nsapi);
if (!sne || sne->llc_sapi != llc_sapi)
return NULL;
return sne;
}
int gprs_sndcp_sne_submit_llc_ll_establish_req(struct gprs_sndcp_entity *sne)
{
struct osmo_gprs_llc_prim *llc_prim_tx;
int rc;
llc_prim_tx = osmo_gprs_llc_prim_alloc_ll_establish_req(sne->snme->tlli, sne->llc_sapi,
sne->l3xid_req, sne->l3xid_req_len);
OSMO_ASSERT(llc_prim_tx);
rc = gprs_sndcp_prim_call_down_cb(llc_prim_tx);
return rc;
}
int gprs_sndcp_sne_submit_llc_ll_xid_req(struct gprs_sndcp_entity *sne)
{
struct osmo_gprs_llc_prim *llc_prim_tx;
int rc;
llc_prim_tx = osmo_gprs_llc_prim_alloc_ll_xid_req(sne->snme->tlli, sne->llc_sapi,
sne->l3xid_req, sne->l3xid_req_len);
OSMO_ASSERT(llc_prim_tx);
rc = gprs_sndcp_prim_call_down_cb(llc_prim_tx);
return rc;
}
int gprs_sndcp_sne_submit_llc_ll_unitdata_req(struct gprs_sndcp_entity *sne, uint8_t *data, unsigned int len)
{
struct osmo_gprs_llc_prim *llc_prim_tx;
int rc;
llc_prim_tx = osmo_gprs_llc_prim_alloc_ll_unitdata_req(sne->snme->tlli, sne->llc_sapi, data, len);
OSMO_ASSERT(llc_prim_tx);
llc_prim_tx->ll.unitdata_req.radio_prio = sne->radio_prio;
rc = gprs_sndcp_prim_call_down_cb(llc_prim_tx);
return rc;
}
int gprs_sndcp_sne_submit_sn_xid_cnf(struct gprs_sndcp_entity *sne)
{
struct osmo_gprs_sndcp_prim *sndcp_prim_tx;
int rc;
sndcp_prim_tx = gprs_sndcp_prim_alloc_sn_xid_cnf(sne->snme->tlli, sne->llc_sapi, sne->nsapi);
OSMO_ASSERT(sndcp_prim_tx);
rc = gprs_sndcp_prim_call_up_cb(sndcp_prim_tx);
return rc;
}
int gprs_sndcp_sne_submit_snsm_activate_rsp(struct gprs_sndcp_entity *sne)
{
struct osmo_gprs_sndcp_prim *sndcp_prim_tx;
int rc;
sndcp_prim_tx = gprs_sndcp_prim_alloc_snsm_activate_rsp(sne->snme->tlli, sne->nsapi);
OSMO_ASSERT(sndcp_prim_tx);
rc = gprs_sndcp_prim_call_snsm_cb(sndcp_prim_tx);
return rc;
}
/* Check if any compression parameters are set in the sgsn configuration */
static inline int any_pcomp_or_dcomp_active(const struct gprs_sndcp_ctx *sgsn)
{
#if 0
/* TODO: */
if (sgsn->cfg.pcomp_rfc1144.active || sgsn->cfg.pcomp_rfc1144.passive ||
sgsn->cfg.dcomp_v42bis.active || sgsn->cfg.dcomp_v42bis.passive)
return true;
//else
#endif
return false;
}
/* Enqueue a fragment into the defragment queue */
static int defrag_enqueue(struct gprs_sndcp_entity *sne, uint8_t seg_nr,
uint8_t *data, uint32_t data_len)
{
struct defrag_queue_entry *dqe;
dqe = talloc_zero(g_sndcp_ctx, struct defrag_queue_entry);
if (!dqe)
return -ENOMEM;
dqe->data = talloc_zero_size(dqe, data_len);
if (!dqe->data) {
talloc_free(dqe);
return -ENOMEM;
}
dqe->seg_nr = seg_nr;
dqe->data_len = data_len;
llist_add(&dqe->list, &sne->defrag.frag_list);
if (seg_nr > sne->defrag.highest_seg)
sne->defrag.highest_seg = seg_nr;
sne->defrag.seg_have |= (1 << seg_nr);
sne->defrag.tot_len += data_len;
memcpy(dqe->data, data, data_len);
return 0;
}
/* return if we have all segments of this N-PDU */
static int defrag_have_all_segments(const struct gprs_sndcp_entity *sne)
{
uint32_t seg_needed = 0;
unsigned int i;
/* create a bitmask of needed segments */
for (i = 0; i <= sne->defrag.highest_seg; i++)
seg_needed |= (1 << i);
if (seg_needed == sne->defrag.seg_have)
return 1;
return 0;
}
static struct defrag_queue_entry *defrag_get_seg(const struct gprs_sndcp_entity *sne,
uint32_t seg_nr)
{
struct defrag_queue_entry *dqe;
llist_for_each_entry(dqe, &sne->defrag.frag_list, list) {
if (dqe->seg_nr == seg_nr) {
llist_del(&dqe->list);
return dqe;
}
}
return NULL;
}
/* Returns talloced buffer containing decompressed data, NULL on error. */
static uint8_t *decompress_segment(struct gprs_sndcp_entity *sne, void *ctx,
const uint8_t *compressed_data, unsigned int compressed_data_len,
unsigned int *decompressed_data_len)
{
int rc;
uint8_t *expnd = NULL;
*decompressed_data_len = 0;
#if DEBUG_IP_PACKETS == 1
LOGSNE(sne, "\n");
LOGSNE(sne, ":::::::::::::::::::::::::::::::::::::::::::::::::::\n");
LOGSNE(sne, "===================================================\n");
#endif
expnd = talloc_zero_size(ctx, compressed_data_len * MAX_DATADECOMPR_FAC +
MAX_HDRDECOMPR_INCR);
memcpy(expnd, compressed_data, compressed_data_len);
/* Apply data decompression */
rc = gprs_sndcp_dcomp_expand(expnd, compressed_data_len, sne->defrag.dcomp,
sne->defrag.data);
if (rc < 0) {
LOGSNE(sne, LOGL_ERROR,
"Data decompression failed!\n");
talloc_free(expnd);
return NULL;
}
/* Apply header decompression */
rc = gprs_sndcp_pcomp_expand(expnd, rc, sne->defrag.pcomp, sne->defrag.proto);
if (rc < 0) {
LOGSNE(sne, LOGL_ERROR,
"TCP/IP Header decompression failed!\n");
talloc_free(expnd);
return NULL;
}
*decompressed_data_len = rc;
#if DEBUG_IP_PACKETS == 1
debug_ip_packet(expnd, *decompressed_data_len, 1, "defrag_segments()");
LOGSNE(sne, "===================================================\n");
LOGSNE(sne, ":::::::::::::::::::::::::::::::::::::::::::::::::::\n");
LOGSNE(sne, "\n");
#endif
return expnd;
}
/* Perform actual defragmentation and create an output packet */
static int defrag_segments(struct gprs_sndcp_entity *sne)
{
struct msgb *msg;
unsigned int seg_nr;
uint8_t *npdu;
unsigned int npdu_len;
int rc;
uint8_t *expnd = NULL;
struct osmo_gprs_sndcp_prim *sndcp_prim_tx;
LOGSNE(sne, LOGL_DEBUG, "TLLI=0x%08x NSAPI=%u: Defragment output PDU %u "
"num_seg=%u tot_len=%u\n", sne->snme->tlli, sne->nsapi,
sne->defrag.npdu, sne->defrag.highest_seg, sne->defrag.tot_len);
msg = msgb_alloc_headroom(sne->defrag.tot_len+256, 128, "SNDCP Defrag");
if (!msg)
return -ENOMEM;
/* FIXME: message headers + identifiers */
npdu = msg->data;
for (seg_nr = 0; seg_nr <= sne->defrag.highest_seg; seg_nr++) {
struct defrag_queue_entry *dqe;
uint8_t *data;
dqe = defrag_get_seg(sne, seg_nr);
if (!dqe) {
LOGSNE(sne, LOGL_ERROR, "Segment %u missing\n", seg_nr);
msgb_free(msg);
return -EIO;
}
/* actually append the segment to the N-PDU */
data = msgb_put(msg, dqe->data_len);
memcpy(data, dqe->data, dqe->data_len);
/* release memory for the fragment queue entry */
talloc_free(dqe);
}
npdu_len = sne->defrag.tot_len;
/* FIXME: cancel timer */
/* actually send the N-PDU to the SGSN core code, which then
* hands it off to the correct GTP tunnel + GGSN via gtp_data_req() */
/* Decompress packet */
if (any_pcomp_or_dcomp_active(g_sndcp_ctx)) {
expnd = decompress_segment(sne, msg, npdu, npdu_len, &npdu_len);
if (!expnd) {
rc = -EIO;
goto ret_free;
}
} else {
expnd = npdu;
}
/* Trigger SN-UNITDATA.ind to upper layers: */
sndcp_prim_tx = gprs_sndcp_prim_alloc_sn_unitdata_ind(sne->snme->tlli, sne->llc_sapi, sne->nsapi, expnd, npdu_len);
rc = gprs_sndcp_prim_call_up_cb(sndcp_prim_tx);
ret_free:
/* we must free the memory we allocated above; ownership is not transferred
* downwards in the call above */
msgb_free(msg);
return rc;
}
static int defrag_input(struct gprs_sndcp_entity *sne, uint8_t *hdr, unsigned int len)
{
struct sndcp_common_hdr *sch;
struct sndcp_udata_hdr *suh;
uint16_t npdu_num;
uint8_t *data;
int rc;
sch = (struct sndcp_common_hdr *) hdr;
if (sch->first) {
suh = (struct sndcp_udata_hdr *) (hdr + 1 + sizeof(struct sndcp_common_hdr));
} else
suh = (struct sndcp_udata_hdr *) (hdr + sizeof(struct sndcp_common_hdr));
data = (uint8_t *)suh + sizeof(struct sndcp_udata_hdr);
npdu_num = (suh->npdu_high << 8) | suh->npdu_low;
LOGSNE(sne, LOGL_DEBUG, "TLLI=0x%08x NSAPI=%u: Input PDU %u Segment %u "
"Length %u %s %s\n", sne->snme->tlli, sne->nsapi, npdu_num,
suh->seg_nr, len, sch->first ? "F " : "", sch->more ? "M" : "");
if (sch->first) {
/* first segment of a new packet. Discard all leftover fragments of
* previous packet */
if (!llist_empty(&sne->defrag.frag_list)) {
struct defrag_queue_entry *dqe, *dqe2;
LOGSNE(sne, LOGL_INFO, "TLLI=0x%08x NSAPI=%u: Dropping "
"SN-PDU %u due to insufficient segments (%04x)\n",
sne->snme->tlli, sne->nsapi, sne->defrag.npdu,
sne->defrag.seg_have);
llist_for_each_entry_safe(dqe, dqe2, &sne->defrag.frag_list, list) {
llist_del(&dqe->list);
talloc_free(dqe);
}
}
/* store the currently de-fragmented PDU number */
sne->defrag.npdu = npdu_num;
/* Re-set fragmentation state */
sne->defrag.no_more = sne->defrag.highest_seg = sne->defrag.seg_have = 0;
sne->defrag.tot_len = 0;
/* FIXME: (re)start timer */
}
if (sne->defrag.npdu != npdu_num) {
LOGSNE(sne, LOGL_INFO, "Segment for different SN-PDU "
"(%u != %u)\n", npdu_num, sne->defrag.npdu);
/* FIXME */
}
/* FIXME: check if seg_nr already exists */
/* make sure to subtract length of SNDCP header from 'len' */
rc = defrag_enqueue(sne, suh->seg_nr, data, len - (data - hdr));
if (rc < 0)
return rc;
if (!sch->more) {
/* this is suppsed to be the last segment of the N-PDU, but it
* might well be not the last to arrive */
sne->defrag.no_more = 1;
}
if (sne->defrag.no_more) {
/* we have already received the last segment before, let's check
* if all the previous segments exist */
if (defrag_have_all_segments(sne))
return defrag_segments(sne);
}
return 0;
}
/* Fragmenter state */
struct sndcp_frag_state {
uint8_t frag_nr;
struct msgb *msg; /* original message */
uint8_t *next_byte; /* first byte of next fragment */
struct gprs_sndcp_entity *sne;
};
/* returns '1' if there are more fragments to send, '0' if none */
static int gprs_sndcp_send_ud_frag(struct sndcp_frag_state *fs,
uint8_t pcomp, uint8_t dcomp)
{
struct gprs_sndcp_entity *sne = fs->sne;
struct sndcp_common_hdr *sch;
struct sndcp_comp_hdr *scomph;
struct sndcp_udata_hdr *suh;
struct msgb *fmsg;
unsigned int max_payload_len;
unsigned int len;
uint8_t *data;
int rc, more;
fmsg = msgb_alloc_headroom(sne->n201_u+256, 128, "SNDCP Frag");
if (!fmsg) {
msgb_free(fs->msg);
return -ENOMEM;
}
/* prepend common SNDCP header */
sch = (struct sndcp_common_hdr *) msgb_put(fmsg, sizeof(*sch));
sch->nsapi = sne->nsapi;
/* Set FIRST bit if we are the first fragment in a series */
if (fs->frag_nr == 0)
sch->first = 1;
sch->type = 1;
/* append the compression header for first fragment */
if (sch->first) {
scomph = (struct sndcp_comp_hdr *)
msgb_put(fmsg, sizeof(*scomph));
scomph->pcomp = pcomp;
scomph->dcomp = dcomp;
}
/* append the user-data header */
suh = (struct sndcp_udata_hdr *) msgb_put(fmsg, sizeof(*suh));
suh->npdu_low = sne->tx_npdu_nr & 0xff;
suh->npdu_high = (sne->tx_npdu_nr >> 8) & 0xf;
suh->seg_nr = fs->frag_nr % 0xf;
/* calculate remaining length to be sent */
len = (fs->msg->data + fs->msg->len) - fs->next_byte;
/* how much payload can we actually send via LLC? */
max_payload_len = sne->n201_u - (sizeof(*sch) + sizeof(*suh));
if (sch->first)
max_payload_len -= sizeof(*scomph);
/* check if we're exceeding the max */
if (len > max_payload_len)
len = max_payload_len;
/* copy the actual fragment data into our fmsg */
data = msgb_put(fmsg, len);
memcpy(data, fs->next_byte, len);
/* Increment fragment number and data pointer to next fragment */
fs->frag_nr++;
fs->next_byte += len;
/* determine if we have more fragemnts to send */
if ((fs->msg->data + fs->msg->len) <= fs->next_byte)
more = 0;
else
more = 1;
/* set the MORE bit of the SNDCP header accordingly */
sch->more = more;
/* Send down the stack SNDCP->LLC as LL-UNITDATA.req: */
rc = gprs_sndcp_sne_submit_llc_ll_unitdata_req(sne, fmsg->data, fmsg->len);
msgb_free(fmsg);
/* abort in case of error, do not advance frag_nr / next_byte */
if (rc < 0) {
msgb_free(fs->msg);
return rc;
}
if (!more) {
/* we've sent all fragments */
msgb_free(fs->msg);
memset(fs, 0, sizeof(*fs));
/* increment NPDU number for next frame */
sne->tx_npdu_nr = (sne->tx_npdu_nr + 1) % 0xfff;
return 0;
}
/* default: more fragments to send */
return 1;
}
/* 5.1.1.3 SN-UNITDATA.request */
int gprs_sndcp_sne_handle_sn_unitdata_req(struct gprs_sndcp_entity *sne, uint8_t *npdu, unsigned int npdu_len)
{
struct sndcp_common_hdr *sch;
struct sndcp_comp_hdr *scomph;
struct sndcp_udata_hdr *suh;
struct sndcp_frag_state fs;
uint8_t pcomp = 0;
uint8_t dcomp = 0;
int rc;
struct msgb *msg = msgb_alloc_headroom(npdu_len + 256, 128, "sndcp-tx");
memcpy(msgb_put(msg, npdu_len), npdu, npdu_len);
/* Compress packet */
#if DEBUG_IP_PACKETS == 1
LOGSNE(sne, "\n");
LOGSNE(sne, ":::::::::::::::::::::::::::::::::::::::::::::::::::\n");
LOGSNE(sne, "===================================================\n");
debug_ip_packet(npdu, npdu_len, 0, __func__ "()");
#endif
if (any_pcomp_or_dcomp_active(g_sndcp_ctx)) {
/* Apply header compression */
rc = gprs_sndcp_pcomp_compress(msg->data, msg->len, &pcomp,
sne->snme->comp.proto, sne->nsapi);
if (rc < 0) {
LOGSNE(sne, LOGL_ERROR, "TCP/IP Header compression failed!\n");
rc = -EIO;
goto free_ret;
}
/* Fixup pointer locations and sizes in message buffer to match
* the new, compressed buffer size */
msgb_get(msg, msg->len);
msgb_put(msg, rc);
/* Apply data compression */
rc = gprs_sndcp_dcomp_compress(msg->data, msg->len, &dcomp,
sne->snme->comp.data, sne->nsapi);
if (rc < 0) {
LOGSNE(sne, LOGL_ERROR, "Data compression failed!\n");
rc = -EIO;
goto free_ret;
}
/* Fixup pointer locations and sizes in message buffer to match
* the new, compressed buffer size */
msgb_get(msg, msg->len);
msgb_put(msg, rc);
}
#if DEBUG_IP_PACKETS == 1
LOGSNE(sne, "===================================================\n");
DLOGSNE(sne, ":::::::::::::::::::::::::::::::::::::::::::::::::::\n");
LOGSNE(sne, "\n");
#endif
/* Check if we need to fragment this N-PDU into multiple SN-PDUs */
if (msg->len > sne->n201_u -
(sizeof(*sch) + sizeof(*suh) + sizeof(*scomph))) {
/* initialize the fragmenter state */
fs.msg = msg;
fs.frag_nr = 0;
fs.next_byte = msg->data;
fs.sne = sne;
/* call function to generate and send fragments until all
* of the N-PDU has been sent */
while (1) {
int rc = gprs_sndcp_send_ud_frag(&fs, pcomp, dcomp);
if (rc == 0)
return 0;
if (rc < 0)
return rc;
}
/* not reached */
return 0;
}
/* this is the non-fragmenting case where we only build 1 SN-PDU */
/* prepend the user-data header */
suh = (struct sndcp_udata_hdr *) msgb_push(msg, sizeof(*suh));
suh->npdu_low = sne->tx_npdu_nr & 0xff;
suh->npdu_high = (sne->tx_npdu_nr >> 8) & 0xf;
suh->seg_nr = 0;
sne->tx_npdu_nr = (sne->tx_npdu_nr + 1) % 0xfff;
scomph = (struct sndcp_comp_hdr *) msgb_push(msg, sizeof(*scomph));
scomph->pcomp = pcomp;
scomph->dcomp = dcomp;
/* prepend common SNDCP header */
sch = (struct sndcp_common_hdr *) msgb_push(msg, sizeof(*sch));
sch->first = 1;
sch->type = 1;
sch->nsapi = sne->nsapi;
/* Send down the stack SNDCP->LLC as LL-UNITDATA.req: */
rc = gprs_sndcp_sne_submit_llc_ll_unitdata_req(sne, msg->data, msg->len);
free_ret:
msgb_free(msg);
return rc;
}
/* Generate SNDCP-XID message (5.1.1.5 SN-XID.request) */
static int gprs_sndcp_sne_gen_sndcp_xid(struct gprs_sndcp_entity *sne, uint8_t *bytes, int bytes_len, const struct osmo_gprs_sndcp_prim *sndcp_prim)
{
int entity = 0;
LLIST_HEAD(comp_fields);
struct gprs_sndcp_pcomp_rfc1144_params rfc1144_params;
struct gprs_sndcp_comp_field rfc1144_comp_field;
struct gprs_sndcp_dcomp_v42bis_params v42bis_params;
struct gprs_sndcp_comp_field v42bis_comp_field;
memset(&rfc1144_comp_field, 0, sizeof(struct gprs_sndcp_comp_field));
memset(&v42bis_comp_field, 0, sizeof(struct gprs_sndcp_comp_field));
/* Setup rfc1144 */
if (sndcp_prim->sn.xid_req.pcomp_rfc1144.active) {
rfc1144_params.nsapi[0] = sne->nsapi;
rfc1144_params.nsapi_len = 1;
rfc1144_params.s01 = sndcp_prim->sn.xid_req.pcomp_rfc1144.s01;
rfc1144_comp_field.p = 1;
rfc1144_comp_field.entity = entity;
rfc1144_comp_field.algo.pcomp = RFC_1144;
rfc1144_comp_field.comp[RFC1144_PCOMP1] = 1;
rfc1144_comp_field.comp[RFC1144_PCOMP2] = 2;
rfc1144_comp_field.comp_len = RFC1144_PCOMP_NUM;
rfc1144_comp_field.rfc1144_params = &rfc1144_params;
entity++;
llist_add(&rfc1144_comp_field.list, &comp_fields);
}
/* Setup V.42bis */
if (sndcp_prim->sn.xid_req.dcomp_v42bis.active) {
v42bis_params.nsapi[0] = sne->nsapi;
v42bis_params.nsapi_len = 1;
v42bis_params.p0 = sndcp_prim->sn.xid_req.dcomp_v42bis.p0;
v42bis_params.p1 = sndcp_prim->sn.xid_req.dcomp_v42bis.p1;
v42bis_params.p2 = sndcp_prim->sn.xid_req.dcomp_v42bis.p2;
v42bis_comp_field.p = 1;
v42bis_comp_field.entity = entity;
v42bis_comp_field.algo.dcomp = V42BIS;
v42bis_comp_field.comp[V42BIS_DCOMP1] = 1;
v42bis_comp_field.comp_len = V42BIS_DCOMP_NUM;
v42bis_comp_field.v42bis_params = &v42bis_params;
entity++;
llist_add(&v42bis_comp_field.list, &comp_fields);
}
LOGSNE(sne, LOGL_DEBUG, "SN-XID.req comp_fields:\n");
gprs_sndcp_dump_comp_fields(&comp_fields, LOGL_DEBUG);
/* Do not attempt to compile anything if there is no data in the list */
if (llist_empty(&comp_fields))
return 0;
/* Compile bytestream */
return gprs_sndcp_compile_xid(bytes, bytes_len, &comp_fields,
DEFAULT_SNDCP_VERSION);
}
/* 5.1.1.5 SN-XID.request */
int gprs_sndcp_sne_handle_sn_xid_req(struct gprs_sndcp_entity *sne, const struct osmo_gprs_sndcp_prim *sndcp_prim)
{
int rc;
uint8_t l3params[1024];
/* Wipe off all compression entities and their states to
* get rid of possible leftovers from a previous session */
gprs_sndcp_comp_free(sne->snme->comp.proto);
gprs_sndcp_comp_free(sne->snme->comp.data);
sne->snme->comp.proto = gprs_sndcp_comp_alloc(sne->snme);
sne->snme->comp.data = gprs_sndcp_comp_alloc(sne->snme);
/* Generate compression parameter bytestream */
sne->l3xid_req_len = gprs_sndcp_sne_gen_sndcp_xid(sne, l3params, sizeof(l3params), sndcp_prim);
if (sne->l3xid_req_len > 0) {
talloc_free(sne->l3xid_req);
sne->l3xid_req = talloc_size(sne, sne->l3xid_req_len);
memcpy(sne->l3xid_req, l3params, sne->l3xid_req_len);
} else {
talloc_free(sne->l3xid_req);
sne->l3xid_req = NULL;
}
sne->xid_req_in_transit_orig_sn_xid_req = true;
rc = gprs_sndcp_sne_submit_llc_ll_xid_req(sne);
return rc;
}
/* 5.1.1.7 SN-XID.response */
/* FIXME: This is currently uses comp_fields stored from gprs_sndcp_snme_handle_llc_ll_xid_ind().
* It should use info provided by upper layers in the prim instead */
int gprs_sndcp_sne_handle_sn_xid_rsp(struct gprs_sndcp_entity *sne, const struct osmo_gprs_sndcp_prim *sndcp_prim)
{
struct osmo_gprs_llc_prim *llc_prim_tx;
struct msgb *msg;
int rc;
if (!sne->l3_xid_comp_fields_req_from_peer) {
LOGSNE(sne, LOGL_DEBUG, "SN-XID.rsp origianted comp_fields not found!\n");
return -EINVAL;
}
LOGSNE(sne, LOGL_DEBUG, "SN-XID.rsp comp_fields:\n");
gprs_sndcp_dump_comp_fields(sne->l3_xid_comp_fields_req_from_peer, LOGL_DEBUG);
llc_prim_tx = osmo_gprs_llc_prim_alloc_ll_xid_resp(sne->snme->tlli, sne->llc_sapi, NULL, 256);
OSMO_ASSERT(llc_prim_tx);
msg = llc_prim_tx->oph.msg;
llc_prim_tx->ll.l3_pdu = msg->tail;
/* Compile modified SNDCP-XID bytes */
rc = gprs_sndcp_compile_xid(msg->tail,
msgb_tailroom(msg),
sne->l3_xid_comp_fields_req_from_peer, 0);
if (rc < 0) {
msgb_free(msg);
return -EINVAL;
}
msgb_put(msg, rc);
llc_prim_tx->ll.l3_pdu_len = rc;
/* Transmit LL-XID.rsp to lower layers (LLC): */
rc = gprs_sndcp_prim_call_down_cb(llc_prim_tx);
return rc;
}
/* Section 5.1.2.17 LL-UNITDATA.ind, see osmo-sgsn sndcp_llunitdata_ind() */
int gprs_sndcp_sne_handle_llc_ll_unitdata_ind(struct gprs_sndcp_entity *sne, struct sndcp_common_hdr *sch, uint16_t len)
{
struct sndcp_comp_hdr *scomph = NULL;
struct sndcp_udata_hdr *suh;
uint8_t *npdu;
uint16_t npdu_num __attribute__((unused));
int npdu_len;
int rc = 0;
uint8_t *expnd = NULL;
uint8_t *hdr = (uint8_t *)sch;
struct osmo_gprs_sndcp_prim *sndcp_prim_tx;
if (sch->first) {
scomph = (struct sndcp_comp_hdr *) (hdr + 1);
suh = (struct sndcp_udata_hdr *) (hdr + 1 + sizeof(struct sndcp_common_hdr));
} else
suh = (struct sndcp_udata_hdr *) (hdr + sizeof(struct sndcp_common_hdr));
if (sch->type == 0) {
LOGSNE(sne, LOGL_ERROR, "SN-DATA PDU at unitdata_ind() function\n");
return -EINVAL;
}
if (len < sizeof(*sch) + sizeof(*suh)) {
LOGSNE(sne, LOGL_ERROR, "SN-UNITDATA PDU too short (%u)\n", len);
return -EIO;
}
if (scomph) {
sne->defrag.pcomp = scomph->pcomp;
sne->defrag.dcomp = scomph->dcomp;
sne->defrag.proto = sne->snme->comp.proto;
sne->defrag.data = sne->snme->comp.data;
}
/* any non-first segment is by definition something to defragment
* as is any segment that tells us there are more segments */
if (!sch->first || sch->more)
return defrag_input(sne, hdr, len);
npdu_num = (suh->npdu_high << 8) | suh->npdu_low;
npdu = (uint8_t *)suh + sizeof(*suh);
npdu_len = (hdr + len) - npdu;
if (npdu_len <= 0) {
LOGSNE(sne, LOGL_ERROR, "Short SNDCP N-PDU: %d\n", npdu_len);
return -EIO;
}
/* actually send the N-PDU to the SGSN core code, which then
* hands it off to the correct GTP tunnel + GGSN via gtp_data_req() */
/* Decompress packet */
if (any_pcomp_or_dcomp_active(g_sndcp_ctx)) {
expnd = decompress_segment(sne, g_sndcp_ctx, npdu, npdu_len, (unsigned int *)&npdu_len);
if (!expnd) {
rc = -EIO;
goto ret_free;
}
} else {
expnd = npdu;
}
/* Trigger SN-UNITDATA.ind to upper layers: */
sndcp_prim_tx = gprs_sndcp_prim_alloc_sn_unitdata_ind(sne->snme->tlli, sne->llc_sapi, sne->nsapi, expnd, npdu_len);
rc = gprs_sndcp_prim_call_up_cb(sndcp_prim_tx);
ret_free:
if (any_pcomp_or_dcomp_active(g_sndcp_ctx))
talloc_free(expnd);
return rc;
}
/* Handle header compression entities */
static int gprs_sndcp_snme_handle_pcomp_entities(struct gprs_sndcp_mgmt_entity *snme,
uint32_t sapi,
struct gprs_sndcp_comp_field *comp_field)
{
/* Note: This functions also transforms the comp_field into its
* echo form (strips comp values, resets propose bit etc...)
* the processed comp_fields can then be sent back as XID-
* Response without further modification. */
/* Delete propose bit */
comp_field->p = 0;
/* Process proposed parameters */
switch (comp_field->algo.pcomp) {
case RFC_1144:
if (g_sndcp_ctx->cfg.pcomp_rfc1144_passive_accept
&& comp_field->rfc1144_params->nsapi_len > 0) {
LOGSNME(snme, LOGL_DEBUG, "Accepting RFC1144 header compression...\n");
gprs_sndcp_comp_add(snme, snme->comp.proto, comp_field);
} else {
LOGSNME(snme, LOGL_DEBUG, "Rejecting RFC1144 header compression...\n");
gprs_sndcp_comp_delete(snme->comp.proto, comp_field->entity);
comp_field->rfc1144_params->nsapi_len = 0;
}
break;
case RFC_2507:
/* RFC 2507 is not yet supported,
* so we set applicable nsapis to zero */
LOGSNME(snme, LOGL_DEBUG, "Rejecting RFC2507 header compression...\n");
comp_field->rfc2507_params->nsapi_len = 0;
gprs_sndcp_comp_delete(snme->comp.proto, comp_field->entity);
break;
case ROHC:
/* ROHC is not yet supported,
* so we set applicable nsapis to zero */
LOGSNME(snme, LOGL_DEBUG, "Rejecting ROHC header compression...\n");
comp_field->rohc_params->nsapi_len = 0;
gprs_sndcp_comp_delete(snme->comp.proto, comp_field->entity);
break;
}
return 0;
}
/* Hanle data compression entities */
static int gprs_sndcp_snme_handle_dcomp_entities(struct gprs_sndcp_mgmt_entity *snme,
uint32_t sapi,
struct gprs_sndcp_comp_field *comp_field)
{
/* See note in handle_pcomp_entities() */
/* Delete propose bit */
comp_field->p = 0;
/* Process proposed parameters */
switch (comp_field->algo.dcomp) {
case V42BIS:
if (g_sndcp_ctx->cfg.dcomp_v42bis_passive_accept &&
comp_field->v42bis_params->nsapi_len > 0) {
LOGSNME(snme, LOGL_DEBUG, "Accepting V.42bis data compression...\n");
gprs_sndcp_comp_add(snme, snme->comp.data, comp_field);
} else {
LOGSNME(snme, LOGL_DEBUG, "Rejecting V.42bis data compression...\n");
gprs_sndcp_comp_delete(snme->comp.data, comp_field->entity);
comp_field->v42bis_params->nsapi_len = 0;
}
break;
case V44:
/* V44 is not yet supported,
* so we set applicable nsapis to zero */
LOGSNME(snme, LOGL_DEBUG, "Rejecting V.44 data compression...\n");
comp_field->v44_params->nsapi_len = 0;
gprs_sndcp_comp_delete(snme->comp.data, comp_field->entity);
break;
}
return 0;
}
/* 5.1.2.10 SN-XID.indication */
int gprs_sndcp_snme_handle_llc_ll_xid_ind(struct gprs_sndcp_mgmt_entity *snme, uint32_t sapi,
uint16_t n201_u, uint16_t n201_i,
uint8_t *l3params, unsigned int l3params_len)
{
int rc;
int compclass;
int version;
struct llist_head *comp_fields;
struct gprs_sndcp_comp_field *comp_field;
struct gprs_sndcp_entity *sne = NULL;
struct osmo_gprs_sndcp_prim *sndcp_prim_tx;
unsigned int i;
for (i = 0; i < ARRAY_SIZE(snme->sne); i++) {
struct gprs_sndcp_entity *sne_i = snme->sne[i];
if (!sne_i)
continue;
if (sne_i->llc_sapi != sapi)
continue;
LOGSNE(sne_i, LOGL_DEBUG, "LL-XID.ind: Found SNE SAPI=%u\n", sapi);
sne = sne_i;
break;
}
if (!sne) {
LOGSNME(snme, LOGL_ERROR, "LL-XID.ind: No SNDCP entity found having sent a LL-XID.ind (SAPI=%u)\n", sapi);
return -EINVAL;
}
sne->n201_u = n201_u;
sne->n201_i = n201_i;
/* Some phones send zero byte length SNDCP frames
* and do require a confirmation response. */
if (l3params_len == 0) {
/* TS 44.065 6.8: "If the SNDCP entity receives an LL-XID.indication without
* an SNDCP XID block, it shall not respond with the LL-XID.response primitive."
*/
return 0;
}
/* Parse SNDCP-CID XID-Field */
comp_fields = gprs_sndcp_parse_xid(&version, sne,
l3params,
l3params_len,
NULL);
if (!comp_fields) {
LOGSNME(snme, LOGL_NOTICE, "LL-XID.ind: parse failed\n");
return -EINVAL;
}
/* Handle compression entities */
LOGSNME(snme, LOGL_DEBUG, "LL-XID.cnf requested comp_fields:\n");
gprs_sndcp_dump_comp_fields(comp_fields, LOGL_DEBUG);
llist_for_each_entry(comp_field, comp_fields, list) {
compclass = gprs_sndcp_get_compression_class(comp_field);
if (compclass == SNDCP_XID_PROTOCOL_COMPRESSION)
rc = gprs_sndcp_snme_handle_pcomp_entities(snme, sapi, comp_field);
else if (compclass == SNDCP_XID_DATA_COMPRESSION)
rc = gprs_sndcp_snme_handle_dcomp_entities(snme, sapi, comp_field);
else {
gprs_sndcp_comp_delete(snme->comp.proto, comp_field->entity);
gprs_sndcp_comp_delete(snme->comp.data, comp_field->entity);
rc = 0;
}
if (rc < 0) {
talloc_free(comp_fields);
return -EINVAL;
}
}
TALLOC_FREE(sne->l3_xid_comp_fields_req_from_peer);
sne->l3_xid_comp_fields_req_from_peer = comp_fields;
/* Trigger SN-XID.ind to upper layers: */
sndcp_prim_tx = gprs_sndcp_prim_alloc_sn_xid_ind(sne->snme->tlli, sne->llc_sapi, sne->nsapi);
rc = gprs_sndcp_prim_call_up_cb(sndcp_prim_tx);
return rc;
}
/* 5.1.2.12 SN-XID.confirm
* (See also: TS 144 065, Section 6.8 XID parameter negotiation)
*/
int gprs_sndcp_snme_handle_llc_ll_xid_cnf(struct gprs_sndcp_mgmt_entity *snme, uint32_t sapi,
uint16_t n201_u, uint16_t n201_i,
uint8_t *l3params, unsigned int l3params_len)
{
/* Note: This function handles an incoming SNDCP-XID confirmation.
* Since the confirmation fields may lack important parameters we
* will reconstruct these missing fields using the original request
* we have sent. After that we will create (or delete) the
* compression entities */
struct llist_head *comp_fields_req;
struct llist_head *comp_fields_conf;
struct gprs_sndcp_comp_field *comp_field;
int rc = 0;
int compclass;
unsigned int i;
struct gprs_sndcp_entity *sne = NULL;
/* We need both, the confirmation that is sent back by the ms,
* and the original request we have sent. If one of this is missing
* we can not process the confirmation, the caller must check if
* request and confirmation fields are available. */
for (i = 0; i < ARRAY_SIZE(snme->sne); i++) {
struct gprs_sndcp_entity *sne_i = snme->sne[i];
if (!sne_i)
continue;
if (sne_i->llc_sapi != sapi)
continue;
LOGSNE(sne_i, LOGL_DEBUG, "LL-XID.cnf: Found SNE SAPI=%u\n", sapi);
sne = sne_i;
break;
}
if (!sne) {
LOGSNME(snme, LOGL_ERROR, "LL-XID.cnf: No SNDCP entity found having sent a LL-XID.req (SAPI=%u)\n", sapi);
return -EINVAL;
}
sne->n201_u = n201_u;
sne->n201_i = n201_i;
if (sne->l3xid_req && sne->l3xid_req_len > 0) {
/* Parse SNDCP-CID XID-Field */
comp_fields_req = gprs_sndcp_parse_xid(NULL, sne, sne->l3xid_req, sne->l3xid_req_len, NULL);
if (!comp_fields_req)
return -EINVAL;
/* Parse SNDCP-CID XID-Field */
comp_fields_conf = gprs_sndcp_parse_xid(NULL, sne, l3params, l3params_len, comp_fields_req);
if (!comp_fields_conf) {
talloc_free(comp_fields_req);
return -EINVAL;
}
LOGSNDCP(LOGL_DEBUG, "LL-XID.cnf response comp_fields:\n");
gprs_sndcp_dump_comp_fields(comp_fields_conf, LOGL_DEBUG);
/* Handle compression entities */
llist_for_each_entry(comp_field, comp_fields_conf, list) {
compclass = gprs_sndcp_get_compression_class(comp_field);
if (compclass == SNDCP_XID_PROTOCOL_COMPRESSION)
rc = gprs_sndcp_snme_handle_pcomp_entities(snme, sapi, comp_field);
else if (compclass == SNDCP_XID_DATA_COMPRESSION)
rc = gprs_sndcp_snme_handle_dcomp_entities(snme, sapi, comp_field);
else {
gprs_sndcp_comp_delete(snme->comp.proto, comp_field->entity);
gprs_sndcp_comp_delete(snme->comp.data, comp_field->entity);
rc = 0;
}
if (rc < 0)
break;
}
talloc_free(comp_fields_req);
talloc_free(comp_fields_conf);
}
/* Originated by SNSM-ACTIVATE.ind: */
if (sne->xid_req_in_transit_orig_snsm_activate_ind) {
sne->xid_req_in_transit_orig_snsm_activate_ind = false;
rc = gprs_sndcp_sne_submit_snsm_activate_rsp(sne);
}
/* Originated by SN-XID.req: */
if (sne->xid_req_in_transit_orig_sn_xid_req) {
sne->xid_req_in_transit_orig_sn_xid_req = false;
rc = gprs_sndcp_sne_submit_sn_xid_cnf(sne);
}
/* TODO:
* gprs_sndcp_prim_alloc_sn_xid_cnf() SN-XID-CNF if SN-XID-REQ pending
* gprs_sndcp_prim_alloc_snsm_activate_rsp() SNSM-ACTIVATE-RSP if SNSM-ACTIVATE-IND pending
*/
return rc;
}