blob: 104fe08e1a7b01e4cfaefb999f928a48d90a0459 [file] [log] [blame]
Harald Welte17a892f2020-12-07 21:39:03 +01001/* BSSGP2 - second generation of BSSGP library */
2
3/* (C) 2020 Harald Welte <laforge@gnumonks.org>
4 *
5 * All Rights Reserved
6 *
7 * SPDX-License-Identifier: GPL-2.0+
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 *
22 */
23
24#include <osmocom/core/utils.h>
25#include <osmocom/core/byteswap.h>
26#include <osmocom/core/msgb.h>
27
28#include <osmocom/gsm/gsm48.h>
29#include <osmocom/gsm/tlv.h>
30
31#include <osmocom/gprs/gprs_ns2.h>
32#include <osmocom/gprs/gprs_bssgp.h>
33#include <osmocom/gprs/gprs_bssgp2.h>
34
35
36/*! transmit BSSGP PDU over NS (PTP BVC)
37 * \param[in] nsi NS Instance through which to transmit
38 * \param[in] nsei NSEI of NSE through which to transmit
39 * \param[in] bvci BVCI through which to transmit
40 * \param[in] msg BSSGP PDU to transmit
41 * \returns 0 on success; negative on error */
42int bssgp2_nsi_tx_ptp(struct gprs_ns2_inst *nsi, uint16_t nsei, uint16_t bvci,
43 struct msgb *msg, uint32_t lsp)
44{
45 struct osmo_gprs_ns2_prim nsp = {};
46 int rc;
47
48 if (!msg)
49 return 0;
50
51 nsp.bvci = bvci;
52 nsp.nsei = nsei;
53 nsp.u.unitdata.link_selector = lsp;
54
Alexander Couzens138b96f2021-01-25 16:23:29 +010055 osmo_prim_init(&nsp.oph, SAP_NS, GPRS_NS2_PRIM_UNIT_DATA, PRIM_OP_REQUEST, msg);
Harald Welte17a892f2020-12-07 21:39:03 +010056 rc = gprs_ns2_recv_prim(nsi, &nsp.oph);
57
58 return rc;
59}
60
61/*! transmit BSSGP PDU over NS (SIGNALING BVC)
62 * \param[in] nsi NS Instance through which to transmit
63 * \param[in] nsei NSEI of NSE through which to transmit
64 * \param[in] msg BSSGP PDU to transmit
65 * \returns 0 on success; negative on error */
66int bssgp2_nsi_tx_sig(struct gprs_ns2_inst *nsi, uint16_t nsei, struct msgb *msg, uint32_t lsp)
67{
68 return bssgp2_nsi_tx_ptp(nsi, nsei, 0, msg, lsp);
69}
70
71/*! Encode BSSGP BVC-BLOCK PDU as per TS 48.018 Section 10.4.8. */
72struct msgb *bssgp2_enc_bvc_block(uint16_t bvci, enum gprs_bssgp_cause cause)
73{
74 struct msgb *msg = bssgp_msgb_alloc();
75 struct bssgp_normal_hdr *bgph;
76 uint16_t _bvci = osmo_htons(bvci);
77
78 if (!msg)
79 return NULL;
80
81 bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
82 bgph->pdu_type = BSSGP_PDUT_BVC_BLOCK;
83
84 msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
85 msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, (uint8_t *) &cause);
86
87 return msg;
88}
89
90/*! Encode BSSGP BVC-BLOCK-ACK PDU as per TS 48.018 Section 10.4.9. */
91struct msgb *bssgp2_enc_bvc_block_ack(uint16_t bvci)
92{
93 struct msgb *msg = bssgp_msgb_alloc();
94 struct bssgp_normal_hdr *bgph;
95 uint16_t _bvci = osmo_htons(bvci);
96
97 if (!msg)
98 return NULL;
99
100 bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
101 bgph->pdu_type = BSSGP_PDUT_BVC_BLOCK_ACK;
102
103 msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
104
105 return msg;
106}
107
108/*! Encode BSSGP BVC-UNBLOCK PDU as per TS 48.018 Section 10.4.10. */
109struct msgb *bssgp2_enc_bvc_unblock(uint16_t bvci)
110{
111 struct msgb *msg = bssgp_msgb_alloc();
112 struct bssgp_normal_hdr *bgph;
113 uint16_t _bvci = osmo_htons(bvci);
114
115 if (!msg)
116 return NULL;
117
118 bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
119 bgph->pdu_type = BSSGP_PDUT_BVC_UNBLOCK;
120
121 msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
122
123 return msg;
124}
125
126/*! Encode BSSGP BVC-UNBLOCK-ACK PDU as per TS 48.018 Section 10.4.11. */
127struct msgb *bssgp2_enc_bvc_unblock_ack(uint16_t bvci)
128{
129 struct msgb *msg = bssgp_msgb_alloc();
130 struct bssgp_normal_hdr *bgph;
131 uint16_t _bvci = osmo_htons(bvci);
132
133 if (!msg)
134 return NULL;
135
136 bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
137 bgph->pdu_type = BSSGP_PDUT_BVC_UNBLOCK_ACK;
138
139 msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
140
141 return msg;
142}
143
144/*! Encode BSSGP BVC-RESET PDU as per TS 48.018 Section 10.4.12.
145 * \param[in] bvci PTP BVCI to encode into the BVCI IE
146 * \param[in] cause BSSGP Cause value (reason for reset)
147 * \param[in] ra_id Routing Area ID to be encoded to CELL_ID IE (optional)
148 * \param[in] cell_id Cell ID to be encoded to CELL_ID IE (only if ra_id is non-NULL)
149 * \param[in] feat_bm Feature Bitmap (optional)
150 * \param[in] ext_feat_bm Extended Feature Bitmap (optional) */
151struct msgb *bssgp2_enc_bvc_reset(uint16_t bvci, enum gprs_bssgp_cause cause,
152 const struct gprs_ra_id *ra_id, uint16_t cell_id,
153 const uint8_t *feat_bm, const uint8_t *ext_feat_bm)
154{
155 struct msgb *msg = bssgp_msgb_alloc();
156 struct bssgp_normal_hdr *bgph;
157 uint16_t _bvci = osmo_htons(bvci);
158
159 if (!msg)
160 return NULL;
161
162 bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
163 bgph->pdu_type = BSSGP_PDUT_BVC_RESET;
164
165 msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
166 msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, (uint8_t *) &cause);
167 if (ra_id) {
168 uint8_t bssgp_cid[8];
169 bssgp_create_cell_id(bssgp_cid, ra_id, cell_id);
170 msgb_tvlv_put(msg, BSSGP_IE_CELL_ID, sizeof(bssgp_cid), bssgp_cid);
171 }
172
173 if (feat_bm)
174 msgb_tvlv_put(msg, BSSGP_IE_FEATURE_BITMAP, 1, feat_bm);
175
176 if (ext_feat_bm)
177 msgb_tvlv_put(msg, BSSGP_IE_EXT_FEATURE_BITMAP, 1, feat_bm);
178
179 return msg;
180}
181
182/*! Encode BSSGP BVC-RESET-ACK PDU as per TS 48.018 Section 10.4.13.
183 * \param[in] bvci PTP BVCI to encode into the BVCI IE
184 * \param[in] ra_id Routing Area ID to be encoded to CELL_ID IE (optional)
185 * \param[in] cell_id Cell ID to be encoded to CELL_ID IE (only if ra_id is non-NULL)
186 * \param[in] feat_bm Feature Bitmap (optional)
187 * \param[in] ext_feat_bm Extended Feature Bitmap (optional) */
188struct msgb *bssgp2_enc_bvc_reset_ack(uint16_t bvci, const struct gprs_ra_id *ra_id, uint16_t cell_id,
189 const uint8_t *feat_bm, const uint8_t *ext_feat_bm)
190{
191 struct msgb *msg = bssgp_msgb_alloc();
192 struct bssgp_normal_hdr *bgph;
193 uint16_t _bvci = osmo_htons(bvci);
194
195 if (!msg)
196 return NULL;
197
198 bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
199 bgph->pdu_type = BSSGP_PDUT_BVC_RESET_ACK;
200
201 msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
202 if (ra_id) {
203 uint8_t bssgp_cid[8];
204 bssgp_create_cell_id(bssgp_cid, ra_id, cell_id);
205 msgb_tvlv_put(msg, BSSGP_IE_CELL_ID, sizeof(bssgp_cid), bssgp_cid);
206 }
207
208 if (feat_bm)
209 msgb_tvlv_put(msg, BSSGP_IE_FEATURE_BITMAP, 1, feat_bm);
210
211 if (ext_feat_bm)
212 msgb_tvlv_put(msg, BSSGP_IE_EXT_FEATURE_BITMAP, 1, feat_bm);
213
214 return msg;
215}
216
217/*! Encode BSSGP STATUS PDU as per TS 48.018 Section 10.4.14.
218 * \param[in] cause BSSGP Cause value
219 * \param[in] bvci optional BVCI - only encoded if non-NULL
Daniel Willmannfa632b82021-02-12 01:57:52 +0100220 * \param[in] msg optional message buffer containing PDU in error - only encoded if non-NULL
221 * \param[in] max_pdu_len Maximum BSSGP PDU size the NS layer accepts */
222struct msgb *bssgp2_enc_status(uint8_t cause, const uint16_t *bvci, const struct msgb *orig_msg, uint16_t max_pdu_len)
Harald Welte17a892f2020-12-07 21:39:03 +0100223{
224 struct msgb *msg = bssgp_msgb_alloc();
225 struct bssgp_normal_hdr *bgph;
226
227 if (!msg)
228 return NULL;
229
230 bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
231 bgph->pdu_type = BSSGP_PDUT_STATUS;
232 msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, &cause);
Daniel Willmannfa632b82021-02-12 01:57:52 +0100233 /* FIXME: Require/encode BVCI only if cause is BVCI unknown/blocked
234 * See 3GPP TS 48.018 Ch. 10.4.14 */
Harald Welte17a892f2020-12-07 21:39:03 +0100235 if (bvci) {
236 uint16_t _bvci = osmo_htons(*bvci);
237 msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
238 }
Daniel Willmannfa632b82021-02-12 01:57:52 +0100239 if (orig_msg) {
240 uint32_t orig_len, max_orig_len;
241 /* Calculate how big the reply would be: the BSSGP msg so far + size of the PDU IN ERROR including tvl */
242 orig_len = msgb_bssgp_len(orig_msg);
243 max_orig_len = msgb_length(msg) + TVLV_GROSS_LEN(orig_len);
244 /* Truncate the difference between max_orig_len and mtu */
245 if (max_orig_len > max_pdu_len)
246 orig_len -= max_orig_len - max_pdu_len;
247 msgb_tvlv_put(msg, BSSGP_IE_PDU_IN_ERROR, orig_len, msgb_bssgph(orig_msg));
248 }
Harald Welte17a892f2020-12-07 21:39:03 +0100249
250 return msg;
251}
Harald Welte4394bb92020-12-08 20:40:44 +0100252
253static const unsigned int bssgp_fc_gran_tbl[] = {
254 [BSSGP_FC_GRAN_100] = 100,
255 [BSSGP_FC_GRAN_1000] = 1000,
256 [BSSGP_FC_GRAN_10000] = 10000,
257 [BSSGP_FC_GRAN_100000] = 100000,
258};
259
260/*! Decode a FLOW-CONTROL-BVC PDU as per TS 48.018 Section 10.4.4.
261 * \param[out] fc caller-allocated memory for parsed output
262 * \param[in] tp pre-parsed TLVs; caller must ensure mandatory IE presence/length
263 * \returns 0 on success; negative in case of error */
264int bssgp2_dec_fc_bvc(struct bssgp2_flow_ctrl *fc, const struct tlv_parsed *tp)
265{
266 unsigned int granularity = 100;
267
268 /* optional "Flow Control Granularity IE" (11.3.102); applies to
269 * bucket_size_max, bucket_leak_rate and PFC FC params IE */
270 if (TLVP_PRESENT(tp, BSSGP_IE_FLOW_CTRL_GRANULARITY)) {
271 uint8_t gran = *TLVP_VAL(tp, BSSGP_IE_FLOW_CTRL_GRANULARITY);
272 granularity = bssgp_fc_gran_tbl[gran & 3];
273 }
274
275 /* mandatory IEs */
276 fc->tag = *TLVP_VAL(tp, BSSGP_IE_TAG);
277 fc->bucket_size_max = granularity * tlvp_val16be(tp, BSSGP_IE_BVC_BUCKET_SIZE);
278 fc->bucket_leak_rate = (granularity * tlvp_val16be(tp, BSSGP_IE_BUCKET_LEAK_RATE)) / 8;
279 fc->u.bvc.bmax_default_ms = granularity * tlvp_val16be(tp, BSSGP_IE_BMAX_DEFAULT_MS);
280 fc->u.bvc.r_default_ms = (granularity * tlvp_val16be(tp, BSSGP_IE_R_DEFAULT_MS)) / 8;
281
282 /* optional / conditional */
283 if (TLVP_PRESENT(tp, BSSGP_IE_BUCKET_FULL_RATIO)) {
284 fc->bucket_full_ratio_present = true;
285 fc->bucket_full_ratio = *TLVP_VAL(tp, BSSGP_IE_BUCKET_FULL_RATIO);
286 } else {
287 fc->bucket_full_ratio_present = false;
288 }
289
290 if (TLVP_PRESENT(tp, BSSGP_IE_BVC_MEASUREMENT)) {
291 uint16_t val = tlvp_val16be(tp, BSSGP_IE_BVC_MEASUREMENT);
292 fc->u.bvc.measurement_present = true;
293 /* convert from centi-seconds to milli-seconds */
294 if (val == 0xffff)
295 fc->u.bvc.measurement = 0xffffffff;
296 else
297 fc->u.bvc.measurement = val * 10;
298 } else {
299 fc->u.bvc.measurement_present = false;
300 }
301
302 return 0;
303
304}
305
306/*! Encode a FLOW-CONTROL-BVC PDU as per TS 48.018 Section 10.4.4.
307 * \param[in] fc structure describing to-be-encoded FC parameters
308 * \param[in] gran if non-NULL: Encode using specified unit granularity
309 * \returns encoded PDU or NULL in case of error */
310struct msgb *bssgp2_enc_fc_bvc(const struct bssgp2_flow_ctrl *fc, enum bssgp_fc_granularity *gran)
311{
312 struct msgb *msg = bssgp_msgb_alloc();
313 struct bssgp_normal_hdr *bgph;
314 unsigned int granularity = 100;
315
316 if (gran)
317 granularity = bssgp_fc_gran_tbl[*gran & 3];
318
319 if (!msg)
320 return NULL;
321
322 bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
323 bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_BVC;
324
325 msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &fc->tag);
326 msgb_tvlv_put_16be(msg, BSSGP_IE_BVC_BUCKET_SIZE, fc->bucket_size_max / granularity);
327 msgb_tvlv_put_16be(msg, BSSGP_IE_BUCKET_LEAK_RATE, fc->bucket_leak_rate * 8 / granularity);
328 msgb_tvlv_put_16be(msg, BSSGP_IE_BMAX_DEFAULT_MS, fc->u.bvc.bmax_default_ms / granularity);
329 msgb_tvlv_put_16be(msg, BSSGP_IE_R_DEFAULT_MS, fc->u.bvc.r_default_ms * 8 / granularity);
330
331 if (fc->bucket_full_ratio_present)
332 msgb_tvlv_put(msg, BSSGP_IE_BUCKET_FULL_RATIO, 1, &fc->bucket_full_ratio);
333
334 if (fc->u.bvc.measurement_present) {
335 uint16_t val;
336 /* convert from ms to cs */
337 if (fc->u.bvc.measurement == 0xffffffff)
338 val = 0xffff;
339 else
340 val = fc->u.bvc.measurement / 10;
341 msgb_tvlv_put_16be(msg, BSSGP_IE_BVC_MEASUREMENT, val);
342 }
343
344 if (gran) {
345 uint8_t val = *gran & 3;
346 msgb_tvlv_put(msg, BSSGP_IE_FLOW_CTRL_GRANULARITY, 1, &val);
347 }
348
349 return msg;
350}
351
Alexander Couzens71ad5302022-09-28 16:22:29 +0200352/*! Encode BSSGP FLUSH-LL PDU as per TS 48.018 Section 10.4.1.
353 * \param[in] tlli - the TLLI of the MS
354 * \param[in] old_bvci BVCI
355 * \param[in] new_bvci2 optional BVCI - only encoded if non-NULL
356 * \param[in] nsei optional - only encoded if non-NULL
357 * \returns encoded PDU or NULL in case of error */
358struct msgb *bssgp2_enc_flush_ll(uint32_t tlli, uint16_t old_bvci,
359 const uint16_t *new_bvci, const uint16_t *nsei)
360{
361 struct msgb *msg = bssgp_msgb_alloc();
362 struct bssgp_normal_hdr *bgph;
363
364 if (!msg)
365 return NULL;
366
367 bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
368 bgph->pdu_type = BSSGP_PDUT_FLUSH_LL;
369
370 msgb_tvlv_put_32be(msg, BSSGP_IE_TLLI, tlli);
371 msgb_tvlv_put_16be(msg, BSSGP_IE_BVCI, old_bvci);
372 if (new_bvci)
373 msgb_tvlv_put_16be(msg, BSSGP_IE_BVCI, *new_bvci);
374
375 if (nsei)
376 msgb_tvlv_put_16be(msg, BSSGP_IE_BVCI, *nsei);
377
378 return msg;
379}
380
Harald Welte4394bb92020-12-08 20:40:44 +0100381/*! Encode a FLOW-CONTROL-BVC-ACK PDU as per TS 48.018 Section 10.4.4.
382 * \param[in] tag the tag IE value to encode
383 * \returns encoded PDU or NULL in case of error */
384struct msgb *bssgp2_enc_fc_bvc_ack(uint8_t tag)
385{
386 struct msgb *msg = bssgp_msgb_alloc();
387 struct bssgp_normal_hdr *bgph;
388
389 if (!msg)
390 return NULL;
391
392 bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
393 bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_BVC_ACK;
394
395 msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &tag);
396
397 return msg;
398}
399
400/*! Decode a FLOW-CONTROL-MS PDU as per TS 48.018 Section 10.4.6.
401 * \param[out] fc caller-allocated memory for parsed output
402 * \param[in] tp pre-parsed TLVs; caller must ensure mandatory IE presence/length
403 * \returns 0 on success; negative in case of error */
404int bssgp2_dec_fc_ms(struct bssgp2_flow_ctrl *fc, struct tlv_parsed *tp)
405{
406 unsigned int granularity = 100;
407
408 /* optional "Flow Control Granularity IE" (11.3.102); applies to
409 * bucket_size_max, bucket_leak_rate and PFC FC params IE */
410 if (TLVP_PRESENT(tp, BSSGP_IE_FLOW_CTRL_GRANULARITY)) {
411 uint8_t gran = *TLVP_VAL(tp, BSSGP_IE_FLOW_CTRL_GRANULARITY);
412 granularity = bssgp_fc_gran_tbl[gran & 3];
413 }
414
415 /* mandatory IEs */
416 fc->u.ms.tlli = tlvp_val32be(tp, BSSGP_IE_TLLI);
417 fc->tag = *TLVP_VAL(tp, BSSGP_IE_TAG);
418 fc->bucket_size_max = granularity * tlvp_val16be(tp, BSSGP_IE_MS_BUCKET_SIZE);
419 fc->bucket_leak_rate = (granularity * tlvp_val16be(tp, BSSGP_IE_BUCKET_LEAK_RATE)) / 8;
420
421 /* optional / conditional */
422 if (TLVP_PRESENT(tp, BSSGP_IE_BUCKET_FULL_RATIO)) {
423 fc->bucket_full_ratio_present = true;
424 fc->bucket_full_ratio = *TLVP_VAL(tp, BSSGP_IE_BUCKET_FULL_RATIO);
425 } else {
426 fc->bucket_full_ratio_present = false;
427 }
428
429 return 0;
430}
431
432/*! Encode a FLOW-CONTROL-MS PDU as per TS 48.018 Section 10.4.6.
433 * \param[in] fc structure describing to-be-encoded FC parameters
434 * \param[in] gran if non-NULL: Encode using specified unit granularity
435 * \returns encoded PDU or NULL in case of error */
436struct msgb *bssgp2_enc_fc_ms(const struct bssgp2_flow_ctrl *fc, enum bssgp_fc_granularity *gran)
437{
438 struct msgb *msg = bssgp_msgb_alloc();
439 struct bssgp_normal_hdr *bgph;
440 unsigned int granularity = 100;
441
442 if (gran)
443 granularity = bssgp_fc_gran_tbl[*gran & 3];
444
445 if (!msg)
446 return NULL;
447
448 bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
449 bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_MS;
450
451 msgb_tvlv_put_32be(msg, BSSGP_IE_TLLI, fc->u.ms.tlli);
452 msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &fc->tag);
453 msgb_tvlv_put_16be(msg, BSSGP_IE_MS_BUCKET_SIZE, fc->bucket_size_max / granularity);
454 msgb_tvlv_put_16be(msg, BSSGP_IE_BUCKET_LEAK_RATE, fc->bucket_leak_rate * 8 / granularity);
455
456 if (fc->bucket_full_ratio_present)
457 msgb_tvlv_put(msg, BSSGP_IE_BUCKET_FULL_RATIO, 1, &fc->bucket_full_ratio);
458
459 if (gran) {
460 uint8_t val = *gran & 3;
461 msgb_tvlv_put(msg, BSSGP_IE_FLOW_CTRL_GRANULARITY, 1, &val);
462 }
463
464 return msg;
465}
466
467/*! Encode a FLOW-CONTROL-BVC-ACK PDU as per TS 48.018 Section 10.4.7.
468 * \param[in] tlli the TLLI IE value to encode
469 * \param[in] tag the tag IE value to encode
470 * \returns encoded PDU or NULL in case of error */
471struct msgb *bssgp2_enc_fc_ms_ack(uint32_t tlli, uint8_t tag)
472{
473 struct msgb *msg = bssgp_msgb_alloc();
474 struct bssgp_normal_hdr *bgph;
475
476 if (!msg)
477 return NULL;
478
479 bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
Harald Welte1fcfce82020-12-08 21:15:45 +0100480 bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_MS_ACK;
Harald Welte4394bb92020-12-08 20:40:44 +0100481
482 msgb_tvlv_put_32be(msg, BSSGP_IE_TLLI, tlli);
483 msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &tag);
484
485 return msg;
486}