Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  upnp_server.cpp   Sprache: C

 

/*****************************************************************************
 * upnp_server.cpp : UPnP server module
 *****************************************************************************
 * Copyright © 2024 VLC authors and VideoLAN
 *
 * Authors: Hamza Parnica <hparnica@gmail.com>
 *          Alaric Senat <alaric@videolabs.io>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser 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.
 *****************************************************************************/

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

#include <atomic>
#include <cstring>
#include <services_discovery/upnp-wrapper.hpp>

#include <vlc_common.h>

#include <vlc_addons.h>
#include <vlc_interface.h>
#include <vlc_rand.h>

#include "FileHandler.hpp"
#include "cds/Container.hpp"
#include "cds/cds.hpp"
#include "ml.hpp"
#include "upnp_server.hpp"
#include "utils.hpp"
#include "xml_wrapper.hpp"

#define CDS_ID "urn:upnp-org:serviceId:ContentDirectory"
#define CMS_ID "urn:upnp-org:serviceId:ConnectionManager"

#define UPNP_SERVICE_TYPE(service) "urn:schemas-upnp-org:service:" service ":1"

struct intf_sys_t
{
    ml::MediaLibraryContext p_ml;
    std::unique_ptr<vlc_ml_event_callback_t, std::function<void(vlc_ml_event_callback_t *)>>
        ml_callback_handle;

    std::unique_ptr<char, std::function<void(void *)>> uuid;

    UpnpDevice_Handle p_device_handle;
    std::unique_ptr<UpnpInstanceWrapper, std::function<void(UpnpInstanceWrapper *)>> upnp;

    // This integer is atomically incremented at each medialib modification. It will be sent in each
    // response. If the client notice that the update id has been incremented since the last
    // request, he knows that the server state has changed and hence can refetch the exposed
    // hierarchy.
    std::atomic<unsigned int> upnp_update_id;

    std::vector<std::unique_ptr<cds::Object>> obj_hierarchy;

    bool extra_verbose;
};

static void medialibrary_event_callback(void *p_data, const struct vlc_ml_event_t *p_event)
{
    intf_thread_t *p_intf = (intf_thread_t *)p_data;
    intf_sys_t *p_sys = p_intf->p_sys;

    switch (p_event->i_type)
    {
        case VLC_ML_EVENT_MEDIA_ADDED:
        case VLC_ML_EVENT_MEDIA_UPDATED:
        case VLC_ML_EVENT_MEDIA_DELETED:
        case VLC_ML_EVENT_ARTIST_ADDED:
        case VLC_ML_EVENT_ARTIST_UPDATED:
        case VLC_ML_EVENT_ARTIST_DELETED:
        case VLC_ML_EVENT_ALBUM_ADDED:
        case VLC_ML_EVENT_ALBUM_UPDATED:
        case VLC_ML_EVENT_ALBUM_DELETED:
        case VLC_ML_EVENT_PLAYLIST_ADDED:
        case VLC_ML_EVENT_PLAYLIST_UPDATED:
        case VLC_ML_EVENT_PLAYLIST_DELETED:
        case VLC_ML_EVENT_GENRE_ADDED:
        case VLC_ML_EVENT_GENRE_UPDATED:
        case VLC_ML_EVENT_GENRE_DELETED:
            p_sys->upnp_update_id++;
            break;
    }
}

static bool cds_browse(UpnpActionRequest *request, intf_thread_t *intf)
{
    intf_sys_t *sys = intf->p_sys;

    auto *action_rq =
        reinterpret_cast<IXML_Element *>(UpnpActionRequest_get_ActionRequest(request));

    const char *object_id = xml_getChildElementValue(action_rq, "ObjectID");
    if (object_id == nullptr)
        return false;

    const char *browse_flag = xml_getChildElementValue(action_rq, "BrowseFlag");
    const char *starting_index = xml_getChildElementValue(action_rq, "StartingIndex");
    const char *requested_count = xml_getChildElementValue(action_rq, "RequestedCount");

    if (browse_flag == nullptr || starting_index == nullptr || requested_count == nullptr)
        return false;

    cds::Container::BrowseParams browse_params{
        static_cast<uint32_t>(strtoul(starting_index, nullptr, 10)),
        static_cast<uint32_t>(strtoul(requested_count, nullptr, 10)),
    };

    xml::Document result;
    xml::Element didl_lite = result.create_element("DIDL-Lite");

    // Standard upnp attributes
    didl_lite.set_attribute("xmlns", "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/");
    didl_lite.set_attribute("xmlns:dc", "http://purl.org/dc/elements/1.1/");
    didl_lite.set_attribute("xmlns:upnp", "urn:schemas-upnp-org:metadata-1-0/upnp/");

    const std::string id = object_id;
    unsigned obj_idx;
    cds::Object::ExtraId extra_id;

    try
    {
        std::tie(obj_idx, extra_id) = cds::parse_id(id);
    }
    catch (const std::invalid_argument &e)
    {
        UpnpActionRequest_set_ErrCode(request, 500);
        return false;
    }

    std::string str_nb_returned;
    std::string str_total_matches;
    const cds::Object &obj = *sys->obj_hierarchy[obj_idx];
    try
    {
        if (strcmp(browse_flag, "BrowseDirectChildren") == 0 &&
            obj.type == cds::Object::Type::Container)
        {
            const auto browse_stats =
                static_cast<const cds::Container &>(obj).browse_direct_children(
                    didl_lite, browse_params, extra_id);
            str_nb_returned = std::to_string(browse_stats.result_count);
            str_total_matches = std::to_string(browse_stats.total_matches);
        }
        else if (strcmp(browse_flag, "BrowseMetadata") == 0)
        {
            didl_lite.add_child(obj.browse_metadata(result, extra_id));
            str_nb_returned = "1";
            str_total_matches = "1";
        }
    } catch (const ml::errors::ForbiddenAccess&)
    {
        msg_Warn(intf, "Client tried to browse a private medialibrary element.");
        UpnpActionRequest_set_ErrCode(request, 404);
        return false;
    } catch (const ml::errors::UnknownObject&)
    {
        UpnpActionRequest_set_ErrCode(request, 404);
        return false;
    }

    result.set_entry(std::move(didl_lite));

    const char *action_name = UpnpActionRequest_get_ActionName_cstr(request);

    const auto up_reponse_str = result.to_wrapped_cstr();
    const auto str_update_id = std::to_string(sys->upnp_update_id.load());

    auto *p_answer = UpnpActionRequest_get_ActionResult(request);

    static constexpr char service_type[] = UPNP_SERVICE_TYPE("ContentDirectory");
    UpnpAddToActionResponse(&p_answer, action_name, service_type, "Result", up_reponse_str.get());
    UpnpAddToActionResponse(
        &p_answer, action_name, service_type, "NumberReturned", str_nb_returned.c_str());
    UpnpAddToActionResponse(
        &p_answer, action_name, service_type, "TotalMatches", str_total_matches.c_str());
    UpnpAddToActionResponse(
        &p_answer, action_name, service_type, "UpdateID", str_update_id.c_str());

    UpnpActionRequest_set_ActionResult(request, p_answer);
    if (sys->extra_verbose)
        msg_Dbg(intf, "Sending response to client: \n%s", up_reponse_str.get());

    return true;
}

static void handle_action_request(UpnpActionRequest *p_request, intf_thread_t *p_intf)
{
    intf_sys_t *sys = p_intf->p_sys;

    IXML_Document *action_result = nullptr;
    const char *service_id = UpnpActionRequest_get_ServiceID_cstr(p_request);
    const char *action_name = UpnpActionRequest_get_ActionName_cstr(p_request);
    const auto client_addr = utils::addr_to_string(UpnpActionRequest_get_CtrlPtIPAddr(p_request));
    msg_Dbg(p_intf,
            "Received action request \"%s\" for service \"%s\" from %s",
            action_name,
            service_id,
            client_addr.c_str());
    if (strcmp(service_id, CMS_ID) == 0)
    {
        static constexpr char cms_type[] = UPNP_SERVICE_TYPE("ConnectionManager");
        if (strcmp(action_name, "GetProtocolInfo") == 0)
        {
            UpnpAddToActionResponse(
                &action_result, action_name, cms_type, "Source", "http-get:*:*:*");
            UpnpAddToActionResponse(&action_result, action_name, cms_type, "Sink", "");
            UpnpActionRequest_set_ActionResult(p_request, action_result);
        }
        else if (strcmp(action_name, "GetCurrentConnectionIDs") == 0)
        {
            UpnpAddToActionResponse(&action_result, action_name, cms_type, "ConnectionIDs", "");
            UpnpActionRequest_set_ActionResult(p_request, action_result);
        }
    }

    else if (strcmp(service_id, CDS_ID) == 0)
    {
        static constexpr char cds_type[] = UPNP_SERVICE_TYPE("ContentDirectory");

        if (strcmp(action_name, "Browse") == 0)
        {
            if (!cds_browse(p_request, p_intf))
                msg_Err(p_intf, "Failed to respond to browse action request");
        }
        else if (strcmp(action_name, "GetSearchCapabilities") == 0)
        {
            UpnpAddToActionResponse(&action_result, action_name, cds_type, "SearchCaps", "");
            UpnpActionRequest_set_ActionResult(p_request, action_result);
        }
        else if (strcmp(action_name, "GetSortCapabilities") == 0)
        {
            UpnpAddToActionResponse(&action_result, action_name, cds_type, "SortCaps", "");
            UpnpActionRequest_set_ActionResult(p_request, action_result);
        }
        else if (strcmp(action_name, "GetSystemUpdateID") == 0)
        {
            char *psz_update_id;
            if (asprintf(&psz_update_id, "%d", sys->upnp_update_id.load()) == -1)
                return;
            auto up_update_id = vlc::wrap_cptr(psz_update_id);
            UpnpAddToActionResponse(&action_result, action_name, cds_type, "Id", psz_update_id);
            UpnpActionRequest_set_ActionResult(p_request, action_result);
        }
    }
    else if (strcmp(service_id, "urn:microsoft.com:serviceId:X_MS_MediaReceiverRegistrar") == 0)
    {
        // We need a mockup of this service to support Microsoft devices and clients.
        if (strcmp(action_name, "IsAuthorized") == 0)
        {
            UpnpAddToActionResponse(&action_result, action_name, CDS_ID, "Result", "1");
            UpnpActionRequest_set_ActionResult(p_request, action_result);
        }
        else if (strcmp(action_name, "IsValidated") == 0)
        {
            UpnpAddToActionResponse(&action_result, action_name, CDS_ID, "Result", "1");
            UpnpActionRequest_set_ActionResult(p_request, action_result);
        }
    }
}

static int Callback(Upnp_EventType event_type, UpnpEventPtr event, void *cookie)
{
    auto *intf = static_cast<intf_thread_t *>(cookie);

    switch (event_type)
    {
        case UPNP_CONTROL_ACTION_REQUEST:
        {
            msg_Dbg(intf, "Action request");
            // We need to const_cast here because the upnp callback has to take a const void* for
            // the event data even if the sdk also expect us to modify it sometimes (in the case of
            // a upnp response for example)
            auto *rq =
                const_cast<UpnpActionRequest *>(static_cast<const UpnpActionRequest *>(event));
            handle_action_request(rq, intf);
        }
        break;
        case UPNP_CONTROL_GET_VAR_REQUEST:
            msg_Dbg(intf, "Var request");
            break;

        case UPNP_EVENT_SUBSCRIPTION_REQUEST:
            msg_Dbg(intf, "Sub request");
            break;

        default:
            msg_Err(intf, "Unhandled event: %d", event_type);
            return UPNP_E_INVALID_ACTION;
    }

    return UPNP_E_SUCCESS;
}

// UPNP Callbacks

static int
getinfo_cb(const char *url, UpnpFileInfo *info, const void *void_intf, const void **fhandler)
{
    intf_thread_t *intf = (intf_thread_t *)(void_intf);
    const char *user_agent = UpnpFileInfo_get_Os_cstr(info);
    if (user_agent == nullptr)
        return UPNP_E_BAD_REQUEST;

    msg_Dbg(intf, "GetInfo callback on: \"%s\" from: \"%s\"", url, user_agent);

    UpnpFileInfo_set_IsReadable(info, true);
    UpnpFileInfo_set_IsDirectory(info, false);

    std::unique_ptr<FileHandler> file_handler;
    try
    {
        file_handler = parse_url(url, intf->p_sys->p_ml);
    }
    catch (const std::exception &)
    {
        return UPNP_E_FILE_NOT_FOUND;
    }

    if (file_handler == nullptr)
        return UPNP_E_FILE_NOT_FOUND;

    file_handler->get_info(*info);

    // Pass the filehandler ownership to the open callback to avoid reparsing the url
    *fhandler = static_cast<void*>(file_handler.release());

    return UPNP_E_SUCCESS;
}

static UpnpWebFileHandle
open_cb(const char *url, enum UpnpOpenFileMode, const void *void_intf, const void *file_handler)
{
    intf_thread_t *intf = (intf_thread_t *)(void_intf);
    msg_Dbg(intf, "Opening: %s", url);

    FileHandler *ret = (FileHandler *)(file_handler);
    if (!ret)
    {
        try
        {
            ret = parse_url(url, intf->p_sys->p_ml).release();
        }
        catch (const std::exception &)
        {
            return nullptr;
        }
    }

    if (ret == nullptr)
        return nullptr;

    if (!ret->open(VLC_OBJECT(intf)))
    {
        msg_Err(intf, "Failed to open %s", url);
        delete ret;
        return nullptr;
    }
    return ret;
}

static int
read_cb(UpnpWebFileHandle fileHnd, char *buf, size_t buflen, const void *void_intf, const void *)
{
    intf_thread_t *intf = (intf_thread_t *)(void_intf);
    assert(fileHnd);

    auto *impl = static_cast<FileHandler *>(fileHnd);
    const size_t bytes_read = impl->read((uint8_t*)buf, buflen);

    msg_Dbg(intf, "http read callback, %zub requested %zub returned", buflen, bytes_read);

    return bytes_read;
}

static int
seek_cb(UpnpWebFileHandle fileHnd, off_t offset, int origin, const void *void_intf, const void *)
{
    intf_thread_t *intf = (intf_thread_t *)(void_intf);
    assert(fileHnd);
    msg_Dbg(
        intf, "http seek callback offset: %jd origin: %d", static_cast<intmax_t>(offset), origin);

    auto *impl = static_cast<FileHandler *>(fileHnd);
    const auto seek_type = static_cast<FileHandler::SeekType>(origin);
    const bool success = impl->seek(seek_type, offset);

    return success == true ? 0 : -1;
}

static int close_cb(UpnpWebFileHandle fileHnd, const void *void_intf, const void *)
{
    intf_thread_t *intf = (intf_thread_t *)(void_intf);
    assert(fileHnd);
    msg_Dbg(intf, "http close callback");
    delete static_cast<FileHandler *>(fileHnd);
    return 0;
}

static xml::Document make_server_identity(const char *uuid, const char *server_name)
{
    xml::Document ret;

    const auto icon_elem =
        [&ret](const char *url, const char *width, const char *height) -> xml::Element {
        return ret.create_element("icon",
                                  ret.create_element("mimetype", ret.create_text_node("image/png")),
                                  ret.create_element("width", ret.create_text_node(width)),
                                  ret.create_element("height", ret.create_text_node(height)),
                                  ret.create_element("depth", ret.create_text_node("8")),
                                  ret.create_element("url", ret.create_text_node(url)));
    };

    const auto service_elem = [&ret](const char *service) -> xml::Element {
        const auto type = std::string("urn:schemas-upnp-org:service:") + service + ":1";
        const auto id = std::string("urn:upnp-org:serviceId:") + service;
        const auto scpd_url = std::string("/") + service + ".xml";
        const auto control_url = std::string("/") + service + "/Control";
        const auto event_url = std::string("/") + service + "/Event";
        return ret.create_element(
            "service",
            ret.create_element("serviceType", ret.create_text_node(type.c_str())),
            ret.create_element("serviceId", ret.create_text_node(id.c_str())),
            ret.create_element("SCPDURL", ret.create_text_node(scpd_url.c_str())),
            ret.create_element("controlURL", ret.create_text_node(control_url.c_str())),
            ret.create_element("eventSubURL", ret.create_text_node(event_url.c_str())));
    };

    const std::string url = utils::get_server_url();

    const auto uuid_attr = std::string("uuid:") + uuid;

    xml::Element dlna_doc = ret.create_element("dlna:X_DLNADOC", ret.create_text_node("DMS-1.50"));
    dlna_doc.set_attribute("xmlns:dlna", "urn:schemas-dlna-org:device-1-0");

    xml::Element root = ret.create_element(
        "root",
        ret.create_element("specVersion",
                           ret.create_element("major", ret.create_text_node("1")),
                           ret.create_element("minor", ret.create_text_node("0"))),
        ret.create_element(
            "device",
            std::move(dlna_doc),

            ret.create_element("deviceType",
                               ret.create_text_node("urn:schemas-upnp-org:device:MediaServer:1")),
            ret.create_element("presentationUrl", ret.create_text_node(url.c_str())),
            ret.create_element("friendlyName", ret.create_text_node(server_name)),
            ret.create_element("manufacturer", ret.create_text_node("VideoLAN")),
            ret.create_element("manufacturerURL", ret.create_text_node("https://videolan.org")),
            ret.create_element("modelDescription", ret.create_text_node("VLC UPNP Media Server")),
            ret.create_element("modelName", ret.create_text_node("VLC")),
            ret.create_element("modelNumber", ret.create_text_node(PACKAGE_VERSION)),
            ret.create_element("modelURL", ret.create_text_node("https://videolan.org/vlc/")),
            ret.create_element("serialNumber", ret.create_text_node("1")),
            ret.create_element("UDN", ret.create_text_node(uuid_attr.c_str())),
            ret.create_element("iconList",
                               icon_elem("/vlc.png", "32", "32"),
                               icon_elem("/vlc512x512.png", "512", "512")),
            ret.create_element(
                "serviceList",
                service_elem("ConnectionManager"),
                service_elem("ContentDirectory"),

                ret.create_element(
                    "service",
                    ret.create_element(
                        "serviceType",
                        ret.create_text_node(
                            "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1")),
                    ret.create_element(
                        "serviceId",
                        ret.create_text_node(
                            "urn:microsoft.com:serviceId:X_MS_MediaReceiverRegistrar")),
                    ret.create_element("SCPDURL",
                                       ret.create_text_node("/X_MS_MediaReceiverRegistrar.xml")),
                    ret.create_element(
                        "controlURL", ret.create_text_node("/X_MS_MediaReceiverRegistrar/Control")),
                    ret.create_element(
                        "eventSubURL",
                        ret.create_text_node("/X_MS_MediaReceiverRegistrar/Event"))))));
    root.set_attribute("xmlns", "urn:schemas-upnp-org:device-1-0");

    ret.set_entry(std::move(root));
    return ret;
}

static bool init_upnp(intf_thread_t *intf)
{
    intf_sys_t *sys = intf->p_sys;

    addon_uuid_t uuid;
    vlc_rand_bytes(uuid, sizeof(uuid));
    sys->uuid = vlc::wrap_cptr(addons_uuid_to_psz(&uuid), &free);

    int res = UpnpEnableWebserver(true);
    if (res != UPNP_E_SUCCESS)
    {
        msg_Err(intf, "Enabling libupnp webserver failed: %s", UpnpGetErrorMessage(res));
        return false;
    }

    msg_Info(intf, "Upnp server enabled on %s:%d", UpnpGetServerIpAddress(), UpnpGetServerPort());

    const auto str_rootdir = utils::get_root_dir();

    res = UpnpSetWebServerRootDir(str_rootdir.c_str());
    msg_Dbg(intf, "Webserver root dir set to: \"%s\"", str_rootdir.c_str());
    if (res != UPNP_E_SUCCESS)
    {
        msg_Err(intf, "Setting webserver root dir failed: %s", UpnpGetErrorMessage(res));
        UpnpEnableWebserver(false);
        return false;
    }

    const auto server_name = vlc::wrap_cptr(var_InheritString(intf, SERVER_PREFIX "name"), &free);
    assert(server_name);
    const auto presentation_doc = make_server_identity(sys->uuid.get(), server_name.get());
    const auto up_presentation_str = presentation_doc.to_wrapped_cstr();

    if (sys->extra_verbose)
        msg_Dbg(intf, "%s", up_presentation_str.get());

    res = UpnpRegisterRootDevice2(UPNPREG_BUF_DESC,
                                  up_presentation_str.get(),
                                  strlen(up_presentation_str.get()),
                                  1,
                                  Callback,
                                  intf,
                                  &sys->p_device_handle);
    if (res != UPNP_E_SUCCESS)
    {
        msg_Err(intf, "Registration failed: %s", UpnpGetErrorMessage(res));
        UpnpEnableWebserver(false);
        return false;
    }

    UpnpVirtualDir_set_GetInfoCallback(reinterpret_cast<VDCallback_GetInfo>(getinfo_cb));
    UpnpVirtualDir_set_OpenCallback(reinterpret_cast<VDCallback_Open>(open_cb));
    UpnpVirtualDir_set_ReadCallback(reinterpret_cast<VDCallback_Read>(read_cb));
    UpnpVirtualDir_set_SeekCallback(reinterpret_cast<VDCallback_Seek>(seek_cb));
    UpnpVirtualDir_set_CloseCallback(reinterpret_cast<VDCallback_Close>(close_cb));

    UpnpAddVirtualDir("/media", intf, nullptr);
    UpnpAddVirtualDir("/thumbnail", intf, nullptr);
    UpnpAddVirtualDir("/subtitle", intf, nullptr);

    res = UpnpSendAdvertisement(sys->p_device_handle, 1800);
    if (res != UPNP_E_SUCCESS)
    {
        msg_Dbg(intf, "Advertisement failed: %s", UpnpGetErrorMessage(res));
        UpnpUnRegisterRootDevice(sys->p_device_handle);
        UpnpEnableWebserver(false);
        return false;
    }

    sys->upnp_update_id = 0;

    return true;
}

namespace Server
{

int open(vlc_object_t *p_this)
{
    intf_thread_t *intf = container_of(p_this, intf_thread_t, obj);
    intf_sys_t *sys = new (std::nothrow) intf_sys_t;
    if (unlikely(sys == nullptr))
        return VLC_ENOMEM;
    intf->p_sys = sys;

    const bool share_private_media = var_InheritBool(p_this, SERVER_PREFIX "share-private-media");
    sys->p_ml = ml::MediaLibraryContext{vlc_ml_instance_get(p_this), share_private_media};
    if (!sys->p_ml.handle)
    {
        msg_Err(intf, "Medialibrary not initialized");
        delete sys;
        return VLC_EGENERIC;
    }

    vlc_ml_event_callback_t *cb =
        vlc_ml_event_register_callback(sys->p_ml.handle, medialibrary_event_callback, intf);
    const auto release_cb = [sys](vlc_ml_event_callback_t *cb) {
        vlc_ml_event_unregister_callback(sys->p_ml.handle, cb);
    };
    sys->ml_callback_handle = {cb, release_cb};

    sys->upnp = vlc::wrap_cptr(UpnpInstanceWrapper::get(p_this),
                               [](UpnpInstanceWrapper *p_upnp) { p_upnp->release(); });

    sys->obj_hierarchy = cds::init_hierarchy(sys->p_ml);
    sys->extra_verbose = var_InheritInteger(p_this, "verbose") >= 4;

    if (!init_upnp(intf))
    {
        delete sys;
        return VLC_EGENERIC;
    }

    return VLC_SUCCESS;
}

void close(vlc_object_t *p_this)
{
    intf_thread_t *intf = container_of(p_this, intf_thread_t, obj);
    intf_sys_t *sys = intf->p_sys;
    UpnpUnRegisterRootDevice(sys->p_device_handle);
    UpnpEnableWebserver(false);
    delete sys;
}
} // namespace Server

Messung
CHZG
83999893

¤ Dauer der Verarbeitung: 0.2 Sekunden  (vorverarbeitet am  2026-11-04) ¤

*© 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.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1104852
#Domains=417160