| /* |
| * camapplication.c - GStreamer CAM (EN50221) Application Layer |
| * Copyright (C) 2007 Alessandro Decina |
| * |
| * Authors: |
| * Alessandro Decina <alessandro@nnva.org> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library 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 |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #include "camapplication.h" |
| |
| #define GST_CAT_DEFAULT cam_debug_cat |
| |
| /* Resource Manager */ |
| #define TAG_PROFILE_ENQUIRY 0x9F8010 |
| #define TAG_PROFILE_REPLY 0x9F8011 |
| #define TAG_PROFILE_CHANGE 0x9F8012 |
| |
| /* Application Info */ |
| #define TAG_APPLICATION_INFO_ENQUIRY 0x9F8020 |
| #define TAG_APPLICATION_INFO_REPLY 0x9F8021 |
| #define TAG_APPLICATION_INFO_ENTER_MENU 0x9F8022 |
| |
| /* Conditional Access */ |
| #define TAG_CONDITIONAL_ACCESS_INFO_ENQUIRY 0x9F8030 |
| #define TAG_CONDITIONAL_ACCESS_INFO_REPLY 0x9F8031 |
| #define TAG_CONDITIONAL_ACCESS_PMT 0x9F8032 |
| #define TAG_CONDITIONAL_ACCESS_PMT_REPLY 0x9F8033 |
| |
| typedef struct |
| { |
| guint tagid; |
| const gchar *description; |
| } CamTagMessage; |
| |
| static const CamTagMessage debugmessage[] = { |
| {TAG_PROFILE_ENQUIRY, "PROFILE_ENQUIRY"}, |
| {TAG_PROFILE_REPLY, "PROFILE_REPLY"}, |
| {TAG_PROFILE_CHANGE, "PROFILE_CHANGE"}, |
| {TAG_APPLICATION_INFO_ENQUIRY, "APPLICATION_INFO_ENQUIRY"}, |
| {TAG_APPLICATION_INFO_REPLY, "APPLICATION_INFO_REPLY"}, |
| {TAG_APPLICATION_INFO_ENTER_MENU, "APPLICATION_INFO_ENTER_MENU"}, |
| {TAG_CONDITIONAL_ACCESS_INFO_ENQUIRY, "CONDITIONAL_ACCESS_INFO_ENQUIRY"}, |
| {TAG_CONDITIONAL_ACCESS_INFO_REPLY, "CONDITIONAL_ACCESS_INFO_REPLY"}, |
| {TAG_CONDITIONAL_ACCESS_PMT, "CONDITIONAL_ACCESS_PMT"}, |
| {TAG_CONDITIONAL_ACCESS_PMT_REPLY, "CONDITIONAL_ACCESS_PMT_REPLY"} |
| }; |
| |
| static inline const gchar * |
| tag_get_name (guint tagid) |
| { |
| guint i; |
| |
| for (i = 0; i < G_N_ELEMENTS (debugmessage); i++) |
| if (debugmessage[i].tagid == tagid) |
| return debugmessage[i].description; |
| return "UNKNOWN"; |
| } |
| |
| static CamReturn open_session_request_cb (CamSL * sl, |
| CamSLSession * session, CamSLResourceStatus * status); |
| static CamReturn session_opened_cb (CamSL * sl, CamSLSession * session); |
| static CamReturn session_closed_cb (CamSL * sl, CamSLSession * session); |
| static CamReturn session_data_cb (CamSL * sl, |
| CamSLSession * session, guint8 * data, guint length); |
| |
| static guint |
| resource_id_hash (gconstpointer key) |
| { |
| guint resource_id = GPOINTER_TO_UINT (key); |
| |
| if (!CAM_AL_RESOURCE_ID_IS_PUBLIC (resource_id)) { |
| /* private identifier, leave it as is */ |
| return resource_id; |
| } |
| |
| /* public identifier, mask out the version number */ |
| return resource_id >> 6; |
| } |
| |
| CamAL * |
| cam_al_new (CamSL * sl) |
| { |
| CamAL *al = g_new0 (CamAL, 1); |
| |
| al->sl = sl; |
| al->applications = g_hash_table_new (resource_id_hash, g_direct_equal); |
| |
| sl->user_data = al; |
| sl->open_session_request = open_session_request_cb; |
| sl->session_opened = session_opened_cb; |
| sl->session_closed = session_closed_cb; |
| sl->session_data = session_data_cb; |
| |
| return al; |
| } |
| |
| void |
| cam_al_destroy (CamAL * al) |
| { |
| g_hash_table_destroy (al->applications); |
| g_free (al); |
| } |
| |
| gboolean |
| cam_al_install (CamAL * al, CamALApplication * application) |
| { |
| if (g_hash_table_lookup (al->applications, |
| GINT_TO_POINTER (application->resource_id)) != NULL) |
| return FALSE; |
| |
| application->al = al; |
| |
| g_hash_table_insert (al->applications, |
| GINT_TO_POINTER (application->resource_id), application); |
| |
| return TRUE; |
| } |
| |
| gboolean |
| cam_al_uninstall (CamAL * al, CamALApplication * application) |
| { |
| gboolean ret; |
| |
| ret = g_hash_table_remove (al->applications, |
| GINT_TO_POINTER (application->resource_id)); |
| |
| return ret; |
| } |
| |
| CamALApplication * |
| cam_al_get (CamAL * al, guint resource_id) |
| { |
| return CAM_AL_APPLICATION (g_hash_table_lookup (al->applications, |
| GINT_TO_POINTER (resource_id))); |
| } |
| |
| void |
| _cam_al_application_init (CamALApplication * application) |
| { |
| application->sessions = NULL; |
| } |
| |
| void |
| _cam_al_application_destroy (CamALApplication * application) |
| { |
| g_list_free (application->sessions); |
| } |
| |
| static void |
| foreach_get_key (gpointer key, gpointer value, gpointer user_data) |
| { |
| GList **lst = (GList **) user_data; |
| |
| *lst = g_list_append (*lst, key); |
| } |
| |
| GList * |
| cam_al_get_resource_ids (CamAL * al) |
| { |
| GList *resource_ids = NULL; |
| |
| g_hash_table_foreach (al->applications, foreach_get_key, &resource_ids); |
| |
| return resource_ids; |
| } |
| |
| void |
| cam_al_calc_buffer_size (CamAL * al, guint body_length, |
| guint * buffer_size, guint * offset) |
| { |
| guint apdu_header_length; |
| guint8 length_field_len; |
| |
| /* get the length of the lenght_field() */ |
| length_field_len = cam_calc_length_field_size (body_length); |
| |
| /* sum the APDU header */ |
| apdu_header_length = 3 + length_field_len; |
| |
| /* chain up to the session layer to get the size of the buffer that can |
| * contain the whole APDU */ |
| cam_sl_calc_buffer_size (al->sl, apdu_header_length + body_length, |
| buffer_size, offset); |
| |
| /* add the APDU header to the SPDU offset */ |
| *offset += apdu_header_length; |
| } |
| |
| CamReturn |
| cam_al_application_write (CamALApplication * application, |
| CamSLSession * session, guint tag, guint8 * buffer, guint buffer_size, |
| guint body_length) |
| { |
| guint length_field_len; |
| guint apdu_header_length; |
| guint8 *apdu; |
| |
| GST_DEBUG ("tag:0x%x (%s), buffer_size:%d, body_length:%d", tag, |
| tag_get_name (tag), buffer_size, body_length); |
| |
| length_field_len = cam_calc_length_field_size (body_length); |
| apdu_header_length = 3 + length_field_len; |
| apdu = (buffer + buffer_size) - body_length - apdu_header_length; |
| apdu[0] = tag >> 16; |
| apdu[1] = (tag >> 8) & 0xFF; |
| apdu[2] = tag & 0xFF; |
| |
| cam_write_length_field (&apdu[3], body_length); |
| |
| return cam_sl_session_write (session, buffer, buffer_size, |
| apdu_header_length + body_length); |
| } |
| |
| static CamReturn |
| open_session_request_cb (CamSL * sl, CamSLSession * session, |
| CamSLResourceStatus * status) |
| { |
| CamAL *al = CAM_AL (sl->user_data); |
| CamALApplication *application; |
| guint resource_id = session->resource_id; |
| CamReturn ret; |
| |
| application = g_hash_table_lookup (al->applications, |
| GINT_TO_POINTER (resource_id)); |
| if (application == NULL) { |
| *status = CAM_SL_RESOURCE_STATUS_NOT_FOUND; |
| |
| return CAM_RETURN_OK; |
| } |
| |
| if (CAM_AL_RESOURCE_ID_VERSION (application->resource_id) |
| < CAM_AL_RESOURCE_ID_VERSION (resource_id)) { |
| *status = CAM_SL_RESOURCE_STATUS_INVALID_VERSION; |
| |
| return CAM_RETURN_OK; |
| } |
| |
| ret = application->session_request (application, session, status); |
| if (CAM_FAILED (ret)) { |
| *status = CAM_SL_RESOURCE_STATUS_NOT_FOUND; |
| |
| return ret; |
| } |
| |
| if (*status == CAM_SL_RESOURCE_STATUS_OPEN) { |
| session->user_data = application; |
| application->sessions = g_list_append (application->sessions, session); |
| } |
| |
| return CAM_RETURN_OK; |
| } |
| |
| static CamReturn |
| session_opened_cb (CamSL * sl, CamSLSession * session) |
| { |
| CamALApplication *application; |
| |
| application = CAM_AL_APPLICATION (session->user_data); |
| if (application == NULL) { |
| GST_ERROR ("session is established but has no application"); |
| return CAM_RETURN_APPLICATION_ERROR; |
| } |
| |
| return application->open (application, session); |
| } |
| |
| static CamReturn |
| session_closed_cb (CamSL * sl, CamSLSession * session) |
| { |
| CamALApplication *application; |
| CamReturn ret; |
| GList *walk; |
| |
| application = CAM_AL_APPLICATION (session->user_data); |
| if (application == NULL) { |
| GST_ERROR ("session is established but has no application"); |
| return CAM_RETURN_APPLICATION_ERROR; |
| } |
| |
| ret = application->close (application, session); |
| for (walk = application->sessions; walk; walk = g_list_next (walk)) { |
| CamSLSession *s = CAM_SL_SESSION (walk->data); |
| |
| if (s->session_nb == session->session_nb) { |
| application->sessions = g_list_delete_link (application->sessions, walk); |
| break; |
| } |
| } |
| |
| return ret; |
| } |
| |
| static CamReturn |
| session_data_cb (CamSL * sl, CamSLSession * session, guint8 * data, guint size) |
| { |
| CamALApplication *application; |
| guint tag = 0; |
| guint8 length_field_len; |
| guint length; |
| guint i; |
| |
| application = CAM_AL_APPLICATION (session->user_data); |
| if (application == NULL) { |
| GST_ERROR ("session is established but has no application"); |
| return CAM_RETURN_APPLICATION_ERROR; |
| } |
| |
| if (size < 4) { |
| GST_ERROR ("invalid APDU length %d", size); |
| return CAM_RETURN_APPLICATION_ERROR; |
| } |
| |
| for (i = 0; i < 3; ++i) |
| tag = (tag << 8) | data[i]; |
| |
| length_field_len = cam_read_length_field (&data[3], &length); |
| |
| if (length != size - 4) { |
| GST_ERROR ("unexpected APDU length %d expected %d", length, size); |
| |
| return CAM_RETURN_APPLICATION_ERROR; |
| } |
| |
| GST_DEBUG ("Got tag 0x%x (%s) , length:%d", tag, tag_get_name (tag), length); |
| |
| return application->data (application, session, |
| tag, data + 3 + length_field_len, length); |
| } |