Automatic testing of CPAL script -- UDP series functions

Posted by alefort on Tue, 18 Jan 2022 16:55:09 +0100

  UDP is the abbreviation of User Datagram Protocol, and its Chinese name is User Datagram Protocol. It is a connectionless transport layer protocol in OSI (Open System Interconnection) reference model, which provides transaction oriented simple and unreliable information transmission service. IETF RFC 768 is the formal specification of UDP. The protocol number of UDP in IP message is 17. UDP protocol is used to process packets like TCP protocol. In the OSI model, both are located at the transport layer and the upper layer of IP protocol. UDP does not provide packet Grouping, assembly and sorting. In other words, after the message is sent, it is impossible to know whether it arrives safely and completely. UDP is used to support network applications that need to transfer data between computers. Many client / server mode network applications, including network video conference system, need to use UDP protocol. UDP message has no reliability guarantee, sequence guarantee and flow control field, so its reliability is poor. However, because UDP protocol has few control options, it has small delay and high data transmission efficiency in the process of data transmission. It is suitable for applications that do not require high reliability, or applications that can ensure reliability, such as DNS, TFTP, SNMP, etc.


  UDP API is used for UDP communication. It provides a high-level interface for connectionless and datagram oriented communication. Specific function information can be obtained from the following:

  1. UdpClose function
      it mainly closes the UDP Socket. After the function is successfully executed, the socket passed is no longer valid. Please see the specific examples at the end of the article. The specific use structure is as follows:

      the return value of the function is as follows:
      0: function execution succeeded
      WSA_INVALID_PARAMETER (87): invalid socket specified
      SOCKET_ERROR (-1): function execution failed. The error content is queried using the function IpGetLastError

  2. UdpConnect function
      mainly connect this socket to a given remote endpoint. After the socket is connected, UdpSend must be used instead of UdpSendTo. On the server side, when receiving data on the socket for the first time, you can connect to the remote endpoint. After that, you can create a new unconnected socket to wait for the next incoming udp connection. The specific use structure is as follows:

      the return value of the function is as follows:
      0: function execution succeeded
      WSA_INVALID_PARAMETER (87): invalid socket specified
      SOCKET_ERROR (-1): function execution failed. The error content is queried using the function IpGetLastError


      example

// Client.can
variables
{
  UdpSocket gSocket1;
  UdpSocket gSocket2;
  char gBuffer[1500];
}

on start
{
  gSocket1 = UdpSocket::Open( IP_Endpoint(0.0.0.0:0) );
  gSocket2 = UdpSocket::Open( IP_Endpoint(0.0.0.0:0) );
  gSocket1.Connect(IP_Endpoint(192.168.1.3:40002));
  gSocket2.Connect(IP_Endpoint(192.168.1.3:40002));
}

on key '1'
{
  // send on the connected socket
  SendCommand(gSocket1, "Request");

  // wait for response
  gSocket1.ReceiveFrom(gBuffer, elcount(gBuffer));
}

on key '2'
{
  // send on the connected socket
  SendCommand(gSocket2, "Request");

  // wait for response
  gSocket1.ReceiveFrom(gBuffer, elcount(gBuffer));
}

on key 'c'
{
  SendCommand(gSocket1, "End");
  gSocket1.Close();

  SendCommand(gSocket2, "End");
  gSocket1.Close();
}

SendCommand(UdpSocket socket, char command[])
{
  socket.Send(command, strlen(command));
}

OnUdpReceiveFrom(dword socket, long result, IP_Endpoint remoteEndpoint, char buffer[], dword size)
{
  // handle the response from the server here
}

//Server.can
variables
{
  // context to handle the state of the different connections
  struct connectionContext
  {
    int connectionNumber;
  };

  dword gListeningSocket;
  struct connectionContext connections[long];
  char gBuffer[1500];
  dword gConnectionCount;
}

on start
{
  gConnectionCount = 0;
  // open a socket and wait for the first data
  gListeningSocket = UdpOpen( IP_Endpoint(0.0.0.0:40002) );
  UdpReceiveFrom(gListeningSocket, gBuffer, elcount(gBuffer));
}

OnUdpReceiveFrom(dword socket, long result, IP_Endpoint remoteEndpoint, char buffer[], dword size)
{
  if(socket == gListeningSocket)
  {
    // create a context for this connection and open a new socket for the
    // next connection

    UdpConnect(socket, remoteEndpoint);
    connections[socket].connectionNumber = ++gConnectionCount;
    gListeningSocket = UdpOpen( IP_Endpoint(0.0.0.0:40002) );
    UdpReceiveFrom(gListeningSocket, gBuffer, elcount(gBuffer));
  }
  AnswerRequest(socket, buffer, size);
}

AnswerRequest(dword socket, char buffer[], dword size)
{
  char response[100];

  if(strncmp(buffer, "Request", size) == 0)
  {
    snprintf(response, elcount(response), "Response for connection #%d", connections[socket].connectionNumber);
    UdpSend(socket, response, strlen(response));
    UdpReceiveFrom(socket, gBuffer, elcount(gBuffer));
  }
  else if(strncmp(buffer, "End", size) == 0)
  {
    connections.remove(socket);
    UdpClose(socket);
  }
}

  1. UdpSend function
      it mainly sends data on the connected UDP socket. Before calling this function, you need to connect the socket with UdpConnect. The specific use structure is as follows:

      the return value of the function is as follows:
      0: function execution succeeded
      WSA_INVALID_PARAMETER (87): invalid socket specified
      SOCKET_ERROR (-1): function execution failed. The error content is queried using the function IpGetLastError


      example
variables
{
  UdpSocket gSocket;
}

on start
{
  gSocket = UdpSocket::Open( IP_Endpoint(0.0.0.0:0) );
  gSocket.Connect(ip_Endpoint(192.168.1.3:40002));
  gSocket.Send( "Request", 7 );
}

  1. UdpReceiveFrom function
      it mainly receives data to the specified buffer. If the receive operation does not complete immediately, the operation will execute asynchronously and the function will return SOCKET_ERROR (-1). Use IpGetLastSocketError to get more specific error codes. If the specific error code is WSA_IO_PENDING (997), the CAPL callback OnUdpReceiveFrom will be called on completion (success or failure), provided that it is implemented in the same CAPL program. Please see the specific examples at the end of the article. The specific use structure is as follows:

      the return value of the function is as follows:
      0: function execution succeeded
      WSA_INVALID_PARAMETER (87): invalid socket specified
      SOCKET_ERROR (-1): function execution failed. The error content is queried using the function IpGetLastError

  2. UdpSendTo function
      it mainly sends data to the specified location. If the send operation does not complete immediately, the operation will execute asynchronously and the function will return SOCKET_ERROR (-1). Use IpGetLastSocketError to get more specific error codes. If the specific error code is WSA_IO_PENDING (997), and the CAPL callback OnUdpSendTo will be called when it is completed (successful or not), provided that it is implemented in the same CAPL program. If the operation has been synchronized, the CAPL callback OnUdpSendTo is not called. The specific use format is as follows:

      the return value of the function is as follows:
      0: function execution succeeded
      WSA_INVALID_PARAMETER (87): invalid socket specified
      SOCKET_ERROR (-1): function execution failed. The error content is queried using the function IpGetLastError


      example
variables
{
  UdpSocket gSocket;
  char gRxBuffer[1000];
}

on start
{
  gSocket = UdpSocket::Open( IP_Endpoint(0.0.0.0:40001) );
  gSocket.SendTo( IP_Endpoint(192.168.0.2:40002), "Hello", 5 );
  gSocket.ReceiveFrom( gRxBuffer, elcount( gRxBuffer) );
}

OnUdpReceiveFrom( UdpSocket socket, long result, IP_Endpoint remoteEndpoint, char buffer[], dword size)
{
  if (result == 0)
  {
    write( "Received: %s", buffer );
    gSocket.ReceiveFrom( gRxBuffer, elcount( gRxBuffer) );
  }
}

  1. OnUdpReceiveFrom function
      it is mainly called when the asynchronous receive operation on the UDP socket is completed. The stack contains a data queue. Once the data is in the queue, the queue will be reduced by udpceivefrom. Therefore, in order to receive additional data from the data queue for socket in the future, udpceivefrom must be called again in the callback. The specific use format is as follows:

      example
variables
{
  UdpSocket gSocket;
  char gRxBuffer[1000];
}

on start
{
  gSocket = UdpSocket::Open( IP_Endpoint(0.0.0.0:40001) );
  gSocket.SendTo( IP_Endpoint(192.168.0.2:40002), "Hello", 5 );
  gSocket.ReceiveFrom( gRxBuffer, elcount( gRxBuffer) );
}

OnUdpReceiveFrom( UdpSocket socket, long result, IP_Endpoint remoteEndpoint, char buffer[], dword size)
{
  if (result == 0)
  {
    write( "Received: %s", buffer );
    gSocket.ReceiveFrom( gRxBuffer, elcount( gRxBuffer) );
  }
}
  1. OnUdpSendTo function
      it is mainly called when the asynchronous sending operation on the UDP socket is completed. The specific use format is as follows
      example
// ---------------------------------------------------
// node global variables.
// ---------------------------------------------------
variables
{
  const int gMTU = 1500;               // tcp mtu
  const int gSERVER_REPLY_WELCOME = 1; // server reply for welcome.
  const int gSERVER_REPLY_ANSWER = 2;  // server reply for answer.
  const dword gIPV4_STR_SIZE = 16;     // IPv4 string size
  const dword gINVALID_SOCKET = ~0;    // invalid socket constant
  dword gListenPort = 12345;           // port to send udp to.
  dword gListenSocket = gINVALID_SOCKET;       // server side: server listen socket
  dword gServerClientSocket = gINVALID_SOCKET; // server side: a client's socket
  char gServerTcpBuffer[gMTU];                 // tcp receive buffer of server
}

// ---------------------------------------------------
// Interaction: Server disconnects client...
// ---------------------------------------------------
on key 'x'
{
  serverDisconnectClient();
}

// ---------------------------------------------------
// on measurement start. (initialize and start demo)
// ---------------------------------------------------
on start
{
  serverStart();
}

// ---------------------------------------------------
// before measurement stops.
// ---------------------------------------------------
on preStop
{
  // close server ( this implies closing all server and client sockets. )
  serverClose();
}

// ---------------------------------------------------
// Callback when client connects to server's listen socket.
// ---------------------------------------------------
void OnTcpListen( dword socket, long result)
{
  dword errCode;
  writeLineEx(1, 1, " [ S: OnTcpListen called. (result: %d)]", result);
  if (gServerClientSocket != gINVALID_SOCKET)
  {
    writeLineEx(1, 2, "S: A client is already connected. Not accepting.");
    return;
  }
  if (result == 0)
  {
    if (socket == gListenSocket)
    {
      write("S: Incoming client.", socket);
      gServerClientSocket = TcpAccept( gListenSocket );
      if (gServerClientSocket == gINVALID_SOCKET)
      {
        errCode = IpGetLastSocketError(gListenSocket);
        if (errCode == 87)
        {
          writeLineEx( 1, 3, "S: TcpAccept: Invalid listen socket given.");
          // handle error...
        }
        else
        {
          writeLineEx( 1, 3, "S: TcpAccept: Socket error: %d", IpGetLastSocketError(gListenSocket) );
          // handle error...
        }
      }
      else
      {
        write("S: Accepted client on socket %d to socket %d.", gListenSocket, gServerClientSocket);
        // start to receive data on the server's client socket...
        startReceive(gServerClientSocket, gServerTcpBuffer);
        // do more stuff on server to initialize the incoming accepted client...
        // welcome client.
        serverSend(gSERVER_REPLY_WELCOME, gServerClientSocket);
      }
    }
    else
    {
      writeLineEx( 1, 3, "S: OnTcpListen: Unexpected connection on socket %d. (result:%d)", socket, result);
      // handle error...
    }
  }
}

// ---------------------------------------------------
// When asynchronous TcpSend completes...
// ---------------------------------------------------
void OnTcpSend( dword socket, long result, char buffer[], dword size)
{
  writeLineEx(1, 1, " [ S: OnTcpSend called. (result: %d)]", result);
  if (result == 0)
  {
    if (socket != gINVALID_SOCKET)
    {
      if (socket == gServerClientSocket)
      {
        write("S: Server sent %d bytes to client done. (socket %d, result: %d)", size, socket, result);
      }
    }
  }
}

// ---------------------------------------------------
// When receiving data on socket...
// ---------------------------------------------------
void OnTcpReceive( dword socket, long result, dword address, dword port, char buffer[], dword size)
{
  writeLineEx(1, 1, " [ S: OnTcpReceive called. (result: %d)]", result);
  if (result == 0)
  {
    if (socket == gServerClientSocket)
    {
      // server receives from client...
      write("S: Server received %d bytes from client: %s (result: %d)", size, buffer, result);
      // check client's request...
      if (strstr(buffer, "REQUEST") >= 0)
      {
        write("S: Client request OK, sending answer.");
        serverSend(gSERVER_REPLY_ANSWER, gServerClientSocket); // answer
      }
      // continue receiving data.
      startReceive(gServerClientSocket, gServerTcpBuffer);
    }
    else if (socket != gINVALID_SOCKET)
    {
      writeLineEx(1, 3, " [ S: UNIMPLEMENTED: Received %d bytes on socket %d from 0x%x:%d with data: %s (result: %d) ]", size, socket, address, port, buffer, result);
    }
  }
}

// ---------------------------------------------------
// TCP socket receives a close notification
// (remote closed)
// ---------------------------------------------------
void OnTcpClose( dword socket, long result)
{
  if (socket == gServerClientSocket)
  {
    TcpClose(gServerClientSocket);
    gServerClientSocket = gINVALID_SOCKET;
    writeLineEx(1, 1, " [ S: OnTcpClose called. (socket: %d, result: %d) ]", socket, result);
  }
}

// ---------------------------------------------------
// server disconnects client
// ---------------------------------------------------
void serverDisconnectClient()
{
  if (gServerClientSocket != gINVALID_SOCKET)
  {
    write("S: Server disconnects client. (socket %d)", gServerClientSocket);
    TcpClose(gServerClientSocket);
    gServerClientSocket = gINVALID_SOCKET;
  }
}

// ---------------------------------------------------
// start receiving on given socket into given buffer.
// ---------------------------------------------------
void startReceive ( dword socket, char buffer[] )
{
  long result;
  result = TcpReceive( socket, buffer, elcount(buffer) );
  if (result == -1)
  {
    result = IpGetLastSocketError(socket);
    if (result != 997) // not asynchronous
    {
      // failure
      writeLineEx( 1, 3, "S: TcpReceive error %d", result);
    }
  }
  else if (result != 0) // synchronous sending failed
  {
    // failure
    writeLineEx( 1, 3, "S: TcpReceive error %d", result);
  }
}

// ---------------------------------------------------
// start server
// ---------------------------------------------------
void serverStart()
{
  writeLineEx(1, 1, " [ S: Open TCP server socket at port %d... ]", gListenPort);
  gListenSocket = TcpOpen(0, gListenPort);
  if (gListenSocket == gINVALID_SOCKET)
  {
    writeLineEx( 1, 3, " [ S: TcpOpen: Error opening TCP Socket on port %d. (Error %d) ]", gListenPort, IpGetLastError() );
    // handle error...
  }
  else
  {
    if (TcpListen( gListenSocket ) != 0)
    {
      writeLineEx(1, 3, " [ S: TcpListen: Error listening on socket. ]");
      TcpClose(gListenSocket);
      gListenSocket = gINVALID_SOCKET;
      // handle error...
    }
    else
    {
      writeLineEx(1, 1, " [ S: Start listening on server socket %d... ]", gListenSocket);
    }
  }
}

// ---------------------------------------------------
// close server
// ---------------------------------------------------
void serverClose()
{
  if (gListenSocket != gINVALID_SOCKET)
  {
    writeLineEx(1, 1, " [ Server shutdown. ]");
    serverDisconnectClient();
    TcpClose(gListenSocket);
    gListenSocket = gINVALID_SOCKET;
  }
}

// ---------------------------------------------------
// server sends data to client
// ---------------------------------------------------
void serverSend(int replyNr, dword socket)
{
  if (socket != gINVALID_SOCKET)
  {
    writeLineEx(1, 1, "S: Sending data to client. (socket %d)", socket);
    switch(replyNr)
    {
      case gSERVER_REPLY_WELCOME:
      {
        sendTcpData( socket, " Server data: WELCOME.");
        break;
      }
      case gSERVER_REPLY_ANSWER:
      {
        sendTcpData(socket, "Server data: ANSWER.");
        break;
      }
      default:
      {
        writeLineEx(1, 3, " [ serverSend: UNSUPPORTED REPLY NR %d ]", replyNr);
        break;
      }
    }
  }
}

// ---------------------------------------------------
// send tcp data.
// ---------------------------------------------------
void sendTcpData( dword socket, char data[] )
{
  long result;
  dword size;
  size = elcount(data);
  result = TcpSend(socket, data, size);
  if (result == 0)
  {
    // sending took place immediately.
    writeLineEx(1, 1, " [ S: Synchronous sending: '%s' on socket %d ]", data, socket);
    OnTcpSend(socket, result, data, size); // trigger callback manually
  }
  else
  {
    if (result == -1)
    {
      result = IpGetLastSocketError(socket);
      if (result == 997)
      {
        // sending is done asynchronously.
        writeLineEx(1, 1, " [ S: Asynchronous sending: '%s' on socket %d ]", data, socket);
        // => OnTcpSend is called when done sending.
      }
      else
      {
        writeLineEx( 1, 3, " [ S: sendTcpData: Error sending data. (%d) ]", result);
      }
    }
    else
    {
      writeLineEx( 1, 3, " [ S: sendTcpData: Error sending data. (%d) ]", result);
    }
  }
}

Topics: software testing CAPL