* Copyright 2005, Ingo Weinhold <bonefish@cs.tu-berlin.de>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include <boot/net/RemoteDisk.h>
#include <new>
#include <endian.h>
#include <stdio.h>
#include <OS.h>
#include <SupportDefs.h>
#include <boot/net/UDP.h>
static const bigtime_t kRequestTimeout = 100000LL;
using std::nothrow;
#if __BYTE_ORDER == __LITTLE_ENDIAN
static inline
uint64_t swap_uint64(uint64_t data)
{
return ((data & 0xff) << 56)
| ((data & 0xff00) << 40)
| ((data & 0xff0000) << 24)
| ((data & 0xff000000) << 8)
| ((data >> 8) & 0xff000000)
| ((data >> 24) & 0xff0000)
| ((data >> 40) & 0xff00)
| ((data >> 56) & 0xff);
}
#define host_to_net64(data) swap_uint64(data)
#define net_to_host64(data) swap_uint64(data)
#endif
#if __BYTE_ORDER == __BIG_ENDIAN
#define host_to_net64(data) (data)
#define net_to_host64(data) (data)
#endif
#undef htonll
#undef ntohll
#define htonll(data) host_to_net64(data)
#define ntohll(data) net_to_host64(data)
RemoteDisk::RemoteDisk()
: fServerAddress(INADDR_ANY),
fServerPort(0),
fImageSize(0),
fRequestID(0),
fSocket(NULL),
fPacket(NULL)
{
}
RemoteDisk::~RemoteDisk()
{
delete fSocket;
delete fPacket;
}
status_t
RemoteDisk::Init(ip_addr_t serverAddress, uint16 serverPort, off_t imageSize)
{
fServerAddress = serverAddress;
fServerPort = serverPort;
fImageSize = imageSize;
fSocket = new(nothrow) UDPSocket;
if (!fSocket)
return B_NO_MEMORY;
status_t error = fSocket->Bind(INADDR_ANY, 6666);
if (error != B_OK)
return error;
return B_OK;
}
ssize_t
RemoteDisk::ReadAt(void *, off_t pos, void *_buffer,
size_t bufferSize)
{
if (!fSocket)
return B_NO_INIT;
uint8 *buffer = (uint8*)_buffer;
if (!buffer || pos < 0)
return B_BAD_VALUE;
if (bufferSize == 0)
return 0;
ssize_t bytesRead = _ReadFromPacket(pos, buffer, bufferSize);
status_t error = B_OK;
while (bufferSize > 0) {
remote_disk_header request;
request.offset = htonll(pos);
uint32 toRead = min_c(bufferSize, REMOTE_DISK_BLOCK_SIZE);
request.size = htons(toRead);
request.command = REMOTE_DISK_READ_REQUEST;
UDPPacket *packet;
error = _SendRequest(&request, sizeof(request), REMOTE_DISK_READ_REPLY,
&packet);
if (error != B_OK)
break;
int16 packetSize = ntohs(((remote_disk_header*)packet->Data())->size);
if (packetSize < 0) {
if (packetSize == REMOTE_DISK_IO_ERROR)
error = B_IO_ERROR;
else if (packetSize == REMOTE_DISK_BAD_REQUEST)
error = B_BAD_VALUE;
break;
}
delete fPacket;
fPacket = packet;
size_t packetBytesRead = _ReadFromPacket(pos, buffer, bufferSize);
if (packetBytesRead == 0)
break;
bytesRead += packetBytesRead;
}
return (bytesRead == 0 ? error : bytesRead);
}
ssize_t
RemoteDisk::WriteAt(void *, off_t pos, const void *buffer,
size_t bufferSize)
{
return B_PERMISSION_DENIED;
}
status_t
RemoteDisk::GetName(char *nameBuffer, size_t bufferSize) const
{
if (!nameBuffer)
return B_BAD_VALUE;
snprintf(nameBuffer, bufferSize,
"RemoteDisk:%" B_PRIu32 ".%" B_PRIu32 ".%" B_PRIu32 ".%" B_PRIu32 ":%hd",
(fServerAddress >> 24) & 0xff, (fServerAddress >> 16) & 0xff,
(fServerAddress >> 8) & 0xff, fServerAddress & 0xff, fServerPort);
return B_OK;
}
off_t
RemoteDisk::Size() const
{
return fImageSize;
}
ip_addr_t
RemoteDisk::ServerIPAddress() const
{
return fServerAddress;
}
uint16
RemoteDisk::ServerPort() const
{
return fServerPort;
}
RemoteDisk *
RemoteDisk::FindAnyRemoteDisk()
{
UDPSocket socket;
status_t error = socket.Bind(INADDR_ANY, 6665);
if (error != B_OK) {
printf("RemoteDisk::GetAnyRemoteDisk(): Failed to bind socket.\n");
return NULL;
}
remote_disk_header request;
request.command = REMOTE_DISK_HELLO_REQUEST;
UDPPacket *packet;
error = _SendRequest(&socket, INADDR_BROADCAST, REMOTE_DISK_SERVER_PORT,
&request, sizeof(request), REMOTE_DISK_HELLO_REPLY, &packet);
if (error != B_OK) {
printf("RemoteDisk::GetAnyRemoteDisk(): Got no server reply.\n");
return NULL;
}
remote_disk_header *reply = (remote_disk_header*)packet->Data();
RemoteDisk *remoteDisk = new(nothrow) RemoteDisk;
if (remoteDisk) {
error = remoteDisk->Init(packet->SourceAddress(), ntohs(reply->port),
ntohll(reply->offset));
if (error != B_OK) {
delete remoteDisk;
remoteDisk = NULL;
}
}
delete packet;
return remoteDisk;
}
ssize_t
RemoteDisk::_ReadFromPacket(off_t &pos, uint8 *&buffer, size_t &bufferSize)
{
if (!fPacket)
return 0;
remote_disk_header *header = (remote_disk_header*)fPacket->Data();
uint64 packetOffset = ntohll(header->offset);
uint32 packetSize = ntohs(header->size);
if (packetOffset > (uint64)pos || packetOffset + packetSize <= (uint64)pos)
return 0;
size_t toCopy = size_t(packetOffset + packetSize - (uint64)pos);
if (toCopy > bufferSize)
toCopy = bufferSize;
memcpy(buffer, header->data + (pos - packetOffset), toCopy);
pos += toCopy;
buffer += toCopy;
bufferSize -= toCopy;
return toCopy;
}
status_t
RemoteDisk::_SendRequest(UDPSocket *socket, ip_addr_t serverAddress,
uint16 serverPort, remote_disk_header *request, size_t size,
uint8 expectedReply, UDPPacket **_packet)
{
request->port = htons(socket->Port());
for (int i = 0; i < 3; i++) {
status_t error = socket->Send(serverAddress, serverPort, request, size);
if (error != B_OK)
return error;
bigtime_t timeout = system_time() + kRequestTimeout;
do {
UDPPacket *packet;
error = socket->Receive(&packet, timeout - system_time());
if (error == B_OK) {
if (packet->DataSize() >= sizeof(remote_disk_header)) {
remote_disk_header *reply
= (remote_disk_header*)packet->Data();
if (reply->request_id == request->request_id
&& reply->command == expectedReply) {
*_packet = packet;
return B_OK;
}
}
delete packet;
} else if (error != B_TIMED_OUT && error != B_WOULD_BLOCK)
return error;
} while (timeout > system_time());
}
return B_ERROR;
}
status_t
RemoteDisk::_SendRequest(remote_disk_header *request, size_t size,
uint8 expectedReply, UDPPacket **packet)
{
request->request_id = fRequestID++;
return _SendRequest(fSocket, fServerAddress, fServerPort, request, size,
expectedReply, packet);
}