/** * Check whether the Gnutella host vector already contains the address:port. * * @return TRUE if the host vector already contains it. */ bool gnet_host_vec_contains(gnet_host_vec_t *vec, host_addr_t addr, uint16 port) { size_t i; g_return_val_if_fail(vec, FALSE); switch (host_addr_net(addr)) { case NET_TYPE_IPV4: for (i = 0; i < vec->n_ipv4; i++) { char *dest = cast_to_pointer(&vec->hvec_v4[i]); uint32 ip = peek_be32(&dest[0]); uint16 pt = peek_le16(&dest[4]); if (pt == port && host_addr_ipv4(addr) == ip) return TRUE; } break; case NET_TYPE_IPV6: for (i = 0; i < vec->n_ipv6; i++) { char *dest = cast_to_pointer(&vec->hvec_v6[i]); uint16 pt = peek_le16(&dest[16]); if (pt == port && 0 == memcmp(dest, host_addr_ipv6(&addr), 16)) return TRUE; } break; case NET_TYPE_LOCAL: case NET_TYPE_NONE: break; } return FALSE; }
/** * Fill `p_addr' with the socket's local address/port information. */ int socket_addr_getsockname(socket_addr_t *p_addr, int fd) { struct sockaddr_in sin4; socklen_t len; host_addr_t addr = zero_host_addr; uint16 port = 0; bool success = FALSE; len = sizeof sin4; if (-1 != getsockname(fd, cast_to_pointer(&sin4), &len)) { addr = host_addr_peek_ipv4(&sin4.sin_addr.s_addr); port = ntohs(sin4.sin_port); success = TRUE; } #ifdef HAS_IPV6 if (!success) { struct sockaddr_in6 sin6; len = sizeof sin6; if (-1 != getsockname(fd, cast_to_pointer(&sin6), &len)) { addr = host_addr_peek_ipv6(sin6.sin6_addr.s6_addr); port = ntohs(sin6.sin6_port); success = TRUE; } } #endif /* HAS_IPV6 */ if (success) { socket_addr_set(p_addr, addr, port); } return success ? 0 : -1; }
/** * Dispose of the data structure, but not of the items it holds. * * @param hl_ptr pointer to the variable containing the address of the list * * As a side effect, the variable containing the address of the list * is nullified, since it is no longer allowed to refer to the structure. */ void hash_list_free(hash_list_t **hl_ptr) { g_assert(NULL != hl_ptr); if (*hl_ptr) { hash_list_t *hl = *hl_ptr; link_t *lk, *next; hash_list_check(hl); if (--hl->refcount != 0) { g_carp("%s: hash list is still referenced! " "(hl=%p, hl->refcount=%d)", G_STRFUNC, cast_to_pointer(hl), hl->refcount); } hikset_free_null(&hl->ht); for (lk = elist_first(&hl->list); lk != NULL; lk = next) { struct hash_list_item *item = ITEM(lk); next = elist_next(lk); /* Embedded, get next before freeing */ WFREE(item); } elist_discard(&hl->list); hl->magic = 0; WFREE(hl); *hl_ptr = NULL; } }
/** * Add new host (identified by address and port) to the Gnutella host vector. */ void gnet_host_vec_add(gnet_host_vec_t *vec, host_addr_t addr, uint16 port) { g_return_if_fail(vec); switch (host_addr_net(addr)) { case NET_TYPE_IPV4: if (vec->n_ipv4 < 255) { size_t size, old_size; char *dest; old_size = vec->n_ipv4 * sizeof vec->hvec_v4[0]; size = old_size + sizeof vec->hvec_v4[0]; vec->hvec_v4 = wrealloc(vec->hvec_v4, old_size, size); dest = cast_to_pointer(&vec->hvec_v4[vec->n_ipv4++]); poke_be32(&dest[0], host_addr_ipv4(addr)); poke_le16(&dest[4], port); } break; case NET_TYPE_IPV6: if (vec->n_ipv6 < 255) { size_t size, old_size; char *dest; old_size = vec->n_ipv6 * sizeof vec->hvec_v6[0]; size = old_size + sizeof vec->hvec_v6[0]; vec->hvec_v6 = wrealloc(vec->hvec_v6, old_size, size); dest = cast_to_pointer(&vec->hvec_v6[vec->n_ipv6++]); dest = mempcpy(dest, host_addr_ipv6(&addr), 16); poke_le16(dest, port); } break; case NET_TYPE_LOCAL: case NET_TYPE_NONE: break; } }
static void hsep_sanity_check(void) { const GSList *sl; hsep_triple sum[G_N_ELEMENTS(hsep_global_table)]; unsigned int i, j; ZERO(&sum); g_assert(1 == hsep_own[HSEP_IDX_NODES]); /* * Iterate over all HSEP-capable nodes, and for each triple index * sum up all the connections' triple values. */ for (sl = node_all_nodes() ; sl; sl = g_slist_next(sl)) { struct gnutella_node *n = sl->data; /* also consider unestablished connections here */ if (!(n->attrs & NODE_A_CAN_HSEP)) continue; g_assert(0 == n->hsep->table[0][HSEP_IDX_NODES]); /* check nodes */ g_assert(0 == n->hsep->table[0][HSEP_IDX_FILES]); /* check files */ g_assert(0 == n->hsep->table[0][HSEP_IDX_KIB]); /* check KiB */ g_assert(1 == n->hsep->table[1][HSEP_IDX_NODES]); /* check nodes */ /* check if values are monotonously increasing (skip first) */ g_assert( hsep_check_monotony(cast_to_pointer(n->hsep->table[1]), G_N_ELEMENTS(n->hsep->table[1]) - 1) ); /* sum up the values */ for (i = 0; i < G_N_ELEMENTS(sum); i++) { for (j = 0; j < G_N_ELEMENTS(sum[0]); j++) sum[i][j] += n->hsep->table[i][j]; } } /* check sums */ for (i = 0; i < G_N_ELEMENTS(sum); i++) { for (j = 0; j < G_N_ELEMENTS(sum[0]); j++) g_assert(hsep_global_table[i][j] == sum[i][j]); } }
static void hsep_sanity_check(void) { const pslist_t *sl; hsep_triple sum[N_ITEMS(hsep_global_table)]; unsigned int i, j; ZERO(&sum); g_assert(1 == hsep_own[HSEP_IDX_NODES]); /* * Iterate over all HSEP-capable nodes, and for each triple index * sum up all the connections' triple values. */ PSLIST_FOREACH(node_all_gnet_nodes(), sl) { gnutella_node_t *n = sl->data; /* also consider unestablished connections here */ if (!(n->attrs & NODE_A_CAN_HSEP)) continue; g_assert(0 == n->hsep->table[0][HSEP_IDX_NODES]); /* check nodes */ g_assert(0 == n->hsep->table[0][HSEP_IDX_FILES]); /* check files */ g_assert(0 == n->hsep->table[0][HSEP_IDX_KIB]); /* check KiB */ g_assert(1 == n->hsep->table[1][HSEP_IDX_NODES]); /* check nodes */ /* check if values are monotonously increasing (skip first) */ g_assert( hsep_check_monotony(cast_to_pointer(n->hsep->table[1]), N_ITEMS(n->hsep->table[1]) - 1) ); /* sum up the values */ for (i = 0; i < N_ITEMS(sum); i++) { for (j = 0; j < N_ITEMS(sum[0]); j++) sum[i][j] += n->hsep->table[i][j]; } }
void hsep_send_msg(struct gnutella_node *n, time_t now) { hsep_triple tmp[G_N_ELEMENTS(n->hsep->sent_table)], other; unsigned int i, j, msglen, msgsize, triples, opttriples; gnutella_msg_hsep_t *msg; hsep_ctx_t *hsep; g_assert(n); g_assert(n->hsep); hsep = n->hsep; ZERO(&other); /* * If we are a leaf, we just need to send one triple, * which contains our own data (this triple is expanded * to the needed number of triples on the peer's side). * As the 0'th global and 0'th connection triple are zero, * it contains only our own triple, which is correct. */ triples = settings_is_leaf() ? 1 : G_N_ELEMENTS(tmp); /* * Allocate and initialize message to send. */ msgsize = GTA_HEADER_SIZE + triples * (sizeof *msg - GTA_HEADER_SIZE); msg = walloc(msgsize); { gnutella_header_t *header; header = gnutella_msg_hsep_header(msg); message_set_muid(header, GTA_MSG_HSEP_DATA); gnutella_header_set_function(header, GTA_MSG_HSEP_DATA); gnutella_header_set_ttl(header, 1); gnutella_header_set_hops(header, 0); } /* * Collect HSEP data to send and convert the data to * little endian byte order. */ if (triples > 1) { /* determine what we know about non-HSEP nodes in 1 hop distance */ hsep_get_non_hsep_triple(&other); } for (i = 0; i < triples; i++) { for (j = 0; j < G_N_ELEMENTS(other); j++) { uint64 val; val = hsep_own[j] + (0 == i ? 0 : other[j]) + hsep_global_table[i][j] - hsep->table[i][j]; poke_le64(&tmp[i][j], val); } } STATIC_ASSERT(sizeof hsep->sent_table == sizeof tmp); /* check if the table differs from the previously sent table */ if ( 0 == memcmp(tmp, hsep->sent_table, sizeof tmp) ) { WFREE_NULL(msg, msgsize); goto charge_timer; } memcpy(cast_to_char_ptr(msg) + GTA_HEADER_SIZE, tmp, triples * sizeof tmp[0]); /* store the table for later comparison */ memcpy(hsep->sent_table, tmp, triples * sizeof tmp[0]); /* * Note that on big endian architectures the message data is now in * the wrong byte order. Nevertheless, we can use hsep_triples_to_send() * with that data. */ /* optimize number of triples to send */ opttriples = hsep_triples_to_send(cast_to_pointer(tmp), triples); if (GNET_PROPERTY(hsep_debug) > 1) { printf("HSEP: Sending %d %s to node %s (msg #%u): ", opttriples, opttriples == 1 ? "triple" : "triples", host_addr_port_to_string(n->addr, n->port), hsep->msgs_sent + 1); } for (i = 0; i < opttriples; i++) { if (GNET_PROPERTY(hsep_debug) > 1) { char buf[G_N_ELEMENTS(hsep_own)][32]; for (j = 0; j < G_N_ELEMENTS(buf); j++) { uint64 v; v = hsep_own[j] + hsep_global_table[i][j] - hsep->table[i][j]; uint64_to_string_buf(v, buf[j], sizeof buf[0]); } STATIC_ASSERT(3 == G_N_ELEMENTS(buf)); printf("(%s, %s, %s) ", buf[0], buf[1], buf[2]); } } if (GNET_PROPERTY(hsep_debug) > 1) puts("\n"); /* write message size */ msglen = opttriples * 24; gnutella_header_set_size(gnutella_msg_hsep_header(msg), msglen); /* correct message length */ msglen += GTA_HEADER_SIZE; /* send message to peer node */ gmsg_sendto_one(n, msg, msglen); WFREE_NULL(msg, msgsize); /* * Update counters. */ hsep->msgs_sent++; hsep->triples_sent += opttriples; charge_timer: hsep->last_sent = now; hsep->random_skew = random_value(2 * HSEP_MSG_SKEW) - HSEP_MSG_SKEW; }
void hsep_process_msg(struct gnutella_node *n, time_t now) { unsigned int i, j, k, max, msgmax, length; hsep_triple *messaget; hsep_ctx_t *hsep; g_assert(n); g_assert(n->hsep); hsep = n->hsep; length = n->size; /* note the offset between message and local data by 1 triple */ messaget = cast_to_pointer(n->data); if (length == 0) { /* error, at least 1 triple must be present */ if (GNET_PROPERTY(hsep_debug) > 1) printf("HSEP: Node %s sent empty message\n", host_addr_port_to_string(n->addr, n->port)); return; } if (length % 24) { /* error, # of triples not an integer */ if (GNET_PROPERTY(hsep_debug) > 1) printf("HSEP: Node %s sent broken message\n", host_addr_port_to_string(n->addr, n->port)); return; } /* get N_MAX of peer servent (other_n_max) */ msgmax = length / 24; if (NODE_IS_LEAF(n) && msgmax > 1) { if (GNET_PROPERTY(hsep_debug) > 1) { printf( "HSEP: Node %s is a leaf, but sent %u triples instead of 1\n", host_addr_port_to_string(n->addr, n->port), msgmax); } return; } /* truncate if peer servent sent more triples than we need */ max = MIN(msgmax, HSEP_N_MAX); hsep_fix_endian(messaget, max); /* * Perform sanity check on received message. */ if (messaget[0][HSEP_IDX_NODES] != 1) { /* # of nodes for 1 hop must be 1 */ if (GNET_PROPERTY(hsep_debug) > 1) printf("HSEP: Node %s's message's #nodes for 1 hop is not 1\n", host_addr_port_to_string(n->addr, n->port)); return; } if (!hsep_check_monotony(messaget, max)) { if (GNET_PROPERTY(hsep_debug) > 1) printf("HSEP: Node %s's message's monotony check failed\n", host_addr_port_to_string(n->addr, n->port)); return; } if (GNET_PROPERTY(hsep_debug) > 1) { printf("HSEP: Received %d %s from node %s (msg #%u): ", max, max == 1 ? "triple" : "triples", host_addr_port_to_string(n->addr, n->port), hsep->msgs_received + 1); } /* * Update global and per-connection tables. */ for (k = 0, i = 1; k < max; k++, i++) { if (GNET_PROPERTY(hsep_debug) > 1) { char buf[G_N_ELEMENTS(messaget[0])][32]; for (j = 0; j < G_N_ELEMENTS(buf); j++) uint64_to_string_buf(messaget[k][j], buf[j], sizeof buf[0]); STATIC_ASSERT(3 == G_N_ELEMENTS(buf)); printf("(%s, %s, %s) ", buf[0], buf[1], buf[2]); } for (j = 0; j < G_N_ELEMENTS(hsep_global_table[0]); j++) { hsep_global_table[i][j] += messaget[k][j] - hsep->table[i][j]; hsep->table[i][j] = messaget[k][j]; } } if (GNET_PROPERTY(hsep_debug) > 1) puts("\n"); /* * If the peer servent sent less triples than we need, * repeat the last triple until we have enough triples */ /* Go back to last triple */ if (k > 0) k--; for (/* NOTHING */; i < G_N_ELEMENTS(hsep_global_table); i++) { for (j = 0; j < G_N_ELEMENTS(hsep_global_table[0]); j++) { hsep_global_table[i][j] += messaget[k][j] - hsep->table[i][j]; hsep->table[i][j] = messaget[k][j]; } } /* * Update counters and timestamps. */ hsep->msgs_received++; hsep->triples_received += msgmax; hsep->last_received = now; if (GNET_PROPERTY(hsep_debug) > 1) hsep_dump_table(); hsep_fire_global_table_changed(now); }
/** * Create a new Gnutella host vector out of a sequence of gnet_host_t items. */ static gnet_host_vec_t * gnet_host_vec_from_sequence(sequence_t *s) { sequence_iter_t *iter; gnet_host_vec_t *vec; uint n_ipv6 = 0, n_ipv4 = 0, hcnt; if (sequence_is_empty(s)) return NULL; hcnt = 0; iter = sequence_forward_iterator(s); while (sequence_iter_has_next(iter)) { const gnet_host_t *host = sequence_iter_next(iter); switch (gnet_host_get_net(host)) { case NET_TYPE_IPV4: n_ipv4++; hcnt++; break; case NET_TYPE_IPV6: n_ipv6++; hcnt++; break; case NET_TYPE_LOCAL: case NET_TYPE_NONE: break; } } sequence_iterator_release(&iter); if (0 == hcnt) return NULL; vec = gnet_host_vec_alloc(); vec->n_ipv4 = MIN(n_ipv4, 255); vec->n_ipv6 = MIN(n_ipv6, 255); if (vec->n_ipv4 > 0) WALLOC_ARRAY(vec->hvec_v4, vec->n_ipv4); if (vec->n_ipv6 > 0) WALLOC_ARRAY(vec->hvec_v6, vec->n_ipv6); n_ipv4 = 0; n_ipv6 = 0; iter = sequence_forward_iterator(s); while (sequence_iter_has_next(iter)) { const gnet_host_t *host = sequence_iter_next(iter); host_addr_t addr = gnet_host_get_addr(host); uint16 port = gnet_host_get_port(host); switch (gnet_host_get_net(host)) { case NET_TYPE_IPV4: if (n_ipv4 < vec->n_ipv4) { char *dest = cast_to_pointer(&vec->hvec_v4[n_ipv4++]); poke_be32(&dest[0], host_addr_ipv4(addr)); poke_le16(&dest[4], port); } break; case NET_TYPE_IPV6: if (n_ipv6 < vec->n_ipv6) { char *dest = cast_to_pointer(&vec->hvec_v6[n_ipv6++]); dest = mempcpy(dest, host_addr_ipv6(&addr), 16); poke_le16(dest, port); } break; case NET_TYPE_LOCAL: case NET_TYPE_NONE: break; } } sequence_iterator_release(&iter); return vec; }
/** * Callback function for inputevt_add(). This function invokes the callback * function given in DNS query on the client-side i.e., gtk-gnutella itself. * It handles partial reads if necessary. In case of an unrecoverable error * the reply pipe will be closed and the callback will be lost. */ static void adns_reply_callback(void *data, int source, inputevt_cond_t condition) { static struct adns_response ans; static void *buf; static size_t size, pos; g_assert(NULL == data); g_assert(condition & INPUT_EVENT_RX); /* * Consume all the data available in the pipe, potentially handling * several pending replies. */ for (;;) { ssize_t ret; size_t n; if (pos == size) { pos = 0; if (cast_to_pointer(&ans.common) == buf) { /* * Finished reading the generic reply header, now read * the specific part. */ g_assert(ADNS_COMMON_MAGIC == ans.common.magic); if (ans.common.reverse) { buf = &ans.reply.reverse; size = sizeof ans.reply.reverse; } else { buf = &ans.reply.by_addr; size = sizeof ans.reply.by_addr; } } else { if (buf) { /* * Completed reading the specific part of the reply. * Inform issuer of request by invoking the user callback. */ adns_reply_ready(&ans); } /* * Continue reading the next reply, if any, which will start * by the generic header. */ buf = &ans.common; size = sizeof ans.common; ans.common.magic = 0; } } g_assert(buf); g_assert(size > 0); g_assert(pos < size); n = size - pos; ret = read(source, cast_to_gchar_ptr(buf) + pos, n); if ((ssize_t) -1 == ret) { if (!is_temporary_error(errno)) { g_warning("%s: read() failed: %m", G_STRFUNC); goto error; } break; } else if (0 == ret) { g_warning("%s: read() failed: EOF", G_STRFUNC); goto error; } else { g_assert(ret > 0); g_assert(UNSIGNED(ret) <= n); pos += (size_t) ret; } } return; error: inputevt_remove(&adns_reply_event_id); g_warning("%s: removed myself", G_STRFUNC); fd_close(&source); }
/** * Compress as much data as possible to the output buffer, sending data * as we go along. * * @return the amount of input bytes that were consumed ("added"), -1 on error. */ static int deflate_add(txdrv_t *tx, const void *data, int len) { struct attr *attr = tx->opaque; z_streamp outz = attr->outz; int added = 0; if (tx_deflate_debugging(9)) { g_debug("TX %s: (%s) given %u bytes (buffer #%d, nagle %s, " "unflushed %zu) [%c%c]%s", G_STRFUNC, gnet_host_to_string(&tx->host), len, attr->fill_idx, (attr->flags & DF_NAGLE) ? "on" : "off", attr->unflushed, (attr->flags & DF_FLOWC) ? 'C' : '-', (attr->flags & DF_FLUSH) ? 'f' : '-', (tx->flags & TX_ERROR) ? " ERROR" : ""); } /* * If an error was already reported, the whole deflate stream is dead * and we cannot accept any more data. */ if G_UNLIKELY(tx->flags & TX_ERROR) return -1; while (added < len) { struct buffer *b = &attr->buf[attr->fill_idx]; /* Buffer we fill */ int ret; int old_added = added; bool flush_started = (attr->flags & DF_FLUSH) ? TRUE : FALSE; int old_avail; const char *in, *old_in; /* * Prepare call to deflate(). */ outz->next_out = cast_to_pointer(b->wptr); outz->avail_out = old_avail = b->end - b->wptr; in = data; old_in = &in[added]; outz->next_in = deconstify_pointer(old_in); outz->avail_in = len - added; g_assert(outz->avail_out > 0); g_assert(outz->avail_in > 0); /* * Compress data. * * If we previously started to flush, continue the operation, now * that we have more room available for the output. */ ret = deflate(outz, flush_started ? Z_SYNC_FLUSH : 0); if (Z_OK != ret) { attr->flags |= DF_SHUTDOWN; (*attr->cb->shutdown)(tx->owner, "Compression failed: %s", zlib_strerror(ret)); return -1; } /* * Update the parameters. */ b->wptr = cast_to_pointer(outz->next_out); added = ptr_diff(outz->next_in, in); g_assert(added >= old_added); attr->unflushed += added - old_added; attr->flushed += old_avail - outz->avail_out; if (NULL != attr->cb->add_tx_deflated) attr->cb->add_tx_deflated(tx->owner, old_avail - outz->avail_out); if (attr->gzip.enabled) { size_t r; r = ptr_diff(outz->next_in, old_in); attr->gzip.size += r; attr->gzip.crc = crc32(attr->gzip.crc, cast_to_constpointer(old_in), r); } if (tx_deflate_debugging(9)) { g_debug("TX %s: (%s) deflated %d bytes into %d " "(buffer #%d, nagle %s, flushed %zu, unflushed %zu) [%c%c]", G_STRFUNC, gnet_host_to_string(&tx->host), added, old_avail - outz->avail_out, attr->fill_idx, (attr->flags & DF_NAGLE) ? "on" : "off", attr->flushed, attr->unflushed, (attr->flags & DF_FLOWC) ? 'C' : '-', (attr->flags & DF_FLUSH) ? 'f' : '-'); } /* * If we filled the output buffer, check whether we have a pending * send buffer. If we do, we cannot process more data. Otherwise * send it now and continue. */ if (0 == outz->avail_out) { if (attr->send_idx >= 0) { deflate_set_flowc(tx, TRUE); /* Enter flow control */ return added; } deflate_rotate_and_send(tx); /* Can set TX_ERROR */ if (tx->flags & TX_ERROR) return -1; } /* * If we were flushing and we consumed all the input, then * the flush is done and we're starting normal compression again. * * This must be done after we made sure that we had enough output * space avaialable. */ if (flush_started && 0 == outz->avail_in) deflate_flushed(tx); } g_assert(0 == outz->avail_in); /* * Start Nagle if not already on. */ if (attr->flags & DF_NAGLE) deflate_nagle_delay(tx); else deflate_nagle_start(tx); /* * We're going to ask for a flush if not already started yet and the * amount of bytes we have written since the last flush is greater * than attr->buffer_flush. */ if (attr->unflushed > attr->buffer_flush) { if (!deflate_flush(tx)) return -1; } return added; }
/** * Flush compression within filling buffer. * * @return success status, failure meaning we shutdown. */ static bool deflate_flush(txdrv_t *tx) { struct attr *attr = tx->opaque; z_streamp outz = attr->outz; struct buffer *b; int ret; int old_avail; retry: b = &attr->buf[attr->fill_idx]; /* Buffer we fill */ if (tx_deflate_debugging(9)) { g_debug("TX %s: (%s) flushing %zu bytes " "(buffer #%d, flushed %zu, unflushed %zu) [%c%c]", G_STRFUNC, gnet_host_to_string(&tx->host), b->wptr - b->rptr, attr->fill_idx, attr->flushed, attr->unflushed, (attr->flags & DF_FLOWC) ? 'C' : '-', (attr->flags & DF_FLUSH) ? 'f' : '-'); } /* * Prepare call to deflate(). * * We force avail_in to 0, and don't touch next_in: no input should * be consumed. */ outz->next_out = cast_to_pointer(b->wptr); outz->avail_out = old_avail = b->end - b->wptr; outz->avail_in = 0; g_assert(outz->avail_out > 0); ret = deflate(outz, (tx->flags & TX_CLOSING) ? Z_FINISH : Z_SYNC_FLUSH); switch (ret) { case Z_BUF_ERROR: /* Nothing to flush */ goto done; case Z_OK: case Z_STREAM_END: break; default: attr->flags |= DF_SHUTDOWN; tx_error(tx); /* XXX: The callback must not destroy the tx! */ (*attr->cb->shutdown)(tx->owner, "Compression flush failed: %s", zlib_strerror(ret)); return FALSE; } { size_t written; written = old_avail - outz->avail_out; b->wptr += written; attr->flushed += written; if (NULL != attr->cb->add_tx_deflated) attr->cb->add_tx_deflated(tx->owner, written); } /* * Check whether avail_out is 0. * * If it is, then we lacked room to complete the flush. Try to send the * buffer and continue. */ if (0 == outz->avail_out) { if (attr->send_idx >= 0) { /* Send buffer not sent yet */ attr->flags |= DF_FLUSH; /* In flush mode */ deflate_set_flowc(tx, TRUE); /* Starting flow-control */ return TRUE; } deflate_rotate_and_send(tx); /* Can set TX_ERROR */ if (tx->flags & TX_ERROR) return FALSE; goto retry; } done: deflate_flushed(tx); return TRUE; /* Fully flushed */ }