diff options
| -rw-r--r-- | openbsc/include/openbsc/gprs_bssgp.h | 232 | ||||
| -rw-r--r-- | openbsc/include/openbsc/gprs_ns.h | 232 | ||||
| -rw-r--r-- | openbsc/include/openbsc/gprs_ns_frgre.h | 6 | ||||
| -rw-r--r-- | openbsc/src/gprs/gprs_bssgp.c | 852 | ||||
| -rw-r--r-- | openbsc/src/gprs/gprs_bssgp_util.c | 120 | ||||
| -rw-r--r-- | openbsc/src/gprs/gprs_bssgp_vty.c | 178 | ||||
| -rw-r--r-- | openbsc/src/gprs/gprs_ns.c | 989 | ||||
| -rw-r--r-- | openbsc/src/gprs/gprs_ns_frgre.c | 305 | ||||
| -rw-r--r-- | openbsc/src/gprs/gprs_ns_vty.c | 571 | 
9 files changed, 3485 insertions, 0 deletions
| diff --git a/openbsc/include/openbsc/gprs_bssgp.h b/openbsc/include/openbsc/gprs_bssgp.h new file mode 100644 index 00000000..e432cf75 --- /dev/null +++ b/openbsc/include/openbsc/gprs_bssgp.h @@ -0,0 +1,232 @@ +#ifndef _GPRS_BSSGP_H +#define _GPRS_BSSGP_H + +#include <stdint.h> + +/* Section 5.4.1 */ +#define BVCI_SIGNALLING	0x0000 +#define BVCI_PTM	0x0001 + +/* Section 11.3.26 / Table 11.27 */ +enum bssgp_pdu_type { +	/* PDUs between RL and BSSGP SAPs */ +	BSSGP_PDUT_DL_UNITDATA		= 0x00, +	BSSGP_PDUT_UL_UNITDATA		= 0x01, +	BSSGP_PDUT_RA_CAPABILITY	= 0x02, +	BSSGP_PDUT_PTM_UNITDATA		= 0x03, +	/* PDUs between GMM SAPs */ +	BSSGP_PDUT_PAGING_PS		= 0x06, +	BSSGP_PDUT_PAGING_CS		= 0x07, +	BSSGP_PDUT_RA_CAPA_UDPATE	= 0x08, +	BSSGP_PDUT_RA_CAPA_UPDATE_ACK	= 0x09, +	BSSGP_PDUT_RADIO_STATUS		= 0x0a, +	BSSGP_PDUT_SUSPEND		= 0x0b, +	BSSGP_PDUT_SUSPEND_ACK		= 0x0c, +	BSSGP_PDUT_SUSPEND_NACK		= 0x0d, +	BSSGP_PDUT_RESUME		= 0x0e, +	BSSGP_PDUT_RESUME_ACK		= 0x0f, +	BSSGP_PDUT_RESUME_NACK		= 0x10, +	/* PDus between NM SAPs */ +	BSSGP_PDUT_BVC_BLOCK		= 0x20, +	BSSGP_PDUT_BVC_BLOCK_ACK	= 0x21, +	BSSGP_PDUT_BVC_RESET		= 0x22, +	BSSGP_PDUT_BVC_RESET_ACK	= 0x23, +	BSSGP_PDUT_BVC_UNBLOCK		= 0x24, +	BSSGP_PDUT_BVC_UNBLOCK_ACK	= 0x25, +	BSSGP_PDUT_FLOW_CONTROL_BVC	= 0x26, +	BSSGP_PDUT_FLOW_CONTROL_BVC_ACK	= 0x27, +	BSSGP_PDUT_FLOW_CONTROL_MS	= 0x28, +	BSSGP_PDUT_FLOW_CONTROL_MS_ACK	= 0x29, +	BSSGP_PDUT_FLUSH_LL		= 0x2a, +	BSSGP_PDUT_FLUSH_LL_ACK		= 0x2b, +	BSSGP_PDUT_LLC_DISCARD		= 0x2c, +	BSSGP_PDUT_SGSN_INVOKE_TRACE	= 0x40, +	BSSGP_PDUT_STATUS		= 0x41, +	/* PDUs between PFM SAP's */ +	BSSGP_PDUT_DOWNLOAD_BSS_PFC	= 0x50, +	BSSGP_PDUT_CREATE_BSS_PFC	= 0x51, +	BSSGP_PDUT_CREATE_BSS_PFC_ACK	= 0x52, +	BSSGP_PDUT_CREATE_BSS_PFC_NACK	= 0x53, +	BSSGP_PDUT_MODIFY_BSS_PFC	= 0x54, +	BSSGP_PDUT_MODIFY_BSS_PFC_ACK	= 0x55, +	BSSGP_PDUT_DELETE_BSS_PFC	= 0x56, +	BSSGP_PDUT_DELETE_BSS_PFC_ACK	= 0x57, +}; + +/* Section 10.2.1 and 10.2.2 */ +struct bssgp_ud_hdr { +	uint8_t pdu_type; +	uint32_t tlli; +	uint8_t qos_profile[3]; +	uint8_t data[0];	/* TLV's */ +} __attribute__((packed)); + +struct bssgp_normal_hdr { +	uint8_t pdu_type; +	uint8_t data[0];	/* TLV's */ +}; + +enum bssgp_iei_type { +	BSSGP_IE_ALIGNMENT		= 0x00, +	BSSGP_IE_BMAX_DEFAULT_MS	= 0x01, +	BSSGP_IE_BSS_AREA_ID		= 0x02, +	BSSGP_IE_BUCKET_LEAK_RATE	= 0x03, +	BSSGP_IE_BVCI			= 0x04, +	BSSGP_IE_BVC_BUCKET_SIZE	= 0x05, +	BSSGP_IE_BVC_MEASUREMENT	= 0x06, +	BSSGP_IE_CAUSE			= 0x07, +	BSSGP_IE_CELL_ID		= 0x08, +	BSSGP_IE_CHAN_NEEDED		= 0x09, +	BSSGP_IE_DRX_PARAMS		= 0x0a, +	BSSGP_IE_EMLPP_PRIO		= 0x0b, +	BSSGP_IE_FLUSH_ACTION		= 0x0c, +	BSSGP_IE_IMSI			= 0x0d, +	BSSGP_IE_LLC_PDU		= 0x0e, +	BSSGP_IE_LLC_FRAMES_DISCARDED	= 0x0f, +	BSSGP_IE_LOCATION_AREA		= 0x10, +	BSSGP_IE_MOBILE_ID		= 0x11, +	BSSGP_IE_MS_BUCKET_SIZE		= 0x12, +	BSSGP_IE_MS_RADIO_ACCESS_CAP	= 0x13, +	BSSGP_IE_OMC_ID			= 0x14, +	BSSGP_IE_PDU_IN_ERROR		= 0x15, +	BSSGP_IE_PDU_LIFETIME		= 0x16, +	BSSGP_IE_PRIORITY		= 0x17, +	BSSGP_IE_QOS_PROFILE		= 0x18, +	BSSGP_IE_RADIO_CAUSE		= 0x19, +	BSSGP_IE_RA_CAP_UPD_CAUSE	= 0x1a, +	BSSGP_IE_ROUTEING_AREA		= 0x1b, +	BSSGP_IE_R_DEFAULT_MS		= 0x1c, +	BSSGP_IE_SUSPEND_REF_NR		= 0x1d, +	BSSGP_IE_TAG			= 0x1e, +	BSSGP_IE_TLLI			= 0x1f, +	BSSGP_IE_TMSI			= 0x20, +	BSSGP_IE_TRACE_REFERENC		= 0x21, +	BSSGP_IE_TRACE_TYPE		= 0x22, +	BSSGP_IE_TRANSACTION_ID		= 0x23, +	BSSGP_IE_TRIGGER_ID		= 0x24, +	BSSGP_IE_NUM_OCT_AFF		= 0x25, +	BSSGP_IE_LSA_ID_LIST		= 0x26, +	BSSGP_IE_LSA_INFORMATION	= 0x27, +	BSSGP_IE_PACKET_FLOW_ID		= 0x28, +	BSSGP_IE_PACKET_FLOW_TIMER	= 0x29, +	BSSGP_IE_AGG_BSS_QOS_PROFILE	= 0x3a, +	BSSGP_IE_FEATURE_BITMAP		= 0x3b, +	BSSGP_IE_BUCKET_FULL_RATIO	= 0x3c, +	BSSGP_IE_SERVICE_UTRAN_CCO	= 0x3d, +}; + +/* Section 11.3.8 / Table 11.10: Cause coding */ +enum gprs_bssgp_cause { +	BSSGP_CAUSE_PROC_OVERLOAD	= 0x00, +	BSSGP_CAUSE_EQUIP_FAIL		= 0x01, +	BSSGP_CAUSE_TRASIT_NET_FAIL	= 0x02, +	BSSGP_CAUSE_CAPA_GREATER_0KPBS	= 0x03, +	BSSGP_CAUSE_UNKNOWN_MS		= 0x04, +	BSSGP_CAUSE_UNKNOWN_BVCI	= 0x05, +	BSSGP_CAUSE_CELL_TRAF_CONG	= 0x06, +	BSSGP_CAUSE_SGSN_CONG		= 0x07, +	BSSGP_CAUSE_OML_INTERV		= 0x08, +	BSSGP_CAUSE_BVCI_BLOCKED	= 0x09, +	BSSGP_CAUSE_PFC_CREATE_FAIL	= 0x0a, +	BSSGP_CAUSE_SEM_INCORR_PDU	= 0x20, +	BSSGP_CAUSE_INV_MAND_INF	= 0x21, +	BSSGP_CAUSE_MISSING_MAND_IE	= 0x22, +	BSSGP_CAUSE_MISSING_COND_IE	= 0x23, +	BSSGP_CAUSE_UNEXP_COND_IE	= 0x24, +	BSSGP_CAUSE_COND_IE_ERR		= 0x25, +	BSSGP_CAUSE_PDU_INCOMP_STATE	= 0x26, +	BSSGP_CAUSE_PROTO_ERR_UNSPEC	= 0x27, +	BSSGP_CAUSE_PDU_INCOMP_FEAT	= 0x28, +}; + +/* Our implementation */ + +/* gprs_bssgp_util.c */ +extern struct gprs_ns_inst *bssgp_nsi; +struct msgb *bssgp_msgb_alloc(void); +const char *bssgp_cause_str(enum gprs_bssgp_cause cause); +/* Transmit a simple response such as BLOCK/UNBLOCK/RESET ACK/NACK */ +int bssgp_tx_simple_bvci(uint8_t pdu_type, uint16_t nsei, +			 uint16_t bvci, uint16_t ns_bvci); +/* Chapter 10.4.14: Status */ +int bssgp_tx_status(uint8_t cause, uint16_t *bvci, struct msgb *orig_msg); + +/* gprs_bssgp.c */ + +#define BVC_S_BLOCKED	0x0001 + +/* The per-BTS context that we keep on the SGSN side of the BSSGP link */ +struct bssgp_bvc_ctx { +	struct llist_head list; + +	/* parsed RA ID and Cell ID of the remote BTS */ +	struct gprs_ra_id ra_id; +	uint16_t cell_id; + +	/* NSEI and BVCI of underlying Gb link.  Together they +	 * uniquely identify a link to a BTS (5.4.4) */ +	uint16_t bvci; +	uint16_t nsei; + +	uint32_t state; + +	struct rate_ctr_group *ctrg; + +	/* we might want to add this as a shortcut later, avoiding the NSVC +	 * lookup for every packet, similar to a routing cache */ +	//struct gprs_nsvc *nsvc; +}; +extern struct llist_head bssgp_bvc_ctxts; +/* Find a BTS Context based on parsed RA ID and Cell ID */ +struct bssgp_bvc_ctx *btsctx_by_raid_cid(const struct gprs_ra_id *raid, uint16_t cid); +/* Find a BTS context based on BVCI+NSEI tuple */ +struct bssgp_bvc_ctx *btsctx_by_bvci_nsei(uint16_t bvci, uint16_t nsei); + +#include <osmocore/tlv.h> + +/* BSSGP-UL-UNITDATA.ind */ +int gprs_bssgp_rcvmsg(struct msgb *msg); + +/* BSSGP-DL-UNITDATA.req */ +struct sgsn_mm_ctx; +int gprs_bssgp_tx_dl_ud(struct msgb *msg, struct sgsn_mm_ctx *mmctx); + +uint16_t bssgp_parse_cell_id(struct gprs_ra_id *raid, const uint8_t *buf); + +/* Wrapper around TLV parser to parse BSSGP IEs */ +static inline int bssgp_tlv_parse(struct tlv_parsed *tp, uint8_t *buf, int len) +{ +	return tlv_parse(tp, &tvlv_att_def, buf, len, 0, 0); +} + +enum bssgp_paging_mode { +	BSSGP_PAGING_PS, +	BSSGP_PAGING_CS, +}; + +enum bssgp_paging_scope { +	BSSGP_PAGING_BSS_AREA,		/* all cells in BSS */ +	BSSGP_PAGING_LOCATION_AREA,	/* all cells in LA */ +	BSSGP_PAGING_ROUTEING_AREA,	/* all cells in RA */ +	BSSGP_PAGING_BVCI,		/* one cell */ +}; + +struct bssgp_paging_info { +	enum bssgp_paging_mode mode; +	enum bssgp_paging_scope scope; +	struct gprs_ra_id raid; +	uint16_t bvci; +	const char *imsi; +	uint32_t *ptmsi; +	uint16_t drx_params; +	uint8_t qos[3]; +}; + +/* Send a single GMM-PAGING.req to a given NSEI/NS-BVCI */ +int gprs_bssgp_tx_paging(uint16_t nsei, uint16_t ns_bvci, +			 struct bssgp_paging_info *pinfo); + +/* gprs_bssgp_vty.c */ +int gprs_bssgp_vty_init(void); + +#endif /* _GPRS_BSSGP_H */ diff --git a/openbsc/include/openbsc/gprs_ns.h b/openbsc/include/openbsc/gprs_ns.h new file mode 100644 index 00000000..953c364b --- /dev/null +++ b/openbsc/include/openbsc/gprs_ns.h @@ -0,0 +1,232 @@ +#ifndef _GPRS_NS_H +#define _GPRS_NS_H + +#include <stdint.h> + +/* GPRS Networks Service (NS) messages on the Gb interface + * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05) + * 3GPP TS 48.016 version 6.5.0 Release 6 / ETSI TS 148 016 V6.5.0 (2005-11) */ + +struct gprs_ns_hdr { +	uint8_t pdu_type; +	uint8_t data[0]; +} __attribute__((packed)); + +/* TS 08.16, Section 10.3.7, Table 14 */ +enum ns_pdu_type { +	NS_PDUT_UNITDATA	= 0x00, +	NS_PDUT_RESET		= 0x02, +	NS_PDUT_RESET_ACK	= 0x03, +	NS_PDUT_BLOCK		= 0x04, +	NS_PDUT_BLOCK_ACK	= 0x05, +	NS_PDUT_UNBLOCK		= 0x06, +	NS_PDUT_UNBLOCK_ACK	= 0x07, +	NS_PDUT_STATUS		= 0x08, +	NS_PDUT_ALIVE		= 0x0a, +	NS_PDUT_ALIVE_ACK	= 0x0b, +	/* TS 48.016 Section 10.3.7, Table 10.3.7.1 */ +	SNS_PDUT_ACK		= 0x0c, +	SNS_PDUT_ADD		= 0x0d, +	SNS_PDUT_CHANGE_WEIGHT	= 0x0e, +	SNS_PDUT_CONFIG		= 0x0f, +	SNS_PDUT_CONFIG_ACK	= 0x10, +	SNS_PDUT_DELETE		= 0x11, +	SNS_PDUT_SIZE		= 0x12, +	SNS_PDUT_SIZE_ACK	= 0x13, +}; + +/* TS 08.16, Section 10.3, Table 12 */ +enum ns_ctrl_ie { +	NS_IE_CAUSE		= 0x00, +	NS_IE_VCI		= 0x01, +	NS_IE_PDU		= 0x02, +	NS_IE_BVCI		= 0x03, +	NS_IE_NSEI		= 0x04, +	/* TS 48.016 Section 10.3, Table 10.3.1 */ +	NS_IE_IPv4_LIST		= 0x05, +	NS_IE_IPv6_LIST		= 0x06, +	NS_IE_MAX_NR_NSVC	= 0x07, +	NS_IE_IPv4_EP_NR	= 0x08, +	NS_IE_IPv6_EP_NR	= 0x09, +	NS_IE_RESET_FLAG	= 0x0a, +	NS_IE_IP_ADDR		= 0x0b, +}; + +/* TS 08.16, Section 10.3.2, Table 13 */ +enum ns_cause { +	NS_CAUSE_TRANSIT_FAIL		= 0x00, +	NS_CAUSE_OM_INTERVENTION	= 0x01, +	NS_CAUSE_EQUIP_FAIL		= 0x02, +	NS_CAUSE_NSVC_BLOCKED		= 0x03, +	NS_CAUSE_NSVC_UNKNOWN		= 0x04, +	NS_CAUSE_BVCI_UNKNOWN		= 0x05, +	NS_CAUSE_SEM_INCORR_PDU		= 0x08, +	NS_CAUSE_PDU_INCOMP_PSTATE	= 0x0a, +	NS_CAUSE_PROTO_ERR_UNSPEC	= 0x0b, +	NS_CAUSE_INVAL_ESSENT_IE	= 0x0c, +	NS_CAUSE_MISSING_ESSENT_IE	= 0x0d, +	/* TS 48.016 Section 10.3.2, Table 10.3.2.1 */ +	NS_CAUSE_INVAL_NR_IPv4_EP	= 0x0e, +	NS_CAUSE_INVAL_NR_IPv6_EP	= 0x0f, +	NS_CAUSE_INVAL_NR_NS_VC		= 0x10, +	NS_CAUSE_INVAL_WEIGH		= 0x11, +	NS_CAUSE_UNKN_IP_EP		= 0x12, +	NS_CAUSE_UNKN_IP_ADDR		= 0x13, +	NS_CAUSE_UNKN_IP_TEST_FAILED	= 0x14, +}; + +/* Our Implementation */ +#include <netinet/in.h> +#include <osmocore/linuxlist.h> +#include <osmocore/msgb.h> +#include <osmocore/timer.h> +#include <osmocore/select.h> + +#define NS_TIMERS_COUNT 7 +#define NS_TIMERS "(tns-block|tns-block-retries|tns-reset|tns-reset-retries|tns-test|tns-alive|tns-alive-retries)" +#define NS_TIMERS_HELP	\ +	"(un)blocking Timer (Tns-block) timeout\n"		\ +	"(un)blocking Timer (Tns-block) number of retries\n"	\ +	"Reset Timer (Tns-reset) timeout\n"			\ +	"Reset Timer (Tns-reset) number of retries\n"		\ +	"Test Timer (Tns-test) timeout\n"			\ + +enum ns_timeout { +	NS_TOUT_TNS_BLOCK, +	NS_TOUT_TNS_BLOCK_RETRIES, +	NS_TOUT_TNS_RESET, +	NS_TOUT_TNS_RESET_RETRIES, +	NS_TOUT_TNS_TEST, +	NS_TOUT_TNS_ALIVE, +	NS_TOUT_TNS_ALIVE_RETRIES, +}; + +#define NSE_S_BLOCKED	0x0001 +#define NSE_S_ALIVE	0x0002 + +enum gprs_ns_ll { +	GPRS_NS_LL_UDP, +	GPRS_NS_LL_E1, +	GPRS_NS_LL_FR_GRE, +}; + +enum gprs_ns_evt { +	GPRS_NS_EVT_UNIT_DATA, +}; + +struct gprs_nsvc; +typedef int gprs_ns_cb_t(enum gprs_ns_evt event, struct gprs_nsvc *nsvc, +			 struct msgb *msg, uint16_t bvci); + +/* An instance of the NS protocol stack */ +struct gprs_ns_inst { +	/* callback to the user for incoming UNIT DATA IND */ +	gprs_ns_cb_t *cb; + +	/* linked lists of all NSVC in this instance */ +	struct llist_head gprs_nsvcs; + +	/* a NSVC object that's needed to deal with packets for unknown NSVC */ +	struct gprs_nsvc *unknown_nsvc; + +	uint16_t timeout[NS_TIMERS_COUNT]; + +	/* NS-over-IP specific bits */ +	struct { +		struct bsc_fd fd; +		uint32_t local_ip; +		uint16_t local_port; +	} nsip; +	/* NS-over-FR-over-GRE-over-IP specific bits */ +	struct { +		struct bsc_fd fd; +		uint32_t local_ip; +		int enabled:1; +	} frgre; +}; + +enum nsvc_timer_mode { +	/* standard timers */ +	NSVC_TIMER_TNS_TEST, +	NSVC_TIMER_TNS_ALIVE, +	NSVC_TIMER_TNS_RESET, +	_NSVC_TIMER_NR, +}; + +struct gprs_nsvc { +	struct llist_head list; +	struct gprs_ns_inst *nsi; + +	uint16_t nsei;		/* end-to-end significance */ +	uint16_t nsvci;	/* uniquely identifies NS-VC at SGSN */ + +	uint32_t state; +	uint32_t remote_state; + +	struct timer_list timer; +	enum nsvc_timer_mode timer_mode; +	int alive_retries; + +	unsigned int remote_end_is_sgsn:1; +	unsigned int persistent:1; + +	struct rate_ctr_group *ctrg; + +	/* which link-layer are we based on? */ +	enum gprs_ns_ll ll; + +	union { +		struct { +			struct sockaddr_in bts_addr; +		} ip; +		struct { +			struct sockaddr_in bts_addr; +		} frgre; +	}; +}; + +/* Create a new NS protocol instance */ +struct gprs_ns_inst *gprs_ns_instantiate(gprs_ns_cb_t *cb); + +/* Destroy a NS protocol instance */ +void gprs_ns_destroy(struct gprs_ns_inst *nsi); + +/* Listen for incoming GPRS packets via NS/UDP */ +int gprs_ns_nsip_listen(struct gprs_ns_inst *nsi); + +struct sockaddr_in; + +/* main function for higher layers (BSSGP) to send NS messages */ +int gprs_ns_sendmsg(struct gprs_ns_inst *nsi, struct msgb *msg); + +int gprs_ns_tx_reset(struct gprs_nsvc *nsvc, uint8_t cause); +int gprs_ns_tx_block(struct gprs_nsvc *nsvc, uint8_t cause); +int gprs_ns_tx_unblock(struct gprs_nsvc *nsvc); + +/* Listen for incoming GPRS packets via NS/FR/GRE */ +int gprs_ns_frgre_listen(struct gprs_ns_inst *nsi); + +/* Establish a connection (from the BSS) to the SGSN */ +struct gprs_nsvc *nsip_connect(struct gprs_ns_inst *nsi, +				struct sockaddr_in *dest, uint16_t nsei, +				uint16_t nsvci); + +struct gprs_nsvc *nsvc_create(struct gprs_ns_inst *nsi, uint16_t nsvci); +void nsvc_delete(struct gprs_nsvc *nsvc); +struct gprs_nsvc *nsvc_by_nsei(struct gprs_ns_inst *nsi, uint16_t nsei); +struct gprs_nsvc *nsvc_by_nsvci(struct gprs_ns_inst *nsi, uint16_t nsvci); + +/* Initiate a RESET procedure (including timer start, ...)*/ +void gprs_nsvc_reset(struct gprs_nsvc *nsvc, uint8_t cause); + +/* Add NS-specific VTY stuff */ +int gprs_ns_vty_init(struct gprs_ns_inst *nsi); + +#define NS_ALLOC_SIZE	2048 +#define NS_ALLOC_HEADROOM 20 +static inline struct msgb *gprs_ns_msgb_alloc(void) +{ +	return msgb_alloc_headroom(NS_ALLOC_SIZE, NS_ALLOC_HEADROOM, "GPRS/NS"); +} + +#endif diff --git a/openbsc/include/openbsc/gprs_ns_frgre.h b/openbsc/include/openbsc/gprs_ns_frgre.h new file mode 100644 index 00000000..abcd43ff --- /dev/null +++ b/openbsc/include/openbsc/gprs_ns_frgre.h @@ -0,0 +1,6 @@ +#ifndef _GPRS_NS_FRGRE_H +#define _GPRS_NS_FRGRE_H + +int gprs_ns_frgre_sendmsg(struct gprs_nsvc *nsvc, struct msgb *msg); + +#endif diff --git a/openbsc/src/gprs/gprs_bssgp.c b/openbsc/src/gprs/gprs_bssgp.c new file mode 100644 index 00000000..0ec873ca --- /dev/null +++ b/openbsc/src/gprs/gprs_bssgp.c @@ -0,0 +1,852 @@ +/* GPRS BSSGP protocol implementation as per 3GPP TS 08.18 */ + +/* (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. + * + * TODO: + *  o  properly count incoming BVC-RESET packets in counter group + *  o  set log context as early as possible for outgoing packets + */ + +#include <errno.h> +#include <stdint.h> + +#include <netinet/in.h> + +#include <osmocore/msgb.h> +#include <osmocore/tlv.h> +#include <osmocore/talloc.h> +#include <osmocore/rate_ctr.h> + +#include <openbsc/debug.h> +#include <openbsc/gsm_data.h> +#include <openbsc/gsm_04_08_gprs.h> +#include <openbsc/gprs_bssgp.h> +#include <openbsc/gprs_llc.h> +#include <openbsc/gprs_ns.h> +#include <openbsc/gprs_sgsn.h> +#include <openbsc/gprs_gmm.h> + +void *bssgp_tall_ctx = NULL; + +#define BVC_F_BLOCKED	0x0001 + +enum bssgp_ctr { +	BSSGP_CTR_PKTS_IN, +	BSSGP_CTR_PKTS_OUT, +	BSSGP_CTR_BYTES_IN, +	BSSGP_CTR_BYTES_OUT, +	BSSGP_CTR_BLOCKED, +	BSSGP_CTR_DISCARDED, +}; + +static const struct rate_ctr_desc bssgp_ctr_description[] = { +	{ "packets.in",	"Packets at BSSGP Level ( In)" }, +	{ "packets.out","Packets at BSSGP Level (Out)" }, +	{ "bytes.in",	"Bytes at BSSGP Level   ( In)" }, +	{ "bytes.out",	"Bytes at BSSGP Level   (Out)" }, +	{ "blocked",	"BVC Blocking count" }, +	{ "discarded",	"BVC LLC Discarded count" }, +}; + +static const struct rate_ctr_group_desc bssgp_ctrg_desc = { +	.group_name_prefix = "bssgp.bss_ctx", +	.group_description = "BSSGP Peer Statistics", +	.num_ctr = ARRAY_SIZE(bssgp_ctr_description), +	.ctr_desc = bssgp_ctr_description, +}; + +LLIST_HEAD(bssgp_bvc_ctxts); + +/* Find a BTS Context based on parsed RA ID and Cell ID */ +struct bssgp_bvc_ctx *btsctx_by_raid_cid(const struct gprs_ra_id *raid, uint16_t cid) +{ +	struct bssgp_bvc_ctx *bctx; + +	llist_for_each_entry(bctx, &bssgp_bvc_ctxts, list) { +		if (!memcmp(&bctx->ra_id, raid, sizeof(bctx->ra_id)) && +		    bctx->cell_id == cid) +			return bctx; +	} +	return NULL; +} + +/* Find a BTS context based on BVCI+NSEI tuple */ +struct bssgp_bvc_ctx *btsctx_by_bvci_nsei(uint16_t bvci, uint16_t nsei) +{ +	struct bssgp_bvc_ctx *bctx; + +	llist_for_each_entry(bctx, &bssgp_bvc_ctxts, list) { +		if (bctx->nsei == nsei && bctx->bvci == bvci) +			return bctx; +	} +	return NULL; +} + +struct bssgp_bvc_ctx *btsctx_alloc(uint16_t bvci, uint16_t nsei) +{ +	struct bssgp_bvc_ctx *ctx; + +	ctx = talloc_zero(bssgp_tall_ctx, struct bssgp_bvc_ctx); +	if (!ctx) +		return NULL; +	ctx->bvci = bvci; +	ctx->nsei = nsei; +	/* FIXME: BVCI is not unique, only BVCI+NSEI ?!? */ +	ctx->ctrg = rate_ctr_group_alloc(ctx, &bssgp_ctrg_desc, bvci); + +	llist_add(&ctx->list, &bssgp_bvc_ctxts); + +	return ctx; +} + +/* Chapter 10.4.5: Flow Control BVC ACK */ +static int bssgp_tx_fc_bvc_ack(uint16_t nsei, uint8_t tag, uint16_t ns_bvci) +{ +	struct msgb *msg = bssgp_msgb_alloc(); +	struct bssgp_normal_hdr *bgph = +			(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); + +	msgb_nsei(msg) = nsei; +	msgb_bvci(msg) = ns_bvci; + +	bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_BVC_ACK; +	msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &tag); + +	return gprs_ns_sendmsg(bssgp_nsi, msg); +} + +/* 10.3.7 SUSPEND-ACK PDU */ +int bssgp_tx_suspend_ack(uint16_t nsei, uint32_t tlli, +			 const struct gprs_ra_id *ra_id, uint8_t suspend_ref) +{ +	struct msgb *msg = bssgp_msgb_alloc(); +	struct bssgp_normal_hdr *bgph = +		(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); +	uint32_t _tlli; +	uint8_t ra[6]; + +	msgb_nsei(msg) = nsei; +	msgb_bvci(msg) = 0; /* Signalling */ +	bgph->pdu_type = BSSGP_PDUT_SUSPEND_ACK; + +	_tlli = htonl(tlli); +	msgb_tvlv_put(msg, BSSGP_IE_TLLI, 4, (uint8_t *) &_tlli); +	gsm48_construct_ra(ra, ra_id); +	msgb_tvlv_put(msg, BSSGP_IE_ROUTEING_AREA, 6, ra); +	msgb_tvlv_put(msg, BSSGP_IE_SUSPEND_REF_NR, 1, &suspend_ref); + +	return gprs_ns_sendmsg(bssgp_nsi, msg); +} + +/* 10.3.8 SUSPEND-NACK PDU */ +int bssgp_tx_suspend_nack(uint16_t nsei, uint32_t tlli, +			  uint8_t *cause) +{ +	struct msgb *msg = bssgp_msgb_alloc(); +	struct bssgp_normal_hdr *bgph = +		(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); +	uint32_t _tlli; + +	msgb_nsei(msg) = nsei; +	msgb_bvci(msg) = 0; /* Signalling */ +	bgph->pdu_type = BSSGP_PDUT_SUSPEND_NACK; + +	_tlli = htonl(tlli); +	msgb_tvlv_put(msg, BSSGP_IE_TLLI, 4, (uint8_t *) &_tlli); +	if (cause) +		msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, cause); + +	return gprs_ns_sendmsg(bssgp_nsi, msg); +} + +/* 10.3.10 RESUME-ACK PDU */ +int bssgp_tx_resume_ack(uint16_t nsei, uint32_t tlli, +			const struct gprs_ra_id *ra_id) +{ +	struct msgb *msg = bssgp_msgb_alloc(); +	struct bssgp_normal_hdr *bgph = +		(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); +	uint32_t _tlli; +	uint8_t ra[6]; + +	msgb_nsei(msg) = nsei; +	msgb_bvci(msg) = 0; /* Signalling */ +	bgph->pdu_type = BSSGP_PDUT_RESUME_ACK; + +	_tlli = htonl(tlli); +	msgb_tvlv_put(msg, BSSGP_IE_TLLI, 4, (uint8_t *) &_tlli); +	gsm48_construct_ra(ra, ra_id); +	msgb_tvlv_put(msg, BSSGP_IE_ROUTEING_AREA, 6, ra); + +	return gprs_ns_sendmsg(bssgp_nsi, msg); +} + +/* 10.3.11 RESUME-NACK PDU */ +int bssgp_tx_resume_nack(uint16_t nsei, uint32_t tlli, +			 const struct gprs_ra_id *ra_id, uint8_t *cause) +{ +	struct msgb *msg = bssgp_msgb_alloc(); +	struct bssgp_normal_hdr *bgph = +		(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); +	uint32_t _tlli; +	uint8_t ra[6]; + +	msgb_nsei(msg) = nsei; +	msgb_bvci(msg) = 0; /* Signalling */ +	bgph->pdu_type = BSSGP_PDUT_SUSPEND_NACK; + +	_tlli = htonl(tlli); +	msgb_tvlv_put(msg, BSSGP_IE_TLLI, 4, (uint8_t *) &_tlli); +	gsm48_construct_ra(ra, ra_id); +	msgb_tvlv_put(msg, BSSGP_IE_ROUTEING_AREA, 6, ra); +	if (cause) +		msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, cause); + +	return gprs_ns_sendmsg(bssgp_nsi, msg); +} + +uint16_t bssgp_parse_cell_id(struct gprs_ra_id *raid, const uint8_t *buf) +{ +	/* 6 octets RAC */ +	gsm48_parse_ra(raid, buf); +	/* 2 octets CID */ +	return ntohs(*(uint16_t *) (buf+6)); +} + +/* Chapter 8.4 BVC-Reset Procedure */ +static int bssgp_rx_bvc_reset(struct msgb *msg, struct tlv_parsed *tp,	 +			      uint16_t ns_bvci) +{ +	struct bssgp_bvc_ctx *bctx; +	uint16_t nsei = msgb_nsei(msg); +	uint16_t bvci; +	int rc; + +	bvci = ntohs(*(uint16_t *)TLVP_VAL(tp, BSSGP_IE_BVCI)); +	DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx RESET cause=%s\n", bvci, +		bssgp_cause_str(*TLVP_VAL(tp, BSSGP_IE_CAUSE))); + +	/* look-up or create the BTS context for this BVC */ +	bctx = btsctx_by_bvci_nsei(bvci, nsei); +	if (!bctx) +		bctx = btsctx_alloc(bvci, nsei); + +	/* As opposed to NS-VCs, BVCs are NOT blocked after RESET */ +	bctx->state &= ~BVC_S_BLOCKED; + +	/* When we receive a BVC-RESET PDU (at least of a PTP BVCI), the BSS +	 * informs us about its RAC + Cell ID, so we can create a mapping */ +	if (bvci != 0 && bvci != 1) { +		if (!TLVP_PRESENT(tp, BSSGP_IE_CELL_ID)) { +			LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u RESET " +				"missing mandatory IE\n", bvci); +			return -EINVAL; +		} +		/* actually extract RAC / CID */ +		bctx->cell_id = bssgp_parse_cell_id(&bctx->ra_id, +						TLVP_VAL(tp, BSSGP_IE_CELL_ID)); +		LOGP(DBSSGP, LOGL_NOTICE, "Cell %u-%u-%u-%u CI %u on BVCI %u\n", +			bctx->ra_id.mcc, bctx->ra_id.mnc, bctx->ra_id.lac, +			bctx->ra_id.rac, bctx->cell_id, bvci); +	} + +	/* Acknowledge the RESET to the BTS */ +	rc = bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_RESET_ACK, +				  nsei, bvci, ns_bvci); +	return 0; +} + +static int bssgp_rx_bvc_block(struct msgb *msg, struct tlv_parsed *tp) +{ +	uint16_t bvci; +	struct bssgp_bvc_ctx *ptp_ctx; + +	bvci = ntohs(*(uint16_t *)TLVP_VAL(tp, BSSGP_IE_BVCI)); +	if (bvci == BVCI_SIGNALLING) { +		/* 8.3.2: Signalling BVC shall never be blocked */ +		LOGP(DBSSGP, LOGL_ERROR, "NSEI=%u/BVCI=%u " +			"received block for signalling BVC!?!\n", +			msgb_nsei(msg), msgb_bvci(msg)); +		return 0; +	} + +	LOGP(DBSSGP, LOGL_INFO, "BSSGP BVCI=%u BVC-BLOCK\n", bvci); + +	ptp_ctx = btsctx_by_bvci_nsei(bvci, msgb_nsei(msg)); +	if (!ptp_ctx) +		return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, &bvci, msg); + +	ptp_ctx->state |= BVC_S_BLOCKED; +	rate_ctr_inc(&ptp_ctx->ctrg->ctr[BSSGP_CTR_BLOCKED]); + +	/* FIXME: Send NM_BVC_BLOCK.ind to NM */ + +	/* We always acknowledge the BLOCKing */ +	return bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_BLOCK_ACK, msgb_nsei(msg), +				    bvci, msgb_bvci(msg)); +}; + +static int bssgp_rx_bvc_unblock(struct msgb *msg, struct tlv_parsed *tp) +{ +	uint16_t bvci; +	struct bssgp_bvc_ctx *ptp_ctx; + +	bvci = ntohs(*(uint16_t *)TLVP_VAL(tp, BSSGP_IE_BVCI)); +	if (bvci == BVCI_SIGNALLING) { +		/* 8.3.2: Signalling BVC shall never be blocked */ +		LOGP(DBSSGP, LOGL_ERROR, "NSEI=%u/BVCI=%u " +			"received unblock for signalling BVC!?!\n", +			msgb_nsei(msg), msgb_bvci(msg)); +		return 0; +	} + +	DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx BVC-UNBLOCK\n", bvci); + +	ptp_ctx = btsctx_by_bvci_nsei(bvci, msgb_nsei(msg)); +	if (!ptp_ctx) +		return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, &bvci, msg); + +	ptp_ctx->state &= ~BVC_S_BLOCKED; + +	/* FIXME: Send NM_BVC_UNBLOCK.ind to NM */ + +	/* We always acknowledge the unBLOCKing */ +	return bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_UNBLOCK_ACK, msgb_nsei(msg), +				    bvci, msgb_bvci(msg)); +}; + +/* Uplink unit-data */ +static int bssgp_rx_ul_ud(struct msgb *msg, struct tlv_parsed *tp, +			  struct bssgp_bvc_ctx *ctx) +{ +	struct bssgp_ud_hdr *budh = (struct bssgp_ud_hdr *) msgb_bssgph(msg); + +	/* extract TLLI and parse TLV IEs */ +	msgb_tlli(msg) = ntohl(budh->tlli); + +	DEBUGP(DBSSGP, "BSSGP TLLI=0x%08x UPLINK-UNITDATA\n", msgb_tlli(msg)); + +	/* Cell ID and LLC_PDU are the only mandatory IE */ +	if (!TLVP_PRESENT(tp, BSSGP_IE_CELL_ID) || +	    !TLVP_PRESENT(tp, BSSGP_IE_LLC_PDU)) { +		LOGP(DBSSGP, LOGL_ERROR, "BSSGP TLLI=0x%08x Rx UL-UD " +			"missing mandatory IE\n", msgb_tlli(msg)); +		return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg); +	} + +	/* store pointer to LLC header and CELL ID in msgb->cb */ +	msgb_llch(msg) = (uint8_t *) TLVP_VAL(tp, BSSGP_IE_LLC_PDU); +	msgb_bcid(msg) = (uint8_t *) TLVP_VAL(tp, BSSGP_IE_CELL_ID); + +	return gprs_llc_rcvmsg(msg, tp); +} + +static int bssgp_rx_suspend(struct msgb *msg, struct tlv_parsed *tp, +			    struct bssgp_bvc_ctx *ctx) +{ +	struct bssgp_normal_hdr *bgph = +			(struct bssgp_normal_hdr *) msgb_bssgph(msg); +	struct gprs_ra_id raid; +	uint32_t tlli; +	int rc; + +	if (!TLVP_PRESENT(tp, BSSGP_IE_TLLI) || +	    !TLVP_PRESENT(tp, BSSGP_IE_ROUTEING_AREA)) { +		LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx SUSPEND " +			"missing mandatory IE\n", ctx->bvci); +		return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg); +	} + +	tlli = ntohl(*(uint32_t *)TLVP_VAL(tp, BSSGP_IE_TLLI)); + +	DEBUGP(DBSSGP, "BSSGP BVCI=%u TLLI=0x%08x Rx SUSPEND\n", +		ctx->bvci, tlli); + +	gsm48_parse_ra(&raid, TLVP_VAL(tp, BSSGP_IE_ROUTEING_AREA)); + +	/* Inform GMM about the SUSPEND request */ +	rc = gprs_gmm_rx_suspend(&raid, tlli); +	if (rc < 0) +		return bssgp_tx_suspend_nack(msgb_nsei(msg), tlli, NULL); + +	bssgp_tx_suspend_ack(msgb_nsei(msg), tlli, &raid, 0); + +	return 0; +} + +static int bssgp_rx_resume(struct msgb *msg, struct tlv_parsed *tp, +			   struct bssgp_bvc_ctx *ctx) +{ +	struct bssgp_normal_hdr *bgph = +			(struct bssgp_normal_hdr *) msgb_bssgph(msg); +	struct gprs_ra_id raid; +	uint32_t tlli; +	uint8_t suspend_ref; +	int rc; + +	if (!TLVP_PRESENT(tp, BSSGP_IE_TLLI) || +	    !TLVP_PRESENT(tp, BSSGP_IE_ROUTEING_AREA) || +	    !TLVP_PRESENT(tp, BSSGP_IE_SUSPEND_REF_NR)) { +		LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx RESUME " +			"missing mandatory IE\n", ctx->bvci); +		return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg); +	} + +	tlli = ntohl(*(uint32_t *)TLVP_VAL(tp, BSSGP_IE_TLLI)); +	suspend_ref = *TLVP_VAL(tp, BSSGP_IE_SUSPEND_REF_NR); + +	DEBUGP(DBSSGP, "BSSGP BVCI=%u TLLI=0x%08x RESUME\n", ctx->bvci, tlli); + +	gsm48_parse_ra(&raid, TLVP_VAL(tp, BSSGP_IE_ROUTEING_AREA)); + +	/* Inform GMM about the RESUME request */ +	rc = gprs_gmm_rx_resume(&raid, tlli, suspend_ref); +	if (rc < 0) +		return bssgp_tx_resume_nack(msgb_nsei(msg), tlli, &raid, +					    NULL); + +	bssgp_tx_resume_ack(msgb_nsei(msg), tlli, &raid); +	return 0; +} + + +static int bssgp_rx_llc_disc(struct msgb *msg, struct tlv_parsed *tp, +			     struct bssgp_bvc_ctx *ctx) +{ +	uint32_t tlli; + +	if (!TLVP_PRESENT(tp, BSSGP_IE_TLLI) || +	    !TLVP_PRESENT(tp, BSSGP_IE_LLC_FRAMES_DISCARDED) || +	    !TLVP_PRESENT(tp, BSSGP_IE_BVCI) || +	    !TLVP_PRESENT(tp, BSSGP_IE_NUM_OCT_AFF)) { +		LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx LLC DISCARDED " +			"missing mandatory IE\n", ctx->bvci); +	} + +	tlli = ntohl(*(uint32_t *)TLVP_VAL(tp, BSSGP_IE_TLLI)); + +	DEBUGP(DBSSGP, "BSSGP BVCI=%u TLLI=%u LLC DISCARDED\n", +		ctx->bvci, tlli); + +	rate_ctr_inc(&ctx->ctrg->ctr[BSSGP_CTR_DISCARDED]); + +	/* FIXME: send NM_LLC_DISCARDED to NM */ +	return 0; +} + +static int bssgp_rx_fc_bvc(struct msgb *msg, struct tlv_parsed *tp, +			   struct bssgp_bvc_ctx *bctx) +{ + +	DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx Flow Control BVC\n", +		bctx->bvci); + +	if (!TLVP_PRESENT(tp, BSSGP_IE_TAG) || +	    !TLVP_PRESENT(tp, BSSGP_IE_BVC_BUCKET_SIZE) || +	    !TLVP_PRESENT(tp, BSSGP_IE_BUCKET_LEAK_RATE) || +	    !TLVP_PRESENT(tp, BSSGP_IE_BMAX_DEFAULT_MS) || +	    !TLVP_PRESENT(tp, BSSGP_IE_R_DEFAULT_MS)) { +		LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx FC BVC " +			"missing mandatory IE\n", bctx->bvci); +		return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg); +	} + +	/* FIXME: actually implement flow control */ + +	/* Send FLOW_CONTROL_BVC_ACK */ +	return bssgp_tx_fc_bvc_ack(msgb_nsei(msg), *TLVP_VAL(tp, BSSGP_IE_TAG), +				   msgb_bvci(msg)); +} + +/* Receive a BSSGP PDU from a BSS on a PTP BVCI */ +static int gprs_bssgp_rx_ptp(struct msgb *msg, struct tlv_parsed *tp, +			     struct bssgp_bvc_ctx *bctx) +{ +	struct bssgp_normal_hdr *bgph = +			(struct bssgp_normal_hdr *) msgb_bssgph(msg); +	uint8_t pdu_type = bgph->pdu_type; +	int rc = 0; + +	/* If traffic is received on a BVC that is marked as blocked, the +	 * received PDU shall not be accepted and a STATUS PDU (Cause value: +	 * BVC Blocked) shall be sent to the peer entity on the signalling BVC */ +	if (bctx->state & BVC_S_BLOCKED && pdu_type != BSSGP_PDUT_STATUS) { +		uint16_t bvci = msgb_bvci(msg); +		return bssgp_tx_status(BSSGP_CAUSE_BVCI_BLOCKED, &bvci, msg); +	} + +	switch (pdu_type) { +	case BSSGP_PDUT_UL_UNITDATA: +		/* some LLC data from the MS */ +		rc = bssgp_rx_ul_ud(msg, tp, bctx); +		break; +	case BSSGP_PDUT_RA_CAPABILITY: +		/* BSS requests RA capability or IMSI */ +		DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx RA CAPABILITY UPDATE\n", +			bctx->bvci); +		/* FIXME: send GMM_RA_CAPABILITY_UPDATE.ind to GMM */ +		/* FIXME: send RA_CAPA_UPDATE_ACK */ +		break; +	case BSSGP_PDUT_RADIO_STATUS: +		DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx RADIO STATUS\n", bctx->bvci); +		/* BSS informs us of some exception */ +		/* FIXME: send GMM_RADIO_STATUS.ind to GMM */ +		break; +	case BSSGP_PDUT_FLOW_CONTROL_BVC: +		/* BSS informs us of available bandwidth in Gb interface */ +		rc = bssgp_rx_fc_bvc(msg, tp, bctx); +		break; +	case BSSGP_PDUT_FLOW_CONTROL_MS: +		/* BSS informs us of available bandwidth to one MS */ +		DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx Flow Control MS\n", +			bctx->bvci); +		/* FIXME: actually implement flow control */ +		/* FIXME: Send FLOW_CONTROL_MS_ACK */ +		break; +	case BSSGP_PDUT_STATUS: +		/* Some exception has occurred */ +		/* FIXME: send NM_STATUS.ind to NM */ +	case BSSGP_PDUT_DOWNLOAD_BSS_PFC: +	case BSSGP_PDUT_CREATE_BSS_PFC_ACK: +	case BSSGP_PDUT_CREATE_BSS_PFC_NACK: +	case BSSGP_PDUT_MODIFY_BSS_PFC: +	case BSSGP_PDUT_DELETE_BSS_PFC_ACK: +		DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx PDU type 0x%02x not [yet] " +			"implemented\n", bctx->bvci, pdu_type); +		rc = bssgp_tx_status(BSSGP_CAUSE_PDU_INCOMP_FEAT, NULL, msg); +		break; +	/* those only exist in the SGSN -> BSS direction */ +	case BSSGP_PDUT_DL_UNITDATA: +	case BSSGP_PDUT_PAGING_PS: +	case BSSGP_PDUT_PAGING_CS: +	case BSSGP_PDUT_RA_CAPA_UPDATE_ACK: +	case BSSGP_PDUT_FLOW_CONTROL_BVC_ACK: +	case BSSGP_PDUT_FLOW_CONTROL_MS_ACK: +		DEBUGP(DBSSGP, "BSSGP BVCI=%u PDU type 0x%02x only exists " +			"in DL\n", bctx->bvci, pdu_type); +		bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg); +		rc = -EINVAL; +		break; +	default: +		DEBUGP(DBSSGP, "BSSGP BVCI=%u PDU type 0x%02x unknown\n", +			bctx->bvci, pdu_type); +		rc = bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg); +		break; +	} + +	return rc; +} + +/* Receive a BSSGP PDU from a BSS on a SIGNALLING BVCI */ +static int gprs_bssgp_rx_sign(struct msgb *msg, struct tlv_parsed *tp, +				struct bssgp_bvc_ctx *bctx) +{ +	struct bssgp_normal_hdr *bgph = +			(struct bssgp_normal_hdr *) msgb_bssgph(msg); +	uint8_t pdu_type = bgph->pdu_type; +	int rc = 0; +	uint16_t ns_bvci = msgb_bvci(msg); +	uint16_t bvci; + +	switch (bgph->pdu_type) { +	case BSSGP_PDUT_SUSPEND: +		/* MS wants to suspend */ +		rc = bssgp_rx_suspend(msg, tp, bctx); +		break; +	case BSSGP_PDUT_RESUME: +		/* MS wants to resume */ +		rc = bssgp_rx_resume(msg, tp, bctx); +		break; +	case BSSGP_PDUT_FLUSH_LL_ACK: +		/* BSS informs us it has performed LL FLUSH */ +		DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx FLUSH LL ACK\n", bctx->bvci); +		/* FIXME: send NM_FLUSH_LL.res to NM */ +		break; +	case BSSGP_PDUT_LLC_DISCARD: +		/* BSS informs that some LLC PDU's have been discarded */ | 
