gsplus/src/atbridge/atbridge.c

415 lines
12 KiB
C

/*
GSPLUS - Advanced Apple IIGS Emulator Environment
Based on the KEGS emulator written by Kent Dickey
See COPYRIGHT.txt for Copyright information
See LICENSE.txt for license (GPL v2)
*/
/** This module is the "heart" of the bridge and provides the connection between the ELAP and LLAP ports. **/
#include <stdbool.h>
#include "../defc.h"
#include <stddef.h>
#include <time.h>
#include "atbridge.h"
#include "port.h"
#include "elap.h"
#include "llap.h"
#include "aarp.h"
#ifdef WIN32
#include <winsock.h>
#elif __linux__
#include <netinet/in.h>
#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);
}
}