/* * transport.c * * Code used to send and receive channelized data, which may be larger than * single frames, and require reliability services. These are files, * messages, group and directory listings. * * Copyright (c) 2004, 2005 Todd MacDermid * */ #include #include #include #include #include #include #include #include #include #include #include #include /* * cutlass_transport_poll provides a method for the user of libcutlass * to poll existing transfers, retriving stats about them, as well * as retriving a user-defined object that got stored with * cutlass_transport_store() */ poll_t * cutlass_transport_poll(cutlass_t *cut_handle, uint8_t *fingerprint, int channel_id) { conn_t *conn; poll_t *poll_obj; struct channel *channel; if((NULL == cut_handle) || (NULL == fingerprint)) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_poll: Passed a NULL handle\n"); return(NULL); } if((channel_id > 2*CUT_NUM_CHANNELS) || (channel_id < 1)) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_poll: Channel out of bounds\n"); return(NULL); } poll_obj = NULL; conn = hashtable_fingerprint_find(cut_handle, fingerprint); if(NULL == conn) { cutlass_sysmsg(cut_handle, CUT_INFO, "cutlass_transport_poll: Couldn't find connection\n"); return(NULL); } if(channel_id > CUT_NUM_CHANNELS) { channel = &(conn->out_channels[channel_id - CUT_NUM_CHANNELS]); } else { channel = &(conn->in_channels[channel_id]); } if(channel->channel_type == CHANNEL_NONE) { cutlass_sysmsg(cut_handle, CUT_DEBUG, "cutlass_transport_poll: Channel %d is unused\n", channel_id); goto fail; } else { poll_obj = calloc(1, sizeof(poll_t)); if(NULL == poll_obj) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_poll: Could not allocate memory\n"); goto fail; } poll_obj->channel_type = channel->channel_type; poll_obj->channel_state = channel->channel_state; poll_obj->total_bytes = channel->data_size; poll_obj->sent_bytes = channel->data_size - total_gapsleft(channel->gap_list); poll_obj->user_obj = channel->user_obj; } pthread_mutex_unlock(&(conn->conn_mutex)); return(poll_obj); fail: pthread_mutex_unlock(&(conn->conn_mutex)); return(NULL); } static int transport_init_info_unpack(cutlass_t *cut_handle, conn_t *conn, uint8_t *clear_packet, struct cutlass_packet_hdr *packet_info, struct cutlass_transport_init_hdr *init_header, void **transit_info) { char *name_ptr; struct msg_transit_info *msg_info; struct cutlass_file_init_hdr file_init_hdr; struct file_transit_info *file_info; uint8_t *header_loc; switch(init_header->type) { case CHANNEL_MSG: msg_info = msg_info_init(cut_handle, conn, CUT_IN); if(msg_info == NULL) { cutlass_sysmsg(cut_handle, CUT_ERROR, "transport_init_info_unpack: " "msg_info_init failed\n"); return(-1); } *transit_info = msg_info; break; case CHANNEL_FILE: header_loc = clear_packet + TRANSPORT_INIT_HDR_LEN_MIN; if(file_init_unpack(cut_handle, header_loc, &file_init_hdr) < 0) { cutlass_sysmsg(cut_handle, CUT_ERROR, "transport_init_info_unpack: " "Error unpacking file init header\n"); return(-1); } if((name_ptr = strrchr(file_init_hdr.file_name, '/')) != NULL) { name_ptr++; file_info = file_info_init(cut_handle, conn, CUT_IN, name_ptr, &file_init_hdr); strncpy(file_info->name, name_ptr, CUTLASS_FILENAME_LEN); } else { file_info = file_info_init(cut_handle, conn, CUT_IN, file_init_hdr.file_name, &file_init_hdr); strncpy(file_info->name, file_init_hdr.file_name, CUTLASS_FILENAME_LEN); } if(file_info == NULL) { cutlass_sysmsg(cut_handle, CUT_ERROR, "transport_init_info_unpack: " "Error allocating file_init\n"); return(-1); } *transit_info = file_info; break; default: break; } return(0); } static int transit_info_destroy(cutlass_t *cut_handle, int type, void *transit_info) { struct msg_transit_info *msg_info; struct file_transit_info *file_info; switch(type) { case CHANNEL_MSG: msg_info = transit_info; msg_info_destroy(cut_handle, msg_info); break; case CHANNEL_FILE: file_info = transit_info; file_info_destroy(cut_handle, file_info); break; } return(0); } /* * transport_inbound_permit_check is called by cutlass_transport_init_parse, * and will determine if a particular inbound request will be permitted * by the tranport subsystem, or if we should just kill it now. * * Returns -1 on error, 0 if we should not allow this transport, or * 1 if we should allow the transport to be allocated. */ static int transport_inbound_permit_check(cutlass_t *cut_handle, conn_t *conn, struct cutlass_transport_init_hdr *init_header, void *transit_info) { int matched_request = 0; request_t *request; struct msg_transit_info *msg_info; struct file_transit_info *file_info; if((NULL == cut_handle) || (NULL == conn) || (NULL == init_header)) { cutlass_sysmsg(cut_handle, CUT_ERROR, "transport_inbound_permit_check: " "Passed a NULL pointer\n"); return(-1); } if(init_header->request_id != 0) { request = cutlass_request_match(cut_handle, conn, init_header->request_id); printf("request_id %d matched!\n", init_header->request_id); if(request == NULL) { cutlass_sysmsg(cut_handle, CUT_INFO, "transport_inbound_permit_check: " "Got an unmatched request_id of %d\n", init_header->request_id); return(NOT_ALLOWED); } else { matched_request = 1; /* We can make use of this later */ } } switch(init_header->type) { case CHANNEL_MSG: msg_info = transit_info; if(!(cut_handle->local_info.capabilities & CAN_RECV_MSGS)) { cutlass_sysmsg(cut_handle, CUT_INFO, "transport_inbound_permit_check: " "Sent a message, and I don't handle messages\n"); return(NOT_ALLOWED); } break; case CHANNEL_FILE: file_info = transit_info; if(!(cut_handle->local_info.capabilities & CAN_RECV_FILES)) { cutlass_sysmsg(cut_handle, CUT_INFO, "transport_inbound_permit_check: " "Sent a file, and I don't accept files\n"); return(NOT_ALLOWED); } return(cut_handle->perms.permit_push); break; case CHANNEL_REQUEST: break; case CHANNEL_DIR_INFO: break; case CHANNEL_REPLY: break; default: return(NOT_ALLOWED); break; } return(ALL_ALLOWED); } /* * transport_init_pack takes a transport_header and type_info structure, * and packs their contents into a a packet buffer header. The caller is * responsible for making sure the packet buffer is large enough. * CUT_MSG_LEN_MAX bytes should be just dandy. ;) * * Returns 0 on success, or -1 on error. */ static int transport_init_pack(cutlass_t *cut_handle, uint8_t *packet, struct cutlass_transport_init_hdr *transport_header, void *type_info) { int tmp_len; uint8_t *packet_loc; uint32_t tmp_size; uint32_t tmp_request; uint16_t tmp_hdr_len; struct msg_transit_info *msg_info; struct file_transit_info *file_info; struct cutlass_file_init_hdr file_hdr; if((cut_handle == NULL) || (packet == NULL) || (transport_header == NULL)) { cutlass_sysmsg(cut_handle, CUT_ERROR, "transport_init_pack: Passed a NULL pointer\n"); return(-1); } if(transport_header->init_hdr_len < TRANSPORT_INIT_HDR_LEN_MIN) { cutlass_sysmsg(cut_handle, CUT_ERROR, "transport_init_pack: Invalid header length\n"); return(-1); } packet_loc = packet; tmp_size = htonl(transport_header->transport_size); memcpy(packet_loc, &tmp_size, 4); packet_loc += 4; tmp_request = htonl(transport_header->request_id); memcpy(packet_loc, &tmp_request, 4); packet_loc += 4; tmp_hdr_len = htons(transport_header->init_hdr_len); memcpy(packet_loc, &tmp_hdr_len, 2); packet_loc += 2; memcpy(packet_loc, &(transport_header->type), 1); packet_loc += 1; switch(transport_header->type) { case CHANNEL_MSG: if(NULL == type_info) { cutlass_sysmsg(cut_handle, CUT_ERROR, "transport_init_pack: Passed a NULL type_info\n"); return(-1); } msg_info = type_info; *packet_loc = msg_info->msg_dest_type; packet_loc += sizeof(msg_info->msg_dest_type); memcpy(packet_loc, msg_info->destination, CUT_ID_LEN); break; case CHANNEL_FILE: if(NULL == type_info) { cutlass_sysmsg(cut_handle, CUT_ERROR, "transport_init_pack: Passed a NULL type_info\n"); return(-1); } file_info = type_info; tmp_len = strlen(file_info->name); if(tmp_len > (CUTLASS_FILENAME_LEN - 1)) return(-1); file_hdr.file_name_len = tmp_len; memcpy(&(file_hdr.file_csum), file_info->file_csum, CUT_CSUM_LEN); strncpy(file_hdr.file_name, file_info->name, CUTLASS_FILENAME_LEN); file_init_pack(cut_handle, packet_loc, &file_hdr); break; case CHANNEL_GROUP: cutlass_sysmsg(cut_handle, CUT_ERROR, "transport_init_pack: Group channel not yet implemented\n"); return(-1); break; case CHANNEL_REQUEST: case CHANNEL_DIR_INFO: case CHANNEL_REPLY: /* Do nothing, for now */ break; default: cutlass_sysmsg(cut_handle, CUT_ERROR, "transport_init_pack: Unknown transport type\n"); return(-1); } return(0); } /* * transport_init_unpack takes in a packet buffer and unpacks the * contents into the supplied cutlass_transport_init_hdr structure. * * Returns 0 on success, -1 on failure. */ static int transport_init_unpack(cutlass_t *cut_handle, uint8_t *packet, struct cutlass_packet_hdr *packet_info, struct cutlass_transport_init_hdr *transport_header) { uint8_t *packet_loc; uint32_t tmp_size; uint32_t tmp_request; uint16_t tmp_hdr_len; if((NULL == cut_handle) || (NULL == packet) || (NULL == transport_header)) { cutlass_sysmsg(cut_handle, CUT_ERROR, "transport_init_unpack: Passed a NULL pointer\n"); return(-1); } if(packet_info->length < TRANSPORT_INIT_HDR_LEN_MIN) { cutlass_sysmsg(cut_handle, CUT_INFO, "transport_init_unpack: Undersized payload - %d bytes\n", packet_info->length); return(-1); } packet_loc = packet; memcpy(&tmp_size, packet_loc, 4); transport_header->transport_size = ntohl(tmp_size); packet_loc += 4; memcpy(&tmp_request, packet_loc, 4); transport_header->request_id = ntohl(tmp_request); packet_loc += 4; memcpy(&tmp_hdr_len, packet_loc, 2); transport_header->init_hdr_len = ntohs(tmp_hdr_len); packet_loc += 2; transport_header->type = *packet_loc; packet_loc += sizeof(transport_header->type); return(0); } static int transport_action_populate(cutlass_t *cut_handle, conn_t *conn, void *transit_info, struct channel *channel, struct cut_action_obj *action_obj, int type, int direction) { struct msg_transit_info *msg_info; struct file_transit_info *file_info; if((NULL == cut_handle) || (NULL == conn) || (NULL == transit_info) || (NULL == channel) || (NULL == action_obj)) { cutlass_sysmsg(cut_handle, CUT_ERROR, "transport_action_populate: Passed a NULL pointer\n"); return(-1); } cutlass_action_populate(cut_handle, conn, action_obj); if(direction == CUT_IN) { action_obj->external_channel_id = channel->channel_id; } else { action_obj->external_channel_id = channel->channel_id + CUT_NUM_CHANNELS; } action_obj->action_type = CUT_NOACTION; switch(type) { case CHANNEL_MSG: msg_info = transit_info; break; case CHANNEL_FILE: file_info = transit_info; action_obj->action_type = CUT_FILE_OFFER; strncpy(action_obj->file_name, file_info->name, CUTLASS_FILENAME_LEN); action_obj->file_name[CUTLASS_FILENAME_LEN - 1] = 0; action_obj->file_size = channel->data_size; memcpy(action_obj->file_csum, file_info->file_csum, CUT_CSUM_LEN); break; } return(0); } /* * cutlass_transport_init_send is the entry point to the transport * layer by the users (which are _NOT_ cutlass clients, but * rather the message system, the file transfer system, etc.) * * It initializes the required structures and sends the intialization * packet to the remote connection's side. * * returns 0 on success, -1 on failure. */ int cutlass_transport_init_send(cutlass_t *cut_handle, conn_t *conn, struct channel *transport_info, struct timespec *now) { int tmp_len; int write_len; struct cutlass_packet_hdr header; struct cutlass_transport_init_hdr init_header; struct msg_transit_info *msg_info; struct file_transit_info *file_info; uint8_t packet[CUT_MSG_LEN_MAX] = { 0 }; if((NULL == cut_handle) || (NULL == conn) || (NULL == transport_info) || (NULL == now)) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_init_send: Passed a NULL pointer\n"); return(-1); } if(CUTSTATE_ACTIVE != conn->conn_state) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_init_send: Connection not active\n"); return(-1); } memset(&init_header, 0, sizeof(struct cutlass_transport_init_hdr)); init_header.request_id = transport_info->request_id; switch(transport_info->channel_type) { case CHANNEL_NONE: cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_init_send: Passed an unused channel\n"); return(-1); break; case CHANNEL_MSG: msg_info = transport_info->channel_info; init_header.init_hdr_len = TRANSPORT_INIT_HDR_LEN_MIN + MSG_INIT_HDR_LEN; init_header.transport_size = transport_info->data_size; init_header.type = CHANNEL_MSG; transport_init_pack(cut_handle, packet, &init_header, transport_info->channel_info); header.length = init_header.init_hdr_len; write_len = conn->mtu - header.length; if(transport_info->data_size > write_len) { memcpy(packet + header.length, transport_info->buffer, write_len); header.length += write_len; } else { memcpy(packet + header.length, transport_info->buffer, transport_info->data_size); header.length += transport_info->data_size; } break; case CHANNEL_FILE: file_info = transport_info->channel_info; tmp_len = strlen(file_info->name); if(tmp_len > (CUTLASS_FILENAME_LEN - 1)) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_init_send: filename too long\n"); return(-1); } init_header.init_hdr_len = TRANSPORT_INIT_HDR_LEN_MIN + FILE_INIT_HDR_LEN + tmp_len; init_header.transport_size = transport_info->data_size; init_header.type = CHANNEL_FILE; transport_init_pack(cut_handle, packet, &init_header, transport_info->channel_info); header.length = init_header.init_hdr_len; break; case CHANNEL_REQUEST: init_header.init_hdr_len = TRANSPORT_INIT_HDR_LEN_MIN; init_header.transport_size = transport_info->data_size; init_header.type = CHANNEL_REQUEST; transport_init_pack(cut_handle, packet, &init_header, transport_info->channel_info); header.length = init_header.init_hdr_len; break; case CHANNEL_DIR_INFO: init_header.init_hdr_len = TRANSPORT_INIT_HDR_LEN_MIN; init_header.transport_size = transport_info->data_size; init_header.type = CHANNEL_DIR_INFO; transport_init_pack(cut_handle, packet, &init_header, transport_info->channel_info); header.length = init_header.init_hdr_len; break; case CHANNEL_REPLY: init_header.init_hdr_len = TRANSPORT_INIT_HDR_LEN_MIN; init_header.transport_size = transport_info->data_size; init_header.type = CHANNEL_REPLY; transport_init_pack(cut_handle, packet, &init_header, transport_info->channel_info); header.length = init_header.init_hdr_len; break; default: cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_init_send: Unknown channel type\n"); return(-1); } header.cut_type = CUT_TRANS_INIT; header.channel_id = transport_info->channel_id; init_header.type = transport_info->channel_type; init_header.transport_size = transport_info->data_size; if(cutlass_send_process(cut_handle, conn, &header, packet, header.length) != 0) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_init_send: cutlass_send failed\n"); return(-1); } memcpy(&(transport_info->last_sent), now, sizeof(struct timespec)); return(0); } /* * cutlass_transport_init_parse is called by the listening loop to * take appropriate action on a transport_init packet. It will * call cutlass_transport_ack_send() or cutlass_transport_init_ack_send() * after parsing in most cases. * * returns 0 on success, 1 on nonfatal error, -1 on fatal error. */ int cutlass_transport_init_parse(cutlass_t *cut_handle, conn_t *conn, uint8_t *clear_packet, struct cutlass_packet_hdr *packet_info, struct timespec *now) { int permit; int read_len; struct cutlass_transport_init_hdr init_header; struct cut_action_obj action_obj; struct file_transit_info *file_info; uint8_t channel_id; void *transit_info = NULL; if((NULL == cut_handle) || (NULL == conn) || (NULL == packet_info) || (NULL == clear_packet) || (NULL == now)) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_init_parse: Passed a NULL pointer\n"); return(-1); } channel_id = packet_info->channel_id; if (transport_init_unpack(cut_handle, clear_packet, packet_info, &init_header) < 0) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_init_parse: " "Error decoding file init hdr\n"); return(1); } cutlass_sysmsg(cut_handle, CUT_DEBUG, "cutlass_transport_init_parse: Channel %d\n", channel_id); if((conn->in_channels[channel_id].channel_type != CHANNEL_NONE) && (conn->in_channels[channel_id].channel_type != init_header.type)) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_init_parse: " "Channel %d already allocated?\n", channel_id); return(1); } if(conn->in_channels[channel_id].channel_type != CHANNEL_NONE) { /* * We're going to see if the channel is already in use by a transport * of our type (aka is this a retransmitted packet? */ switch(conn->in_channels[channel_id].channel_state) { case CUT_CHANNEL_STATE_INIT: cutlass_sysmsg(cut_handle, CUT_DEBUG, "cutlass_transport_init_parse: Init state\n"); /* Fall through to below */ case CUT_CHANNEL_STATE_WAITING: cutlass_sysmsg(cut_handle, CUT_DEBUG, "cutlass_transport_init_parse: Waiting state\n"); /* They must have lost our init ack, or we've timed out */ cutlass_transport_init_ack_send(cut_handle, conn, &conn->in_channels[channel_id], clear_packet, now); break; case CUT_CHANNEL_STATE_TRANSFERRING: cutlass_sysmsg(cut_handle, CUT_DEBUG, "cutlass_transport_init_parse: Transferring state\n"); /* Fall through to below */ case CUT_CHANNEL_STATE_FINISH: cutlass_sysmsg(cut_handle, CUT_DEBUG, "cutlass_transport_init_parse: Finish state\n"); /* They must have lost our regular ack */ cutlass_transport_ack_send(cut_handle, conn, &conn->in_channels[channel_id], clear_packet, now); break; default: cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_init_parse: " "Somehow in odd state? (Should not happen)\n"); return(1); break; } } else { /* Channel has _NOT_ been previously allocated */ if(transport_init_info_unpack(cut_handle, conn, clear_packet, packet_info, &init_header, &transit_info) < 0) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_init_parse: " "failed to unpack transport type-specific info\n"); cutlass_channel_close_send(cut_handle, conn, CUT_IN, channel_id); return(1); } permit = transport_inbound_permit_check(cut_handle, conn, &init_header, &transit_info); switch(permit) { case USER_ALLOW_CHECK: case ALL_ALLOWED: /* Do nothing? */ break; case NOT_ALLOWED: default: cutlass_sysmsg(cut_handle, CUT_INFO, "cutlass_transport_init_parse: " "transport_inbound_permit_check failed\n"); /* * We need to better take care of our remote file-sending compatriots. * They're never getting a reject action handler, which they are * entitled to */ transit_info_destroy(cut_handle, init_header.type, transit_info); cutlass_channel_close_send(cut_handle, conn, CUT_IN, channel_id); return(1); break; } if(new_cut_channel_in(cut_handle, conn->in_channels, channel_id, init_header.type, init_header.transport_size, transit_info) < 0) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_init_parse: " "new_cut_channel_in failed\n"); transit_info_destroy(cut_handle, init_header.type, transit_info); cutlass_channel_close_send(cut_handle, conn, CUT_IN, channel_id); return(1); } if(permit == USER_ALLOW_CHECK) { transport_action_populate(cut_handle, conn, transit_info, &(conn->in_channels[channel_id]), &action_obj, init_header.type, CUT_IN); if(cutlass_action(cut_handle, conn, &action_obj) < 0) { cutlass_sysmsg(cut_handle, CUT_INFO, "cutlass_transport_init_parse: " "Action handler could not be called - " "terminating transfer\n"); transit_info_destroy(cut_handle, init_header.type, transit_info); cutlass_channel_close_send(cut_handle, conn, CUT_IN, channel_id); return(1); } conn->in_channels[channel_id].channel_state = CUT_CHANNEL_STATE_WAITING; cutlass_sysmsg(cut_handle, CUT_DEBUG, "cutlass_transport_init_parse: Querying user\n"); cutlass_transport_init_ack_send(cut_handle, conn, &(conn->in_channels[channel_id]), clear_packet, now); return(0); } switch(init_header.type) { case CHANNEL_MSG: case CHANNEL_REQUEST: case CHANNEL_DIR_INFO: case CHANNEL_REPLY: read_len = packet_info->length - (init_header.init_hdr_len); if(read_len > conn->in_channels[channel_id].data_size) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_init_parse: " "Received too much data for buffer %d on %d bytes\n", read_len, conn->in_channels[channel_id].data_size); transit_info_destroy(cut_handle, init_header.type, transit_info); del_cut_channel(cut_handle, conn, CUT_IN, channel_id); return(1); } else if (read_len == conn->in_channels[channel_id].data_size) { memcpy(conn->in_channels[channel_id].buffer, clear_packet + (init_header.init_hdr_len), read_len); update_gaplist(cut_handle, conn->in_channels[channel_id].gap_list, 0, read_len); conn->in_channels[channel_id].channel_state = CUT_CHANNEL_STATE_TRANSFERRING; cutlass_transport_complete(cut_handle, conn, &conn->in_channels[channel_id]); return(0); } else { memcpy(conn->in_channels[channel_id].buffer, clear_packet + (init_header.init_hdr_len), read_len); update_gaplist(cut_handle, conn->in_channels[channel_id].gap_list, 0, read_len); } break; case CHANNEL_FILE: file_info = transit_info; if(cutlass_file_start_recv(cut_handle, conn, channel_id, file_info->name, 0) < 0) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_init_parse: " "Could not start file transfer\n"); transit_info_destroy(cut_handle, init_header.type, transit_info); del_cut_channel(cut_handle, conn, CUT_IN, channel_id); } return(0); break; default: cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_init_parse: " "Unsupported channel type: %d\n", init_header.type); transit_info_destroy(cut_handle, init_header.type, transit_info); del_cut_channel(cut_handle, conn, CUT_IN, channel_id); return(1); break; } conn->in_channels[channel_id].channel_state = CUT_CHANNEL_STATE_TRANSFERRING; cutlass_transport_ack_send(cut_handle, conn, &conn->in_channels[channel_id], clear_packet, now); } return(0); } /* * cutlass_transport_init_ack_send sends an acknowledgement of * the receipt of the initialization, but does not request any data * to be yet sent. This could be a case of the user deciding whether * or not to accept an offered file, for example. * * returns 0 on success, or -1 on failure. */ int cutlass_transport_init_ack_send(cutlass_t *cut_handle, conn_t *conn, struct channel *transport_info, uint8_t *clear_packet, struct timespec *now) { struct cutlass_packet_hdr header; if((NULL == cut_handle) || (NULL == conn) || (NULL == transport_info) || (NULL == clear_packet) || (NULL == now)) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_init_ack_send: Passed a NULL pointer\n"); return(-1); } if(CUTSTATE_ACTIVE != conn->conn_state) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_init_ack_send: Connection not active\n"); return(-1); } if((transport_info->channel_state == CUT_CHANNEL_STATE_INIT) || (transport_info->channel_state == CUT_CHANNEL_STATE_WAITING)) { transport_info->channel_state = CUT_CHANNEL_STATE_WAITING; header.cut_type = CUT_TRANS_INIT_ACK; header.channel_id = transport_info->channel_id; header.length = 0; if(cutlass_send_process(cut_handle, conn, &header, NULL, header.length) != 0) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_init_ack_send: " "cutlass_send_process failed\n"); return(-1); } } else { cutlass_sysmsg(cut_handle, CUT_DEBUG, "cutlass_transport_init_ack_send: " "got called on non-init state\n"); return(-1); } memcpy(&(transport_info->last_sent), now, sizeof(struct timespec)); return(0); } /* * cutlass_transport_init_ack_parse receives a CUT_TRANS_INIT_ACK * packet, and updates the channel's state to take note of the * fact that the remote side did receive the initialization * packet, and stop sending init retransmissions. * * returns 0 on success, 1 on nonfatal error, or -1 on fatal error. */ int cutlass_transport_init_ack_parse(cutlass_t *cut_handle, conn_t *conn, uint8_t *clear_packet, struct cutlass_packet_hdr *packet_info, struct timespec *now) { struct channel *transport_info; if((NULL == cut_handle) || (NULL == conn) || (NULL == packet_info) || (NULL == clear_packet) || (NULL == now)) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_init_ack_parse: " "Passed a NULL pointer\n"); return(-1); } transport_info = &(conn->out_channels[packet_info->channel_id]); switch(transport_info->channel_type) { case CHANNEL_FILE: if((transport_info->channel_state == CUT_CHANNEL_STATE_INIT) || (transport_info->channel_state == CUT_CHANNEL_STATE_WAITING)) { transport_info->channel_state = CUT_CHANNEL_STATE_WAITING; } else { cutlass_sysmsg(cut_handle, CUT_INFO, "cutlass_transport_init_ack_parse: " "got init_ack on non-init state\n"); } break; case CHANNEL_NONE: cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_init_ack_parse: " "Called on unallocated channel: %d\n", packet_info->channel_id); cutlass_channel_close_send(cut_handle, conn, CUT_OUT, packet_info->channel_id); return(1); break; default: cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_init_ack_parse: " "Called on unsupported channel type\n"); del_cut_channel(cut_handle, conn, CUT_OUT, packet_info->channel_id); return(1); break; } memcpy(&(transport_info->last_received), now, sizeof(struct timespec)); return(0); } /* * cutlass_transport_send locates bytes available in the buffer at * the requested offsets, and sends them to the remote side. * * returns 0 on success, 1 on non-transmit due to congestion control, * -1 on error. */ int cutlass_transport_send(cutlass_t *cut_handle, conn_t *conn, struct channel *transport_info, struct timespec *now) { struct cutlass_packet_hdr header; struct file_transit_info *file_info; struct timespec rate_allowed; struct timespec time_remaining; int req_bytes; int read_bytes; uint8_t packet[CUT_MSG_LEN_MAX] = { 0 }; uint32_t tmp_start; uint32_t write_start; if((NULL == cut_handle) || (NULL == conn) || (NULL == transport_info) || (NULL == now)) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_send: Passed a NULL pointer\n"); return(-1); } if(CUTSTATE_ACTIVE != conn->conn_state) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_send: Connection not active\n"); return(-1); } header.cut_type = CUT_TRANS_SEND; header.channel_id = transport_info->channel_id; if(memcmp(now, &(transport_info->last_received), sizeof(struct timespec)) != 0) { rate_allowed.tv_sec = 0; rate_allowed.tv_nsec = 1000000000 / conn->rate; if(cutlass_time_check(now, &(transport_info->last_sent), &rate_allowed, &time_remaining) == 0) { cutlass_sysmsg(cut_handle, CUT_INFO, "cutlass_transport_send: Congestion delay\n"); return(1); } } /* * If we've dropped a packet or reordered a packet, switch * the side of the gaps that we're writing to to the other side, * so any old send frames in transit will hopefully get there * before our next drop. Cuts down on retransmits. */ if(transport_info->reset_loc) { if(transport_info->end == CUT_FRONT_END) { transport_info->end = CUT_BACK_END; transport_info->cur_offset = gaplist_last(transport_info); } else { transport_info->end = CUT_FRONT_END; transport_info->cur_offset = gaplist_first(transport_info); } transport_info->reset_loc = 0; } /* Locate the bytes that still need to be sent */ req_bytes = gaplist_bytes_find(cut_handle, conn, transport_info, &write_start); cutlass_sysmsg(cut_handle, CUT_DEBUG, "cutlass_transport_send: sending %d bytes at offset %d\n", req_bytes, write_start); if(req_bytes > 0) { if(transport_info->channel_type == CHANNEL_FILE) { file_info = transport_info->channel_info; if(lseek(file_info->fd, write_start, SEEK_SET) != write_start) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_send: Error positioning file\n"); return(-1); } read_bytes = read(file_info->fd, (packet + CUT_TRANS_SEND_HDR_LEN), req_bytes); if(read_bytes < 0) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_send: Error reading file\n"); return(-1); } if(read_bytes != req_bytes) { cutlass_sysmsg(cut_handle, CUT_INFO, "cutlass_transport_send: " "low number of bytes returned\n"); } } else { memcpy(packet + CUT_TRANS_SEND_HDR_LEN, transport_info->buffer + write_start, req_bytes); read_bytes = req_bytes; } tmp_start = htonl(write_start); memcpy(packet, &tmp_start, 4); header.length = CUT_TRANS_SEND_HDR_LEN + read_bytes; if(cutlass_send_process(cut_handle, conn, &header, packet, header.length) != 0) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_send: cutlass_send failed\n"); return(-1); } if(transport_info->end == CUT_FRONT_END) { transport_info->cur_offset += read_bytes; } else { transport_info->cur_offset -= read_bytes; } } else { cutlass_sysmsg(cut_handle, CUT_INFO, "cutlass_transport_send: Transport bytes exhausted\n"); transport_info->reset_loc = 1; return(1); } memcpy(&(transport_info->last_sent), now, sizeof(struct timespec)); return(0); } /* * cutlass_transport_parse receives a transport packet and updates * the gaplist for the channel. If we're complete, it shuts down * the channel, else it will send an acknowledgement containing the * request for the bytes it's still missing, using * cutlass_transport_ack_send(). * * returns 0 on success, 1 on nonfatal error, -1 on fatal error. */ int cutlass_transport_parse(cutlass_t *cut_handle, conn_t *conn, uint8_t *clear_packet, struct cutlass_packet_hdr *packet_info, struct timespec *now) { int retval; int i; int write_done; uint8_t *data_ptr; uint16_t length; uint16_t to_write; uint32_t data_offset; uint32_t tmp_offset; struct channel *transport_info; struct file_transit_info *file_info; struct msg_transit_info *msg_info; if((NULL == cut_handle) || (NULL == conn) || (NULL == packet_info) || (NULL == clear_packet) || (NULL == now)) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_parse: Passed a NULL pointer\n"); return(-1); } length = packet_info->length; file_info = NULL; transport_info = &(conn->in_channels[packet_info->channel_id]); if(transport_info->channel_state != CUT_CHANNEL_STATE_TRANSFERRING) { cutlass_sysmsg(cut_handle, CUT_INFO, "cutlass_transport_parse: " "Received a packet on non-transfer state: %d\n", transport_info->channel_state); return(1); } switch(transport_info->channel_type) { case CHANNEL_FILE: file_info = transport_info->channel_info; break; case CHANNEL_MSG: msg_info = transport_info->channel_info; break; case CHANNEL_REQUEST: case CHANNEL_DIR_INFO: case CHANNEL_REPLY: /* Do nothing, for now. */ break; default: cutlass_sysmsg(cut_handle, CUT_INFO, "cutlass_transport_parse: " "received packet on unallocated channel: %d\n", packet_info->channel_id); del_cut_channel(cut_handle, conn, CUT_IN, packet_info->channel_id); return(1); break; } if (length < CUT_TRANS_SEND_HDR_LEN) { cutlass_sysmsg(cut_handle, CUT_DEBUG, "cutlass_transport_parse: received undersized packet\n"); return(1); } length -= CUT_TRANS_SEND_HDR_LEN; to_write = length; memcpy(&tmp_offset, clear_packet, 4); data_offset = ntohl(tmp_offset); data_ptr = clear_packet + sizeof(data_offset); cutlass_sysmsg(cut_handle, CUT_DEBUG, "cutlass_transport_parse: got %d bytes at offset %d\n", length, data_offset); if((data_offset + length) > transport_info->data_size) { cutlass_sysmsg(cut_handle, CUT_DEBUG, "cutlass_transport_parse: packet out of range\n"); return(1); } if(transport_info->channel_type == CHANNEL_FILE) { i = 0; write_done = 0; lseek(file_info->fd, data_offset, SEEK_SET); while(!write_done) { retval = write(file_info->fd, &(data_ptr[i]), to_write); if(retval == to_write) { write_done = 1; } else { if(retval < 0) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_parse: " "Error writing file %s (%d bytes at %d): " "%d (state %d)\n", file_info->name, to_write, data_offset, retval, transport_info->channel_state); perror(""); return(1); } else { to_write -= retval; i += retval; } } } } else { memcpy(transport_info->buffer + data_offset, data_ptr, to_write); } retval = update_gaplist(cut_handle, transport_info->gap_list, data_offset, data_offset+length); cutlass_transport_ack_send(cut_handle, conn, transport_info, clear_packet, now); if(retval > 0) { cutlass_transport_complete(cut_handle, conn, transport_info); } memcpy(&(transport_info->last_received), now, sizeof(struct timespec)); return(0); } /* * cutlass_transport_ack_send will figure out what bytes are still missing * from the transport buffer, and issue a request to the remote connection * for those bytes. * * returns 0 on success, -1 on failure. */ int cutlass_transport_ack_send(cutlass_t *cut_handle, conn_t *conn, struct channel *transport_info, uint8_t *clear_packet, struct timespec *now) { int i; int numgaps; uint8_t out_packet[CUT_MSG_LEN_MAX] = { 0 }; uint8_t *packet_loc; uint16_t tmp_numgaps; uint32_t gap_array[CUT_MAX_GAPS]; struct cutlass_packet_hdr header; if((NULL == cut_handle) || (NULL == conn) || (NULL == transport_info) || (NULL == now)) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_ack_send: Passed a NULL pointer\n"); return(-1); } if(CUTSTATE_ACTIVE != conn->conn_state) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_ack_send: Connection not active\n"); return(-1); } if((transport_info->channel_state != CUT_CHANNEL_STATE_TRANSFERRING) && (transport_info->channel_state != CUT_CHANNEL_STATE_FINISH)) { cutlass_sysmsg(cut_handle, CUT_INFO, "cutlass_transport_ack_send: " "Called on non-transiting state: %d\n", transport_info->channel_state); return(1); } numgaps = retrieve_gaplist(transport_info->gap_list, gap_array, CUT_MAX_GAPS); if(numgaps < 0) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_ack_send: retrieve_gaplist failed\n"); return(-1); } if(numgaps % 2) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_ack_send: odd number of gaps\n"); return(-1); } header.cut_type = CUT_TRANS_ACK; header.channel_id = transport_info->channel_id; header.length = CUT_TRANSPORT_ACK_HDR_LEN + (4 * numgaps); packet_loc = out_packet; tmp_numgaps = htons(numgaps); memcpy(packet_loc, &tmp_numgaps, 2); packet_loc += 2; for(i = 0; i < numgaps; i++) { memcpy(packet_loc, &(gap_array[i]), 4); packet_loc += 4; } if(cutlass_send_process(cut_handle, conn, &header, out_packet, header.length) != 0) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_ack_send: " "cutlass_send_process failed\n"); return(-1); } memcpy(&(transport_info->last_sent), now, sizeof(struct timespec)); return(0); } /* * cutlass_transport_ack_parse will receive a CUT_TRANS_ACK packet, * determine if we've dropped a packet somewhere, and send more data * using cutlass_transport_send(). * * Note that we _always_ send more data in response to a CUT_TRANS_ACK * packet with a nonzero number of gaps. Our "slow-start" algorithm * isn't _that_ slow! */ int cutlass_transport_ack_parse(cutlass_t *cut_handle, conn_t *conn, uint8_t *clear_packet, struct cutlass_packet_hdr *packet_info, struct timespec *now) { int old_num_gaps; struct channel *transport_info; struct cut_action_obj action_obj; uint8_t channel_id; uint16_t length; uint16_t tmp_gaps; if((NULL == cut_handle) || (NULL == conn) || (NULL == packet_info) || (NULL == clear_packet) || (NULL == now)) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_ack_parse: Passed a NULL pointer\n"); return(-1); } length = packet_info->length; transport_info = &(conn->out_channels[packet_info->channel_id]); switch(transport_info->channel_type) { case CHANNEL_FILE: case CHANNEL_MSG: case CHANNEL_REQUEST: case CHANNEL_DIR_INFO: case CHANNEL_REPLY: break; default: cutlass_sysmsg(cut_handle, CUT_INFO, "cutlass_transport_ack_parse: " "received packet on unallocated channel: %d\n", packet_info->channel_id); del_cut_channel(cut_handle, conn, CUT_IN, packet_info->channel_id); return(1); break; } if(transport_info->gap_list != NULL) { old_num_gaps = transport_info->gap_list->num_gaps; } else { old_num_gaps = 1; } memcpy(&tmp_gaps, clear_packet, 2); transport_info->gap_list->num_gaps = ntohs(tmp_gaps); cutlass_sysmsg(cut_handle, CUT_DEBUG, "cutlass_transport_ack_parse: " "requested %d gaps\n", transport_info->gap_list->num_gaps); if(transport_info->gap_list->num_gaps == 0) { if((transport_info->channel_state == CUT_CHANNEL_STATE_INIT) || (transport_info->channel_state == CUT_CHANNEL_STATE_WAITING) || (transport_info->channel_state == CUT_CHANNEL_STATE_TRANSFERRING)) { if((transport_info->channel_state == CUT_CHANNEL_STATE_INIT) || (transport_info->channel_state == CUT_CHANNEL_STATE_WAITING)) { switch(transport_info->channel_type) { case CHANNEL_FILE: memset(&action_obj, 0, sizeof(struct cut_action_obj)); cutlass_file_action_populate(cut_handle, conn, transport_info->channel_info, transport_info, &action_obj, CUT_FILE_REJECT, CUT_OUT); cutlass_action(cut_handle, conn, &action_obj); break; } } else { if(transport_info->channel_type == CHANNEL_FILE) { cutlass_file_send_complete(cut_handle, conn, transport_info); } } channel_id = transport_info->channel_id; cutlass_sysmsg(cut_handle, CUT_DEBUG, "cutlass_transport_ack_parse: " "No gaps requested, cleaning up\n"); del_cut_channel(cut_handle, conn, CUT_OUT, channel_id); return(1); } else { cutlass_sysmsg(cut_handle, CUT_DEBUG, "cutlass_transport_ack_parse: " "received extraneous ACK packet\n"); return(1); } } else { if((transport_info->channel_state == CUT_CHANNEL_STATE_INIT) || (transport_info->channel_state == CUT_CHANNEL_STATE_WAITING)|| (transport_info->channel_state == CUT_CHANNEL_STATE_TRANSFERRING)) { if((transport_info->channel_state == CUT_CHANNEL_STATE_INIT) || (transport_info->channel_state == CUT_CHANNEL_STATE_WAITING)) { switch(transport_info->channel_type) { case CHANNEL_FILE: memset(&action_obj, 0, sizeof(struct cut_action_obj)); cutlass_file_action_populate(cut_handle, conn, transport_info->channel_info, transport_info, &action_obj, CUT_FILE_ACCEPT, CUT_OUT); cutlass_action(cut_handle, conn, &action_obj); break; } } transport_info->channel_state = CUT_CHANNEL_STATE_TRANSFERRING; } else { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_ack_parse: " "Received non-0 ACK on finished connection\n"); return(1); } } if(length < (sizeof(uint32_t) * transport_info->gap_list->num_gaps) + CUT_TRANSPORT_ACK_HDR_LEN) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_ack_parse: " "Received bad packet length\n"); return(1); } if(transport_info->gap_list->num_gaps > old_num_gaps) { parse_gaplist(cut_handle, transport_info->gap_list, (clear_packet + CUT_TRANSPORT_ACK_HDR_LEN), transport_info->gap_list->num_gaps); conn->rate = (2*(conn->rate / 3)) + 1; transport_info->reset_loc = 1; cutlass_sysmsg(cut_handle, CUT_INFO, "cutlass_transport_ack_parse: " "Resetting rate to %d\n", conn->rate); } else { conn->rate++; } memcpy(&(transport_info->last_received), now, sizeof(struct timespec)); if(cutlass_transport_send(cut_handle, conn, transport_info, now) != 0) { cutlass_sysmsg(cut_handle, CUT_DEBUG, "cutlass_transport_ack_parse: New packet send failed\n"); return(1); } return(0); } /* * cutlass_transport_housekeeping is responsible for retransmitting * packets depending on what state we're in. Try to keep the connections * moving. If our connection has been good enough, it may send unsolicited * packets depending on the link congestion. * * returns 0 on success, 1 on nonfatal error, -1 on fatal error. */ int cutlass_transport_housekeeping(cutlass_t *cut_handle, conn_t *conn, struct channel *transport_info, int direction, struct timespec *now, struct timespec *sleep_time) { struct timespec timeout; int retval = 0; if((NULL == cut_handle) || (NULL == conn) || (NULL == transport_info) || (NULL == now) || (NULL == sleep_time)) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_housekeeping: Passed a NULL handle\n"); return(-1); } switch(transport_info->channel_state) { case CUT_CHANNEL_STATE_INIT: timeout.tv_sec = CUT_INIT_RETRANSMIT_INTERVAL; timeout.tv_nsec = 0; if(direction == CUT_IN) { /* Do nothing */ } else { if(cutlass_time_check(now, &(transport_info->last_sent), &timeout, sleep_time) == 1) { retval = cutlass_transport_init_send(cut_handle, conn, transport_info, now); } } break; case CUT_CHANNEL_STATE_WAITING: /* Do nothing, for now */ break; case CUT_CHANNEL_STATE_TRANSFERRING: if(direction == CUT_IN) { timeout.tv_sec = CUT_INIT_RETRANSMIT_INTERVAL; timeout.tv_nsec = 0; if(cutlass_time_check(now, &(transport_info->last_sent), &timeout, sleep_time) == 1) { retval = cutlass_transport_ack_send(cut_handle, conn, transport_info, NULL, now); } } else { timeout.tv_sec = 0; timeout.tv_nsec = 999999999 / conn->rate; if(cutlass_time_check(now, &(transport_info->last_sent), &timeout, sleep_time) == 1) { retval = cutlass_transport_send(cut_handle, conn, transport_info, now); } } break; case CUT_CHANNEL_STATE_FINISH: if(direction == CUT_IN) { timeout.tv_sec = CUT_CLOSE_RETRANSMIT_INTERVAL; timeout.tv_nsec = 0; if(cutlass_time_check(now, &(transport_info->last_sent), &timeout, sleep_time) == 1) { retval = cutlass_transport_ack_send(cut_handle, conn, transport_info, NULL, now); } } else { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_housekeeping: " "in FINISH state on outbound channel " "(should not happen)\n"); } break; default: cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_housekeeping: Unknown state\n"); return(1); } return(retval); } /* * cutlass_transport_complete is called to clean up all data structures, * and to reset the transport's channel, marking it as unused. Returns * 0 on success, -1 on fatal failure, 1 on nonfatal failure. */ int cutlass_transport_complete(cutlass_t *cut_handle, conn_t *conn, struct channel *transport_info) { uint8_t hash[CUT_CSUM_LEN]; struct file_transit_info *file_info; struct msg_transit_info *msg_info; struct cut_action_obj action_obj; if((NULL == cut_handle) || (NULL == conn) || (NULL == transport_info)) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_complete: Passed a NULL pointer\n"); return(-1); } if(transport_info->channel_state == CUT_CHANNEL_STATE_TRANSFERRING) { memset(&action_obj, 0, sizeof(struct cut_action_obj)); switch(transport_info->channel_type) { case CHANNEL_MSG: cutlass_action_populate(cut_handle, conn, &action_obj); msg_info = transport_info->channel_info; if(cut_handle->action_handlers[CUT_MSG_RECV] != NULL) { if(transport_info->data_size > (CUT_MSG_LEN_MAX - 1)) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_complete: oversized packet\n"); return(1); } action_obj.action_type = CUT_MSG_RECV; strncpy(action_obj.msg, transport_info->buffer, transport_info->data_size); cutlass_action(cut_handle, conn, &action_obj); } break; case CHANNEL_FILE: file_info = transport_info->channel_info; file_hash(file_info->fd, hash); if(memcmp(file_info->file_csum, hash, CUT_CSUM_LEN) != 0) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_complete: File checksum bad\n"); } close(file_info->fd); if(cut_handle->action_handlers[CUT_FILE_RECV_DONE] != NULL) { cutlass_file_action_populate(cut_handle, conn, file_info, transport_info, &action_obj, CUT_FILE_RECV_DONE, CUT_IN); cutlass_action(cut_handle, conn, &action_obj); } break; case CHANNEL_REQUEST: cutlass_request_process(cut_handle, conn, transport_info->buffer, transport_info->data_size); break; case CHANNEL_DIR_INFO: cutlass_dir_info_process(cut_handle, conn, transport_info->buffer, transport_info->data_size); break; case CHANNEL_REPLY: break; default: cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_transport_complete: unknown channel type\n"); return(1); break; } } transport_info->channel_state = CUT_CHANNEL_STATE_FINISH; return(0); } /* * cutlass_file_send_complete is called when a transmitting file has been * completed. This pops the action handler to let our side know that * we're done sending. (This should probably move to a different file). * * returns 0 on success, -1 on failure. */ int cutlass_file_send_complete(cutlass_t *cut_handle, conn_t *conn, struct channel *transport_info) { struct cut_action_obj action_obj; struct file_transit_info *file_info; if((NULL == cut_handle) || (NULL == conn) || (NULL == transport_info)) { cutlass_sysmsg(cut_handle, CUT_ERROR, "cutlass_file_send_complete: Passed a NULL pointer\n"); return(-1); } file_info = transport_info->channel_info; memset(&action_obj, 0, sizeof(struct cut_action_obj)); strncpy(action_obj.remote_nick, conn->conn_name, CUTLASS_NAME_LEN-1); memcpy(&(action_obj.remote_addr), &(conn->remote_addr), sizeof(struct sockaddr_in)); action_obj.external_channel_id = transport_info->channel_id + CUT_NUM_CHANNELS; action_obj.action_type = CUT_FILE_SEND_DONE; strncpy(action_obj.file_name, file_info->name, CUTLASS_FILENAME_LEN); action_obj.file_size = transport_info->data_size; memcpy(action_obj.file_csum, file_info->file_csum, CUT_CSUM_LEN); cutlass_action(cut_handle, conn, &action_obj); return(0); }