blob: 4f1445347591a337ebb4340b3ab2850adb51975f [file] [log] [blame]
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +01001
2/*
3 * minimal standalone network-side lap-d implementation
4 * oystein@homelien.no, 2009
5 */
6
7#include <stdio.h>
8#include <string.h>
9#include <assert.h>
10
11#include "lapd.h"
12#include "openbsc/debug.h"
13
14#define DEBUG_LAPD(f, args...) { printf("lapd "); printf(f, ## args); };
15
16typedef enum {
Harald Welte30fe6412011-02-04 20:34:08 +010017 LAPD_TEI_NONE = 0,
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +010018
19 LAPD_TEI_ASSIGNED,
20
21 LAPD_TEI_ACTIVE,
22} lapd_tei_state;
23
24const char *lapd_tei_states[] = {
25 "NONE",
26 "ASSIGNED",
27 "ACTIVE",
28};
29
30typedef enum {
Harald Welte30fe6412011-02-04 20:34:08 +010031 LAPD_TYPE_NONE = 0,
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +010032
33 LAPD_TYPE_I,
34 LAPD_TYPE_S,
35 LAPD_TYPE_U,
36} lapd_msg_type;
37
38typedef enum {
Harald Welte7e859bc2011-02-04 20:36:50 +010039 /* commands/responses */
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +010040 LAPD_CMD_NONE = 0,
41
42 LAPD_CMD_I,
43 LAPD_CMD_RR,
44 LAPD_CMD_RNR,
45 LAPD_CMD_REJ,
46
47 LAPD_CMD_SABME,
48 LAPD_CMD_DM,
49 LAPD_CMD_UI,
50 LAPD_CMD_DISC,
51 LAPD_CMD_UA,
52 LAPD_CMD_FRMR,
53 LAPD_CMD_XID,
54} lapd_cmd_type;
55
56const char *lapd_cmd_types[] = {
57 "NONE",
58
59 "I",
60 "RR",
61 "RNR",
62 "REJ",
63
64 "SABME",
65 "DM",
66 "UI",
67 "DISC",
68 "UA",
69 "FRMR",
70 "XID",
71
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +010072};
73
Harald Welte30fe6412011-02-04 20:34:08 +010074const char *lapd_msg_types = "?ISU";
Harald Welte7e859bc2011-02-04 20:36:50 +010075const int network_side = 1; /* 0 for user side */
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +010076
77typedef struct {
Harald Welte30fe6412011-02-04 20:34:08 +010078 int tei;
79 int sapi;
Harald Welte7e859bc2011-02-04 20:36:50 +010080 /* A valid N(R) value is one that is in the range V(A) ≤ N(R) ≤ V(S). */
81 int vs; /* next to be transmitted */
82 int va; /* last acked by peer */
83 int vr; /* next expected to be received */
Harald Welte30fe6412011-02-04 20:34:08 +010084 lapd_tei_state state;
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +010085} lapd_tei_t;
86
Harald Welte7e859bc2011-02-04 20:36:50 +010087/* 3.5.2.2 Send state variable V(S)
88 * Each point-to-point data link connection endpoint shall have an associated V(S) when using I frame
89 * commands. V(S) denotes the sequence number of the next I frame to be transmitted. The V(S) can
90 * take on the value 0 through n minus 1. The value of V(S) shall be incremented by 1 with each
91 * successive I frame transmission, and shall not exceed V(A) by more than the maximum number of
92 * outstanding I frames k. The value of k may be in the range of 1 ≤ k ≤ 127.
93 *
94 * 3.5.2.3 Acknowledge state variable V(A)
95 * Each point-to-point data link connection endpoint shall have an associated V(A) when using I frame
96 * commands and supervisory frame commands/responses. V(A) identifies the last I frame that has been
97 * acknowledged by its peer [V(A) − 1 equals the N(S) of the last acknowledged I frame]. V(A) can
98 * take on the value 0 through n minus 1. The value of V(A) shall be updated by the valid N(R) values
99 * received from its peer (see 3.5.2.6). A valid N(R) value is one that is in the range V(A) ≤ N(R) ≤
100 * V(S).
101 *
102 * 3.5.2.5 Receive state variable V(R)
103 * Each point-to-point data link connection endpoint shall have an associated V(R) when using I frame
104 * commands and supervisory frame commands/responses. V(R) denotes the sequence number of the
105 * next in-sequence I frame expected to be received. V(R) can take on the value 0 through n minus 1.
106 * The value of V(R) shall be incremented by one with the receipt of an error-free, in-sequence I frame
107 * whose N(S) equals V(R).
108 */
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100109#define LAPD_NS(teip) (teip->vs)
110#define LAPD_NR(teip) (teip->vr)
111
Harald Welte7e859bc2011-02-04 20:36:50 +0100112/* 3.5.2.4 Send sequence number N(S)
113 * Only I frames contain N(S), the send sequence number of transmitted I frames. At the time that an in-
114 * sequence I frame is designated for transmission, the value of N(S) is set equal to V(S).
115 *
116 * 3.5.2.6 Receive sequence number N(R)
117 * All I frames and supervisory frames contain N(R), the expected send sequence number of the next
118 * received I frame. At the time that a frame of the above types is designated for transmission, the value
119 * of N(R) is set equal to V(R). N(R) indicates that the data link layer entity transmitting the N(R) has
120 * correctly received all I frames numbered up to and including N(R) − 1.
121 */
Harald Welte30fe6412011-02-04 20:34:08 +0100122void (*lapd_transmit_cb) (uint8_t * data, int len, void *cbdata);
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100123
124static lapd_tei_t tei_list[] = {
Harald Welte30fe6412011-02-04 20:34:08 +0100125 {25, 62,},
126 {1, 0,},
127 {-1},
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100128};
129
Harald Welted2735292011-02-04 20:38:46 +0100130static lapd_tei_t *teip_from_tei(int tei)
Harald Welte30fe6412011-02-04 20:34:08 +0100131{
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100132 lapd_tei_t *p;
133 for (p = tei_list; p->tei != -1; p++) {
Harald Welte30fe6412011-02-04 20:34:08 +0100134 if (p->tei == tei)
135 return p;
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100136 };
137 return NULL;
138};
139
Harald Welted2735292011-02-04 20:38:46 +0100140static void lapd_tei_set_state(lapd_tei_t * teip, int newstate)
Harald Welte30fe6412011-02-04 20:34:08 +0100141{
142 DEBUG_LAPD("state change on tei %d: %s -> %s\n", teip->tei,
143 lapd_tei_states[teip->state], lapd_tei_states[newstate]);
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100144 teip->state = newstate;
145};
146
Harald Welted2735292011-02-04 20:38:46 +0100147static void lapd_tei_receive(uint8_t * data, int len, void *cbdata)
Harald Welte30fe6412011-02-04 20:34:08 +0100148{
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100149 //DEBUG_LAPD("tei receive %p, %d\n", data, len);
150 int entity = data[0];
Harald Welte30fe6412011-02-04 20:34:08 +0100151 int ref = data[1];
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100152 int mt = data[3];
Harald Welte30fe6412011-02-04 20:34:08 +0100153 int action = data[4] >> 1;
154 int e = data[4] & 1;
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100155 //DEBUG_LAPD("tei mgmt: entity %x, ref %x, mt %x, action %x, e %x\n", entity, ref, mt, action, e);
156
157 switch (mt) {
Harald Welte30fe6412011-02-04 20:34:08 +0100158 case 0x01:{ // identity request
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100159 int tei = action;
Harald Welte30fe6412011-02-04 20:34:08 +0100160 DEBUG_LAPD
161 ("tei mgmt: identity request, accepting tei %d\n",
162 tei);
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100163 //printf("tei: %d\n", tei);
164 uint8_t resp[8];
165 memmove(resp, "\xfe\xff\x03\x0f\x00\x00\x02\x00", 8);
166 resp[7] = (tei << 1) | 1;
167 lapd_transmit_cb(resp, 8, cbdata);
Harald Welte30fe6412011-02-04 20:34:08 +0100168 lapd_tei_t *teip = teip_from_tei(tei);
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100169 if (teip->state == LAPD_TEI_NONE)
170 lapd_tei_set_state(teip, LAPD_TEI_ASSIGNED);
Harald Welte30fe6412011-02-04 20:34:08 +0100171 break;
172 }
173 default:
174 DEBUG_LAPD("tei mgmt: unknown mt %x action %x\n", mt, action);
175 assert(0);
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100176 };
177};
178
Harald Welte30fe6412011-02-04 20:34:08 +0100179uint8_t *lapd_receive(uint8_t * data, int len, int *ilen, lapd_mph_type * prim,
180 void *cbdata)
181{
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100182#if 0
183 DEBUG_LAPD("receive %p, %d\n", data, len);
184 hexdump(data, len);
185#endif
186
187 *ilen = 0;
188 *prim = 0;
189
190 if (len < 2) {
191 DEBUG_LAPD("len %d < 2\n", len);
192 return NULL;
193 };
194
195 if ((data[0] & 1) != 0 || (data[1] & 1) != 1) {
Harald Welte30fe6412011-02-04 20:34:08 +0100196 DEBUG_LAPD("address field %x/%x not well formed\n", data[0],
197 data[1]);
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100198 return NULL;
199 };
200
Harald Welte30fe6412011-02-04 20:34:08 +0100201 int sapi = data[0] >> 2;
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100202 int cr = (data[0] >> 1) & 1;
203 int tei = data[1] >> 1;
204 int command = network_side ^ cr;
205 //DEBUG_LAPD(" address sapi %x tei %d cmd %d cr %d\n", sapi, tei, command, cr);
206
207 if (len < 3) {
Matthew Fredrickson69245a02010-03-15 12:24:39 -0500208 DEBUG_LAPD("len %d < 3\n", len);
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100209 return NULL;
210 };
211
212 lapd_msg_type typ = 0;
213 lapd_cmd_type cmd = 0;
Harald Welte30fe6412011-02-04 20:34:08 +0100214 int pf = -1;
215 int ns = -1;
216 int nr = -1;
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100217 if ((data[2] & 1) == 0) {
218 typ = LAPD_TYPE_I;
219 assert(len >= 4);
220 ns = data[2] >> 1;
221 nr = data[3] >> 1;
222 pf = data[3] & 1;
223 cmd = LAPD_CMD_I;
224 } else if ((data[2] & 3) == 1) {
225 typ = LAPD_TYPE_S;
226 assert(len >= 4);
227 nr = data[3] >> 1;
228 pf = data[3] & 1;
229 switch (data[2]) {
Harald Welte30fe6412011-02-04 20:34:08 +0100230 case 0x1:
231 cmd = LAPD_CMD_RR;
232 break;
233 case 0x5:
234 cmd = LAPD_CMD_RNR;
235 break;
236 case 0x9:
237 cmd = LAPD_CMD_REJ;
238 break;
239 default:
240 DEBUG_LAPD("unknown S cmd %x\n", data[2]);
241 assert(0);
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100242 };
243 } else if ((data[2] & 3) == 3) {
244 typ = LAPD_TYPE_U;
245 pf = (data[2] >> 4) & 1;
Harald Welte30fe6412011-02-04 20:34:08 +0100246 int val = data[2] & ~(1 << 4);
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100247 switch (val) {
Harald Welte30fe6412011-02-04 20:34:08 +0100248 case 0x6f:
249 cmd = LAPD_CMD_SABME;
250 break;
251 case 0x0f:
252 cmd = LAPD_CMD_DM;
253 break;
254 case 0x03:
255 cmd = LAPD_CMD_UI;
256 break;
257 case 0x43:
258 cmd = LAPD_CMD_DISC;
259 break;
260 case 0x63:
261 cmd = LAPD_CMD_UA;
262 break;
263 case 0x87:
264 cmd = LAPD_CMD_FRMR;
265 break;
266 case 0xaf:
267 cmd = LAPD_CMD_XID;
268 break;
269
270 default:
271 DEBUG_LAPD("unknown U cmd %x (pf %x data %x)\n", val,
272 pf, data[2]);
273 assert(0);
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100274 };
275 };
Harald Welte30fe6412011-02-04 20:34:08 +0100276
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100277 uint8_t *contents = &data[4];
Harald Welte30fe6412011-02-04 20:34:08 +0100278 if (typ == LAPD_TYPE_U)
279 contents--;
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100280 *ilen = len - (contents - data);
Harald Welte30fe6412011-02-04 20:34:08 +0100281
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100282 lapd_tei_t *teip = teip_from_tei(tei);
283 if (tei == 127)
284 lapd_tei_receive(contents, *ilen, cbdata);
285
Harald Welte30fe6412011-02-04 20:34:08 +0100286 DEBUG_LAPD
287 ("<- %c %s sapi %x tei %3d cmd %x pf %x ns %3d nr %3d ilen %d teip %p vs %d va %d vr %d len %d\n",
288 lapd_msg_types[typ], lapd_cmd_types[cmd], sapi, tei, command, pf,
289 ns, nr, *ilen, teip, teip ? teip->vs : -1, teip ? teip->va : -1,
290 teip ? teip->vr : -1, len);
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100291
292 if (teip) {
293 switch (cmd) {
Harald Welte30fe6412011-02-04 20:34:08 +0100294 case LAPD_CMD_I:{
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100295 if (ns != teip->vr) {
Harald Welte30fe6412011-02-04 20:34:08 +0100296 DEBUG_LAPD("ns %d != vr %d\n", ns,
297 teip->vr);
298 if (ns == ((teip->vr - 1) & 0x7f)) {
299 DEBUG_LAPD
300 ("DOUBLE FRAME, ignoring\n");
301 cmd = 0; // ignore
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100302 } else {
303 assert(0);
304 };
305 } else {
306 //printf("IN SEQUENCE\n");
Harald Welte30fe6412011-02-04 20:34:08 +0100307 teip->vr = (ns + 1) & 0x7f; // FIXME: hack!
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100308 };
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100309
Matthew Fredrickson69245a02010-03-15 12:24:39 -0500310 break;
Harald Welte30fe6412011-02-04 20:34:08 +0100311 }
312 case LAPD_CMD_UI:
313 break;
314 case LAPD_CMD_SABME:{
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100315 teip->vs = 0;
316 teip->vr = 0;
317 teip->va = 0;
318
319 // ua
320 uint8_t resp[8];
321 int l = 0;
322 resp[l++] = data[0];
323 resp[l++] = (tei << 1) | 1;
324 resp[l++] = 0x73;
325 lapd_transmit_cb(resp, l, cbdata);
Matthew Fredrickson69245a02010-03-15 12:24:39 -0500326 if (teip->state != LAPD_TEI_ACTIVE) {
327 if (teip->state == LAPD_TEI_ASSIGNED) {
Harald Welte30fe6412011-02-04 20:34:08 +0100328 lapd_tei_set_state(teip,
329 LAPD_TEI_ACTIVE);
Matthew Fredrickson69245a02010-03-15 12:24:39 -0500330 //printf("ASSIGNED and ACTIVE\n");
331 } else {
332#if 0
Harald Welte30fe6412011-02-04 20:34:08 +0100333 DEBUG_LAPD
334 ("rr in strange state, send rej\n");
Matthew Fredrickson69245a02010-03-15 12:24:39 -0500335
336 // rej
337 uint8_t resp[8];
338 int l = 0;
Harald Welte30fe6412011-02-04 20:34:08 +0100339 resp[l++] =
340 (teip->
341 sapi << 2) | (network_side
342 ? 0 : 2);
Matthew Fredrickson69245a02010-03-15 12:24:39 -0500343 resp[l++] = (tei << 1) | 1;
Harald Welte30fe6412011-02-04 20:34:08 +0100344 resp[l++] = 0x09; //rej
345 resp[l++] =
346 ((teip->vr + 1) << 1) | 0;
347 lapd_transmit_cb(resp, l,
348 cbdata);
349 pf = 0; // dont reply
Matthew Fredrickson69245a02010-03-15 12:24:39 -0500350#endif
351 };
352 };
Harald Welte30fe6412011-02-04 20:34:08 +0100353
Matthew Fredrickson69245a02010-03-15 12:24:39 -0500354 *prim = LAPD_MPH_ACTIVATE_IND;
Harald Welte30fe6412011-02-04 20:34:08 +0100355 break;
356 }
357 case LAPD_CMD_RR:{
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100358 teip->va = (nr & 0x7f);
Matthew Fredrickson69245a02010-03-15 12:24:39 -0500359#if 0
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100360 if (teip->state != LAPD_TEI_ACTIVE) {
361 if (teip->state == LAPD_TEI_ASSIGNED) {
Harald Welte30fe6412011-02-04 20:34:08 +0100362 lapd_tei_set_state(teip,
363 LAPD_TEI_ACTIVE);
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100364 *prim = LAPD_MPH_ACTIVATE_IND;
365 //printf("ASSIGNED and ACTIVE\n");
366 } else {
367#if 0
Harald Welte30fe6412011-02-04 20:34:08 +0100368 DEBUG_LAPD
369 ("rr in strange state, send rej\n");
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100370
371 // rej
372 uint8_t resp[8];
373 int l = 0;
Harald Welte30fe6412011-02-04 20:34:08 +0100374 resp[l++] =
375 (teip->
376 sapi << 2) | (network_side
377 ? 0 : 2);
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100378 resp[l++] = (tei << 1) | 1;
Harald Welte30fe6412011-02-04 20:34:08 +0100379 resp[l++] = 0x09; //rej
380 resp[l++] =
381 ((teip->vr + 1) << 1) | 0;
382 lapd_transmit_cb(resp, l,
383 cbdata);
384 pf = 0; // dont reply
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100385#endif
386 };
387 };
Matthew Fredrickson69245a02010-03-15 12:24:39 -0500388#endif
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100389 if (pf) {
390 // interrogating us, send rr
391 uint8_t resp[8];
392 int l = 0;
393 resp[l++] = data[0];
394 resp[l++] = (tei << 1) | 1;
Harald Welte30fe6412011-02-04 20:34:08 +0100395 resp[l++] = 0x01; // rr
396 resp[l++] = (LAPD_NR(teip) << 1) | (data[3] & 1); // pf bit from req
397
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100398 lapd_transmit_cb(resp, l, cbdata);
Harald Welte30fe6412011-02-04 20:34:08 +0100399
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100400 };
401
Harald Welte30fe6412011-02-04 20:34:08 +0100402 break;
403 }
404 case LAPD_CMD_FRMR:{
405 // frame reject
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100406#if 0
407 if (teip->state == LAPD_TEI_ACTIVE)
408 *prim = LAPD_MPH_DEACTIVATE_IND;
409 lapd_tei_set_state(teip, LAPD_TEI_ASSIGNED);
410#endif
411 DEBUG_LAPD("frame reject, ignoring\n");
412 assert(0);
Harald Welte30fe6412011-02-04 20:34:08 +0100413 break;
414 }
415 case LAPD_CMD_DISC:{
416 // disconnect
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100417 uint8_t resp[8];
418 int l = 0;
419 resp[l++] = data[0];
420 resp[l++] = (tei << 1) | 1;
421 resp[l++] = 0x73;
422 lapd_transmit_cb(resp, l, cbdata);
423 lapd_tei_set_state(teip, LAPD_TEI_NONE);
Harald Welte30fe6412011-02-04 20:34:08 +0100424 break;
425 }
426 default:
427 DEBUG_LAPD("unknown cmd for tei %d (cmd %x)\n", tei,
428 cmd);
429 assert(0);
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100430 };
431 };
432
433 //if ((*prim == 0) && (ilen > 0) && (typ != LAPD_TYPE_S)) {
434 //if (cmd == LAPD_CMD_I) {
435 if (typ == LAPD_TYPE_I) {
436 // send rr
437 // Thu Jan 22 19:17:13 2009 <4000> sangoma.c:340 read (62/25) 4: fa 33 01 0a
438 // lapd <- S RR sapi 3e tei 25 cmd 0 pf 0 ns -1 nr 5 ilen 0 teip 0x613800 vs 7 va 5 vr 2 len 4
439
440 // interrogating us, send rr
Matthew Fredrickson69245a02010-03-15 12:24:39 -0500441 DEBUG_LAPD("Sending RR response\n");
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100442 uint8_t resp[8];
443 int l = 0;
444 resp[l++] = data[0];
445 resp[l++] = (tei << 1) | 1;
Harald Welte30fe6412011-02-04 20:34:08 +0100446 resp[l++] = 0x01; // rr
447 resp[l++] = (LAPD_NR(teip) << 1) | (data[3] & 1); // pf bit from req
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100448
449 lapd_transmit_cb(resp, l, cbdata);
450
Matthew Fredrickson69245a02010-03-15 12:24:39 -0500451 if (cmd != 0) {
452 *prim = LAPD_DL_DATA_IND;
453 return contents;
454 }
455 } else if (tei != 127 && typ == LAPD_TYPE_U && cmd == LAPD_CMD_UI) {
456 *prim = LAPD_DL_UNITDATA_IND;
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100457 return contents;
Matthew Fredrickson69245a02010-03-15 12:24:39 -0500458 }
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100459
460 return NULL;
461};
462
Harald Welte30fe6412011-02-04 20:34:08 +0100463void lapd_transmit(int tei, uint8_t * data, int len, void *cbdata)
464{
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100465 //printf("lapd_transmit %d, %d\n", tei, len);
466 //hexdump(data, len);
467 lapd_tei_t *teip = teip_from_tei(tei);
468 //printf("teip %p\n", teip);
469
470 // prepend stuff
Harald Welte30fe6412011-02-04 20:34:08 +0100471 uint8_t buf[10000];
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100472 memset(buf, 0, sizeof(buf));
Harald Welte30fe6412011-02-04 20:34:08 +0100473 memmove(buf + 4, data, len);
Matthew Fredricksonbc6649e2010-02-16 22:01:36 +0100474 len += 4;
475
476 buf[0] = (teip->sapi << 2) | (network_side ? 2 : 0);
477 buf[1] = (teip->tei << 1) | 1;
478 buf[2] = (LAPD_NS(teip) << 1);
479 buf[3] = (LAPD_NR(teip) << 1) | 0;
480
481 teip->vs = (teip->vs + 1) & 0x7f;
482
483 lapd_transmit_cb(buf, len, cbdata);
484};