Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/VLC/modules/services_discovery/   (VLC Media Plyer ©)  Datei vom 23.9.2026 mit Größe 20 kB image not shown  

Quelle  sap.c   Sprache: C

 

/*****************************************************************************
 * sap.c :  SAP interface module
 *****************************************************************************
 * Copyright (C) 2004-2005 the VideoLAN team
 * Copyright © 2007 Rémi Denis-Courmont
 *
 * Authors: Clément Stenac <zorglub@videolan.org>
 *          Rémi Denis-Courmont
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/


/*****************************************************************************
 * Includes
 *****************************************************************************/

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#define VLC_MODULE_LICENSE VLC_LICENSE_GPL_2_PLUS
#include <vlc_common.h>
#include <vlc_arrays.h>
#include <vlc_threads.h>
#include <vlc_poll.h>
#include <vlc_plugin.h>
#include <assert.h>

#include <vlc_demux.h>
#include <vlc_services_discovery.h>

#include <vlc_network.h>
#include <vlc_charset.h>

#include <errno.h>
#include <unistd.h>
#ifdef HAVE_ARPA_INET_H
# include <arpa/inet.h>
#endif
#ifdef HAVE_POLL_H
# include <poll.h>
#endif

#ifdef HAVE_ZLIB_H
#   include <zlib.h>
#endif

#ifdef HAVE_NET_IF_H
#   include <net/if.h>
#endif

#ifdef HAVE_SEARCH_H
#   include <search.h>
#endif

#include "access/rtp/sdp.h"

/************************************************************************
 * Macros and definitions
 ************************************************************************/


#define MAX_LINE_LENGTH 256

/* SAP is always on that port */
#define SAP_PORT 9875
/* Global-scope SAP address */
#define SAP_V4_GLOBAL_ADDRESS   "224.2.127.254"
/* Organization-local SAP address */
#define SAP_V4_ORG_ADDRESS      "239.195.255.255"
/* Local (smallest non-link-local scope) SAP address */
#define SAP_V4_LOCAL_ADDRESS    "239.255.255.255"
/* Link-local SAP address */
#define SAP_V4_LINK_ADDRESS     "224.0.0.255"
#define ADD_SESSION 1

typedef struct
{
    vlc_thread_t thread;

    /* Socket descriptors */
    int i_fd;
    int *pi_fd;

    /* Table of announces */
    int i_announces;
    struct sap_announce_t **pp_announces;

    vlc_tick_t i_timeout;

    void *root_cat;
} services_discovery_sys_t;

static int Decompress( const unsigned char *psz_src, unsigned char **_dst, int i_len )
{
#ifdef HAVE_ZLIB
    int i_result, i_dstsize, n = 0;
    unsigned char *psz_dst = NULL;
    z_stream d_stream;

    memset (&d_stream, 0, sizeof (d_stream));

    i_result = inflateInit(&d_stream);
    if( i_result != Z_OK )
        return( -1 );

    d_stream.next_in = (Bytef *)psz_src;
    d_stream.avail_in = i_len;

    do
    {
        n++;
        psz_dst = xrealloc( psz_dst, n * 1000 );
        d_stream.next_out = (Bytef *)&psz_dst[(n - 1) * 1000];
        d_stream.avail_out = 1000;

        i_result = inflate(&d_stream, Z_NO_FLUSH);
        if( ( i_result != Z_OK ) && ( i_result != Z_STREAM_END ) )
        {
            inflateEnd( &d_stream );
            free( psz_dst );
            return( -1 );
        }
    }
    while( ( d_stream.avail_out == 0 ) && ( d_stream.avail_in != 0 ) &&
           ( i_result != Z_STREAM_END ) );

    i_dstsize = d_stream.total_out;
    inflateEnd( &d_stream );

    *_dst = xrealloc( psz_dst, i_dstsize );

    return i_dstsize;
#else
    (void)psz_src;
    (void)_dst;
    (void)i_len;
    return -1;
#endif
}

struct category
{
    input_item_t *item;
    services_discovery_t *sd;
    char name[];
};

static void DestroyCategory(void *data)
{
    struct category *c = data;

    services_discovery_RemoveItem(c->sd, c->item);
    input_item_Release(c->item);
    free(c);
}

static int cmpcat (const void *a, const void *b)
{
    const struct category *ca = a, *cb = b;
    return strcmp(ca->name, cb->name);
}

static input_item_t *AddCategory (services_discovery_t *sd, const char *name)
{
    services_discovery_sys_t *sys = sd->p_sys;

    struct category *c = malloc(sizeof(*c) + strlen(name) + 1);
    if (unlikely(c == NULL))
        return NULL;
    strcpy(c->name, name);

    void **cp = tsearch(c, &sys->root_cat, cmpcat);
    if (cp == NULL) /* Out-of-memory */
    {
        free(c);
        return NULL;
    }
    if (*cp != c)
    {
        free(c);
        c = *cp;
        assert(c->item != NULL);
        return c->item;
    }

    c->item = input_item_NewExt("vlc://nop", c->name,
                                INPUT_DURATION_INDEFINITE,
                                ITEM_TYPE_NODE, ITEM_LOCAL);

    if (unlikely(c->item == NULL))
    {
        tdelete(c, &sys->root_cat, cmpcat);
        return NULL;
    }
    services_discovery_AddItem(sd, c->item);
    c->sd = sd;

    return c->item;
}

typedef struct sap_announce_t
{
    vlc_tick_t i_last;
    vlc_tick_t i_period;
    uint8_t i_period_trust;

    uint16_t    i_hash;
    uint32_t    i_source[4];

    input_item_t * p_item;
} sap_announce_t;

static sap_announce_t *CreateAnnounce(services_discovery_t *p_sd,
                                      const uint32_t *i_source,
                                      uint16_t i_hash, const char *psz_sdp)
{
    /* Parse SDP info */
    struct vlc_sdp *p_sdp = vlc_sdp_parse(psz_sdp, strlen(psz_sdp));
    if (p_sdp == NULL)
        return NULL;

    sap_announce_t *p_sap = malloc(sizeof (*p_sap));
    if( p_sap == NULL )
        goto error;

    char *uri = NULL;

    if (asprintf(&uri, "sdp://%s", psz_sdp) == -1)
        goto error;

    input_item_t *p_input;
    const char *psz_value;

    p_sap->i_last = vlc_tick_now();
    p_sap->i_period = 0;
    p_sap->i_period_trust = 0;
    p_sap->i_hash = i_hash;
    memcpy (p_sap->i_source, i_source, sizeof(p_sap->i_source));

    /* Released in RemoveAnnounce */
    p_input = input_item_NewStream(uri, p_sdp->name,
                                   INPUT_DURATION_INDEFINITE);
    free(uri);
    if( unlikely(p_input == NULL) )
        goto error;

    p_sap->p_item = p_input;

    input_item_SetMeta(p_input, vlc_meta_Description, p_sdp->info);

    psz_value = vlc_sdp_attr_value(p_sdp, "tool");
    if( psz_value != NULL )
    {
        input_item_AddInfo( p_input, _("Session"), _("Tool"), "%s", psz_value );
    }

    /* Handle category */
    psz_value = vlc_sdp_attr_value(p_sdp, "cat");
    char *str = NULL;
    if (psz_value != NULL)
    {
        /* a=cat provides a dot-separated hierarchy.
         * For the time being only replace dots with pipe. TODO: FIXME */

        str = strdup(psz_value);
        if (likely(str != NULL))
        {
            for (char *p = strchr(str, '.'); p != NULL; p = strchr(p, '.'))
                *(p++) = '|';
            psz_value = str;
        }
    }
    else
    {
        /* backward compatibility with VLC 0.7.3-2.0.0 senders */
        psz_value = vlc_sdp_attr_value(p_sdp, "x-plgroup");
    }
    input_item_t *cat = psz_value == NULL ? NULL : AddCategory(p_sd, psz_value);
    free(str);
    services_discovery_AddSubItem(p_sd, cat, p_input);

    vlc_sdp_free(p_sdp);
    return p_sap;
error:
    free(p_sap);
    vlc_sdp_free(p_sdp);
    return NULL;
}

static int RemoveAnnounce( services_discovery_t *p_sd,
                           sap_announce_t *p_announce )
{
    if( p_announce->p_item )
    {
        services_discovery_RemoveItem( p_sd, p_announce->p_item );
        input_item_Release( p_announce->p_item );
        p_announce->p_item = NULL;
    }

    services_discovery_sys_t *p_sys = p_sd->p_sys;
    TAB_REMOVE(p_sys->i_announces, p_sys->pp_announces, p_announce);
    free( p_announce );

    return VLC_SUCCESS;
}

static int InitSocket( services_discovery_t *p_sd, const char *psz_address,
                       int i_port )
{
    int i_fd = net_ListenUDP1 (VLC_OBJECT(p_sd), psz_address, i_port);
    if (i_fd == -1)
        return VLC_EGENERIC;

    shutdown( i_fd, SHUT_WR );
    services_discovery_sys_t *p_sys = p_sd->p_sys;
    TAB_APPEND(p_sys->i_fd, p_sys->pi_fd, i_fd);
    return VLC_SUCCESS;
}

/* i_read is at least > 6 */
static int ParseSAP( services_discovery_t *p_sd, const uint8_t *buf,
                     size_t len )
{
    services_discovery_sys_t *p_sys = p_sd->p_sys;
    const char          *psz_sdp;
    const uint8_t *end = buf + len;
    uint32_t            i_source[4];

    assert (buf[len] == '\0');

    if (len < 4)
        return VLC_EGENERIC;

    uint8_t flags = buf[0];
    uint8_t auth_len = buf[1];

    /* First, check the sap announce is correct */
    if ((flags >> 5) != 1)
        return VLC_EGENERIC;

    bool b_ipv6 = (flags & 0x10) != 0;
    bool b_need_delete = (flags & 0x04) != 0;

    if (flags & 0x02)
    {
        msg_Dbg( p_sd, "encrypted packet, unsupported" );
        return VLC_EGENERIC;
    }

    bool b_compressed = (flags & 0x01) != 0;

    uint16_t i_hash = U16_AT (buf + 2);

    if( i_hash == 0 )
        return VLC_EGENERIC;

    buf += 4;
    if( b_ipv6 )
    {
        for( int i = 0; i < 4; i++,buf+=4)
            i_source[i] = U32_AT(buf);
    }
    else
    {
        memset(i_source, 0, sizeof(i_source));
        i_source[3] = U32_AT(buf);
        buf+=4;
    }
    // Skips auth data
    buf += auth_len;
    if (buf > end)
        return VLC_EGENERIC;

    uint8_t *decomp = NULL;
    if( b_compressed )
    {
        int newsize = Decompress (buf, &decomp, end - buf);
        if (newsize < 0)
        {
            msg_Dbg( p_sd, "decompression of SAP packet failed" );
            return VLC_EGENERIC;
        }

        decomp = xrealloc (decomp, newsize + 1);
        decomp[newsize] = '\0';
        psz_sdp = (const char *)decomp;
        len = newsize;
    }
    else
    {
        psz_sdp = (const char *)buf;
        len = end - buf;
    }

    /* len is a strlen here here. both buf and decomp are len+1 where the 1 should be a \0 */
    assert( psz_sdp[len] == '\0');

    /* Skip payload type */
    /* SAPv1 has implicit "application/sdp" payload type: first line is v=0 */
    if (strncmp (psz_sdp, "v=0", 3))
    {
        size_t clen = strlen (psz_sdp) + 1;

        if (strcmp (psz_sdp, "application/sdp"))
        {
            msg_Dbg (p_sd, "unsupported content type: %s", psz_sdp);
            goto error;
        }

        // skips content type
        if (len <= clen)
            goto error;

        len -= clen;
        psz_sdp += clen;
    }

    for( int i = 0 ; i < p_sys->i_announces ; i++ )
    {
        sap_announce_t * p_announce = p_sys->pp_announces[i];
        /* FIXME: slow */
        if (p_announce->i_hash == i_hash
         && memcmp(p_announce->i_source, i_source, sizeof (i_source)) == 0)
        {
            /* We don't support delete announcement as they can easily
             * Be used to hijack an announcement by a third party.
             * Instead we cleverly implement Implicit Announcement removal.
             *
             * if( b_need_delete )
             *    RemoveAnnounce( p_sd, p_sys->pp_announces[i]);
             * else
             */


            if( !b_need_delete )
            {
                /* No need to go after six, as we start to trust the
                 * average period at six */

                if( p_announce->i_period_trust <= 5 )
                    p_announce->i_period_trust++;

                /* Compute the average period */
                vlc_tick_t now = vlc_tick_now();
                p_announce->i_period = ( p_announce->i_period * (p_announce->i_period_trust-1) + (now - p_announce->i_last) ) / p_announce->i_period_trust;
                p_announce->i_last = now;
            }
            free (decomp);
            return VLC_SUCCESS;
        }
    }

    sap_announce_t *sap = CreateAnnounce(p_sd, i_source, i_hash, psz_sdp);
    if (sap != NULL)
        TAB_APPEND(p_sys->i_announces, p_sys->pp_announces, sap);

    free (decomp);
    return VLC_SUCCESS;
error:
    free (decomp);
    return VLC_EGENERIC;
}

/*****************************************************************************
 * Run: main SAP thread
 *****************************************************************************
 * Listens to SAP packets, and sends them to packet_handle
 *****************************************************************************/

#define MAX_SAP_BUFFER 5000

static void *Run( void *data )
{
    vlc_thread_set_name("vlc-sap");

    services_discovery_t *p_sd = data;
    services_discovery_sys_t *p_sys = p_sd->p_sys;
    char *psz_addr;
    int timeout = -1;
    int canc = vlc_savecancel ();

    /* Braindead Winsock DNS resolver will get stuck over 2 seconds per failed
     * DNS queries, even if the DNS server returns an error with milliseconds.
     * You don't want to know why the bug (as of XP SP2) wasn't fixed since
     * Winsock 1.1 from Windows 95, if not Windows 3.1.
     * Anyway, to avoid a 30 seconds delay for failed IPv6 socket creation,
     * we have to open sockets in Run() rather than Open(). */

    InitSocket( p_sd, SAP_V4_GLOBAL_ADDRESS, SAP_PORT );
    InitSocket( p_sd, SAP_V4_ORG_ADDRESS, SAP_PORT );
    InitSocket( p_sd, SAP_V4_LOCAL_ADDRESS, SAP_PORT );
    InitSocket( p_sd, SAP_V4_LINK_ADDRESS, SAP_PORT );

    char psz_address[NI_MAXNUMERICHOST] = "ff02::2:7ffe%";
#ifndef _WIN32
    struct if_nameindex *l = if_nameindex ();
    if (l != NULL)
    {
        char *ptr = strchr (psz_address, '%') + 1;
        for (unsigned i = 0; l[i].if_index; i++)
        {
            strcpy (ptr, l[i].if_name);
            InitSocket (p_sd, psz_address, SAP_PORT);
        }
        if_freenameindex (l);
    }
#else
        /* this is the Winsock2 equivalent of SIOCGIFCONF on BSD stacks,
           which if_nameindex uses internally anyway */


        // first create a dummy socket to pin down the protocol family
        SOCKET s = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
        if( s != INVALID_SOCKET )
        {
            INTERFACE_INFO ifaces[10]; // Assume there will be no more than 10 IP interfaces
            DWORD len = sizeof(ifaces);

            if( SOCKET_ERROR != WSAIoctl(s, SIO_GET_INTERFACE_LIST, NULL, 0, &ifaces, len, &len, NULL, NULL) )
            {
                unsigned ifcount = len/sizeof(INTERFACE_INFO);
                char *ptr = strchr (psz_address, '%') + 1;
                for(unsigned i = 1; i<=ifcount; ++i )
                {
                    // append link-local zone identifier
                    sprintf(ptr, "%d", i);
                }
            }
            closesocket(s);
        }
#endif
    *strchr (psz_address, '%') = '\0';

    static const char ipv6_scopes[] = "1456789ABCDE";
    for (const char *c_scope = ipv6_scopes; *c_scope; c_scope++)
    {
        psz_address[3] = *c_scope;
        InitSocket( p_sd, psz_address, SAP_PORT );
    }

    psz_addr = var_CreateGetString( p_sd, "sap-addr" );
    if( psz_addr && *psz_addr )
        InitSocket( p_sd, psz_addr, SAP_PORT );
    free( psz_addr );

    if( p_sys->i_fd == 0 )
    {
        msg_Err( p_sd, "unable to listen on any address" );
        return NULL;
    }

    /* read SAP packets */
    for (;;)
    {
        vlc_restorecancel (canc);
        unsigned n = p_sys->i_fd;
        struct pollfd ufd[n];

        for (unsigned i = 0; i < n; i++)
        {
            ufd[i].fd = p_sys->pi_fd[i];
            ufd[i].events = POLLIN;
            ufd[i].revents = 0;
        }

        int val = poll (ufd, n, timeout);
        canc = vlc_savecancel ();
        if (val > 0)
        {
            for (unsigned i = 0; i < n; i++)
            {
                if (ufd[i].revents)
                {
                    uint8_t p_buffer[MAX_SAP_BUFFER+1];
                    ssize_t i_read;

                    i_read = recv (ufd[i].fd, p_buffer, MAX_SAP_BUFFER, 0);
                    if (i_read < 0)
                        msg_Warn (p_sd, "receive error: %s",
                                  vlc_strerror_c(errno));
                    if (i_read > 6)
                    {
                        /* Parse the packet */
                        p_buffer[i_read] = '\0';
                        ParseSAP (p_sd, p_buffer, i_read);
                    }
                }
            }
        }

        vlc_tick_t now = vlc_tick_now();

        /* A 1 hour timeout correspond to the RFC Implicit timeout.
         * This timeout is tuned in the following loop. */

        timeout = 1000 * 60 * 60;

        /* Check for items that need deletion */
        for( int i = 0; i < p_sys->i_announces; i++ )
        {
            sap_announce_t * p_announce = p_sys->pp_announces[i];
            vlc_tick_t i_last_period = now - p_announce->i_last;

            /* Remove the announcement, if the last announcement was 1 hour ago
             * or if the last packet emitted was 10 times the average time
             * between two packets */

            if( ( p_announce->i_period_trust > 5 && i_last_period > 10 * p_announce->i_period ) ||
                i_last_period > p_sys->i_timeout )
            {
                RemoveAnnounce( p_sd, p_announce );
            }
            else
            {
                /* Compute next timeout */
                if( p_announce->i_period_trust > 5 )
                    timeout = __MIN(MS_FROM_VLC_TICK(10 * p_announce->i_period - i_last_period), timeout);
                timeout = __MIN(MS_FROM_VLC_TICK(p_sys->i_timeout - i_last_period), timeout);
            }
        }

        if( !p_sys->i_announces )
            timeout = -1; /* We can safely poll indefinitely. */
        else if( timeout < 200 )
            timeout = 200; /* Don't wakeup too fast. */
    }
    vlc_assert_unreachable ();
}

/*****************************************************************************
 * Open: initialize and create stuff
 *****************************************************************************/

static int Open( vlc_object_t *p_this )
{
    services_discovery_t *p_sd = ( services_discovery_t* )p_this;
    services_discovery_sys_t *p_sys  = (services_discovery_sys_t *)
                                malloc( sizeof( services_discovery_sys_t ) );
    if( !p_sys )
        return VLC_ENOMEM;

    p_sys->i_timeout = vlc_tick_from_sec(var_CreateGetInteger( p_sd, "sap-timeout" ));

    p_sd->p_sys  = p_sys;
    p_sd->description = _("Network streams (SAP)");

    p_sys->pi_fd = NULL;
    p_sys->i_fd = 0;

    p_sys->i_announces = 0;
    p_sys->pp_announces = NULL;
    p_sys->root_cat = NULL;
    /* TODO: create sockets here, and fix racy sockets table */
    if (vlc_clone (&p_sys->thread, Run, p_sd))
    {
        free (p_sys);
        return VLC_EGENERIC;
    }

    return VLC_SUCCESS;
}

/*****************************************************************************
 * Close:
 *****************************************************************************/

static void Close( vlc_object_t *p_this )
{
    services_discovery_t *p_sd = ( services_discovery_t* )p_this;
    services_discovery_sys_t    *p_sys  = p_sd->p_sys;
    int i;

    vlc_cancel (p_sys->thread);
    vlc_join (p_sys->thread, NULL);

    for( i = p_sys->i_fd-1 ; i >= 0 ; i-- )
    {
        net_Close( p_sys->pi_fd[i] );
    }
    FREENULL( p_sys->pi_fd );

    for( i = p_sys->i_announces  - 1;  i>= 0; i-- )
    {
        RemoveAnnounce( p_sd, p_sys->pp_announces[i] );
    }
    FREENULL( p_sys->pp_announces );
    tdestroy(p_sys->root_cat, DestroyCategory);

    free( p_sys );
}

/*****************************************************************************
 * Module descriptor
 *****************************************************************************/

#define SAP_ADDR_TEXT N_( "SAP multicast address" )
#define SAP_ADDR_LONGTEXT N_( "The SAP module normally chooses itself the " \
                              "right addresses to listen to. However, you " \
                              "can specify a specific address." )
#define SAP_TIMEOUT_TEXT N_( "SAP timeout (seconds)" )
#define SAP_TIMEOUT_LONGTEXT N_( \
       "Delay after which SAP items get deleted if no new announcement " \
       "is received." )
#define SAP_PARSE_TEXT N_( "Try to parse the announce" )
#define SAP_PARSE_LONGTEXT N_( \
       "This enables actual parsing of the announces by the SAP module. " \
       "Otherwise, all announcements are parsed by the \"live555\" " \
       "(RTP/RTSP) module." )

VLC_SD_PROBE_HELPER("sap", N_("Network streams (SAP)"), SD_CAT_LAN)

vlc_module_begin()
    set_shortname(N_("SAP"))
    set_description(N_("Network streams (SAP)") )
    set_subcategory(SUBCAT_PLAYLIST_SD)

    add_string("sap-addr", NULL, SAP_ADDR_TEXT, SAP_ADDR_LONGTEXT)
    add_integer("sap-timeout", 1800,
                SAP_TIMEOUT_TEXT, SAP_TIMEOUT_LONGTEXT)
    add_obsolete_bool("sap-parse") /* since 4.0.0 */
    add_obsolete_bool("sap-strict") /* since 4.0.0 */

    set_capability("services_discovery", 0)
    set_callbacks(Open, Close)

    VLC_SD_PROBE_SUBMODULE
vlc_module_end()

Messung
CHZG
839610093

¤ Dauer der Verarbeitung: 0.12 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.