/* Waits for the last flight or retransmits * the previous on timeout. Returns 0 on success. */ int _dtls_wait_and_retransmit(gnutls_session_t session) { int ret; if (session->internals.dtls.blocking != 0) ret = _gnutls_io_check_recv(session, TIMER_WINDOW); else ret = _gnutls_io_check_recv(session, 0); if (ret == GNUTLS_E_TIMEDOUT) { ret = _dtls_retransmit(session); if (ret == 0) { RETURN_DTLS_EAGAIN_OR_TIMEOUT(session, 0); } else return gnutls_assert_val(ret); } RESET_TIMER; return 0; }
static ssize_t _gnutls_stream_read(gnutls_session_t session, mbuffer_st ** bufel, size_t size, gnutls_pull_func pull_func, unsigned int *ms) { size_t left; ssize_t i = 0; size_t max_size = max_record_recv_size(session); uint8_t *ptr; gnutls_transport_ptr_t fd = session->internals.transport_recv_ptr; int ret; struct timespec t1, t2; unsigned int diff; session->internals.direction = 0; *bufel = _mbuffer_alloc_align16(MAX(max_size, size), get_total_headers(session)); if (!*bufel) { gnutls_assert(); return GNUTLS_E_MEMORY_ERROR; } ptr = (*bufel)->msg.data; left = size; while (left > 0) { if (ms && *ms > 0) { ret = _gnutls_io_check_recv(session, *ms); if (ret < 0) { gnutls_assert(); goto cleanup; } gnutls_gettime(&t1); } reset_errno(session); i = pull_func(fd, &ptr[size - left], left); if (i < 0) { int err = get_errno(session); _gnutls_read_log ("READ: %d returned from %p, errno=%d gerrno=%d\n", (int) i, fd, errno, session->internals.errnum); if (err == EAGAIN || err == EINTR) { if (size - left > 0) { _gnutls_read_log ("READ: returning %d bytes from %p\n", (int) (size - left), fd); goto finish; } ret = errno_to_gerr(err, 0); goto cleanup; } else { gnutls_assert(); ret = GNUTLS_E_PULL_ERROR; goto cleanup; } } else { _gnutls_read_log("READ: Got %d bytes from %p\n", (int) i, fd); if (i == 0) break; /* EOF */ } left -= i; (*bufel)->msg.size += i; if (ms && *ms > 0 && *ms != GNUTLS_INDEFINITE_TIMEOUT) { gnutls_gettime(&t2); diff = timespec_sub_ms(&t2, &t1); if (diff < *ms) *ms -= diff; else { ret = gnutls_assert_val(GNUTLS_E_TIMEDOUT); goto cleanup; } } } finish: _gnutls_read_log("READ: read %d bytes from %p\n", (int) (size - left), fd); if (size - left == 0) _mbuffer_xfree(bufel); return (size - left); cleanup: _mbuffer_xfree(bufel); return ret; }
static ssize_t _gnutls_dgram_read(gnutls_session_t session, mbuffer_st ** bufel, gnutls_pull_func pull_func, unsigned int *ms) { ssize_t i, ret; uint8_t *ptr; struct timespec t1, t2; size_t max_size, recv_size; gnutls_transport_ptr_t fd = session->internals.transport_recv_ptr; unsigned int diff; max_size = max_record_recv_size(session); recv_size = max_size; session->internals.direction = 0; if (ms && *ms > 0) { ret = _gnutls_io_check_recv(session, *ms); if (ret < 0) return gnutls_assert_val(ret); gnutls_gettime(&t1); } *bufel = _mbuffer_alloc_align16(max_size, get_total_headers(session)); if (*bufel == NULL) return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); ptr = (*bufel)->msg.data; reset_errno(session); i = pull_func(fd, ptr, recv_size); if (i < 0) { int err = get_errno(session); _gnutls_read_log("READ: %d returned from %p, errno=%d\n", (int) i, fd, err); ret = errno_to_gerr(err, 1); goto cleanup; } else { _gnutls_read_log("READ: Got %d bytes from %p\n", (int) i, fd); if (i == 0) { /* If we get here, we likely have a stream socket. * That assumption may not work on DCCP. */ gnutls_assert(); ret = 0; goto cleanup; } _mbuffer_set_udata_size(*bufel, i); } if (ms && *ms > 0) { gnutls_gettime(&t2); diff = timespec_sub_ms(&t2, &t1); if (diff < *ms) *ms -= diff; else { ret = gnutls_assert_val(GNUTLS_E_TIMEDOUT); goto cleanup; } } _gnutls_read_log("READ: read %d bytes from %p\n", (int) i, fd); return i; cleanup: _mbuffer_xfree(bufel); return ret; }
/* This function transmits the flight that has been previously * buffered. * * This function is called from the handshake layer and calls the * record layer. */ int _dtls_transmit (gnutls_session_t session) { int ret; uint8_t* buf = NULL; unsigned int timeout; /* PREPARING -> SENDING state transition */ mbuffer_head_st *const send_buffer = &session->internals.handshake_send_buffer; mbuffer_st *cur; gnutls_handshake_description_t last_type = 0; unsigned int diff; struct timespec now; gettime(&now); /* If we have already sent a flight and we are operating in a * non blocking way, check if it is time to retransmit or just * return. */ if (session->internals.dtls.flight_init != 0 && session->internals.dtls.blocking == 0) { /* just in case previous run was interrupted */ ret = _gnutls_io_write_flush (session); if (ret < 0) { gnutls_assert(); goto cleanup; } if (session->internals.dtls.last_flight == 0 || !_dtls_is_async(session)) { /* check for ACK */ ret = _gnutls_io_check_recv(session, 0); if (ret == GNUTLS_E_TIMEDOUT) { /* if no retransmission is required yet just return */ if (_dtls_timespec_sub_ms(&now, &session->internals.dtls.last_retransmit) < TIMER_WINDOW) { gnutls_assert(); goto nb_timeout; } } else /* received something */ { if (ret == 0) { ret = is_next_hpacket_expected(session); if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED) goto nb_timeout; if (ret < 0 && ret != GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET) { gnutls_assert(); goto cleanup; } if (ret == 0) goto end_flight; /* if ret == GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET retransmit */ } else goto nb_timeout; } } } do { timeout = TIMER_WINDOW; diff = _dtls_timespec_sub_ms(&now, &session->internals.dtls.handshake_start_time); if (diff >= session->internals.dtls.total_timeout_ms) { _gnutls_dtls_log("Session timeout: %u ms\n", diff); ret = gnutls_assert_val(GNUTLS_E_TIMEDOUT); goto end_flight; } diff = _dtls_timespec_sub_ms(&now, &session->internals.dtls.last_retransmit); if (session->internals.dtls.flight_init == 0 || diff >= TIMER_WINDOW) { _gnutls_dtls_log ("DTLS[%p]: %sStart of flight transmission.\n", session, (session->internals.dtls.flight_init == 0)?"":"re-"); for (cur = send_buffer->head; cur != NULL; cur = cur->next) { ret = transmit_message (session, cur, &buf); if (ret < 0) { gnutls_assert(); goto end_flight; } last_type = cur->htype; } gettime(&session->internals.dtls.last_retransmit); if (session->internals.dtls.flight_init == 0) { session->internals.dtls.flight_init = 1; RESET_TIMER; timeout = TIMER_WINDOW; if (last_type == GNUTLS_HANDSHAKE_FINISHED) { /* On the last flight we cannot ensure retransmission * from here. _dtls_wait_and_retransmit() is being called * by handshake. */ session->internals.dtls.last_flight = 1; } else session->internals.dtls.last_flight = 0; } else { UPDATE_TIMER; } } ret = _gnutls_io_write_flush (session); if (ret < 0) { ret = gnutls_assert_val(ret); goto cleanup; } /* last message in handshake -> no ack */ if (session->internals.dtls.last_flight != 0) { /* we don't wait here. We just return 0 and * if a retransmission occurs because peer didn't receive it * we rely on the record or handshake * layer calling this function again. */ ret = 0; goto cleanup; } else /* all other messages -> implicit ack (receive of next flight) */ { if (session->internals.dtls.blocking != 0) ret = _gnutls_io_check_recv(session, timeout); else { ret = _gnutls_io_check_recv(session, 0); if (ret == GNUTLS_E_TIMEDOUT) { goto nb_timeout; } } if (ret == 0) { ret = is_next_hpacket_expected(session); if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED) goto nb_timeout; if (ret == GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET) { ret = GNUTLS_E_TIMEDOUT; goto keep_up; } if (ret < 0) { gnutls_assert(); goto cleanup; } goto end_flight; } } keep_up: gettime(&now); } while(ret == GNUTLS_E_TIMEDOUT); if (ret < 0) { ret = gnutls_assert_val(ret); goto end_flight; } ret = 0; end_flight: _gnutls_dtls_log ("DTLS[%p]: End of flight transmission.\n", session); _dtls_reset_hsk_state(session); cleanup: if (buf != NULL) gnutls_free(buf); /* SENDING -> WAITING state transition */ return ret; nb_timeout: if (buf != NULL) gnutls_free(buf); RETURN_DTLS_EAGAIN_OR_TIMEOUT(session, ret); }