1/*
2 * Copyright 2005, Ingo Weinhold <bonefish@cs.tu-berlin.de>.
3 * All rights reserved. Distributed under the terms of the MIT License.
4 */
5
6#include <boot/net/RemoteDisk.h>
7
8#include <new>
9
10#include <endian.h>
11#include <stdio.h>
12
13#include <OS.h>
14#include <SupportDefs.h>
15
16#include <boot/net/UDP.h>
17
18
19static const bigtime_t kRequestTimeout = 100000LL;
20
21
22using std::nothrow;
23
24
25#if __BYTE_ORDER == __LITTLE_ENDIAN
26
27static inline
28uint64_t swap_uint64(uint64_t data)
29{
30	return ((data & 0xff) << 56)
31		| ((data & 0xff00) << 40)
32		| ((data & 0xff0000) << 24)
33		| ((data & 0xff000000) << 8)
34		| ((data >> 8) & 0xff000000)
35		| ((data >> 24) & 0xff0000)
36		| ((data >> 40) & 0xff00)
37		| ((data >> 56) & 0xff);
38}
39
40#define host_to_net64(data)	swap_uint64(data)
41#define net_to_host64(data)	swap_uint64(data)
42
43#endif
44
45#if __BYTE_ORDER == __BIG_ENDIAN
46#define host_to_net64(data)	(data)
47#define net_to_host64(data)	(data)
48#endif
49
50#undef htonll
51#undef ntohll
52#define htonll(data)	host_to_net64(data)
53#define ntohll(data)	net_to_host64(data)
54
55
56// constructor
57RemoteDisk::RemoteDisk()
58	: fServerAddress(INADDR_ANY),
59	  fServerPort(0),
60	  fImageSize(0),
61	  fRequestID(0),
62	  fSocket(NULL),
63	  fPacket(NULL)
64{
65}
66
67// destructor
68RemoteDisk::~RemoteDisk()
69{
70	delete fSocket;
71	delete fPacket;
72}
73
74// Init
75status_t
76RemoteDisk::Init(ip_addr_t serverAddress, uint16 serverPort, off_t imageSize)
77{
78	fServerAddress = serverAddress;
79	fServerPort = serverPort;
80	fImageSize = imageSize;
81
82	// create and bind socket
83	fSocket = new(nothrow) UDPSocket;
84	if (!fSocket)
85		return B_NO_MEMORY;
86
87	status_t error = fSocket->Bind(INADDR_ANY, 6666);
88	if (error != B_OK)
89		return error;
90
91	return B_OK;
92}
93
94// ReadAt
95ssize_t
96RemoteDisk::ReadAt(void */*cookie*/, off_t pos, void *_buffer,
97	size_t bufferSize)
98{
99	if (!fSocket)
100		return B_NO_INIT;
101
102	uint8 *buffer = (uint8*)_buffer;
103	if (!buffer || pos < 0)
104		return B_BAD_VALUE;
105
106	if (bufferSize == 0)
107		return 0;
108
109	// Check whether the current packet already contains the beginning of the
110	// data to read.
111	ssize_t bytesRead = _ReadFromPacket(pos, buffer, bufferSize);
112
113	// If there still remains something to be read, we need to get it from the
114	// server.
115	status_t error = B_OK;
116	while (bufferSize > 0) {
117		// prepare request
118		remote_disk_header request;
119		request.offset = htonll(pos);
120		uint32 toRead = min_c(bufferSize, REMOTE_DISK_BLOCK_SIZE);
121		request.size = htons(toRead);
122		request.command = REMOTE_DISK_READ_REQUEST;
123
124		// send request
125		UDPPacket *packet;
126		error = _SendRequest(&request, sizeof(request), REMOTE_DISK_READ_REPLY,
127			&packet);
128		if (error != B_OK)
129			break;
130
131		// check for errors
132		int16 packetSize = ntohs(((remote_disk_header*)packet->Data())->size);
133		if (packetSize < 0) {
134			if (packetSize == REMOTE_DISK_IO_ERROR)
135				error = B_IO_ERROR;
136			else if (packetSize == REMOTE_DISK_BAD_REQUEST)
137				error = B_BAD_VALUE;
138			break;
139		}
140
141		// make the reply packet the current packet
142		delete fPacket;
143		fPacket = packet;
144
145		// read from the packet
146		size_t packetBytesRead = _ReadFromPacket(pos, buffer, bufferSize);
147		if (packetBytesRead == 0)
148			break;
149		bytesRead += packetBytesRead;
150	}
151
152	// only return an error, when we were not able to read anything at all
153	return (bytesRead == 0 ? error : bytesRead);
154}
155
156// WriteAt
157ssize_t
158RemoteDisk::WriteAt(void */*cookie*/, off_t pos, const void *buffer,
159	size_t bufferSize)
160{
161	// Not needed in the boot loader.
162	return B_PERMISSION_DENIED;
163}
164
165// GetName
166status_t
167RemoteDisk::GetName(char *nameBuffer, size_t bufferSize) const
168{
169	if (!nameBuffer)
170		return B_BAD_VALUE;
171
172	snprintf(nameBuffer, bufferSize,
173		"RemoteDisk:%" B_PRIu32 ".%" B_PRIu32 ".%" B_PRIu32 ".%" B_PRIu32 ":%hd",
174		(fServerAddress >> 24) & 0xff, (fServerAddress >> 16) & 0xff,
175		(fServerAddress >> 8) & 0xff, fServerAddress & 0xff, fServerPort);
176
177	return B_OK;
178}
179
180// Size
181off_t
182RemoteDisk::Size() const
183{
184	return fImageSize;
185}
186
187ip_addr_t
188RemoteDisk::ServerIPAddress() const
189{
190	return fServerAddress;
191}
192
193uint16
194RemoteDisk::ServerPort() const
195{
196	return fServerPort;
197}
198
199// FindAnyRemoteDisk
200RemoteDisk *
201RemoteDisk::FindAnyRemoteDisk()
202{
203	// create a socket and bind it
204	UDPSocket socket;
205	status_t error = socket.Bind(INADDR_ANY, 6665);
206	if (error != B_OK) {
207		printf("RemoteDisk::GetAnyRemoteDisk(): Failed to bind socket.\n");
208		return NULL;
209	}
210
211	// prepare request
212	remote_disk_header request;
213	request.command = REMOTE_DISK_HELLO_REQUEST;
214
215	// send request
216	UDPPacket *packet;
217	error = _SendRequest(&socket, INADDR_BROADCAST, REMOTE_DISK_SERVER_PORT,
218		&request, sizeof(request), REMOTE_DISK_HELLO_REPLY, &packet);
219	if (error != B_OK) {
220		printf("RemoteDisk::GetAnyRemoteDisk(): Got no server reply.\n");
221		return NULL;
222	}
223	remote_disk_header *reply = (remote_disk_header*)packet->Data();
224
225	// create a RemoteDisk object
226	RemoteDisk *remoteDisk = new(nothrow) RemoteDisk;
227	if (remoteDisk) {
228		error = remoteDisk->Init(packet->SourceAddress(), ntohs(reply->port),
229			ntohll(reply->offset));
230		if (error != B_OK) {
231			delete remoteDisk;
232			remoteDisk = NULL;
233		}
234	}
235
236	delete packet;
237
238	return remoteDisk;
239}
240
241// _ReadFromPacket
242ssize_t
243RemoteDisk::_ReadFromPacket(off_t &pos, uint8 *&buffer, size_t &bufferSize)
244{
245	if (!fPacket)
246		return 0;
247
248	remote_disk_header *header = (remote_disk_header*)fPacket->Data();
249	uint64 packetOffset = ntohll(header->offset);
250	uint32 packetSize = ntohs(header->size);
251	if (packetOffset > (uint64)pos || packetOffset + packetSize <= (uint64)pos)
252		return 0;
253
254	// we do indeed have some bytes already
255	size_t toCopy = size_t(packetOffset + packetSize - (uint64)pos);
256	if (toCopy > bufferSize)
257		toCopy = bufferSize;
258	memcpy(buffer, header->data + (pos - packetOffset), toCopy);
259
260	pos += toCopy;
261	buffer += toCopy;
262	bufferSize -= toCopy;
263	return toCopy;
264}
265
266// _SendRequest
267status_t
268RemoteDisk::_SendRequest(UDPSocket *socket, ip_addr_t serverAddress,
269	uint16 serverPort, remote_disk_header *request, size_t size,
270	uint8 expectedReply, UDPPacket **_packet)
271{
272	request->port = htons(socket->Port());
273
274	// try sending the request 3 times at most
275	for (int i = 0; i < 3; i++) {
276		// send request
277		status_t error = socket->Send(serverAddress, serverPort, request, size);
278		if (error != B_OK)
279			return error;
280
281		// receive reply
282		bigtime_t timeout = system_time() + kRequestTimeout;
283		do {
284			UDPPacket *packet;
285			error = socket->Receive(&packet, timeout - system_time());
286			if (error == B_OK) {
287				// got something; check, if it is looks good
288				if (packet->DataSize() >= sizeof(remote_disk_header)) {
289					remote_disk_header *reply
290						= (remote_disk_header*)packet->Data();
291					if (reply->request_id == request->request_id
292						&& reply->command == expectedReply) {
293						*_packet = packet;
294						return B_OK;
295					}
296				}
297
298				// reply not OK
299				delete packet;
300			} else if (error != B_TIMED_OUT && error != B_WOULD_BLOCK)
301				return error;
302
303		} while (timeout > system_time());
304	}
305
306	// no reply
307	return B_ERROR;
308}
309
310// _SendRequest
311status_t
312RemoteDisk::_SendRequest(remote_disk_header *request, size_t size,
313	uint8 expectedReply, UDPPacket **packet)
314{
315	request->request_id = fRequestID++;
316	return _SendRequest(fSocket, fServerAddress, fServerPort, request, size,
317		expectedReply, packet);
318}
319