bssgp2: Encoding + Decoding functions for BVC and MS flow control

Change-Id: I9c89bb1c03550930c07aad7ff8f67129ee7a6320
Related: OS#4891
diff --git a/src/gb/gprs_bssgp2.c b/src/gb/gprs_bssgp2.c
index e741fb4..a54a8d9 100644
--- a/src/gb/gprs_bssgp2.c
+++ b/src/gb/gprs_bssgp2.c
@@ -238,3 +238,209 @@
 
 	return msg;
 }
+
+static const unsigned int bssgp_fc_gran_tbl[] = {
+	[BSSGP_FC_GRAN_100]	= 100,
+	[BSSGP_FC_GRAN_1000]	= 1000,
+	[BSSGP_FC_GRAN_10000]	= 10000,
+	[BSSGP_FC_GRAN_100000]	= 100000,
+};
+
+/*! Decode a FLOW-CONTROL-BVC PDU as per TS 48.018 Section 10.4.4.
+ *  \param[out] fc caller-allocated memory for parsed output
+ *  \param[in] tp pre-parsed TLVs; caller must ensure mandatory IE presence/length
+ *  \returns 0 on success; negative in case of error */
+int bssgp2_dec_fc_bvc(struct bssgp2_flow_ctrl *fc, const struct tlv_parsed *tp)
+{
+	unsigned int granularity = 100;
+
+	/* optional "Flow Control Granularity IE" (11.3.102); applies to
+	 * bucket_size_max, bucket_leak_rate and PFC FC params IE */
+	if (TLVP_PRESENT(tp, BSSGP_IE_FLOW_CTRL_GRANULARITY)) {
+		uint8_t gran = *TLVP_VAL(tp, BSSGP_IE_FLOW_CTRL_GRANULARITY);
+		granularity = bssgp_fc_gran_tbl[gran & 3];
+	}
+
+	/* mandatory IEs */
+	fc->tag = *TLVP_VAL(tp, BSSGP_IE_TAG);
+	fc->bucket_size_max = granularity * tlvp_val16be(tp, BSSGP_IE_BVC_BUCKET_SIZE);
+	fc->bucket_leak_rate = (granularity * tlvp_val16be(tp, BSSGP_IE_BUCKET_LEAK_RATE)) / 8;
+	fc->u.bvc.bmax_default_ms = granularity * tlvp_val16be(tp, BSSGP_IE_BMAX_DEFAULT_MS);
+	fc->u.bvc.r_default_ms = (granularity * tlvp_val16be(tp, BSSGP_IE_R_DEFAULT_MS)) / 8;
+
+	/* optional / conditional */
+	if (TLVP_PRESENT(tp, BSSGP_IE_BUCKET_FULL_RATIO)) {
+		fc->bucket_full_ratio_present = true;
+		fc->bucket_full_ratio = *TLVP_VAL(tp, BSSGP_IE_BUCKET_FULL_RATIO);
+	} else {
+		fc->bucket_full_ratio_present = false;
+	}
+
+	if (TLVP_PRESENT(tp, BSSGP_IE_BVC_MEASUREMENT)) {
+		uint16_t val = tlvp_val16be(tp, BSSGP_IE_BVC_MEASUREMENT);
+		fc->u.bvc.measurement_present = true;
+		/* convert from centi-seconds to milli-seconds */
+		if (val == 0xffff)
+			fc->u.bvc.measurement = 0xffffffff;
+		else
+			fc->u.bvc.measurement = val * 10;
+	} else {
+		fc->u.bvc.measurement_present = false;
+	}
+
+	return 0;
+
+}
+
+/*! Encode a FLOW-CONTROL-BVC PDU as per TS 48.018 Section 10.4.4.
+ *  \param[in] fc structure describing to-be-encoded FC parameters
+ *  \param[in] gran if non-NULL: Encode using specified unit granularity
+ *  \returns encoded PDU or NULL in case of error */
+struct msgb *bssgp2_enc_fc_bvc(const struct bssgp2_flow_ctrl *fc, enum bssgp_fc_granularity *gran)
+{
+	struct msgb *msg = bssgp_msgb_alloc();
+	struct bssgp_normal_hdr *bgph;
+	unsigned int granularity = 100;
+
+	if (gran)
+		granularity = bssgp_fc_gran_tbl[*gran & 3];
+
+	if (!msg)
+		return NULL;
+
+	bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+	bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_BVC;
+
+	msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &fc->tag);
+	msgb_tvlv_put_16be(msg, BSSGP_IE_BVC_BUCKET_SIZE, fc->bucket_size_max / granularity);
+	msgb_tvlv_put_16be(msg, BSSGP_IE_BUCKET_LEAK_RATE, fc->bucket_leak_rate * 8 / granularity);
+	msgb_tvlv_put_16be(msg, BSSGP_IE_BMAX_DEFAULT_MS, fc->u.bvc.bmax_default_ms / granularity);
+	msgb_tvlv_put_16be(msg, BSSGP_IE_R_DEFAULT_MS, fc->u.bvc.r_default_ms * 8 / granularity);
+
+	if (fc->bucket_full_ratio_present)
+		msgb_tvlv_put(msg, BSSGP_IE_BUCKET_FULL_RATIO, 1, &fc->bucket_full_ratio);
+
+	if (fc->u.bvc.measurement_present) {
+		uint16_t val;
+		/* convert from ms to cs */
+		if (fc->u.bvc.measurement == 0xffffffff)
+			val = 0xffff;
+		else
+			val = fc->u.bvc.measurement / 10;
+		msgb_tvlv_put_16be(msg, BSSGP_IE_BVC_MEASUREMENT, val);
+	}
+
+	if (gran) {
+		uint8_t val = *gran & 3;
+		msgb_tvlv_put(msg, BSSGP_IE_FLOW_CTRL_GRANULARITY, 1, &val);
+	}
+
+	return msg;
+}
+
+/*! Encode a FLOW-CONTROL-BVC-ACK PDU as per TS 48.018 Section 10.4.4.
+ *  \param[in] tag the tag IE value to encode
+ *  \returns encoded PDU or NULL in case of error */
+struct msgb *bssgp2_enc_fc_bvc_ack(uint8_t tag)
+{
+	struct msgb *msg = bssgp_msgb_alloc();
+	struct bssgp_normal_hdr *bgph;
+
+	if (!msg)
+		return NULL;
+
+	bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+	bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_BVC_ACK;
+
+	msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &tag);
+
+	return msg;
+}
+
+/*! Decode a FLOW-CONTROL-MS PDU as per TS 48.018 Section 10.4.6.
+ *  \param[out] fc caller-allocated memory for parsed output
+ *  \param[in] tp pre-parsed TLVs; caller must ensure mandatory IE presence/length
+ *  \returns 0 on success; negative in case of error */
+int bssgp2_dec_fc_ms(struct bssgp2_flow_ctrl *fc, struct tlv_parsed *tp)
+{
+	unsigned int granularity = 100;
+
+	/* optional "Flow Control Granularity IE" (11.3.102); applies to
+	 * bucket_size_max, bucket_leak_rate and PFC FC params IE */
+	if (TLVP_PRESENT(tp, BSSGP_IE_FLOW_CTRL_GRANULARITY)) {
+		uint8_t gran = *TLVP_VAL(tp, BSSGP_IE_FLOW_CTRL_GRANULARITY);
+		granularity = bssgp_fc_gran_tbl[gran & 3];
+	}
+
+	/* mandatory IEs */
+	fc->u.ms.tlli = tlvp_val32be(tp, BSSGP_IE_TLLI);
+	fc->tag = *TLVP_VAL(tp, BSSGP_IE_TAG);
+	fc->bucket_size_max = granularity * tlvp_val16be(tp, BSSGP_IE_MS_BUCKET_SIZE);
+	fc->bucket_leak_rate = (granularity * tlvp_val16be(tp, BSSGP_IE_BUCKET_LEAK_RATE)) / 8;
+
+	/* optional / conditional */
+	if (TLVP_PRESENT(tp, BSSGP_IE_BUCKET_FULL_RATIO)) {
+		fc->bucket_full_ratio_present = true;
+		fc->bucket_full_ratio = *TLVP_VAL(tp, BSSGP_IE_BUCKET_FULL_RATIO);
+	} else {
+		fc->bucket_full_ratio_present = false;
+	}
+
+	return 0;
+}
+
+/*! Encode a FLOW-CONTROL-MS PDU as per TS 48.018 Section 10.4.6.
+ *  \param[in] fc structure describing to-be-encoded FC parameters
+ *  \param[in] gran if non-NULL: Encode using specified unit granularity
+ *  \returns encoded PDU or NULL in case of error */
+struct msgb *bssgp2_enc_fc_ms(const struct bssgp2_flow_ctrl *fc, enum bssgp_fc_granularity *gran)
+{
+	struct msgb *msg = bssgp_msgb_alloc();
+	struct bssgp_normal_hdr *bgph;
+	unsigned int granularity = 100;
+
+	if (gran)
+		granularity = bssgp_fc_gran_tbl[*gran & 3];
+
+	if (!msg)
+		return NULL;
+
+	bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+	bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_MS;
+
+	msgb_tvlv_put_32be(msg, BSSGP_IE_TLLI, fc->u.ms.tlli);
+	msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &fc->tag);
+	msgb_tvlv_put_16be(msg, BSSGP_IE_MS_BUCKET_SIZE, fc->bucket_size_max / granularity);
+	msgb_tvlv_put_16be(msg, BSSGP_IE_BUCKET_LEAK_RATE, fc->bucket_leak_rate * 8 / granularity);
+
+	if (fc->bucket_full_ratio_present)
+		msgb_tvlv_put(msg, BSSGP_IE_BUCKET_FULL_RATIO, 1, &fc->bucket_full_ratio);
+
+	if (gran) {
+		uint8_t val = *gran & 3;
+		msgb_tvlv_put(msg, BSSGP_IE_FLOW_CTRL_GRANULARITY, 1, &val);
+	}
+
+	return msg;
+}
+
+/*! Encode a FLOW-CONTROL-BVC-ACK PDU as per TS 48.018 Section 10.4.7.
+ *  \param[in] tlli the TLLI IE value to encode
+ *  \param[in] tag the tag IE value to encode
+ *  \returns encoded PDU or NULL in case of error */
+struct msgb *bssgp2_enc_fc_ms_ack(uint32_t tlli, uint8_t tag)
+{
+	struct msgb *msg = bssgp_msgb_alloc();
+	struct bssgp_normal_hdr *bgph;
+
+	if (!msg)
+		return NULL;
+
+	bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+	bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_BVC_ACK;
+
+	msgb_tvlv_put_32be(msg, BSSGP_IE_TLLI, tlli);
+	msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &tag);
+
+	return msg;
+}