1 /*
2  * camsession.c - GStreamer CAM (EN50221) Session Layer
3  * Copyright (C) 2007 Alessandro Decina
4  *
5  * Authors:
6  *   Alessandro Decina <alessandro.d@gmail.com>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  */
23 
24 #include "camsession.h"
25 
26 #define GST_CAT_DEFAULT cam_debug_cat
27 #define I_TAG 0
28 #define I_LENGTH_FB 1
29 
30 #define TAG_SESSION_NUMBER 0x90
31 #define TAG_OPEN_SESSION_REQUEST 0x91
32 #define TAG_OPEN_SESSION_RESPONSE 0x92
33 #define TAG_CREATE_SESSION 0x93
34 #define TAG_CREATE_SESSION_RESPONSE 0x94
35 #define TAG_CLOSE_SESSION_REQUEST 0x95
36 #define TAG_CLOSE_SESSION_RESPONSE 0x96
37 
38 static CamReturn connection_data_cb (CamTL * tl, CamTLConnection * connection,
39     guint8 * spdu, guint spdu_length);
40 
41 static CamSLSession *
cam_sl_session_new(CamSL * sl,CamTLConnection * connection,guint16 session_nb,guint resource_id)42 cam_sl_session_new (CamSL * sl, CamTLConnection * connection,
43     guint16 session_nb, guint resource_id)
44 {
45   CamSLSession *session = g_new0 (CamSLSession, 1);
46 
47   session->state = CAM_SL_SESSION_STATE_IDLE;
48   session->sl = sl;
49   session->connection = connection;
50   session->session_nb = session_nb;
51   session->resource_id = resource_id;
52 
53   return session;
54 }
55 
56 static void
cam_sl_session_destroy(CamSLSession * session)57 cam_sl_session_destroy (CamSLSession * session)
58 {
59   g_free (session);
60 }
61 
62 CamSL *
cam_sl_new(CamTL * tl)63 cam_sl_new (CamTL * tl)
64 {
65   CamSL *sl = g_new0 (CamSL, 1);
66 
67   sl->sessions = g_hash_table_new_full (g_direct_hash, g_direct_equal,
68       NULL, (GDestroyNotify) cam_sl_session_destroy);
69 
70   tl->user_data = sl;
71   tl->connection_data = connection_data_cb;
72 
73   return sl;
74 }
75 
76 void
cam_sl_destroy(CamSL * sl)77 cam_sl_destroy (CamSL * sl)
78 {
79   g_hash_table_destroy (sl->sessions);
80 
81   g_free (sl);
82 }
83 
84 CamReturn
cam_sl_create_session(CamSL * sl,CamTLConnection * connection,guint resource_id,CamSLSession ** out_session)85 cam_sl_create_session (CamSL * sl,
86     CamTLConnection * connection, guint resource_id,
87     CamSLSession ** out_session)
88 {
89   CamReturn ret;
90   CamSLSession *session = NULL;
91   guint size;
92   guint offset;
93   guint8 *tpdu = NULL;
94   guint8 *spdu;
95   guint16 session_nb;
96 
97   /* FIXME: implement session number allocations properly */
98   if (sl->session_ids == G_MAXUINT16)
99     return CAM_RETURN_SESSION_TOO_MANY_SESSIONS;
100 
101   session_nb = ++sl->session_ids;
102   session = cam_sl_session_new (sl, connection, session_nb, resource_id);
103 
104   /* SPDU layout (8 bytes):
105    * TAG_CREATE_SESSION 1 byte
106    * length_field () 1 byte
107    * resource_id 4 bytes
108    * session_nb 2 bytes
109    */
110 
111   /* get TPDU size */
112   cam_tl_calc_buffer_size (sl->tl, 8, &size, &offset);
113 
114   tpdu = (guint8 *) g_malloc (size);
115   spdu = tpdu + offset;
116 
117   /* SPDU header */
118   /* tag */
119   spdu[0] = TAG_CREATE_SESSION;
120   /* fixed length_field */
121   spdu[1] = 6;
122 
123   /* SPDU body */
124   /* resource id */
125   GST_WRITE_UINT32_BE (&spdu[2], resource_id);
126   /* session_nb */
127   GST_WRITE_UINT16_BE (&spdu[6], session_nb);
128 
129   /* write the TPDU */
130   ret = cam_tl_connection_write (session->connection, tpdu, size, 8);
131   if (CAM_FAILED (ret))
132     goto error;
133 
134   *out_session = session;
135 
136   g_free (tpdu);
137   return CAM_RETURN_OK;
138 
139 error:
140   if (session)
141     cam_sl_session_destroy (session);
142 
143   g_free (tpdu);
144 
145   return ret;
146 }
147 
148 /* send a TAG_CLOSE_SESSION SPDU */
149 CamReturn
cam_sl_session_close(CamSLSession * session)150 cam_sl_session_close (CamSLSession * session)
151 {
152   CamReturn ret;
153   guint size;
154   guint offset;
155   guint8 *tpdu = NULL;
156   guint8 *spdu;
157   CamSL *sl = session->sl;
158 
159   /* SPDU layout (4 bytes):
160    * TAG_CLOSE_SESSION 1 byte
161    * length_field () 1 byte
162    * session_nb 2 bytes
163    */
164 
165   /* get the size of the TPDU */
166   cam_tl_calc_buffer_size (sl->tl, 4, &size, &offset);
167 
168   tpdu = (guint8 *) g_malloc (size);
169   /* the spdu header starts after the TPDU headers */
170   spdu = tpdu + offset;
171 
172   /* SPDU header */
173   /* tag */
174   spdu[0] = TAG_CLOSE_SESSION_REQUEST;
175   /* fixed length_field */
176   spdu[1] = 2;
177   /* SPDU body */
178   /* session_nb */
179   GST_WRITE_UINT16_BE (&spdu[2], session->session_nb);
180 
181   /* write the TPDU */
182   ret = cam_tl_connection_write (session->connection, tpdu, size, 4);
183   if (CAM_FAILED (ret))
184     goto error;
185 
186   session->state = CAM_SL_SESSION_STATE_CLOSING;
187 
188   g_free (tpdu);
189 
190   return CAM_RETURN_OK;
191 
192 error:
193   g_free (tpdu);
194 
195   return ret;
196 }
197 
198 void
cam_sl_calc_buffer_size(CamSL * sl,guint body_length,guint * buffer_size,guint * offset)199 cam_sl_calc_buffer_size (CamSL * sl, guint body_length,
200     guint * buffer_size, guint * offset)
201 {
202   /* an APDU is sent in a SESSION_NUMBER SPDU, which has a fixed header size (4
203    * bytes) */
204   cam_tl_calc_buffer_size (sl->tl, 4 + body_length, buffer_size, offset);
205   *offset += 4;
206 }
207 
208 CamReturn
cam_sl_session_write(CamSLSession * session,guint8 * buffer,guint buffer_size,guint body_length)209 cam_sl_session_write (CamSLSession * session,
210     guint8 * buffer, guint buffer_size, guint body_length)
211 {
212   guint8 *spdu;
213 
214   /* SPDU layout (4 + body_length bytes):
215    * TAG_SESSION_NUMBER (1 byte)
216    * length_field (1 byte)
217    * session number (2 bytes)
218    * one or more APDUs (body_length bytes)
219    */
220 
221   spdu = (buffer + buffer_size) - body_length - 4;
222   spdu[0] = TAG_SESSION_NUMBER;
223   spdu[1] = 2;
224   GST_WRITE_UINT16_BE (&spdu[2], session->session_nb);
225 
226   /* add our header to the body length */
227   return cam_tl_connection_write (session->connection,
228       buffer, buffer_size, 4 + body_length);
229 }
230 
231 static CamReturn
send_open_session_response(CamSL * sl,CamSLSession * session,guint8 status)232 send_open_session_response (CamSL * sl, CamSLSession * session, guint8 status)
233 {
234   CamReturn ret;
235   guint8 *tpdu;
236   guint size;
237   guint offset;
238   guint8 *spdu;
239 
240   /* SPDU layout (9 bytes):
241    * TAG_OPEN_SESSION_RESPONSE 1 byte
242    * length_field () 1 byte
243    * session_status 1 byte
244    * resource_id 4 bytes
245    * session_nb 2 bytes
246    */
247 
248   cam_tl_calc_buffer_size (session->sl->tl, 9, &size, &offset);
249 
250   tpdu = g_malloc0 (size);
251   spdu = tpdu + offset;
252 
253   spdu[0] = TAG_OPEN_SESSION_RESPONSE;
254   /* fixed length_field () */
255   spdu[1] = 7;
256   spdu[2] = status;
257   GST_WRITE_UINT32_BE (&spdu[3], session->resource_id);
258   GST_WRITE_UINT16_BE (&spdu[7], session->session_nb);
259 
260   ret = cam_tl_connection_write (session->connection, tpdu, size, 9);
261   g_free (tpdu);
262   if (CAM_FAILED (ret))
263     return ret;
264 
265   return CAM_RETURN_OK;
266 }
267 
268 static CamReturn
send_close_session_response(CamSL * sl,CamSLSession * session,guint8 status)269 send_close_session_response (CamSL * sl, CamSLSession * session, guint8 status)
270 {
271   CamReturn ret;
272   guint8 *tpdu;
273   guint size;
274   guint offset;
275   guint8 *spdu;
276 
277   /* SPDU layout (5 bytes):
278    * TAG_CLOSE_SESSION_RESPONSE 1 byte
279    * length_field () 1 byte
280    * session_status 1 byte
281    * session_nb 2 bytes
282    */
283 
284   cam_tl_calc_buffer_size (session->sl->tl, 5, &size, &offset);
285 
286   tpdu = g_malloc0 (size);
287   spdu = tpdu + offset;
288 
289   spdu[0] = TAG_OPEN_SESSION_RESPONSE;
290   /* fixed length_field() */
291   spdu[1] = 3;
292   spdu[2] = status;
293   GST_WRITE_UINT16_BE (&spdu[3], session->session_nb);
294 
295   ret = cam_tl_connection_write (session->connection, tpdu, size, 5);
296   g_free (tpdu);
297   if (CAM_FAILED (ret))
298     return ret;
299 
300   return CAM_RETURN_OK;
301 }
302 
303 static CamReturn
handle_open_session_request(CamSL * sl,CamTLConnection * connection,guint8 * spdu,guint spdu_length)304 handle_open_session_request (CamSL * sl, CamTLConnection * connection,
305     guint8 * spdu, guint spdu_length)
306 {
307   CamReturn ret;
308   guint resource_id;
309   guint status;
310   guint16 session_nb;
311   CamSLSession *session;
312 
313   /* SPDU layout (6 bytes):
314    * TAG_OPEN_SESSION_REQUEST (1 byte)
315    * length_field() (1 byte)
316    * resource id (4 bytes)
317    */
318   if (spdu_length != 6) {
319     GST_ERROR ("expected OPEN_SESSION_REQUEST to be 6 bytes, got %d",
320         spdu_length);
321     return CAM_RETURN_SESSION_ERROR;
322   }
323 
324   /* skip tag and length_field () */
325   resource_id = GST_READ_UINT32_BE (&spdu[2]);
326 
327   /* create a new session */
328   if (sl->session_ids == G_MAXUINT16) {
329     GST_ERROR ("too many sessions opened");
330     return CAM_RETURN_SESSION_TOO_MANY_SESSIONS;
331   }
332 
333   session_nb = ++sl->session_ids;
334   session = cam_sl_session_new (sl, connection, session_nb, resource_id);
335 
336   GST_INFO ("session request: %d %x", session_nb, session->resource_id);
337 
338   if (sl->open_session_request) {
339     /* forward the request to the upper layer */
340     ret = sl->open_session_request (sl, session, &status);
341     if (CAM_FAILED (ret))
342       goto error;
343   } else {
344     status = 0xF0;
345   }
346 
347   ret = send_open_session_response (sl, session, (guint8) status);
348   if (CAM_FAILED (ret))
349     goto error;
350 
351   GST_INFO ("session request response: %d %x", session_nb, status);
352 
353   if (status == CAM_SL_RESOURCE_STATUS_OPEN) {
354     /* if the session has been accepted add it and signal */
355     session->state = CAM_SL_SESSION_STATE_ACTIVE;
356     g_hash_table_insert (sl->sessions,
357         GINT_TO_POINTER ((guint) session_nb), session);
358 
359     if (sl->session_opened) {
360       /* notify the upper layer */
361       ret = sl->session_opened (sl, session);
362       if (CAM_FAILED (ret))
363         return ret;
364     }
365   } else {
366     /* session request wasn't accepted */
367     cam_sl_session_destroy (session);
368   }
369 
370   return CAM_RETURN_OK;
371 
372 error:
373   cam_sl_session_destroy (session);
374 
375   return ret;
376 }
377 
378 static CamReturn
handle_create_session_response(CamSL * sl,CamTLConnection * connection,guint8 * spdu,guint spdu_length)379 handle_create_session_response (CamSL * sl, CamTLConnection * connection,
380     guint8 * spdu, guint spdu_length)
381 {
382   guint16 session_nb;
383   CamSLSession *session;
384 
385   /* SPDU layout (9 bytes):
386    * TAG_CREATE_SESSION_RESPONSE (1 byte)
387    * length_field() (1 byte)
388    * status (1 byte)
389    * resource id (4 bytes)
390    * session number (2 bytes)
391    */
392   if (spdu_length != 9) {
393     GST_ERROR ("expected CREATE_SESSION_RESPONSE to be 9 bytes, got %d",
394         spdu_length);
395     return CAM_RETURN_SESSION_ERROR;
396   }
397 
398   /* skip tag and length */
399   /* status = spdu[2]; */
400   /* resource_id = GST_READ_UINT32_BE (&spdu[3]); */
401   session_nb = GST_READ_UINT16_BE (&spdu[7]);
402 
403   session = g_hash_table_lookup (sl->sessions,
404       GINT_TO_POINTER ((guint) session_nb));
405   if (session == NULL) {
406     GST_DEBUG ("got CREATE_SESSION_RESPONSE for unknown session: %d",
407         session_nb);
408     return CAM_RETURN_SESSION_ERROR;
409   }
410 
411   if (session->state == CAM_SL_SESSION_STATE_CLOSING) {
412     GST_DEBUG ("ignoring CREATE_SESSION_RESPONSE for closing session: %d",
413         session_nb);
414     return CAM_RETURN_OK;
415   }
416 
417   session->state = CAM_SL_SESSION_STATE_ACTIVE;
418 
419   GST_DEBUG ("session opened %d", session->session_nb);
420 
421   if (sl->session_opened)
422     /* notify the upper layer */
423     return sl->session_opened (sl, session);
424   return CAM_RETURN_OK;
425 }
426 
427 static CamReturn
handle_close_session_request(CamSL * sl,CamTLConnection * connection,guint8 * spdu,guint spdu_length)428 handle_close_session_request (CamSL * sl, CamTLConnection * connection,
429     guint8 * spdu, guint spdu_length)
430 {
431   CamReturn ret;
432   guint16 session_nb;
433   CamSLSession *session;
434   guint8 status = 0;
435 
436   /* SPDU layout (4 bytes):
437    * TAG_CLOSE_SESSION_REQUEST (1 byte)
438    * length_field () (1 byte)
439    * session number (2 bytes)
440    */
441   if (spdu_length != 4) {
442     GST_ERROR ("expected CLOSE_SESSION_REQUEST to be 4 bytes, got %d",
443         spdu_length);
444     return CAM_RETURN_SESSION_ERROR;
445   }
446 
447   /* skip tag and length_field() */
448   session_nb = GST_READ_UINT16_BE (&spdu[2]);
449 
450   GST_DEBUG ("close session request %d", session_nb);
451 
452   session = g_hash_table_lookup (sl->sessions,
453       GINT_TO_POINTER ((guint) session_nb));
454 
455   if (session == NULL) {
456     GST_WARNING ("got CLOSE_SESSION_REQUEST for unknown session: %d",
457         session_nb);
458     return CAM_RETURN_OK;
459   }
460 
461   if (session->state == CAM_SL_SESSION_STATE_CLOSING) {
462     GST_WARNING ("got CLOSE_SESSION_REQUEST for closing session: %d",
463         session_nb);
464     status = 0xF0;
465   }
466 
467   GST_DEBUG ("close session response: %d %d", session->session_nb, status);
468 
469   ret = send_close_session_response (sl, session, status);
470   if (CAM_FAILED (ret))
471     return ret;
472 
473   if (session->state != CAM_SL_SESSION_STATE_CLOSING) {
474     GST_DEBUG ("session closed %d", session->session_nb);
475 
476     if (sl->session_closed)
477       ret = sl->session_closed (sl, session);
478 
479     g_hash_table_remove (sl->sessions,
480         GINT_TO_POINTER ((guint) session->session_nb));
481 
482     if (CAM_FAILED (ret))
483       return ret;
484   }
485 
486   return CAM_RETURN_OK;
487 }
488 
489 static CamReturn
handle_close_session_response(CamSL * sl,CamTLConnection * connection,guint8 * spdu,guint spdu_length)490 handle_close_session_response (CamSL * sl, CamTLConnection * connection,
491     guint8 * spdu, guint spdu_length)
492 {
493   guint16 session_nb;
494   CamSLSession *session;
495   CamReturn ret = CAM_RETURN_OK;
496 
497   /* SPDU layout (5 bytes):
498    * TAG_CLOSE_SESSION_RESPONSE (1 byte)
499    * length_field () (1 byte)
500    * status (1 byte)
501    * session number (2 bytes)
502    */
503 
504   if (spdu_length != 5) {
505     GST_ERROR ("expected CLOSE_SESSION_RESPONSE to be 5 bytes, got %d",
506         spdu_length);
507     return CAM_RETURN_SESSION_ERROR;
508   }
509 
510   /* skip tag, length_field() and session_status */
511   session_nb = GST_READ_UINT16_BE (&spdu[3]);
512 
513   session = g_hash_table_lookup (sl->sessions,
514       GINT_TO_POINTER ((guint) session_nb));
515   if (session == NULL || session->state != CAM_SL_SESSION_STATE_ACTIVE) {
516     GST_ERROR ("unexpected CLOSED_SESSION_RESPONSE");
517     return CAM_RETURN_SESSION_ERROR;
518   }
519 
520   GST_DEBUG ("session closed %d", session->session_nb);
521 
522   if (sl->session_closed)
523     ret = sl->session_closed (sl, session);
524 
525   g_hash_table_remove (sl->sessions,
526       GINT_TO_POINTER ((guint) session->session_nb));
527 
528   return ret;
529 }
530 
531 static CamReturn
handle_session_data(CamSL * sl,CamTLConnection * connection,guint8 * spdu,guint length)532 handle_session_data (CamSL * sl, CamTLConnection * connection,
533     guint8 * spdu, guint length)
534 {
535   guint16 session_nb;
536   CamSLSession *session;
537 
538   /* SPDU layout (>= 4 bytes):
539    * TAG_SESSION_NUMBER (1 byte)
540    * length_field() (1 byte)
541    * session number (2 bytes)
542    * one or more APDUs
543    */
544 
545   if (length < 4) {
546     GST_ERROR ("invalid SESSION_NUMBER SPDU length %d", length);
547     return CAM_RETURN_SESSION_ERROR;
548   }
549 
550   session_nb = GST_READ_UINT16_BE (&spdu[2]);
551 
552   session = g_hash_table_lookup (sl->sessions,
553       GINT_TO_POINTER ((guint) session_nb));
554   if (session == NULL) {
555     GST_ERROR ("got SESSION_NUMBER on an unknown connection: %d", session_nb);
556     return CAM_RETURN_SESSION_ERROR;
557   }
558 
559   if (sl->session_data)
560     /* pass the APDUs to the upper layer, removing our 4-bytes header */
561     return sl->session_data (sl, session, spdu + 4, length - 4);
562 
563   return CAM_RETURN_OK;
564 }
565 
566 static CamReturn
connection_data_cb(CamTL * tl,CamTLConnection * connection,guint8 * spdu,guint spdu_length)567 connection_data_cb (CamTL * tl, CamTLConnection * connection,
568     guint8 * spdu, guint spdu_length)
569 {
570   CamReturn ret;
571   CamSL *sl = CAM_SL (tl->user_data);
572 
573   switch (spdu[I_TAG]) {
574     case TAG_CREATE_SESSION_RESPONSE:
575       ret = handle_create_session_response (sl, connection, spdu, spdu_length);
576       break;
577     case TAG_OPEN_SESSION_REQUEST:
578       ret = handle_open_session_request (sl, connection, spdu, spdu_length);
579       break;
580     case TAG_CLOSE_SESSION_REQUEST:
581       ret = handle_close_session_request (sl, connection, spdu, spdu_length);
582       break;
583     case TAG_CLOSE_SESSION_RESPONSE:
584       ret = handle_close_session_response (sl, connection, spdu, spdu_length);
585       break;
586     case TAG_SESSION_NUMBER:
587       ret = handle_session_data (sl, connection, spdu, spdu_length);
588       break;
589     default:
590       g_return_val_if_reached (CAM_RETURN_SESSION_ERROR);
591   }
592 
593   return ret;
594 }
595