Ejemplo n.º 1
0
/** Call tcp_write() in a loop trying smaller and smaller length
 *
 * @param pcb tcp_pcb to send
 * @param ptr Data to send
 * @param length Length of data to send (in/out: on return, contains the
 *        amount of data sent)
 * @param apiflags directly passed to tcp_write
 * @return the return value of tcp_write
 */
static err_t server_tcp_write(struct tcp_pcb* pcb, const void* ptr, uint16_t* length, uint8_t apiflags) {
  uint16_t len;
  err_t err;
  LWIP_ASSERT("length != NULL", length != NULL);
  len = *length;
  do {
    LWIP_DEBUGF(SERVER_TCP_DEBUG | LWIP_DBG_TRACE, ("Trying to send %d bytes\n", len));
    err = tcp_write(pcb, ptr, len, apiflags);

    /* If there's not enough memory to send this data */
    if (err == ERR_MEM) {
      /* If there's no space in the buffers at all */
      if ((tcp_sndbuf(pcb) == 0) || (tcp_sndqueuelen(pcb) >= TCP_SND_QUEUELEN)) {
	len = 1; /* No need to try smaller sizes, exit */
      } else {
	len /= 2;
      }
      LWIP_DEBUGF(SERVER_TCP_DEBUG | LWIP_DBG_TRACE, ("Send failed, trying less (%d bytes)\n", len));
    }
  } while ((err == ERR_MEM) && (len > 1));

  if (err == ERR_OK) {
    LWIP_DEBUGF(SERVER_TCP_DEBUG | LWIP_DBG_TRACE, ("Sent %d bytes\n", len));
  } else {
    LWIP_DEBUGF(SERVER_TCP_DEBUG | LWIP_DBG_TRACE, ("Send failed with err %d (\"%s\")\n", err, lwip_strerr(err)));
  }

  *length = len;
  return err;
}
Ejemplo n.º 2
0
int TcpConnection::write(const char* data, int len, uint8_t apiflags /* = 0*/)
{
   int original = len;
   err_t err;
   do
   {
	 err = tcp_write(tcp, data, len, apiflags);
	 if (err == ERR_MEM)
	 {
	   if ((tcp_sndbuf(tcp) == 0) || (tcp_sndqueuelen(tcp) >= TCP_SND_QUEUELEN)) {
		 /* no need to try smaller sizes */
		 len = 1;
	   } else {
		 len /= 2;
	   }
	 }
   } while ((err == ERR_MEM) && (len > 1));

   if (err == ERR_OK)
   {
		debugf("TCP connection send: %d (%d)", len, original);
		return len;
   } else {
		debugf("TCP connection failed with err %d (\"%s\")", err, lwip_strerr(err));
		return -1;
   }
}
Ejemplo n.º 3
0
int TcpConnection::write(IDataSourceStream* stream)
{
	// Send data from DataStream
	bool repeat;
	bool space;
	int available;
	int total = 0;
	char* buffer = new char[NETWORK_SEND_BUFFER_SIZE];

	do
	{
		space = (tcp_sndqueuelen(tcp) < TCP_SND_QUEUELEN);
		if (!space)
		{
			debugf("WAIT FOR FREE SPACE");
			flush();
			break; // don't try to send buffers if no free space available
		}

		// Join small fragments
		int pushCount = 0;
		do
		{
			pushCount++;
			int read = min(NETWORK_SEND_BUFFER_SIZE, getAvailableWriteSize());
			if (read > 0)
				available = stream->readMemoryBlock(buffer, read);
			else
				available = 0;
			if (available > 0)
			{
				int written = write(buffer, available, TCP_WRITE_FLAG_COPY | TCP_WRITE_FLAG_MORE);
				total += written;
				stream->seek(max(written, 0));
				repeat = written == available && !stream->isFinished() && pushCount < 25;
			}
			else
				repeat = false;
		} while (repeat);

		space = (tcp_sndqueuelen(tcp) < TCP_SND_QUEUELEN);// && tcp_sndbuf(tcp) >= FILE_STREAM_BUFFER_SIZE;
	} while (repeat && space);

	flush();
	free(buffer);
	return total;
}
Ejemplo n.º 4
0
static u16_t
altcp_tcp_sndqueuelen(struct altcp_pcb *conn)
{
  struct tcp_pcb *pcb;
  if (conn == NULL) {
    return 0;
  }
  ALTCP_TCP_ASSERT_CONN(conn);
  pcb = (struct tcp_pcb *)conn->state;
  return tcp_sndqueuelen(pcb);
}
Ejemplo n.º 5
0
int TcpConnection::write(IDataSourceStream* stream)
{
	// Send data from DataStream
	bool repeat;
	bool space;
	int total = 0;
	do
	{
		space = (tcp_sndqueuelen(tcp) < TCP_SND_QUEUELEN);
		if (!space)
		{
			debugf("WAIT FOR FREE SPACE");
			//connection.flush();
			break; // don't try to send buffers if no free space available
		}

		char* pointer;

		// Join small fragments
		int curPart = 0;
		int pushCount = 0;
		do
		{
			if (pushCount > 25) break;
			pushCount++;
			int available = stream->getDataPointer(&pointer);
			if (available <= 0) continue;
			int len = min(available, 4096);
			int written = write(pointer, len, TCP_WRITE_FLAG_COPY | TCP_WRITE_FLAG_MORE);
			curPart += written;
			total += written;
			stream->seek(max(written, 0));
			repeat = len > 0 && written == len && !stream->isFinished();
		} while (repeat && curPart < NETWORK_SEND_BUFFER_SIZE);

		space = (tcp_sndqueuelen(tcp) < TCP_SND_QUEUELEN);// && tcp_sndbuf(tcp) >= FILE_STREAM_BUFFER_SIZE;
	} while (repeat && space);

	flush();
	return total;
}
Ejemplo n.º 6
0
/**
 * Receive callback function for UDP netconns.
 * Posts the packet to conn->recvmbox or deletes it on memory error.
 *
 * @see udp.h (struct udp_pcb.recv) for parameters
 */
static void
recv_udp(void *arg, struct udp_pcb *pcb, struct pbuf *p,
         ip_addr_t *addr, u16_t port)
{
    struct netbuf *buf;
    struct netconn *conn;
    u16_t len;
#if LWIP_SO_RCVBUF
    int recv_avail;
#endif /* LWIP_SO_RCVBUF */

    LWIP_UNUSED_ARG(pcb); /* only used for asserts... */
    LWIP_ASSERT("recv_udp must have a pcb argument", pcb != NULL);
    LWIP_ASSERT("recv_udp must have an argument", arg != NULL);
    conn = (struct netconn *)arg;
    LWIP_ASSERT("recv_udp: recv for wrong pcb!", conn->pcb.udp == pcb);

#if LWIP_SO_RCVBUF
    SYS_ARCH_GET(conn->recv_avail, recv_avail);
    if ((conn == NULL) || !sys_mbox_valid(&conn->recvmbox) ||
            ((recv_avail + (int)(p->tot_len)) > conn->recv_bufsize)) {
#else  /* LWIP_SO_RCVBUF */
    if ((conn == NULL) || !sys_mbox_valid(&conn->recvmbox)) {
#endif /* LWIP_SO_RCVBUF */
        pbuf_free(p);
        return;
    }

    buf = (struct netbuf *)memp_malloc(MEMP_NETBUF);
    if (buf == NULL) {
        pbuf_free(p);
        return;
    } else {
        buf->p = p;
        buf->ptr = p;
        ip_addr_set(&buf->addr, addr);
        buf->port = port;
#if LWIP_NETBUF_RECVINFO
        {
            const struct ip_hdr* iphdr = ip_current_header();
            /* get the UDP header - always in the first pbuf, ensured by udp_input */
            const struct udp_hdr* udphdr = (void*)(((char*)iphdr) + IPH_LEN(iphdr));
#if LWIP_CHECKSUM_ON_COPY
            buf->flags = NETBUF_FLAG_DESTADDR;
#endif /* LWIP_CHECKSUM_ON_COPY */
            ip_addr_set(&buf->toaddr, ip_current_dest_addr());
            buf->toport_chksum = udphdr->dest;
        }
#endif /* LWIP_NETBUF_RECVINFO */
    }

    len = p->tot_len;
    if (sys_mbox_trypost(&conn->recvmbox, buf) != ERR_OK) {
        netbuf_delete(buf);
        return;
    } else {
#if LWIP_SO_RCVBUF
        SYS_ARCH_INC(conn->recv_avail, len);
#endif /* LWIP_SO_RCVBUF */
        /* Register event with callback */
        API_EVENT(conn, NETCONN_EVT_RCVPLUS, len);
    }
}
#endif /* LWIP_UDP */

#if LWIP_TCP
/**
 * Receive callback function for TCP netconns.
 * Posts the packet to conn->recvmbox, but doesn't delete it on errors.
 *
 * @see tcp.h (struct tcp_pcb.recv) for parameters and return value
 */
static err_t
recv_tcp(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
{
    struct netconn *conn;
    u16_t len;

    LWIP_UNUSED_ARG(pcb);
    LWIP_ASSERT("recv_tcp must have a pcb argument", pcb != NULL);
    LWIP_ASSERT("recv_tcp must have an argument", arg != NULL);
    conn = (struct netconn *)arg;
    LWIP_ASSERT("recv_tcp: recv for wrong pcb!", conn->pcb.tcp == pcb);

    if (conn == NULL) {
        return ERR_VAL;
    }
    if (!sys_mbox_valid(&conn->recvmbox)) {
        /* recvmbox already deleted */
        if (p != NULL) {
            tcp_recved(pcb, p->tot_len);
            pbuf_free(p);
        }
        return ERR_OK;
    }
    /* Unlike for UDP or RAW pcbs, don't check for available space
       using recv_avail since that could break the connection
       (data is already ACKed) */

    /* don't overwrite fatal errors! */
    NETCONN_SET_SAFE_ERR(conn, err);

    if (p != NULL) {
        len = p->tot_len;
    } else {
        len = 0;
    }

    if (sys_mbox_trypost(&conn->recvmbox, p) != ERR_OK) {
        /* don't deallocate p: it is presented to us later again from tcp_fasttmr! */
        return ERR_MEM;
    } else {
#if LWIP_SO_RCVBUF
        SYS_ARCH_INC(conn->recv_avail, len);
#endif /* LWIP_SO_RCVBUF */
        /* Register event with callback */
        API_EVENT(conn, NETCONN_EVT_RCVPLUS, len);
    }

    return ERR_OK;
}

/**
 * Poll callback function for TCP netconns.
 * Wakes up an application thread that waits for a connection to close
 * or data to be sent. The application thread then takes the
 * appropriate action to go on.
 *
 * Signals the conn->sem.
 * netconn_close waits for conn->sem if closing failed.
 *
 * @see tcp.h (struct tcp_pcb.poll) for parameters and return value
 */
static err_t
poll_tcp(void *arg, struct tcp_pcb *pcb)
{
    struct netconn *conn = (struct netconn *)arg;

    LWIP_UNUSED_ARG(pcb);
    LWIP_ASSERT("conn != NULL", (conn != NULL));

    if (conn->state == NETCONN_WRITE) {
        do_writemore(conn);
    } else if (conn->state == NETCONN_CLOSE) {
        do_close_internal(conn);
    }
    /* @todo: implement connect timeout here? */

    /* Did a nonblocking write fail before? Then check available write-space. */
    if (conn->flags & NETCONN_FLAG_CHECK_WRITESPACE) {
        /* If the queued byte- or pbuf-count drops below the configured low-water limit,
           let select mark this pcb as writable again. */
        if ((conn->pcb.tcp != NULL) && (tcp_sndbuf(conn->pcb.tcp) > TCP_SNDLOWAT) &&
                (tcp_sndqueuelen(conn->pcb.tcp) < TCP_SNDQUEUELOWAT)) {
            conn->flags &= ~NETCONN_FLAG_CHECK_WRITESPACE;
            API_EVENT(conn, NETCONN_EVT_SENDPLUS, 0);
        }
    }

    return ERR_OK;
}

/**
 * Sent callback function for TCP netconns.
 * Signals the conn->sem and calls API_EVENT.
 * netconn_write waits for conn->sem if send buffer is low.
 *
 * @see tcp.h (struct tcp_pcb.sent) for parameters and return value
 */
static err_t
sent_tcp(void *arg, struct tcp_pcb *pcb, u16_t len)
{
    struct netconn *conn = (struct netconn *)arg;

    LWIP_UNUSED_ARG(pcb);
    LWIP_ASSERT("conn != NULL", (conn != NULL));

    if (conn->state == NETCONN_WRITE) {
        do_writemore(conn);
    } else if (conn->state == NETCONN_CLOSE) {
        do_close_internal(conn);
    }

    if (conn) {
        /* If the queued byte- or pbuf-count drops below the configured low-water limit,
           let select mark this pcb as writable again. */
        if ((conn->pcb.tcp != NULL) && (tcp_sndbuf(conn->pcb.tcp) > TCP_SNDLOWAT) &&
                (tcp_sndqueuelen(conn->pcb.tcp) < TCP_SNDQUEUELOWAT)) {
            conn->flags &= ~NETCONN_FLAG_CHECK_WRITESPACE;
            API_EVENT(conn, NETCONN_EVT_SENDPLUS, len);
        }
    }

    return ERR_OK;
}

/**
 * Error callback function for TCP netconns.
 * Signals conn->sem, posts to all conn mboxes and calls API_EVENT.
 * The application thread has then to decide what to do.
 *
 * @see tcp.h (struct tcp_pcb.err) for parameters
 */
static void
err_tcp(void *arg, err_t err)
{
    struct netconn *conn;
    enum netconn_state old_state;
    SYS_ARCH_DECL_PROTECT(lev);

    conn = (struct netconn *)arg;
    LWIP_ASSERT("conn != NULL", (conn != NULL));

    conn->pcb.tcp = NULL;

    /* no check since this is always fatal! */
    SYS_ARCH_PROTECT(lev);
    conn->last_err = err;
    SYS_ARCH_UNPROTECT(lev);

    /* reset conn->state now before waking up other threads */
    old_state = conn->state;
    conn->state = NETCONN_NONE;

    /* Notify the user layer about a connection error. Used to signal
       select. */
    API_EVENT(conn, NETCONN_EVT_ERROR, 0);
    /* Try to release selects pending on 'read' or 'write', too.
       They will get an error if they actually try to read or write. */
    API_EVENT(conn, NETCONN_EVT_RCVPLUS, 0);
    API_EVENT(conn, NETCONN_EVT_SENDPLUS, 0);

    /* pass NULL-message to recvmbox to wake up pending recv */
    if (sys_mbox_valid(&conn->recvmbox)) {
        /* use trypost to prevent deadlock */
        sys_mbox_trypost(&conn->recvmbox, NULL);
    }
    /* pass NULL-message to acceptmbox to wake up pending accept */
    if (sys_mbox_valid(&conn->acceptmbox)) {
        /* use trypost to preven deadlock */
        sys_mbox_trypost(&conn->acceptmbox, NULL);
    }

    if ((old_state == NETCONN_WRITE) || (old_state == NETCONN_CLOSE) ||
            (old_state == NETCONN_CONNECT)) {
        /* calling do_writemore/do_close_internal is not necessary
           since the pcb has already been deleted! */
        int was_nonblocking_connect = IN_NONBLOCKING_CONNECT(conn);
        SET_NONBLOCKING_CONNECT(conn, 0);

        if (!was_nonblocking_connect) {
            /* set error return code */
            LWIP_ASSERT("conn->current_msg != NULL", conn->current_msg != NULL);
            conn->current_msg->err = err;
            conn->current_msg = NULL;
            /* wake up the waiting task */
            sys_sem_signal(&conn->op_completed);
        }
    } else {
        LWIP_ASSERT("conn->current_msg == NULL", conn->current_msg == NULL);
    }
}

/**
 * Setup a tcp_pcb with the correct callback function pointers
 * and their arguments.
 *
 * @param conn the TCP netconn to setup
 */
static void
setup_tcp(struct netconn *conn)
{
    struct tcp_pcb *pcb;

    pcb = conn->pcb.tcp;
    tcp_arg(pcb, conn);
    tcp_recv(pcb, recv_tcp);
    tcp_sent(pcb, sent_tcp);
    tcp_poll(pcb, poll_tcp, 4);
    tcp_err(pcb, err_tcp);
}

/**
 * Accept callback function for TCP netconns.
 * Allocates a new netconn and posts that to conn->acceptmbox.
 *
 * @see tcp.h (struct tcp_pcb_listen.accept) for parameters and return value
 */
static err_t
accept_function(void *arg, struct tcp_pcb *newpcb, err_t err)
{
    struct netconn *newconn;
    struct netconn *conn = (struct netconn *)arg;

    LWIP_DEBUGF(API_MSG_DEBUG, ("accept_function: newpcb->tate: %s\n", tcp_debug_state_str(newpcb->state)));

    if (!sys_mbox_valid(&conn->acceptmbox)) {
        LWIP_DEBUGF(API_MSG_DEBUG, ("accept_function: acceptmbox already deleted\n"));
        return ERR_VAL;
    }

    /* We have to set the callback here even though
     * the new socket is unknown. conn->socket is marked as -1. */
    newconn = netconn_alloc(conn->type, conn->callback);
    if (newconn == NULL) {
        return ERR_MEM;
    }
    newconn->pcb.tcp = newpcb;
    setup_tcp(newconn);
    /* no protection: when creating the pcb, the netconn is not yet known
       to the application thread */
    newconn->last_err = err;

    if (sys_mbox_trypost(&conn->acceptmbox, newconn) != ERR_OK) {
        /* When returning != ERR_OK, the pcb is aborted in tcp_process(),
           so do nothing here! */
        /* remove all references to this netconn from the pcb */
        struct tcp_pcb* pcb = newconn->pcb.tcp;
        tcp_arg(pcb, NULL);
        tcp_recv(pcb, NULL);
        tcp_sent(pcb, NULL);
        tcp_poll(pcb, NULL, 4);
        tcp_err(pcb, NULL);
        /* remove reference from to the pcb from this netconn */
        newconn->pcb.tcp = NULL;
        /* no need to drain since we know the recvmbox is empty. */
        sys_mbox_free(&newconn->recvmbox);
        sys_mbox_set_invalid(&newconn->recvmbox);
        netconn_free(newconn);
        return ERR_MEM;
    } else {
        /* Register event with callback */
        API_EVENT(conn, NETCONN_EVT_RCVPLUS, 0);
    }

    return ERR_OK;
}
#endif /* LWIP_TCP */

/**
 * Create a new pcb of a specific type.
 * Called from do_newconn().
 *
 * @param msg the api_msg_msg describing the connection type
 * @return msg->conn->err, but the return value is currently ignored
 */
static void
pcb_new(struct api_msg_msg *msg)
{
    LWIP_ASSERT("pcb_new: pcb already allocated", msg->conn->pcb.tcp == NULL);

    /* Allocate a PCB for this connection */
    switch(NETCONNTYPE_GROUP(msg->conn->type)) {
#if LWIP_RAW
    case NETCONN_RAW:
        msg->conn->pcb.raw = raw_new(msg->msg.n.proto);
        if(msg->conn->pcb.raw == NULL) {
            msg->err = ERR_MEM;
            break;
        }
        raw_recv(msg->conn->pcb.raw, recv_raw, msg->conn);
        break;
#endif /* LWIP_RAW */
#if LWIP_UDP
    case NETCONN_UDP:
        msg->conn->pcb.udp = udp_new();
        if(msg->conn->pcb.udp == NULL) {
            msg->err = ERR_MEM;
            break;
        }
#if LWIP_UDPLITE
        if (msg->conn->type==NETCONN_UDPLITE) {
            udp_setflags(msg->conn->pcb.udp, UDP_FLAGS_UDPLITE);
        }
#endif /* LWIP_UDPLITE */
        if (msg->conn->type==NETCONN_UDPNOCHKSUM) {
            udp_setflags(msg->conn->pcb.udp, UDP_FLAGS_NOCHKSUM);
        }
        udp_recv(msg->conn->pcb.udp, recv_udp, msg->conn);
        break;
#endif /* LWIP_UDP */
#if LWIP_TCP
    case NETCONN_TCP:
        msg->conn->pcb.tcp = tcp_new();
        if(msg->conn->pcb.tcp == NULL) {
            msg->err = ERR_MEM;
            break;
        }
        setup_tcp(msg->conn);
        break;
#endif /* LWIP_TCP */
    default:
        /* Unsupported netconn type, e.g. protocol disabled */
        msg->err = ERR_VAL;
        break;
    }
}
Ejemplo n.º 7
0
/**
 * See if more data needs to be written from a previous call to netconn_write.
 * Called initially from do_write. If the first call can't send all data
 * (because of low memory or empty send-buffer), this function is called again
 * from sent_tcp() or poll_tcp() to send more data. If all data is sent, the
 * blocking application thread (waiting in netconn_write) is released.
 *
 * @param conn netconn (that is currently in state NETCONN_WRITE) to process
 * @return ERR_OK
 *         ERR_MEM if LWIP_TCPIP_CORE_LOCKING=1 and sending hasn't yet finished
 */
static err_t
do_writemore(struct netconn *conn)
{
    err_t err;
    void *dataptr;
    u16_t len, available;
    u8_t write_finished = 0;
    size_t diff;
    u8_t dontblock = netconn_is_nonblocking(conn) ||
                     (conn->current_msg->msg.w.apiflags & NETCONN_DONTBLOCK);
    u8_t apiflags = conn->current_msg->msg.w.apiflags;

    LWIP_ASSERT("conn != NULL", conn != NULL);
    LWIP_ASSERT("conn->state == NETCONN_WRITE", (conn->state == NETCONN_WRITE));
    LWIP_ASSERT("conn->current_msg != NULL", conn->current_msg != NULL);
    LWIP_ASSERT("conn->pcb.tcp != NULL", conn->pcb.tcp != NULL);
    LWIP_ASSERT("conn->write_offset < conn->current_msg->msg.w.len",
                conn->write_offset < conn->current_msg->msg.w.len);

#if LWIP_SO_SNDTIMEO
    if ((conn->send_timeout != 0) &&
            ((s32_t)(sys_now() - conn->current_msg->msg.w.time_started) >= conn->send_timeout)) {
        write_finished = 1;
        if (conn->write_offset == 0) {
            /* nothing has been written */
            err = ERR_WOULDBLOCK;
            conn->current_msg->msg.w.len = 0;
        } else {
            /* partial write */
            err = ERR_OK;
            conn->current_msg->msg.w.len = conn->write_offset;
        }
    } else
#endif /* LWIP_SO_SNDTIMEO */
    {
        dataptr = (u8_t*)conn->current_msg->msg.w.dataptr + conn->write_offset;
        diff = conn->current_msg->msg.w.len - conn->write_offset;
        if (diff > 0xffffUL) { /* max_u16_t */
            len = 0xffff;
#if LWIP_TCPIP_CORE_LOCKING
            conn->flags |= NETCONN_FLAG_WRITE_DELAYED;
#endif
            apiflags |= TCP_WRITE_FLAG_MORE;
        } else {
            len = (u16_t)diff;
        }
        available = tcp_sndbuf(conn->pcb.tcp);
        if (available < len) {
            /* don't try to write more than sendbuf */
            len = available;
            if (dontblock) {
                if (!len) {
                    err = ERR_WOULDBLOCK;
                    goto err_mem;
                }
            } else {
#if LWIP_TCPIP_CORE_LOCKING
                conn->flags |= NETCONN_FLAG_WRITE_DELAYED;
#endif
                apiflags |= TCP_WRITE_FLAG_MORE;
            }
        }
        LWIP_ASSERT("do_writemore: invalid length!", ((conn->write_offset + len) <= conn->current_msg->msg.w.len));
        err = tcp_write(conn->pcb.tcp, dataptr, len, apiflags);
        /* if OK or memory error, check available space */
        if ((err == ERR_OK) || (err == ERR_MEM)) {
err_mem:
            if (dontblock && (len < conn->current_msg->msg.w.len)) {
                /* non-blocking write did not write everything: mark the pcb non-writable
                   and let poll_tcp check writable space to mark the pcb writable again */
                API_EVENT(conn, NETCONN_EVT_SENDMINUS, len);
                conn->flags |= NETCONN_FLAG_CHECK_WRITESPACE;
            } else if ((tcp_sndbuf(conn->pcb.tcp) <= TCP_SNDLOWAT) ||
                       (tcp_sndqueuelen(conn->pcb.tcp) >= TCP_SNDQUEUELOWAT)) {
                /* The queued byte- or pbuf-count exceeds the configured low-water limit,
                   let select mark this pcb as non-writable. */
                API_EVENT(conn, NETCONN_EVT_SENDMINUS, len);
            }
        }

        if (err == ERR_OK) {
            conn->write_offset += len;
            if ((conn->write_offset == conn->current_msg->msg.w.len) || dontblock) {
                /* return sent length */
                conn->current_msg->msg.w.len = conn->write_offset;
                /* everything was written */
                write_finished = 1;
                conn->write_offset = 0;
            }
            tcp_output(conn->pcb.tcp);
        } else if ((err == ERR_MEM) && !dontblock) {
            /* If ERR_MEM, we wait for sent_tcp or poll_tcp to be called
               we do NOT return to the application thread, since ERR_MEM is
               only a temporary error! */

            /* tcp_write returned ERR_MEM, try tcp_output anyway */
            tcp_output(conn->pcb.tcp);

#if LWIP_TCPIP_CORE_LOCKING
            conn->flags |= NETCONN_FLAG_WRITE_DELAYED;
#endif
        } else {
            /* On errors != ERR_MEM, we don't try writing any more but return
               the error to the application thread. */
            write_finished = 1;
            conn->current_msg->msg.w.len = 0;
        }
    }
    if (write_finished) {
        /* everything was written: set back connection state
           and back to application task */
        conn->current_msg->err = err;
        conn->current_msg = NULL;
        conn->state = NETCONN_NONE;
#if LWIP_TCPIP_CORE_LOCKING
        if ((conn->flags & NETCONN_FLAG_WRITE_DELAYED) != 0)
#endif
        {
            sys_sem_signal(&conn->op_completed);
        }
    }
#if LWIP_TCPIP_CORE_LOCKING
    else
        return ERR_MEM;
#endif
    return ERR_OK;
}
Ejemplo n.º 8
0
static err_t sent_cb(void *arg, struct tcp_pcb *tcp, uint16_t len)
{
	phase_expected(PHASE_EVENTS);

	outlet_t *ol = (outlet_t *)arg;
	if (ol == 0)
		return ERR_OK;		// outlet has gone already
	assert(ol->tcp == tcp);

	//debug("sent_cb: len %d\n", len);
	
	// inet_reply() may close the outlet
	term_t saved_oid = ol->oid;
	term_t send_reply_to = noval;
	term_t send_error = noval;
	term_t empty_queue_reply_to = noval;

	assert(ol->send_in_progress);
	assert(ol->send_buf_left >= len);
	ol->send_buf_left -= len;
	ol->send_buf_ack += len;
	assert(ol->send_buf_ack <= ol->send_buf_off);

	if (ol->send_buf_ack == ol->send_buf_off)
	{
		if (ol->send_buf_left > 0)
		{
			// write more
			uint16_t write_len = (ol->send_buf_left > TCP_SND_BUF)
				?TCP_SND_BUF
				:ol->send_buf_left;

			ol->send_buf_off += write_len;

			//debug("ol_tcp_send: tcp_write(%d)\n", write_len);
			int rc = tcp_write(ol->tcp, ol->send_buffer +ol->send_buf_ack, write_len, TCP_WRITE_FLAG_COPY);
			if (rc != ERR_OK)
			{
				send_cancel_deferred(ol);
				send_reply_to = ol->send_reply_to;
				send_error = lwip_err_to_term(rc);
			}

			// Kick the TCP/IP stack
			tcp_output(ol->tcp);
		}
		else
		{
			send_cancel_deferred(ol);
			send_reply_to = ol->send_reply_to;
		}
	}

	if (ol->empty_queue_in_progress && tcp_sndqueuelen(tcp) == 0)
	{
		ol->empty_queue_in_progress = 0;
		empty_queue_reply_to = ol->empty_queue_reply_to;
	}

	if (send_reply_to != noval && send_error != noval)
		inet_reply_error(saved_oid, send_reply_to, send_error);
	else if (send_reply_to != noval)
		inet_reply(saved_oid, send_reply_to, A_OK);
	
	if (empty_queue_reply_to != noval)
	{
		// non-standard reply
		proc_t *caller = scheduler_lookup(empty_queue_reply_to);
		if (caller != 0)
		{
			// {empty_out_q,S}
			uint32_t *p = heap_alloc_N(&caller->hp, 1 +2);
			if (p == 0)
				scheduler_signal_exit_N(caller, saved_oid, A_NO_MEMORY);
			else
			{
				heap_set_top(&caller->hp, p +1 +2);
				p[0] = 2;
				p[1] = A_EMPTY_OUT_Q;
				p[2] = saved_oid;
				term_t msg = tag_tuple(p);

				int x = scheduler_new_local_mail_N(caller, msg);
				if (x < 0)
					scheduler_signal_exit_N(caller, saved_oid, err_to_term(x));
			}
		}
	}

	return ERR_OK;
}
Ejemplo n.º 9
0
static term_t ol_tcp_control(outlet_t *ol,
		uint32_t op, uint8_t *data, int dlen, term_t reply_to, heap_t *hp)
{
	char rbuf[256];
	char *reply = rbuf;
	int sz;

	assert(ol != 0);
	assert(ol->tcp != 0 || op == INET_REQ_OPEN || op == INET_REQ_SUBSCRIBE);

	switch (op)
	{
	case INET_REQ_OPEN:
	{
		if (dlen != 2 || data[1] != INET_TYPE_STREAM)
			goto error;
		uint8_t family = data[0];
		if (family != INET_AF_INET && family != INET_AF_INET6)
			goto error;
		assert(ol->tcp == 0);

#if LWIP_IPV6
		ol->tcp = (family == INET_AF_INET6)
			?tcp_new_ip6()
			:tcp_new();
#else
		if (family != INET_AF_INET)
			goto error;
		ol->tcp = tcp_new();
#endif
		assert(ol->tcp != 0);

		// see comment in ol_tcp_animate()
		tcp_setprio(ol->tcp, TCP_PRIO_MAX +1);

		tcp_arg(ol->tcp, ol);	// callback arg
		tcp_recv(ol->tcp, recv_cb);
		tcp_sent(ol->tcp, sent_cb);
		tcp_err(ol->tcp, error_cb);

		*reply++ = INET_REP_OK;
	}
	break;

	case INET_REQ_CONNECT:
	{
		int is_ipv6 = PCB_ISIPV6(ol->tcp);
		if ((is_ipv6 && dlen != 4 +2 +16) || (!is_ipv6 && dlen != 4 +2 +4))
			goto error;

		uint32_t timeout = GET_UINT_32(data);
		uint16_t remote_port = GET_UINT_16(data +4);
	
		err_t err;
		if (!is_ipv6)
		{
			ip_addr_t where_to;
			where_to.addr = ntohl(GET_UINT_32(data +4 +2));
			err = tcp_connect(ol->tcp, &where_to, remote_port, connected_cb);
		}
		else
		{
#if LWIP_IPV6
			ip6_addr_t where_to;
			where_to.addr[0] = ntohl(GET_UINT_32(data +4 +2));
			where_to.addr[1] = ntohl(GET_UINT_32(data +4 +2 +4));
			where_to.addr[2] = ntohl(GET_UINT_32(data +4 +2 +8));
			where_to.addr[3] = ntohl(GET_UINT_32(data +4 +2 +12));
			err = tcp_connect_ip6(ol->tcp, &where_to, remote_port, connected_cb);
#else
			goto error;
#endif
		}

		// Does it make connections faster?
		tcp_output(ol->tcp);

		if (err == ERR_OK)
		{
			cr_defer_reply(ol, reply_to, timeout);

			*reply++ = INET_REP_OK;
			uint16_t ref = ASYNC_REF;	// Why this is needed? A constant will do.
			PUT_UINT_16(reply, ref);
			reply += 2;
		}
		else
		{
			//
			//TODO: ERR_RTE possible too (IPv6)
			//
			assert(err == ERR_MEM);
			REPLY_INET_ERROR("enomem");
		}
	}
	break;

	case INET_REQ_PEER:
	if (ol->tcp->state == CLOSED)
		REPLY_INET_ERROR("enotconn");
	else
	{
		*reply++ = INET_REP_OK;
		*reply++ = INET_AF_INET;
		uint16_t peer_port = ol->tcp->remote_port;
		PUT_UINT_16(reply, peer_port);
		reply += 2;
		if (PCB_ISIPV6(ol->tcp))
		{
			ip_addr_set_hton((ip_addr_t *)reply, (ip_addr_t *)&ol->tcp->remote_ip);
			reply += 4;
		}
		else
		{
#if LWIP_IPV6
			ip6_addr_set_hton((ip6_addr_t *)reply, (ip6_addr_t *)&ol->tcp->remote_ip);
			reply += 16;
#else
			goto error;
#endif
		}
	}
	break;

	case INET_REQ_NAME:
	if (ol->tcp->state == CLOSED)
		REPLY_INET_ERROR("enotconn");
	else
	{
		*reply++ = INET_REP_OK;
		int is_ipv6 = PCB_ISIPV6(ol->tcp);
		*reply++ = (is_ipv6) ?INET_AF_INET6 :INET_AF_INET;
		uint16_t name_port = ol->tcp->local_port;
		PUT_UINT_16(reply, name_port);
		reply += 2;
		if (PCB_ISIPV6(ol->tcp))
		{
			ip_addr_set_hton((ip_addr_t *)reply, (ip_addr_t *)&ol->tcp->local_ip);
			reply += 4;
		}
		else
		{
#if LWIP_IPV6
			ip6_addr_set_hton((ip6_addr_t *)reply, (ip6_addr_t *)&ol->tcp->local_ip);
			reply += 16;
#else
			goto error;
#endif
		}
	}
	break;

	case INET_REQ_BIND:
	{
		int is_ipv6 = PCB_ISIPV6(ol->tcp);
		if ((is_ipv6 && dlen != 2 +16) || (!is_ipv6 && dlen != 2 +4))
			goto error;
		uint16_t port = GET_UINT_16(data);
		if (!is_ipv6)
		{
			ip_addr_t addr;
			addr.addr = ntohl(GET_UINT_32(data +2));
			tcp_bind(ol->tcp, &addr, port); // always succeeds
		}
		else
		{
#if LWIP_IPV6
			ip6_addr_t addr;
			addr.addr[0] = ntohl(GET_UINT_32(data +2));
			addr.addr[1] = ntohl(GET_UINT_32(data +2 +4));
			addr.addr[2] = ntohl(GET_UINT_32(data +2 +8));
			addr.addr[3] = ntohl(GET_UINT_32(data +2 +12));
			tcp_bind_ip6(ol->tcp, &addr, port); // always succeeds
#else
			goto error;
#endif
		}

		uint16_t local_port = ol->tcp->local_port;
		*reply++ = INET_REP_OK;
		PUT_UINT_16(reply, local_port);
		reply += 2;
	}
	break;

	case INET_REQ_LISTEN:
	{
		assert(ol->recv_buf_node == 0);	// or use destroy_private()
		int backlog = GET_UINT_16(data);
		ol_tcp_acc_promote(ol, ol->tcp, backlog);
		*reply++ = INET_REP_OK;
	}
	break;

	case INET_REQ_SETOPTS:
	if (ol_tcp_set_opts(ol, data, dlen) < 0)
		goto error;

	*reply++ = INET_REP_OK;
	break;

	case INET_REQ_GETOPTS:
	sz = ol_tcp_get_opts(ol, data, dlen, rbuf+1, sizeof(rbuf) -1);
	if (sz < 0)
		goto error;

	*reply++ = INET_REP_OK;
	reply += sz;
	break;

	case INET_REQ_GETSTAT:
	//
	// lwIP can provide some of the statistics but not all
	//
	REPLY_INET_ERROR("enotsup");
	break;

	case INET_REQ_SUBSCRIBE:
	if (dlen != 1 && data[0] != INET_SUBS_EMPTY_OUT_Q)
		goto error;
	if (ol->empty_queue_in_progress)
		goto error;		//TODO: allow multiple subscriptions

	int qlen = tcp_sndqueuelen(ol->tcp);
	if (qlen > 0)
	{
		ol->empty_queue_in_progress = 1;
		ol->empty_queue_reply_to = reply_to;
	}

	*reply++ = INET_REP_OK;
	*reply++ = INET_SUBS_EMPTY_OUT_Q;
	PUT_UINT_32(reply, qlen);
	reply += 4;
	break;

	case TCP_REQ_RECV:
	if (dlen != 4 +4)
		goto error;

	uint32_t msecs = GET_UINT_32(data);
	uint32_t recv_num = GET_UINT_32(data +4);

	if (ol->active != INET_PASSIVE)
		goto error;
	if (ol->packet == TCP_PB_RAW && recv_num > ol->recv_bufsize)
		goto error;
	
	if (ol->peer_close_detected)
		inet_async_error(ol->oid, reply_to, ASYNC_REF, A_CLOSED);
	else
	{
		cr_defer_reply(ol, reply_to, msecs);

		if (ol->packet == TCP_PB_RAW)
			ol->recv_expected_size = recv_num;

		// Enough data may have already been buffered
		proc_t *cont_proc = scheduler_lookup(reply_to);
		assert(cont_proc != 0);
		if (recv_bake_packets(ol, cont_proc) < 0)
			goto error;
	}

	*reply++ = INET_REP_OK;
	uint16_t my_ref = ASYNC_REF;
	PUT_UINT_16(reply, my_ref);
	reply += 2;
	break;

	case TCP_REQ_SHUTDOWN:
	if (dlen != 1)
		goto error;

	uint8_t what = data[0];
	// 0 - read
	// 1 - write
	// 2 - read_write
	
	int shut_rx = (what == 0) || (what == 2);
	int shut_tx = (what == 1) || (what == 2);

	if (ol->tcp->state == LISTEN)
		REPLY_INET_ERROR("enotconn");
	else
	{
		tcp_shutdown(ol->tcp, shut_rx, shut_tx);
		// TODO: return code ignored

		*reply++ = INET_REP_OK;
	}
	break;

	default:
error:
	REPLY_INET_ERROR("einval");
	}

	int rlen = reply -rbuf;
	assert(rlen >= 1 && rlen <= sizeof(rbuf));
	term_t result = heap_str_N(hp, rbuf, rlen);
	if (result == noval)
		return A_NO_MEMORY;

	return result;
}