Welcome to mirror list, hosted at ThFree Co, Russian Federation.

Socket.cpp « DuetEthernet « DuetNG « src - github.com/Duet3D/RepRapFirmware.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: cb8ecc05e583cc393822187cede9538efb386a82 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
/*
 * Socket.cpp
 *
 *  Created on: 25 Dec 2016
 *      Author: David
 */

#include "Socket.h"

#include "NetworkTransaction.h"
#include "NetworkBuffer.h"
#include "Network.h"
#include "RepRap.h"
#include "Webserver.h"
#include "socketlib.h"

//***************************************************************************************************
// Socket class

Socket::Socket() : currentTransaction(nullptr), receivedData(nullptr), state(SocketState::inactive)
{
}

// Initialise a TCP socket
void Socket::Init(SocketNumber skt, Port serverPort)
{
	socketNum = skt;
	localPort = serverPort;
	ReInit();
}

void Socket::ReInit()
{
	// Discard any received data
	while (receivedData != nullptr)
	{
		receivedData = receivedData->Release();
	}
	ReleaseTransaction();

	persistConnection = true;
	isTerminated = false;
	isSending = false;
	needTransaction = false;
	state = SocketState::inactive;

	// Re-initialise the socket on the W5500
	socket(socketNum, Sn_MR_TCP, localPort, 0x00);
}

// Close a connection when the last packet has been sent
void Socket::Close()
{
	disconnectNoWait(socketNum);
}

// Release the current transaction
void Socket::ReleaseTransaction()
{
	if (currentTransaction != nullptr)
	{
		currentTransaction->Release();
		currentTransaction = nullptr;
	}
}

// Terminate a connection immediately
void Socket::Terminate()
{
	disconnectNoWait(socketNum);
	isTerminated = true;
	state = SocketState::inactive;
	while (receivedData != nullptr)
	{
		receivedData = receivedData->Release();
	}
	ReleaseTransaction();
}

// Test whether we have a connection on this socket
bool Socket::IsConnected() const
{
	const uint8_t stat = getSn_SR(socketNum);
	return stat == SOCK_ESTABLISHED || stat == SOCK_CLOSE_WAIT;
}

// Return true if there is or may soon be more data to read
bool Socket::HasMoreDataToRead() const
{
	return (receivedData != nullptr && receivedData->TotalRemaining() != 0)		// already have more data
		|| getSn_SR(socketNum) == SOCK_ESTABLISHED;								// still fully connected, so we may receive more
}

bool Socket::CanWrite() const
{
	const uint8_t stat = getSn_SR(socketNum);
	return stat == SOCK_ESTABLISHED || stat == SOCK_CLOSE_WAIT;
}

// Return true if we are in the sending phase
bool Socket::IsSending() const
{
	return currentTransaction != nullptr && currentTransaction->IsSending();
}

// Read 1 character from the receive buffers, returning true if successful
bool Socket::ReadChar(char& c)
{
	while (receivedData != nullptr && receivedData->IsEmpty())
	{
		receivedData = receivedData->Release();		// discard empty buffer at head of chain
	}

	if (receivedData == nullptr)
	{
		c = 0;
		return false;
	}

	bool ret = receivedData->ReadChar(c);
	if (receivedData->IsEmpty())
	{
		receivedData = receivedData->Release();
	}
	return ret;
}

// Return a pointer to data in a buffer and a length available, and mark the data as taken
bool Socket::ReadBuffer(const char *&buffer, size_t &len)
{
	while (receivedData != nullptr && receivedData->IsEmpty())
	{
		receivedData = receivedData->Release();		// discard empty buffer at head of chain
	}

	if (receivedData == nullptr)
	{
		return false;
	}

	len = NetworkBuffer::bufferSize;				// initial value passed to TakeData is the maximum amount we will take
	buffer = reinterpret_cast<const char*>(receivedData->TakeData(len));
//	debugPrintf("Taking %d bytes\n", len);
	return true;
}

// Poll a socket to see if it needs to be serviced
void Socket::Poll(bool full)
{
	// The mechanism used by class OutputBuffer and now by NetworkBuffer of marking data taken as soon as we return a pointer to it
	// is DANGEROUS and will have to be rewritten for RTOS. We need to recycle empty buffers, otherwise multiple file uploads get stalled.
	// However, we MUST NOT do this until the data had DEFINITELY been finished with. Temporarily use this conditional to avoid a bug
	// with data corruption when this is not the case.
	if (full)
	{
		// Recycle any receive buffers that are now empty
		while (receivedData != nullptr && receivedData->IsEmpty())
		{
			receivedData = receivedData->Release();		// discard empty buffer at head of chain
		}
	}

	switch(getSn_SR(socketNum))
	{
	case SOCK_INIT:
		// Socket has been initialised but is not listening yet
		if (localPort != 0)			// localPort for the FTP data socket is 0 until we have decided what port number to use
		{
			ExecCommand(socketNum, Sn_CR_LISTEN);
			state = SocketState::listening;
		}
		break;

	case SOCK_LISTEN:				// Socket is listening but no client has connected to it yet
		break;

	case SOCK_ESTABLISHED:			// A client is connected to this socket
		if (getSn_IR(socketNum) & Sn_IR_CON)
		{
			// New connection, so retrieve the sending IP address and port, and clear the interrupt
			getSn_DIPR(socketNum, reinterpret_cast<uint8_t*>(&remoteIPAddress));
			remotePort = getSn_DPORT(socketNum);
			setSn_IR(socketNum, Sn_IR_CON);
		}

		if (state == SocketState::listening)		// if it is a new connection
		{
			needTransaction = false;
			if (socketNum == FtpSocketNumber || socketNum == TelnetSocketNumber)
			{
				// FTP and Telnet protocols need a connection reply, so tell the Webserver module about the new connection
				if (currentTransaction == nullptr)
				{
					currentTransaction = NetworkTransaction::Allocate();
					if (currentTransaction != nullptr)
					{
						currentTransaction->Set(this, TransactionStatus::connected);
					}
				}
				else
				{
					// This should not happen
//					debugPrintf("ERROR:currentTransation should be null but isn't\n");
				}
			}
			state = SocketState::connected;
		}

		{
			// See if the socket has received any data
			const uint16_t len = getSn_RX_RSR(socketNum);
			if (len != 0)
			{
//				debugPrintf("%u available\n", len);
				// There is data available, so allocate a buffer
				//TODO: if there is already a buffer and it is in an appropriate state (i.e. receiving) and it has enough room, we could just append the data
				NetworkBuffer * const buf = NetworkBuffer::Allocate();
				if (buf != nullptr)
				{
					wiz_recv_data(socketNum, buf->Data(), len);
					ExecCommand(socketNum, Sn_CR_RECV);
					buf->dataLength = (size_t)len;
					buf->readPointer = 0;
					NetworkBuffer::AppendToList(&receivedData, buf);
				}
//				else debugPrintf("no buffer\n");
			}
		}

		if (currentTransaction == nullptr && (receivedData != nullptr || needTransaction))
		{
			currentTransaction = NetworkTransaction::Allocate();
			if (currentTransaction != nullptr)
			{
				currentTransaction->Set(this, (needTransaction) ? TransactionStatus::acquired : TransactionStatus::receiving);
				needTransaction = false;
			}
		}

		// See if we can send any data.
		// Currently we don't send if we are being called from hsmci because we don't want to risk releasing a buffer that we may be reading data into.
		// We could use a buffer locking mechanism instead. However, the speed of sending is not critical, so we don't do that yet.
		if (full && IsSending() && TrySendData())
		{
			const bool closeAfterSending = currentTransaction->CloseAfterSending();
			ReleaseTransaction();
			if (closeAfterSending)
			{
				ExecCommand(socketNum, Sn_CR_DISCON);
				state = SocketState::closing;
				DiscardReceivedData();
			}
		}
		break;

	case SOCK_CLOSE_WAIT:			// A client has asked to disconnect
#ifdef _HTTPSERVER_DEBUG_
		printf("> HTTPSocket[%d] : ClOSE_WAIT\r\n", socketNum);
#endif
		state = SocketState::clientDisconnecting;
		if (IsSending() && TrySendData())
		{
			ReleaseTransaction();				// finished sending
		}

		// Although there is a transaction status for a client disconnecting, the webserver module does nothing with those transactions.
		// So we don't bother to generate them here.
		if (!IsSending() && currentTransaction == nullptr)
		{
			if (HasMoreDataToRead())
			{
				// We have more received data, so make it available to the webserver to process
				currentTransaction = NetworkTransaction::Allocate();
				if (currentTransaction != nullptr)
				{
					currentTransaction->Set(this, TransactionStatus::receiving);
				}
			}
			else
			{
				ExecCommand(socketNum, Sn_CR_DISCON);
				state = SocketState::closing;
			}
		}
		break;

	case SOCK_CLOSED:
#ifdef _HTTPSERVER_DEBUG_
		printf("> HTTPSocket[%d] : CLOSED\r\n", s);
#endif
		reprap.GetWebserver()->ConnectionLost(this);						// the webserver needs this to be called for both graceful and disgraceful disconnects
		state = SocketState::inactive;

		if (socket(socketNum, Sn_MR_TCP, localPort, 0x00) == socketNum)		// Reinitialize the socket
		{
#ifdef _HTTPSERVER_DEBUG_
			printf("> HTTPSocket[%d] : OPEN\r\n", socketNum);
#endif
		}
		break;

	default:
		break;
	} // end of switch

#ifdef _USE_WATCHDOG_
	HTTPServer_WDT_Reset();
#endif
}

// Try to send data, returning true if all data has been sent and we ought to close the socket
// We have already checked that the socket is in the ESTABLISHED or CLOSE_WAIT state.
bool Socket::TrySendData()
{
	if (isSending)									// are we already sending?
	{
		const uint8_t tmp = getSn_IR(socketNum);
		if (tmp & Sn_IR_SENDOK)						// did the previous send complete?
		{
			setSn_IR(socketNum, Sn_IR_SENDOK);		// if yes
			isSending = false;
		}
		else if(tmp & Sn_IR_TIMEOUT)				// did it time out?
		{
			isSending = false;
			disconnectNoWait(socketNum);			// if so, close the socket
			return true;							// and release buffers etc.
		}
		else
		{
			//debugPrintf("Waiting for data to be sent\n");
			return false;							// last send is still in progress
		}
	}

	// Socket is free to send
	if (currentTransaction->GetStatus() == TransactionStatus::finished)
	{
		return true;
	}

	size_t freesize = (size_t)getSn_TX_FSR(socketNum);
	uint16_t ptr = getSn_TX_WR(socketNum);
	bool sent = false;
	while (freesize != 0)
	{
		size_t length = freesize;
		const uint8_t *data = currentTransaction->GetDataToSend(length);
		if (data == nullptr)
		{
			break;									// no more data or can't allocate a file buffer
		}
		//debugPrintf("rp=%04x tp=%04x\n", getSn_TX_RD(socketNum), getSn_TX_WR(socketNum));
		wiz_send_data_at(socketNum, data, length, ptr);
		//debugPrintf("Wrote %u bytes of %u free, %02x %02x %02x\n", length, freesize, data[0], data[1], data[2]);
		freesize -= length;
		ptr += (uint16_t)length;
		sent = true;
	}

	if (sent)
	{
		//debugPrintf("Sending data, rp=%04x tp=%04x\n", getSn_TX_RD(socketNum), getSn_TX_WR(socketNum));
		ExecCommand(socketNum, Sn_CR_SEND);
		isSending = true;
	}
	else if (currentTransaction->GetStatus() == TransactionStatus::finished)
	{
		return true;								// there was no more data to send
	}

	return false;
}

// Discard any received data for this transaction
void Socket::DiscardReceivedData()
{
	while (receivedData != nullptr)
	{
		receivedData = receivedData->Release();
	}
}

// The webserver calls this to tell the socket that it needs a transaction, e.g. for sending a Telnet or FTP response.
// An empty transaction will do.
// Return true if we can do it, false if the connection is closed or closing.
bool Socket::AcquireTransaction()
{
	if (currentTransaction != nullptr && currentTransaction->GetStatus() == TransactionStatus::acquired)
	{
		return true;
	}

	if (getSn_SR(socketNum) == SOCK_ESTABLISHED)
	{
		needTransaction = true;
		return true;
	}
	return false;
}

// End