// network.cc 
//	Routines to simulate a network interface, using UNIX sockets
//	to deliver packets between multiple invocations of nachos.
//
//  DO NOT CHANGE -- part of the machine emulation
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation 
// of liability and disclaimer of warranty provisions.

#include "copyright.h"
#include "system.h"

// Dummy functions because C++ can't call member functions indirectly 
static void NetworkReadPoll(int arg)
{ Network *net = (Network *)arg; net->CheckPktAvail(); }
static void NetworkSendDone(int arg)
{ Network *net = (Network *)arg; net->SendDone(); }

// Initialize the network emulation
//   addr is used to generate the socket name
//   reliability says whether we drop packets to emulate unreliable links
//   readAvail, writeDone, callArg -- analogous to console
Network::Network(NetworkAddress addr, double reliability,
	VoidFunctionPtr readAvail, VoidFunctionPtr writeDone, int callArg)
{
    ident = addr;
    if (reliability < 0) chanceToWork = 0;
    else if (reliability > 1) chanceToWork = 1;
    else chanceToWork = reliability;

    // set up the stuff to emulate asynchronous interrupts
    writeHandler = writeDone;
    readHandler = readAvail;
    handlerArg = callArg;
    sendBusy = FALSE;
    inHdr.length = 0;
    
    sock = OpenSocket();
    sprintf(sockName, "SOCKET_%d", (int)addr);
    AssignNameToSocket(sockName, sock);		 // Bind socket to a filename 
						 // in the current directory.

    // start polling for incoming packets
    interrupt->Schedule(NetworkReadPoll, (int)this, NetworkTime, NetworkRecvInt);
}

Network::~Network()
{
    CloseSocket(sock);
    DeAssignNameToSocket(sockName);
}

// if a packet is already buffered, we simply delay reading 
// the incoming packet.  In real life, the incoming 
// packet might be dropped if we can't read it in time.
void
Network::CheckPktAvail()
{
    // schedule the next time to poll for a packet
    interrupt->Schedule(NetworkReadPoll, (int)this, NetworkTime, NetworkRecvInt);

    if (inHdr.length != 0) 	// do nothing if packet is already buffered
	return;		
    if (!PollSocket(sock)) 	// do nothing if no packet to be read
	return;

    // otherwise, read packet in
    char *buffer = new char[MaxWireSize];
    ReadFromSocket(sock, buffer, MaxWireSize);

    // divide packet into header and data
    inHdr = *(PacketHeader *)buffer;
    ASSERT((inHdr.to == ident) && (inHdr.length <= MaxPacketSize));
    bcopy(buffer + sizeof(PacketHeader), inbox, inHdr.length);
    delete []buffer ;

    DEBUG('n', "Network received packet from %d, length %d...\n",
	  				(int) inHdr.from, inHdr.length);
    stats->numPacketsRecvd++;

    // tell post office that the packet has arrived
    (*readHandler)(handlerArg);	
}

// notify user that another packet can be sent
void
Network::SendDone()
{
    sendBusy = FALSE;
    stats->numPacketsSent++;
    (*writeHandler)(handlerArg);
}

// send a packet by concatenating hdr and data, and schedule
// an interrupt to tell the user when the next packet can be sent 
//
// Note we always pad out a packet to MaxWireSize before putting it into
// the socket, because it's simpler at the receive end.
void
Network::Send(PacketHeader hdr, char* data)
{
    char toName[32];

    sprintf(toName, "SOCKET_%d", (int)hdr.to);
    
    ASSERT((sendBusy == FALSE) && (hdr.length > 0) 
		&& (hdr.length <= MaxPacketSize) && (hdr.from == ident));
    DEBUG('n', "Sending to addr %d, %d bytes... ", hdr.to, hdr.length);

    interrupt->Schedule(NetworkSendDone, (int)this, NetworkTime, NetworkSendInt);

    if (Random() % 100 >= chanceToWork * 100) { // emulate a lost packet
	DEBUG('n', "oops, lost it!\n");
	return;
    }

    // concatenate hdr and data into a single buffer, and send it out
    char *buffer = new char[MaxWireSize];
    *(PacketHeader *)buffer = hdr;
    bcopy(data, buffer + sizeof(PacketHeader), hdr.length);
    SendToSocket(sock, buffer, MaxWireSize, toName);
    delete []buffer;
}

// read a packet, if one is buffered
PacketHeader
Network::Receive(char* data)
{
    PacketHeader hdr = inHdr;

    inHdr.length = 0;
    if (hdr.length != 0)
    	bcopy(inbox, data, hdr.length);
    return hdr;
}
