Webserver+3d print

Dependents:   Nucleo

cyclone_tcp/icecast/icecast_client.c

Committer:
Sergunb
Date:
2017-02-04
Revision:
0:8918a71cdbe9

File content as of revision 0:8918a71cdbe9:

/**
 * @file icecast_client.c
 * @brief Icecast client
 *
 * @section License
 *
 * Copyright (C) 2010-2017 Oryx Embedded SARL. All rights reserved.
 *
 * This file is part of CycloneTCP Open.
 *
 * 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.
 *
 * @author Oryx Embedded SARL (www.oryx-embedded.com)
 * @version 1.7.6
 **/

//Switch to the appropriate trace level
#define TRACE_LEVEL ICECAST_TRACE_LEVEL

//Dependencies
#include <stdlib.h>
#include "icecast/icecast_client.h"
#include "str.h"
#include "debug.h"

//Check TCP/IP stack configuration
#if (ICECAST_CLIENT_SUPPORT == ENABLED)


/**
 * @brief Initialize settings with default values
 * @param[out] settings Structure that contains Icecast client settings
 **/

void icecastClientGetDefaultSettings(IcecastClientSettings *settings)
{
   //Use default interface
   settings->interface = netGetDefaultInterface();

   //Icecast server name
   strcpy(settings->serverName, "");
   //Icecast server port
   settings->serverPort = 8000;
   //Requested resource
   strcpy(settings->resource, "/stream");

   //Streaming buffer size
   settings->bufferSize = 80000;
}


/**
 * @brief Icecast client initialization
 * @param[in] context Pointer to the Icecast client context
 * @param[in] settings Icecast client specific settings
 * @return Error code
 **/

error_t icecastClientInit(IcecastClientContext *context,
   const IcecastClientSettings *settings)
{
   error_t error;

   //Debug message
   TRACE_INFO("Initializing Icecast client...\r\n");

   //Ensure the parameters are valid
   if(!context || !settings)
      return ERROR_INVALID_PARAMETER;

   //Clear the Icecast client context
   memset(context, 0, sizeof(IcecastClientContext));

   //Save user settings
   context->settings = *settings;
   //Get the size of the circular buffer
   context->bufferSize = settings->bufferSize;

   //Start of exception handling block
   do
   {
      //Allocate a memory block to hold the circular buffer
      context->streamBuffer = osAllocMem(context->bufferSize);
      //Failed to allocate memory?
      if(!context->streamBuffer)
      {
         //Report an error to the calling function
         error = ERROR_OUT_OF_MEMORY;
         break;
      }

      //Create mutex object to protect critical sections
      if(!osCreateMutex(&context->mutex))
      {
         //Failed to create mutex
         error = ERROR_OUT_OF_RESOURCES;
         break;
      }

      //Create event to get notified when the buffer is writable
      if(!osCreateEvent(&context->writeEvent))
      {
         //Failed to create event object
         error = ERROR_OUT_OF_RESOURCES;
         break;
      }

      //The buffer is available for writing
      osSetEvent(&context->writeEvent);

      //Create event to get notified when the buffer is readable
      if(!osCreateEvent(&context->readEvent))
      {
         //Failed to create event object
         error = ERROR_OUT_OF_RESOURCES;
         break;
      }

      //Successful initialization
      error = NO_ERROR;

      //End of exception handling block
   } while(0);

   //Check whether an error occurred
   if(error)
   {
      //Clean up side effects...
      osFreeMem(context->streamBuffer);
      osDeleteMutex(&context->mutex);
      osDeleteEvent(&context->writeEvent);
      osDeleteEvent(&context->readEvent);
   }

   //Return status code
   return error;
}


/**
 * @brief Start Icecast client
 * @param[in] context Pointer to the Icecast client context
 * @return Error code
 **/

error_t icecastClientStart(IcecastClientContext *context)
{
   OsTask *task;

   //Debug message
   TRACE_INFO("Starting Icecast client...\r\n");

   //Create the Icecast client task
   task = osCreateTask("Icecast client", icecastClientTask,
      context, ICECAST_CLIENT_STACK_SIZE, ICECAST_CLIENT_PRIORITY);

   //Unable to create the task?
   if(task == OS_INVALID_HANDLE)
      return ERROR_OUT_OF_RESOURCES;

   //Successful processing
   return NO_ERROR;
}


/**
 * @brief Copy data from input stream
 * @param[in] context Pointer to the Icecast client context
 * @param[out] data Pointer to the user buffer
 * @param[in] size Maximum number of bytes that can be read
 * @param[out] length Number of bytes that have been read
 * @param[in] timeout Maximum time to wait before returning
 * @return Error code
 **/

error_t icecastClientReadStream(IcecastClientContext *context,
   uint8_t *data, size_t size, size_t *length, systime_t timeout)
{
   bool_t status;

   //Ensure the parameters are valid
   if(!context || !data)
      return ERROR_INVALID_PARAMETER;

   //Wait for the buffer to be available for reading
   status = osWaitForEvent(&context->readEvent, timeout);
   //Timeout error?
   if(!status)
      return ERROR_TIMEOUT;

   //Enter critical section
   osAcquireMutex(&context->mutex);
   //Compute the number of bytes to read at a time
   *length = MIN(size, context->bufferLength);
   //Leave critical section
   osReleaseMutex(&context->mutex);

   //Check whether the specified data crosses buffer boundaries
   if((context->readIndex + *length) <= context->bufferSize)
   {
      //Copy the data
      memcpy(data, context->streamBuffer + context->readIndex, *length);
   }
   else
   {
      //Copy the first part of the data
      memcpy(data, context->streamBuffer + context->readIndex,
         context->bufferSize - context->readIndex);
      //Wrap around to the beginning of the circular buffer
      memcpy(data + context->bufferSize - context->readIndex, context->streamBuffer,
         *length - context->bufferSize + context->readIndex);
   }

   //Enter critical section
   osAcquireMutex(&context->mutex);

   //Increment read index
   context->readIndex += *length;
   //Wrap around if necessary
   if(context->readIndex >= context->bufferSize)
      context->readIndex -= context->bufferSize;

   //Update buffer length
   context->bufferLength -= *length;
   //Check whether the buffer is available for writing
   if(context->bufferLength < context->bufferSize)
      osSetEvent(&context->writeEvent);
   //Check whether the buffer is available for reading
   if(context->bufferLength > 0)
      osSetEvent(&context->readEvent);

   //Leave critical section
   osReleaseMutex(&context->mutex);

   //Successful read operation
   return NO_ERROR;
}


/**
 * @brief Copy metadata from input stream
 * @param[in] context Pointer to the Icecast client context
 * @param[out] metadata Pointer to the user buffer
 * @param[in] size Maximum number of bytes that can be read
 * @param[out] length Number of bytes that have been read
 * @return Error code
 **/

error_t icecastClientReadMetadata(IcecastClientContext *context,
   char_t *metadata, size_t size, size_t *length)
{
   //Ensure the parameters are valid
   if(!context || !metadata)
      return ERROR_INVALID_PARAMETER;

   //Enter critical section
   osAcquireMutex(&context->mutex);

   //Limit the number of data to read
   *length = MIN(size, context->metadataLength);
   //Save metadata information
   memcpy(metadata, context->metadata, *length);

   //Leave critical section
   osReleaseMutex(&context->mutex);

   //Successful read operation
   return NO_ERROR;
}


/**
 * @brief Icecast client task
 * @param[in] param Pointer to the Icecast client context
 **/

void icecastClientTask(void *param)
{
   error_t error;
   bool_t end;
   size_t n;
   size_t length;
   size_t received;
   IcecastClientContext *context;

   //Retrieve the Icecast client context
   context = (IcecastClientContext *) param;

   //Main loop
   while(1)
   {
      //Debug message
      TRACE_INFO("Icecast client: Connecting to server %s port %" PRIu16 "\r\n",
         context->settings.serverName, context->settings.serverPort);

      //Initiate a connection to the Icecast server
      error = icecastClientConnect(context);

      //Connection to server failed?
      if(error)
      {
         //Debug message
         TRACE_ERROR("Icecast client: Connection to server failed!\r\n");
         //Recovery delay
         osDelayTask(ICECAST_RECOVERY_DELAY);
         //Try to reconnect...
         continue;
      }

      //Debug message
      TRACE_INFO("Block size = %" PRIuSIZE "\r\n", context->blockSize);

      //Check block size
      if(!context->blockSize)
      {
         //Close socket
         socketClose(context->socket);
         //Recovery delay
         osDelayTask(ICECAST_RECOVERY_DELAY);
         //Try to reconnect...
         continue;
      }

      //Initialize loop condition variable
      end = FALSE;

      //Read as much data as possible...
      while(!end)
      {
         //Process the stream block by block
         length = context->blockSize;

         //Read current block
         while(!end && length > 0)
         {
            //Wait for the buffer to be available for writing
            osWaitForEvent(&context->writeEvent, INFINITE_DELAY);

            //Enter critical section
            osAcquireMutex(&context->mutex);
            //Compute the number of bytes to read at a time
            n = MIN(length, context->bufferSize - context->bufferLength);
            //Leave critical section
            osReleaseMutex(&context->mutex);

            //Check whether the specified data crosses buffer boundaries
            if((context->writeIndex + n) > context->bufferSize)
               n = context->bufferSize - context->writeIndex;

            //Receive data
            error = socketReceive(context->socket, context->streamBuffer +
               context->writeIndex, n, &received, SOCKET_FLAG_WAIT_ALL);

            //Any error to report?
            if(error)
            {
               //Stop streaming data
               end = TRUE;
            }
            else
            {
               //Enter critical section
               osAcquireMutex(&context->mutex);

               //Increment write index
               context->writeIndex += n;
               //Wrap around if necessary
               if(context->writeIndex >= context->bufferSize)
                  context->writeIndex -= context->bufferSize;

               //Update buffer length
               context->bufferLength += n;
               //Check whether the buffer is available for writing
               if(context->bufferLength < context->bufferSize)
                  osSetEvent(&context->writeEvent);
               //Check whether the buffer is available for reading
               if(context->bufferLength > 0)
                  osSetEvent(&context->readEvent);

               //Leave critical section
               osReleaseMutex(&context->mutex);

               //Update the total number of bytes that have been received
               context->totalLength += n;
               //Number of remaining data to read
               length -= n;
            }
         }

         //Debug message
         TRACE_DEBUG("Icecast client: Total bytes received = %" PRIuSIZE "\r\n", context->totalLength);

         //Check whether the metadata block should be read
         if(!end)
         {
            //Process the metadata block
            error = icecastClientProcessMetadata(context);
            //Any error to report?
            if(error) end = TRUE;
         }
      }

      //Close connection
      socketClose(context->socket);
   }
}


/**
 * @brief Connect to the specified Icecast server
 * @param[in] context Pointer to the Icecast client context
 **/

error_t icecastClientConnect(IcecastClientContext *context)
{
   error_t error;
   size_t length;
   uint16_t serverPort;
   IpAddr serverIpAddr;
   NetInterface *interface;

   //Underlying network interface
   interface = context->settings.interface;

   //Force traffic to go through a proxy server?
   if(strcmp(interface->proxyName, ""))
   {
      //Icecast request template
      const char_t requestTemplate[] =
         "GET http://%s:%" PRIu16 "%s HTTP/1.1\r\n"
         "Host: %s:%" PRIu16 "\r\n"
         "User-agent: UserAgent\r\n"
         "Icy-MetaData: 1\r\n"
         "Connection: close\r\n"
         "\r\n";

      //Format Icecast request
      length = sprintf(context->buffer, requestTemplate,
         context->settings.serverName, context->settings.serverPort,
         context->settings.resource, context->settings.serverName,
         context->settings.serverPort);

      //The specified proxy server can be either an IP or a host name
      error = getHostByName(interface, interface->proxyName, &serverIpAddr, 0);
      //Unable to resolve server name?
      if(error)
         return error;

      //Proxy server port
      serverPort = interface->proxyPort;
   }
   else
   {
      //Icecast request template
      const char_t requestTemplate[] =
         "GET %s HTTP/1.1\r\n"
         "Host: %s\r\n"
         "User-agent: UserAgent\r\n"
         "Icy-MetaData: 1\r\n"
         "Connection: close\r\n"
         "\r\n";

      //Format Icecast request
      length = sprintf(context->buffer, requestTemplate,
         context->settings.resource, context->settings.serverName);

      //The specified Icecast server can be either an IP or a host name
      error = getHostByName(interface, context->settings.serverName, &serverIpAddr, 0);
      //Unable to resolve server name?
      if(error)
         return error;

      //Icecast server port
      serverPort = context->settings.serverPort;
   }

   //Open a TCP socket
   context->socket = socketOpen(SOCKET_TYPE_STREAM, SOCKET_IP_PROTO_TCP);
   //Failed to open socket?
   if(!context->socket)
      return ERROR_OUT_OF_RESOURCES;

   //Start of exception handling block
   do
   {
      //Associate the socket with the relevant interface
      error = socketBindToInterface(context->socket, interface);
      //Unable to bind the socket to the desired interface?
      if(error)
         break;

      //Adjust receive timeout
      error = socketSetTimeout(context->socket, ICECAST_CLIENT_TIMEOUT);
      //Any error to report?
      if(error)
         break;

      //Connect to the server
      error = socketConnect(context->socket, &serverIpAddr, serverPort);
      //Connection with server failed?
      if(error)
         break;

      //Display Icecast request for debugging purpose
      TRACE_DEBUG(context->buffer);

      //Send request to the server
      error = socketSend(context->socket, context->buffer,
         length, NULL, SOCKET_FLAG_WAIT_ACK);
      //Failed to send the request?
      if(error)
         break;

      //Parse response header
      while(1)
      {
         char_t *separator;
         char_t *property;
         char_t *value;

         //Read a line from the response header
         error = socketReceive(context->socket, context->buffer,
            ICECAST_CLIENT_METADATA_MAX_SIZE, &length, SOCKET_FLAG_BREAK_CRLF);
         //Failed to read data?
         if(error)
            break;

         //Properly terminate the string with a NULL character
         context->buffer[length] = '\0';

         //The end of the header has been reached?
         if(!strcmp(context->buffer, "\r\n"))
            break;

         //Check whether a separator is present
         separator = strchr(context->buffer, ':');

         //Separator found?
         if(separator)
         {
            //Split the line
            *separator = '\0';

            //Get property name and value
            property = strTrimWhitespace(context->buffer);
            value = strTrimWhitespace(separator + 1);

            //Debug message
            TRACE_INFO("<%s>=<%s>\r\n", property, value);

            //Icy-Metaint property found?
            if(!strcasecmp(property, "Icy-Metaint"))
            {
               //Retrieve the block size used by the Icecast server
               context->blockSize = atoi(value);
            }
         }
      }

      //End of exception handling block
   } while(0);

   //Check whether an error occurred
   if(error)
   {
      //Clean up side effects
      socketClose(context->socket);
   }

   //Return status code
   return error;
}


/**
 * @brief Decode metadata block
 * @param[in] context Pointer to the Icecast client context
 **/

error_t icecastClientProcessMetadata(IcecastClientContext *context)
{
   error_t error;
   size_t n;
   size_t length;
   size_t metadataLength;

   //The metadata block begins with a single byte which indicates
   //how many 16-byte segments need to be read
   error = socketReceive(context->socket, context->buffer,
      sizeof(uint8_t), &length, SOCKET_FLAG_WAIT_ALL);

   //Any error to report?
   if(error)
      return error;
   //Make sure the expected number of bytes have been received
   if(length != sizeof(uint8_t))
      return ERROR_INVALID_METADATA;

   //Compute the length of the following metadata block
   metadataLength = context->buffer[0] * 16;
   //Limit the number of bytes to read
   n = MIN(metadataLength, ICECAST_CLIENT_METADATA_MAX_SIZE - 1);

   //Read the metadata information
   error = socketReceive(context->socket, context->buffer,
      n, &length, SOCKET_FLAG_WAIT_ALL);

   //Any error to report?
   if(error)
      return error;
   //Make sure the expected number of bytes have been received
   if(length != n)
      return ERROR_INVALID_METADATA;

   //Enter critical section
   osAcquireMutex(&context->mutex);

   //Save metadata information
   memcpy(context->metadata, context->buffer, n);
   //Terminate the string properly
   context->metadata[n] = '\0';
   //Record the length of the metadata
   context->metadataLength = n;

   //Leave critical section
   osReleaseMutex(&context->mutex);

   //Any metadata information received?
   if(n > 0)
   {
      //Debug message
      TRACE_DEBUG("Icecast client: Metadata = <%s>\r\n", context->metadata);
   }

   //Compute the number of bytes that have not been processed
   metadataLength -= n;

   //Read the complete metadata
   while(metadataLength > 0)
   {
      //Compute the number of data to read at a time
      n = MIN(metadataLength, ICECAST_CLIENT_METADATA_MAX_SIZE);

      //Drop incoming data...
      error = socketReceive(context->socket, context->buffer,
         n, &length, SOCKET_FLAG_WAIT_ALL);

      //Any error to report?
      if(error)
         return error;
      //Make sure the expected number of bytes have been received
      if(length != n)
         return ERROR_INVALID_METADATA;

      //Update byte counter
      metadataLength -= n;
   }

   //Successful processing
   return NO_ERROR;
}

#endif