void sm_resend(sm_t self, int fd) { sm_private_t my = self->private_state; sm_sendq_t sendq = ht_get_value(my->fd_to_sendq, HT_KEY(fd)); while (sendq) { char *head = sendq->head; char *tail = sendq->tail; // send as much as we can without blocking sm_on_debug(self, "ss.sendq<%p> resume send to fd=%d len=%zd", sendq, fd, (tail - head)); while (head < tail) { ssize_t sent_bytes = send(fd, (void*)head, (tail - head), 0); if (sent_bytes <= 0) { if (sent_bytes && errno != EWOULDBLOCK) { perror("sendq retry failed"); self->remove_fd(self, fd); return; } break; } head += sent_bytes; } sendq->head = head; if (head < tail) { // still have stuff to send sm_on_debug(self, "ss.sendq<%p> defer len=%zd", sendq, (tail - head)); break; } self->on_sent(self, fd, sendq->value, sendq->begin, tail - sendq->begin); sm_sendq_t nextq = sendq->next; ht_put(my->fd_to_sendq, HT_KEY(fd), nextq); if (!nextq) { FD_CLR(fd, my->send_fds); } int recv_fd = sendq->recv_fd; if (recv_fd && FD_ISSET(recv_fd, my->all_fds)) { // if no other sendq's match this blocked recv_fd, re-enable it bool found = false; if (ht_size(my->fd_to_sendq)) { sm_sendq_t *qs = (sm_sendq_t *)ht_values(my->fd_to_sendq); sm_sendq_t *q; for (q = qs; *q && !found; q++) { sm_sendq_t sq; for (sq = *q; sq && !found; sq = sq->next) { found |= (sq->recv_fd == recv_fd); } } free(qs); } if (!found) { sm_on_debug(self, "ss.sendq<%p> re-enable recv_fd=%d", sendq, recv_fd); FD_SET(recv_fd, my->recv_fds); // don't FD_SET(tmp_recv_fds), since maybe there was no input // instead, let the next select loop pick it up } } sm_on_debug(self, "ss.sendq<%p> free, next=<%p>", sendq, nextq); sm_sendq_free(sendq); sendq = nextq; } }
sm_status sm_send(sm_t self, int fd, const char *data, size_t length, void* value) { sm_private_t my = self->private_state; sm_sendq_t sendq = (sm_sendq_t)ht_get_value(my->fd_to_sendq, HT_KEY(fd)); const char *head = data; const char *tail = data + length; if (!sendq) { // send as much as we can without blocking while (1) { ssize_t sent_bytes = send(fd, (void*)head, (tail - head), 0); if (sent_bytes <= 0) { if (sent_bytes && errno != EWOULDBLOCK) { sm_on_debug(self, "ss.failed fd=%d", fd); perror("send failed"); return SM_ERROR; } break; } head += sent_bytes; if (head >= tail) { self->on_sent(self, fd, value, data, length); return SM_SUCCESS; // this is the typical case } } } // we can't send this now, so queue it int curr_recv_fd = my->curr_recv_fd; sm_sendq_t newq = sm_sendq_new(curr_recv_fd, value, head, tail - head); if (sendq) { while (sendq->next) { sendq = sendq->next; } sendq->next = newq; } else { ht_put(my->fd_to_sendq, HT_KEY(fd), newq); FD_SET(fd, my->send_fds); } sm_on_debug(self, "ss.sendq<%p> new fd=%d recv_fd=%d length=%zd" ", prev=<%p>", newq, fd, curr_recv_fd, tail - head, sendq); if (curr_recv_fd && FD_ISSET(curr_recv_fd, my->recv_fds)) { // block the current recv_fd, to prevent our sendq from growing too large. // At worst our recv_fds are all trying to send to the same fd, in which // case we'll eventually block all of them until the first blocked send // succeeds. sm_on_debug(self, "ss.sendq<%p> disable recv_fd=%d", newq, curr_recv_fd); FD_CLR(curr_recv_fd, my->recv_fds); FD_CLR(curr_recv_fd, my->tmp_recv_fds); } return SM_SUCCESS; }
sm_status sm_add_fd(sm_t self, int fd, void *value, bool is_server) { sm_private_t my = self->private_state; if (FD_ISSET(fd, my->all_fds)) { return SM_ERROR; } if (ht_put(my->fd_to_value, HT_KEY(fd), value)) { // The above FD_ISSET(..master..) should prevent this return SM_ERROR; } // is_server == getsockopt(..., SO_ACCEPTCONN, ...)? sm_on_debug(self, "ss.add%s_fd(%d)", (is_server ? "_server" : ""), fd); FD_SET(fd, my->all_fds); FD_CLR(fd, my->send_fds); // only set if blocked FD_SET(fd, my->recv_fds); FD_CLR(fd, my->tmp_send_fds); FD_CLR(fd, my->tmp_recv_fds); FD_CLR(fd, my->tmp_fail_fds); if (is_server) { FD_SET(fd, my->server_fds); } if (fd > my->max_fd) { my->max_fd = fd; } return SM_SUCCESS; }
sm_status sm_remove_fd(sm_t self, int fd) { sm_private_t my = self->private_state; if (!FD_ISSET(fd, my->all_fds)) { return SM_ERROR; } void *value = ht_put(my->fd_to_value, HT_KEY(fd), NULL); bool is_server = FD_ISSET(fd, my->server_fds); sm_on_debug(self, "ss.remove%s_fd(%d)", (is_server ? "_server" : ""), fd); sm_status ret = self->on_close(self, fd, value, is_server); close(fd); FD_CLR(fd, my->all_fds); if (is_server) { FD_CLR(fd, my->server_fds); } FD_CLR(fd, my->send_fds); FD_CLR(fd, my->recv_fds); FD_CLR(fd, my->tmp_send_fds); FD_CLR(fd, my->tmp_recv_fds); FD_CLR(fd, my->tmp_fail_fds); if (fd == my->max_fd) { while (my->max_fd >= 0 && !FD_ISSET(my->max_fd, my->all_fds)) { my->max_fd--; } } if (ht_size(my->fd_to_sendq)) { sm_sendq_t *qs = (sm_sendq_t *)ht_values(my->fd_to_sendq); sm_sendq_t *q; for (q = qs; *q; q++) { sm_sendq_t sendq = *q; while (sendq) { if (sendq->recv_fd == fd) { sendq->recv_fd = 0; // don't abort this blocked send, even though the "cause" has ended } sendq = sendq->next; } } free(qs); } return ret; }
void sm_recv(sm_t self, int fd) { sm_private_t my = self->private_state; my->curr_recv_fd = fd; while (1) { ssize_t read_bytes = recv(fd, my->tmp_buf, my->tmp_buf_length, RECV_FLAGS); if (read_bytes < 0) { if (errno != EWOULDBLOCK) { perror("recv failed"); self->remove_fd(self, fd); } break; } sm_on_debug(self, "ss.recv fd=%d len=%zd", fd, read_bytes); void *value = ht_get_value(my->fd_to_value, HT_KEY(fd)); if (read_bytes == 0 || self->on_recv(self, fd, value, my->tmp_buf, read_bytes)) { self->remove_fd(self, fd); break; } } my->curr_recv_fd = 0; }
void sm_accept(sm_t self, int fd) { sm_private_t my = self->private_state; while (1) { int new_fd = accept(fd, NULL, NULL); if (new_fd < 0) { if (errno != EWOULDBLOCK) { perror("accept failed"); self->remove_fd(self, fd); return; } break; } sm_on_debug(self, "ss.accept server=%d new_client=%d", fd, new_fd); void *value = ht_get_value(my->fd_to_value, HT_KEY(fd)); void *new_value = NULL; if (self->on_accept(self, fd, value, new_fd, &new_value)) { close(new_fd); } else if (self->add_fd(self, new_fd, new_value, false)) { self->on_close(self, new_fd, new_value, false); close(new_fd); } } }
dl_status dl_recv_packet(dl_t self, const char *packet, size_t length) { dl_private_t my = self->private_state; const char *tail = packet; uint32_t len = dl_sscanf_uint32(tail); tail += 4; if (len != length || len < 16) { return DL_ERROR; } uint32_t version = dl_sscanf_uint32(tail); tail += 4; uint32_t type = dl_sscanf_uint32(tail); tail += 4; (void)dl_sscanf_uint32(tail); tail += 4; const char *xml = tail; size_t xml_length = length - 16; if (version != 1 || type != TYPE_PLIST) { return DL_SUCCESS; // ignore? } plist_t dict = NULL; plist_from_xml(xml, xml_length, &dict); char *message = NULL; if (dict) { plist_t node = plist_dict_get_item(dict, "MessageType"); if (plist_get_node_type(node) == PLIST_STRING) { plist_get_string_val(node, &message); } } dl_status ret = DL_ERROR; if (!message) { ret = DL_ERROR; } else if (!strcmp(message, "Result")) { plist_t node = plist_dict_get_item(dict, "Number"); if (node) { uint64_t value = 0; plist_get_uint_val(node, &value); // just an ack of our Listen? ret = (value ? DL_ERROR : DL_SUCCESS); } } else if (!strcmp(message, "Attached")) { plist_t props = plist_dict_get_item(dict, "Properties"); if (props) { uint64_t device_num = 0; plist_t node = plist_dict_get_item(props, "DeviceID"); plist_get_uint_val(node, &device_num); uint64_t product_id = 0; node = plist_dict_get_item(props, "ProductID"); plist_get_uint_val(node, &product_id); char *device_id = NULL; node = plist_dict_get_item(props, "SerialNumber"); if (node) { plist_get_string_val(node, &device_id); } uint64_t location = 0; node = plist_dict_get_item(props, "LocationID"); plist_get_uint_val(node, &location); ht_t d_ht = my->device_num_to_device_id; ht_put(d_ht, HT_KEY(device_num), device_id); ret = self->on_attach(self, device_id, (int)device_num); } } else if (strcmp(message, "Detached") == 0) { plist_t node = plist_dict_get_item(dict, "DeviceID"); if (node) { uint64_t device_num = 0; plist_get_uint_val(node, &device_num); ht_t d_ht = my->device_num_to_device_id; char *device_id = (char *)ht_remove(d_ht, HT_KEY(device_num)); if (device_id) { ret = self->on_detach(self, device_id, (int)device_num); free(device_id); } } } free(message); plist_free(dict); return ret; }