mirror of
https://github.com/digarok/gsplus.git
synced 2024-11-24 22:30:55 +00:00
426 lines
13 KiB
Plaintext
426 lines
13 KiB
Plaintext
|
/*
|
||
|
GSport - an Apple //gs Emulator
|
||
|
Copyright (C) 2013-2014 by Peter Neubauer
|
||
|
|
||
|
This program is free software; you can redistribute it and/or modify it
|
||
|
under the terms of the GNU General Public License as published by the
|
||
|
Free Software Foundation; either version 2 of the License, or (at your
|
||
|
option) any later version.
|
||
|
|
||
|
This program is distributed in the hope that it will be useful, but
|
||
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||
|
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||
|
for more details.
|
||
|
|
||
|
You should have received a copy of the GNU General Public License along
|
||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||
|
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||
|
*/
|
||
|
|
||
|
/** 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);
|
||
|
}
|
||
|
}
|