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