tests/onvif: improve robustness
authorMathieu Duponchelle <mathieu@centricular.com>
Tue, 26 Apr 2022 23:13:11 +0000 (01:13 +0200)
committerGStreamer Marge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Wed, 27 Apr 2022 06:39:24 +0000 (06:39 +0000)
The previous iteration of the code was inferring the type of the
frame by looking at the overall size of the gst-payloaded packet.

It is more robust to actually parse the payload and look at the
actual data buffers it contains.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/2303>

subprojects/gst-rtsp-server/tests/check/gst/onvif.c

index 3d115a8..2292d78 100644 (file)
@@ -154,8 +154,8 @@ test_src_create (GstPushSrc * psrc, GstBuffer ** buffer)
     duration =
         MAX (duration * 10,
         duration * ROUND_UP_TO_10 (src->trickmode_interval));
-  } else if ((src->segment->
-          flags & GST_SEGMENT_FLAG_TRICKMODE_FORWARD_PREDICTED)) {
+  } else if ((src->
+          segment->flags & GST_SEGMENT_FLAG_TRICKMODE_FORWARD_PREDICTED)) {
     duration *= 5;
   }
 
@@ -572,6 +572,219 @@ typedef struct
 #define EXTENSION_SIZE 3
 
 static gboolean
+read_length (guint8 * data, guint size, guint * length, guint * skip)
+{
+  guint b, len, offset;
+
+  /* start reading the length, we need this to skip to the data later */
+  len = offset = 0;
+  do {
+    if (offset >= size)
+      return FALSE;
+    b = data[offset++];
+    len = (len << 7) | (b & 0x7f);
+  } while (b & 0x80);
+
+  /* check remaining buffer size */
+  if (size - offset < len)
+    return FALSE;
+
+  *length = len;
+  *skip = offset;
+
+  return TRUE;
+}
+
+static GstCaps *
+read_caps (GstBuffer * buf, guint * skip)
+{
+  guint offset, length;
+  GstCaps *caps;
+  GstMapInfo map;
+
+  gst_buffer_map (buf, &map, GST_MAP_READ);
+
+  if (!read_length (map.data, map.size, &length, &offset))
+    goto too_small;
+
+  if (length == 0 || map.data[offset + length - 1] != '\0')
+    goto invalid_buffer;
+
+  /* parse and store in cache */
+  caps = gst_caps_from_string ((gchar *) & map.data[offset]);
+  gst_buffer_unmap (buf, &map);
+
+  *skip = length + offset;
+
+  return caps;
+
+too_small:
+  {
+    gst_buffer_unmap (buf, &map);
+    return NULL;
+  }
+invalid_buffer:
+  {
+    gst_buffer_unmap (buf, &map);
+    return NULL;
+  }
+}
+
+static GstEvent *
+read_event (guint type, GstBuffer * buf, guint * skip)
+{
+  guint offset, length;
+  GstStructure *s;
+  GstEvent *event;
+  GstEventType etype;
+  gchar *end;
+  GstMapInfo map;
+
+  gst_buffer_map (buf, &map, GST_MAP_READ);
+
+  if (!read_length (map.data, map.size, &length, &offset))
+    goto too_small;
+
+  if (length == 0)
+    goto invalid_buffer;
+  /* backward compat, old payloader did not put 0-byte at the end */
+  if (map.data[offset + length - 1] != '\0'
+      && map.data[offset + length - 1] != ';')
+    goto invalid_buffer;
+
+  /* parse */
+  s = gst_structure_from_string ((gchar *) & map.data[offset], &end);
+  gst_buffer_unmap (buf, &map);
+
+  if (s == NULL)
+    goto parse_failed;
+
+  switch (type) {
+    case 1:
+      etype = GST_EVENT_TAG;
+      break;
+    case 2:
+      etype = GST_EVENT_CUSTOM_DOWNSTREAM;
+      break;
+    case 3:
+      etype = GST_EVENT_CUSTOM_BOTH;
+      break;
+    case 4:
+      etype = GST_EVENT_STREAM_START;
+      break;
+    default:
+      goto unknown_event;
+  }
+  event = gst_event_new_custom (etype, s);
+
+  *skip = length + offset;
+
+  return event;
+
+too_small:
+  {
+    gst_buffer_unmap (buf, &map);
+    return NULL;
+  }
+invalid_buffer:
+  {
+    gst_buffer_unmap (buf, &map);
+    return NULL;
+  }
+parse_failed:
+  {
+    return NULL;
+  }
+unknown_event:
+  {
+    gst_structure_free (s);
+    return NULL;
+  }
+}
+
+static gboolean
+parse_gstpay_payload (GstRTPBuffer * rtp, GstEvent ** event, GstCaps ** caps,
+    GstBuffer ** outbuf)
+{
+  gint payload_len;
+  guint8 *payload;
+  guint avail, offset;
+
+  payload_len = gst_rtp_buffer_get_payload_len (rtp);
+
+  if (payload_len <= 8)
+    goto empty_packet;
+
+  /* We don't need to deal with fragmentation */
+  fail_unless (gst_rtp_buffer_get_marker (rtp));
+
+  payload = gst_rtp_buffer_get_payload (rtp);
+
+  *outbuf = gst_rtp_buffer_get_payload_subbuffer (rtp, 8, -1);
+  avail = gst_buffer_get_size (*outbuf);
+  offset = 0;
+
+  if (payload[0] & 0x80) {
+    guint size;
+
+    /* C bit, we have inline caps */
+    *caps = read_caps (*outbuf, &size);
+    if (*caps == NULL)
+      goto no_caps;
+
+    /* skip caps */
+    offset += size;
+    avail -= size;
+  }
+
+  if (payload[1]) {
+    guint size;
+
+    /* we have an event */
+    *event = read_event (payload[1], *outbuf, &size);
+    if (*event == NULL)
+      goto no_event;
+
+    /* no buffer after event */
+    avail = 0;
+  }
+
+  if (avail) {
+    if (offset != 0) {
+      GstBuffer *temp;
+
+      temp =
+          gst_buffer_copy_region (*outbuf, GST_BUFFER_COPY_ALL, offset, avail);
+
+      gst_buffer_unref (*outbuf);
+      *outbuf = temp;
+    }
+
+    if (payload[0] & 0x8)
+      GST_BUFFER_FLAG_SET (*outbuf, GST_BUFFER_FLAG_DELTA_UNIT);
+  } else {
+    gst_buffer_unref (*outbuf);
+    *outbuf = NULL;
+  }
+
+  return TRUE;
+
+empty_packet:
+  return FALSE;
+
+no_caps:
+  {
+    gst_buffer_unref (*outbuf);
+    return FALSE;
+  }
+no_event:
+  {
+    gst_buffer_unref (*outbuf);
+    return FALSE;
+  }
+}
+
+static gboolean
 test_play_response_200_and_check_data (GstRTSPClient * client,
     GstRTSPMessage * response, gboolean close, gpointer user_data)
 {
@@ -600,37 +813,55 @@ test_play_response_200_and_check_data (GstRTSPClient * client,
       guint16 bits;
       guint wordlen;
       guint8 flags;
-      gint32 expected_interval;
-      gboolean is_custom_event = FALSE;
+      gint32 expected_interval = 0;
+      GstBuffer *outbuf = NULL;
+      GstCaps *outcaps = NULL;
+      GstEvent *outevent = NULL;
 
       fail_unless (gst_rtsp_message_get_body (response, &body,
               &body_size) == GST_RTSP_OK);
 
       buf = gst_rtp_buffer_new_copy_data (body, body_size);
 
-      switch (body_size) {
-        case 120:              /* Ignore our serialized custom events */
-          is_custom_event = TRUE;
-          break;
-        case 56:
-          expected_interval = check->expected_i_frame_ts_interval;
-          check->n_i_frames += 1;
-          break;
-        case 46:
-          expected_interval = check->expected_ts_interval;
-          check->n_p_frames += 1;
-          break;
-        case 41:
-          expected_interval = check->expected_ts_interval;
-          check->n_b_frames += 1;
-          break;
-        default:
-          fail ("Invalid body size %u", body_size);
+      fail_unless (gst_rtp_buffer_map (buf, GST_MAP_READ, &rtp));
+
+      fail_unless (parse_gstpay_payload (&rtp, &outevent, &outcaps, &outbuf));
+
+      if (outbuf) {
+        switch (gst_buffer_get_size (outbuf)) {
+          case 20:
+            expected_interval = check->expected_i_frame_ts_interval;
+            check->n_i_frames += 1;
+            break;
+          case 10:
+            expected_interval = check->expected_ts_interval;
+            check->n_p_frames += 1;
+            break;
+          case 5:
+            expected_interval = check->expected_ts_interval;
+            check->n_b_frames += 1;
+            break;
+          default:
+            fail ("Invalid payload size %u", gst_buffer_get_size (outbuf));
+        }
+
+        gst_buffer_unref (outbuf);
       }
 
-      if (!is_custom_event) {
-        fail_unless (gst_rtp_buffer_map (buf, GST_MAP_READ, &rtp));
+      if (outcaps) {
+        gst_caps_unref (outcaps);
+      }
 
+      if (outevent) {
+        const GstStructure *s;
+
+        fail_unless (GST_EVENT_TYPE (outevent) == GST_EVENT_CUSTOM_DOWNSTREAM);
+        s = gst_event_get_structure (outevent);
+        fail_unless (gst_structure_has_name (s, "GstOnvifTimestamp"));
+        gst_event_unref (outevent);
+      }
+
+      if (expected_interval) {
         if (check->previous_ts) {
           fail_unless_equals_int (gst_rtp_buffer_get_timestamp (&rtp) -
               check->previous_ts, expected_interval);
@@ -646,8 +877,6 @@ test_play_response_200_and_check_data (GstRTSPClient * client,
 
         flags = GST_READ_UINT8 (data + 8);
 
-        gst_rtp_buffer_unmap (&rtp);
-
         if (flags & (1 << 7)) {
           check->n_clean_points += 1;
         }
@@ -669,6 +898,7 @@ test_play_response_200_and_check_data (GstRTSPClient * client,
         }
       }
 
+      gst_rtp_buffer_unmap (&rtp);
       gst_buffer_unref (buf);
     } else if (channel == 1) {  /* RTCP */
       GstBuffer *buf;