/* GSPLUS - Advanced Apple IIGS Emulator Environment Based on the KEGS emulator written by Kent Dickey See COPYRIGHT.txt for Copyright information See COPYING.txt for license (GPL v2) */ /** This module is the "heart" of the bridge and provides the connection between the ELAP and LLAP ports. **/ #include #include "../defc.h" #include #include #include "atbridge.h" #include "port.h" #include "elap.h" #include "llap.h" #include "aarp.h" #ifdef WIN32 #include #elif __linux__ #include #endif extern struct packet_port_t elap_port; static bool diagnostics = false; static bool sent_rtmp_request = false; static struct at_addr_t local_address = { 0, 0 }; static const at_network_t NET_STARTUP_LOW = 0xFF00; static const at_network_t NET_STARTUP_HIGH = 0xFFFE; static const at_node_t NODE_STARTUP_LOW = 0x01; static const at_node_t NODE_STARTUP_HIGH = 0xFE; static void send_rtmp_request(); bool atbridge_init() { // If the GS reboots, we may try to reinitialize the bridge. If this is the case, keep the old address and AMT. if (local_address.network == 0) { // Obtain a provisional node address and startup range network. // // This isn't correct for an extended network (like ELAP) but works adequately on small networks. // The bridge should follow the complicated process on page 4-9 to obtain the network and node number. srand((unsigned int)time(0)); local_address.network = (at_network_t)((double)rand()/RAND_MAX * (NET_STARTUP_HIGH - NET_STARTUP_LOW) + NET_STARTUP_LOW); local_address.node = (at_node_t)((double)rand()/RAND_MAX + (NODE_STARTUP_HIGH - NODE_STARTUP_LOW) + 0x01); aarp_init(); llap_init(); if (!elap_init()) { atbridge_shutdown(); return false; } } return true; } void atbridge_shutdown() { llap_shutdown(); elap_shutdown(); aarp_shutdown(); } void atbridge_set_diagnostics(bool enabled) { diagnostics = enabled; } bool atbridge_get_diagnostics() { return diagnostics; } void atbridge_printf(const char *fmt, ...) { if (atbridge_get_diagnostics()) { va_list args; va_start(args, fmt); vprintf(fmt, args); va_end(args); } } const struct at_addr_t* atbridge_get_addr() { return &local_address; } const at_network_t atbridge_get_net() { return local_address.network; } const at_node_t atbridge_get_node() { return local_address.node; } void atbridge_set_net(at_network_t net) { local_address.network = net; } void atbridge_set_node(at_node_t node) { local_address.node = node; } bool atbridge_address_used(const struct at_addr_t* addr) { if (!sent_rtmp_request) send_rtmp_request(); return aarp_address_used(addr); } /* Calculate a DDP checksum, per Apple's documented algorithm in 4-17 of "Inside AppleTalk". */ static word16 get_checksum(size_t size, byte data[]) { word16 cksum = 0; for (unsigned int i = 0; i < size; i++) { cksum += data[i]; cksum = (cksum << 1) | ((cksum & 0x8000) >> 15); // roll left } if (cksum == 0) cksum = 0xffff; return cksum; } static void calculate_checksum(struct packet_t* packet) { if (packet && packet->data && (packet->size >= sizeof(struct DDP_LONG)) && (packet->type == LAP_DDP_LONG)) { struct DDP_LONG* header = (struct DDP_LONG*)(packet->data); header->checksum = htons(get_checksum( packet->size - offsetof(struct DDP_LONG, dest_net), (byte*)&header->dest_net)); } } /* Convert a long-form DDP header to a short-form header. This function only converts the headers. */ static word16 convert_ddp_header_to_short(const struct DDP_LONG* in, struct DDP_SHORT* out) { word16 size; if (!in || !out) return 0; size = ((in->length[0] & 0x3) << 8) + (in->length[1]) - (sizeof(struct DDP_LONG) - sizeof(struct DDP_SHORT)); out->length[0] = (size >> 8) & 0x03; out->length[1] = size & 0xff; out->dest_socket = in->dest_socket; out->source_socket = in->source_socket; out->type = in->type; return size; } /* Convert a short-form DDP header to a long-form header. ELAP requires long-form, but LLAP often uses short-form. */ /* This function only converts the headers. */ static word16 convert_ddp_header_to_long(const struct at_addr_t dest, const struct at_addr_t source, const struct DDP_SHORT* in, struct DDP_LONG* out) { word16 size; if (!in || !out) return 0; size = ((in->length[0] & 0x3) << 8) + (in->length[1]) + (sizeof(struct DDP_LONG) - sizeof(struct DDP_SHORT)); out->length[0] = (size >> 8) & 0x03; out->length[1] = size & 0xff; out->checksum = 0x0000; /* 0x0000 == no checksum calculated, reference 4-17 */ if (dest.network) out->dest_net = dest.network; else out->dest_net = atbridge_get_net(); out->dest_net = (at_network_t)htons(out->dest_net); if (source.network) out->source_net = source.network; else out->source_net = atbridge_get_net(); out->source_net = (at_network_t)htons(out->source_net); out->dest_node = dest.node; out->source_node = source.node; out->dest_socket = in->dest_socket; out->source_socket = in->source_socket; out->type = in->type; return size; } /* Convert a short-form DDP packet to a long-form packet. */ /* This function converts an entire packet, not just the header. */ static void convert_ddp_packet_to_long(struct packet_t* packet) { if (packet && (packet->type == LAP_DDP_SHORT) && packet->data && (packet->size >= sizeof(struct DDP_SHORT))) { struct DDP_SHORT* header_short = (struct DDP_SHORT*)packet->data; const size_t payload_size = packet->size - sizeof(struct DDP_SHORT); byte* data = (byte*)malloc(payload_size + sizeof(struct DDP_LONG)); struct DDP_LONG* header_long = (struct DDP_LONG*)data; const word16 size = convert_ddp_header_to_long(packet->dest, packet->source, header_short, header_long); packet->dest.network = ntohs(header_long->dest_net); packet->source.network = ntohs(header_long->source_net); memcpy(data + sizeof(struct DDP_LONG), packet->data + sizeof(struct DDP_SHORT), payload_size); packet->type = LAP_DDP_LONG; packet->size = size; // Replace the original short-form packet data. free(packet->data); packet->data = data; calculate_checksum(packet); } } /* Convert a long-form DDP packet to short-form. */ static void convert_ddp_packet_to_short(struct packet_t* packet) { if (packet && (packet->type == LAP_DDP_LONG) && packet->data) { struct DDP_LONG* header_long = (struct DDP_LONG*)packet->data; const size_t payload_size = packet->size - sizeof(struct DDP_LONG); byte* data = (byte*)malloc(payload_size + sizeof(struct DDP_SHORT)); struct DDP_SHORT* header_short = (struct DDP_SHORT*)data; const word16 size = convert_ddp_header_to_short(header_long, header_short); memcpy(data + sizeof(struct DDP_SHORT), packet->data + sizeof(struct DDP_LONG), payload_size); packet->type = LAP_DDP_SHORT; packet->size = size; free(packet->data); packet->data = data; } } /*static void convert_rtmp_to_extended(struct packet_t* packet) { if (packet && (packet->type == LAP_DDP_SHORT) && packet->data) { struct DDP_SHORT* header_short = (struct DDP_SHORT*)packet->data; if (header_short->type != DDP_TYPE_RTMP || header_short->dest_socket != DDP_SOCKET_RTMP) return; struct rtmp_nonextended_data_t* in = (struct rtmp_nonextended_data_t*)(packet->data + sizeof(struct DDP_SHORT)); // Construct a new long-form DDP packet header. size_t size = sizeof(struct DDP_LONG) + sizeof(struct rtmp_extended_data_t); byte* data = (byte*)malloc(size); struct DDP_LONG* header_long = (struct DDP_LONG*)data; convert_ddp_header_to_long(packet->dest, packet->source, header_short, header_long); struct rtmp_extended_data_t* out = (struct rtmp_extended_data_t*)(data + sizeof(struct DDP_LONG)); out->net = in->net; out->id_length = in->id_length; out->node = in->node; // Copy the routing tuples. struct rtmp_nonextended_tuple_t* in_tuple = (struct rtmp_nonextended_tuple_t*)(packet->data + sizeof(struct DDP_SHORT) + sizeof(struct rtmp_nonextended_data_t)); struct rtmp_extended_tuple_t* out_tuple = (struct rtmp_extended_tuple_t*)(data + size); while ((byte*)in_tuple < (packet->data + packet->size)) { size += sizeof(struct rtmp_extended_tuple_t); realloc(data, size); out_tuple->range_start = in_tuple->net; out_tuple->distance = in_tuple->distance | 0x80; out_tuple->range_end = in_tuple->net; out_tuple->delimiter = RTMP_TUPLE_DELIMITER; in_tuple++; } free(packet->data); packet->data = data; packet->size = size; packet->type = LAP_DDP_LONG; } }*/ static void convert_rtmp_to_nonextended(struct packet_t* packet) { if (packet && (packet->type == LAP_DDP_LONG) && packet->data) { struct DDP_LONG* header_long = (struct DDP_LONG*)packet->data; if (header_long->type != DDP_TYPE_RTMP || header_long->dest_socket != DDP_SOCKET_RTMP) return; struct rtmp_extended_data_t* in = (struct rtmp_extended_data_t*)(packet->data + sizeof(struct DDP_LONG)); size_t size = sizeof(struct DDP_SHORT) + sizeof(struct rtmp_nonextended_response_t); byte* data = (byte*)malloc(size); struct DDP_SHORT* header_short = (struct DDP_SHORT*)data; convert_ddp_header_to_short(header_long, header_short); header_short->length[0] = (size >> 8) & 0x03; header_short->length[1] = size & 0xff; struct rtmp_nonextended_response_t* out = (struct rtmp_nonextended_response_t*)(data + sizeof(struct DDP_SHORT)); out->net = in->net; out->id_length = in->id_length; out->node = in->node; /*rtmp_extended_tuple_t* in_tuple = (rtmp_extended_tuple_t*)(packet->data + sizeof(DDP_LONG) + sizeof(rtmp_extended_data_t)); rtmp_nonextended_tuple_t* out_tuple = (rtmp_nonextended_tuple_t*)(data + size); while ((byte*)in_tuple < (packet->data + packet->size)) { size += sizeof(rtmp_nonextended_tuple_t); realloc(data, size); out_tuple->net = in_tuple->range_start; out_tuple->distance = in_tuple->distance & 0x7f; in_tuple++; }*/ free(packet->data); packet->data = data; packet->size = size; packet->type = LAP_DDP_SHORT; } } /* Learn our network number from RTMP packets. */ /* "Inside AppleTalk", section 4-8, describes this approach for non-extended networks. Technically, we probably should be doing the more complicated extended network approach (also on 4-8), but the easy approach using RTMP seems adequate for now. */ static void glean_net_from_rtmp(struct packet_t* packet) { if (packet && (packet->type == LAP_DDP_LONG) && packet->data) { struct DDP_LONG* header_long = (struct DDP_LONG*)packet->data; if (header_long->type != DDP_TYPE_RTMP || header_long->dest_socket != DDP_SOCKET_RTMP) return; struct rtmp_extended_data_t* in = (struct rtmp_extended_data_t*)(packet->data + sizeof(struct DDP_LONG)); atbridge_set_net(ntohs(in->net)); } } static void send_rtmp_request() { struct packet_t* packet = (struct packet_t*)malloc(sizeof(struct packet_t)); packet->type = LAP_DDP_LONG; packet->dest.network = atbridge_get_net(); packet->dest.node = 255; packet->source.network = atbridge_get_net(); packet->source.node = atbridge_get_node(); packet->next = 0; packet->size = sizeof(struct DDP_LONG) + sizeof(struct rtmp_request_t); packet->data = (byte*)malloc(packet->size); struct DDP_LONG* header = (struct DDP_LONG*)packet->data; header->type = DDP_TYPE_RTMP_REQUEST; header->source_net = htons(packet->source.network); header->source_node = packet->source.node; header->source_socket = DDP_SOCKET_RTMP; header->dest_net = htons(packet->dest.network); header->dest_node = packet->dest.node; header->dest_socket = DDP_SOCKET_RTMP; header->length[0] = (packet->size >> 8) & 0x03; header->length[1] = packet->size & 0xff; struct rtmp_request_t* request = (struct rtmp_request_t*)(packet->data + sizeof(struct DDP_LONG)); request->function = RTMP_FUNCTION_REQUEST; calculate_checksum(packet); elap_enqueue_out(packet); sent_rtmp_request = true; } void atbridge_process() { elap_process(); //llap_process(); struct packet_t* packet = elap_dequeue_in(); if (packet) { glean_net_from_rtmp(packet); convert_rtmp_to_nonextended(packet); // The GS should understand long-form DDP, but converting to short-form ought to slightly improve performance (fewer bytes for the GS to process). convert_ddp_packet_to_short(packet); llap_enqueue_out(packet); } packet = llap_dequeue_in(); if (packet) { // ELAP does not support short-form DDP, so convert such packets to long-form. convert_ddp_packet_to_long(packet); elap_enqueue_out(packet); } }