1/*
2 * Copyright 2010, Andreas Färber <andreas.faerber@web.de>
3 * All rights reserved. Distributed under the terms of the MIT License.
4 */
5
6
7#include <boot/net/iSCSITarget.h>
8
9#include <stdio.h>
10#include <string.h>
11#include <ctype.h>
12
13#include <KernelExport.h>
14
15#include <boot/net/ChainBuffer.h>
16#include <boot/net/TCP.h>
17#include <iscsi_cmds.h>
18#include <scsi_cmds.h>
19
20#include "real_time_clock.h"
21
22
23//#define TRACE_ISCSI
24
25#ifndef TRACE_ISCSI
26#define TRACE(fmt, ...) ;
27#else
28#define TRACE(fmt, ...) printf("iSCSI: %s(): " fmt, __func__, ## __VA_ARGS__)
29#endif
30#define PANIC(fmt, ...)  panic("iSCSI: %s(): " fmt, __func__, ## __VA_ARGS__)
31
32
33#define ISCSI_ALIGNMENT 4
34#define ISCSI_ALIGN(x) (((x) + ISCSI_ALIGNMENT - 1) & ~(ISCSI_ALIGNMENT - 1))
35#define ISCSI_PADDING(x) ((((x) % ISCSI_ALIGNMENT) == 0) ? 0 \
36	: (ISCSI_ALIGNMENT - ((x) % ISCSI_ALIGNMENT)))
37
38// derived from the schedulers
39static int
40_rand(void)
41{
42	static int next = 0;
43	if (next == 0)
44		next = real_time_clock_usecs() / 1000;
45
46	next = next * 1103515245 + 12345;
47	return next;
48}
49
50
51bool
52iSCSITarget::DiscoverTargets(ip_addr_t address, uint16 port, NodeList* devicesList)
53{
54	iSCSISession* session = new(nothrow) iSCSISession();
55	if (session == NULL)
56		return false;
57	status_t error = session->Init(address, port);
58	if (error != B_OK) {
59		delete session;
60		return false;
61	}
62	const char* request = "SendTargets=All";
63	char* sendTargets = NULL;
64	size_t sendTargetsLength = 0;
65	error = session->Connection()->GetText(request, strlen(request) + 1,
66		&sendTargets, &sendTargetsLength);
67	session->Close();
68	delete session;
69	if (error != B_OK) {
70		return false;
71	}
72
73	bool addedDisk = false;
74	char* targetName = NULL;
75	bool seenAddress = false;
76	for (char* pair = sendTargets; pair < sendTargets + sendTargetsLength;
77			pair += strlen(pair) + 1) {
78		printf("%s\n", pair);
79		if (strncmp(pair, "TargetName=", strlen("TargetName=")) == 0) {
80			if (targetName != NULL && !seenAddress) {
81				if (_AddDevice(address, port, targetName, devicesList))
82					addedDisk = true;
83			}
84			seenAddress = false;
85			targetName = pair + strlen("TargetName=");
86		} else if (strncmp(pair, "TargetAddress=", strlen("TargetAddress="))
87				== 0) {
88			seenAddress = true;
89			char* targetAddress = pair + strlen("TargetAddress=");
90			char* comma = strrchr(targetAddress, ',');
91			int addressLength = comma - targetAddress;
92			targetAddress = strndup(targetAddress, addressLength);
93			uint16 targetPort = ISCSI_PORT;
94			char* colon = strrchr(targetAddress, ':');
95			if (colon != NULL) {
96				// colon could be part of an IPv6 address, e.g. [::1.2.3.4]
97				char* bracket = strrchr(targetAddress, ']');
98				if (bracket == NULL || bracket < colon) {
99					targetPort = strtol(colon + 1, NULL, 10);
100					colon[0] = '\0';
101				}
102			}
103			if (targetName != NULL && isdigit(targetAddress[0])) {
104				if (_AddDevice(ip_parse_address(targetAddress), targetPort,
105						targetName, devicesList))
106					addedDisk = true;
107			}
108			free(targetAddress);
109		}
110	}
111	if (targetName != NULL && !seenAddress) {
112		if (_AddDevice(address, port, targetName, devicesList))
113			addedDisk = true;
114	}
115	free(sendTargets);
116	return addedDisk;
117}
118
119
120bool
121iSCSITarget::_AddDevice(ip_addr_t address, uint16 port,
122	const char* targetName, NodeList* devicesList)
123{
124	TRACE("=> %s @ %08lx:%u\n", targetName, address, port);
125	iSCSITarget* disk = new(nothrow) iSCSITarget();
126	if (disk == NULL)
127		return false;
128	status_t error = disk->Init(address, port, targetName);
129	if (error != B_OK) {
130		delete disk;
131		return false;
132	}
133	TRACE("disk size = %llu\n", disk->Size());
134	devicesList->Add(disk);
135	return true;
136}
137
138
139iSCSITarget::iSCSITarget()
140	:
141	fSession(NULL),
142	fTargetName(NULL),
143	fTargetAlias(NULL)
144{
145}
146
147
148iSCSITarget::~iSCSITarget()
149{
150	free(fTargetName);
151	free(fTargetAlias);
152	delete fSession;
153}
154
155
156status_t
157iSCSITarget::Init(ip_addr_t address, uint16 port, const char* targetName)
158{
159	fTargetName = strdup(targetName);
160	if (fTargetName == NULL)
161		return B_NO_MEMORY;
162
163	fSession = new(nothrow) iSCSISession(fTargetName);
164	if (fSession == NULL)
165		return B_NO_MEMORY;
166	status_t error = fSession->Init(address, port, &fTargetAlias);
167	if (error != B_OK)
168		return error;
169	if (targetName == NULL)
170		return B_OK;
171
172	error = _GetSize();
173	if (error != B_OK)
174		return error;
175
176	return B_OK;
177}
178
179
180status_t
181iSCSITarget::_GetSize()
182{
183	scsi_cmd_read_capacity cmd;
184	memset(&cmd, 0, sizeof(cmd));
185	cmd.opcode = SCSI_OP_READ_CAPACITY;
186	cmd.relative_address = 0;
187	cmd.lun = 0;
188	cmd.lba = 0;
189	cmd.pmi = 0;
190	cmd.control = 0;
191	scsi_res_read_capacity resp;
192	status_t error = fSession->Connection()->SendCommand(&cmd, sizeof(cmd),
193		true, false, sizeof(resp),
194		&resp, 0, sizeof(resp));
195	if (error != B_OK) {
196		TRACE("error %lx sending command!\n", error);
197		return error;
198	}
199	TRACE("lba = %lu, block size = %lu\n", resp.lba, resp.block_size);
200	fLastLBA = resp.lba;
201	fBlockSize = resp.block_size;
202
203	return B_OK;
204}
205
206
207ssize_t
208iSCSITarget::ReadAt(void* /*cookie*/, off_t pos, void* buffer, size_t bufferSize)
209{
210	TRACE("pos=%llu, size = %lu\n", pos, bufferSize);
211
212	if (fSession == NULL)
213		return B_NO_INIT;
214
215	if (buffer == NULL || pos < 0 || pos >= Size())
216		return B_BAD_VALUE;
217	if (bufferSize == 0)
218		return 0;
219
220	uint32 blockOffset = pos % fBlockSize;
221
222	scsi_cmd_rw_16 cmd;
223	memset(&cmd, 0, sizeof(cmd));
224	cmd.opcode = SCSI_OP_READ_16;
225	cmd.force_unit_access = 0;
226	cmd.disable_page_out = 0;
227	cmd.lba = pos / fBlockSize;
228	cmd.length = (blockOffset + bufferSize + fBlockSize - 1) / fBlockSize;
229	cmd.control = 0;
230	status_t error = fSession->Connection()->SendCommand(&cmd, sizeof(cmd),
231		true, false, cmd.length * fBlockSize, buffer, blockOffset, bufferSize);
232	if (error != B_OK) {
233		TRACE("error %lx sending command!\n", error);
234		return error;
235	}
236
237	return bufferSize;
238}
239
240
241ssize_t
242iSCSITarget::WriteAt(void* /*cookie*/, off_t pos, const void* buffer, size_t bufferSize)
243{
244	return B_PERMISSION_DENIED;
245}
246
247
248status_t
249iSCSITarget::GetName(char* buffer, size_t bufferSize) const
250{
251	if (buffer == NULL)
252		return B_BAD_VALUE;
253
254	snprintf(buffer, bufferSize, "iSCSI:%s", fTargetName);
255
256	return B_OK;
257}
258
259
260off_t
261iSCSITarget::Size() const
262{
263	return (fLastLBA + 1) * fBlockSize;
264}
265
266
267iSCSISession::iSCSISession(const char* targetName)
268	:
269	fDiscovery(targetName == NULL),
270	fTargetName(targetName),
271	fConnection(NULL)
272{
273	fCommandSequenceNumber = _rand();
274}
275
276
277iSCSISession::~iSCSISession()
278{
279	if (Active())
280		Close();
281	delete fConnection;
282}
283
284
285status_t
286iSCSISession::Init(ip_addr_t address, uint16 port, char** targetAlias)
287{
288	fConnection = new(nothrow) iSCSIConnection();
289	if (fConnection == NULL)
290		return B_NO_MEMORY;
291	status_t error = fConnection->Init(this);
292	if (error != B_OK)
293		return error;
294	error = fConnection->Open(address, port);
295	if (error != B_OK)
296		return error;
297	error = fConnection->Login(fTargetName, targetAlias);
298	if (error != B_OK)
299		return error;
300	return B_OK;
301}
302
303
304status_t
305iSCSISession::Close()
306{
307	if (fConnection == NULL)
308		return B_NO_INIT;
309
310	status_t error = fConnection->Logout(true);
311	if (error != B_OK)
312		return error;
313	return B_OK;
314}
315
316
317iSCSIConnection::iSCSIConnection()
318	:
319	fSession(NULL),
320	fSocket(NULL),
321	fConnected(false),
322	fConnectionID(0)
323{
324}
325
326
327iSCSIConnection::~iSCSIConnection()
328{
329	if (fSocket != NULL && fConnected) {
330		if (Logout() != B_OK)
331			fSocket->Close();
332	}
333	delete fSocket;
334}
335
336
337status_t
338iSCSIConnection::Init(iSCSISession* session)
339{
340	fSession = session;
341	return B_OK;
342}
343
344status_t
345iSCSIConnection::Open(ip_addr_t address, uint16 port)
346{
347	fSocket = new(nothrow) TCPSocket();
348	if (fSocket == NULL)
349		return B_NO_MEMORY;
350	TRACE("connecting to target...\n");
351	status_t error = fSocket->Connect(address, port);
352	if (error != B_OK)
353		return error;
354	fConnected = true;
355	TRACE("connected.\n");
356	fConnectionID = _rand();
357	return B_OK;
358}
359
360
361status_t
362iSCSIConnection::Login(const char* targetName, char** targetAlias)
363{
364	char data[256];
365	size_t dataLength = 0;
366	const char* keyValue;
367	keyValue = "InitiatorName=iqn.2002-10.org.haiku-os:haiku.bootloader.hostX";
368	strcpy(data + dataLength, keyValue);
369	dataLength += strlen(keyValue) + 1;
370	if (targetName != NULL) {
371		dataLength += sprintf(data + dataLength, "TargetName=%s", targetName) + 1;
372	} else {
373		keyValue = "SessionType=Discovery";
374		strcpy(data + dataLength, keyValue);
375		dataLength += strlen(keyValue) + 1;
376	}
377
378	iscsi_login_request req;
379	req.transit = true;
380	req.c = false;
381	req.currentStage = ISCSI_SESSION_STAGE_LOGIN_OPERATIONAL_NEGOTIATION;
382	req.nextStage    = ISCSI_SESSION_STAGE_FULL_FEATURE_PHASE;
383	req.versionMax = ISCSI_VERSION;
384	req.versionMin = ISCSI_VERSION;
385	req.totalAHSLength = 0;
386	req.dataSegmentLength = dataLength;
387	TRACE("data segment length = %lu\n", req.dataSegmentLength);
388	req.isid.t = ISCSI_ISID_RANDOM;
389	req.isid.a = 0;
390	uint32 random = _rand();
391	req.isid.b = random >> 8;
392	req.isid.c = random & 0xff;
393	req.isid.d = _rand();
394	req.tsih = 0;
395	req.initiatorTaskTag = _rand();
396	req.cid = fConnectionID;
397	req.cmdSN = fSession->CommandSequenceNumber();
398	req.expStatSN = 0;
399	status_t error = fSocket->Write(&req, sizeof(req));
400	if (error != B_OK) {
401		return error;
402	}
403	for (unsigned int i = dataLength; i < ISCSI_ALIGN(dataLength); i++)
404		data[i] = '\0';
405	dataLength = ISCSI_ALIGN(dataLength);
406	TRACE("data length = %lu\n", dataLength);
407	error = fSocket->Write(data, dataLength);
408	if (error != B_OK) {
409		TRACE("write error\n");
410		return error;
411	}
412	TRACE("request sent.\n");
413
414	iscsi_login_response resp;
415	size_t bytesRead = 0;
416	error = fSocket->Read(&resp, sizeof(resp), &bytesRead, 10000000LL);
417	if (error != B_OK) {
418		TRACE("response read error\n");
419		return error;
420	}
421	TRACE("bytesRead = %lu\n", bytesRead);
422	TRACE("response: opcode = %x, status class = %u, status detail = %u, transit = %u, data length = %lu\n",
423		resp.opcode, resp.statusClass, resp.statusDetail, resp.transit, resp.dataSegmentLength);
424	if (resp.dataSegmentLength > 0) {
425		error = fSocket->Read(data, resp.dataSegmentLength, &bytesRead, 1000000LL);
426		if (error != B_OK) {
427			TRACE("response data read error\n");
428			return error;
429		}
430		TRACE("bytesRead = %lu\n", bytesRead);
431		if (resp.dataSegmentLength % ISCSI_ALIGNMENT) {
432			error = fSocket->Read(NULL, ISCSI_PADDING(resp.dataSegmentLength), &bytesRead, 100000LL);
433			if (error != B_OK) {
434				TRACE("response padding read error\n");
435				return error;
436			}
437			TRACE("bytesRead = %lu\n", bytesRead);
438		}
439		for (char* pair = data; pair < data + resp.dataSegmentLength; pair += strlen(pair) + 1) {
440			TRACE("%s\n", pair);
441			if (targetAlias != NULL && strncmp(pair, "TargetAlias=", strlen("TargetAlias=")) == 0) {
442				*targetAlias = strdup(pair + strlen("TargetAlias="));
443			}
444		}
445	}
446	fStatusSequenceNumber = resp.statSN;
447	if (resp.statusClass != 0) {
448		TRACE("response indicates failure.\n");
449		return B_BAD_VALUE;
450	}
451	return B_OK;
452}
453
454
455status_t
456iSCSIConnection::Logout(bool closeSession)
457{
458	iscsi_logout_request req;
459	TRACE("req size = %lu\n", sizeof(req));
460	req.immediateDelivery = true;
461	req.reasonCode = closeSession ? ISCSI_LOGOUT_REASON_CLOSE_SESSION
462		: ISCSI_LOGOUT_REASON_CLOSE_CONNECTION;
463	req.totalAHSLength = 0;
464	req.dataSegmentLength = 0;
465	req.initiatorTaskTag = _rand();
466	req.cid = closeSession ? 0 : fConnectionID;
467	req.cmdSN = fSession->CommandSequenceNumber();
468	req.expStatSN = fStatusSequenceNumber;
469	status_t error = fSocket->Write(&req, sizeof(req));
470	if (error != B_OK) {
471		TRACE("request write error\n");
472		return error;
473	}
474
475	iscsi_logout_response resp;
476	TRACE("resp size = %lu\n", sizeof(resp));
477	size_t bytesRead = 0;
478	error = fSocket->Read(&resp, sizeof(resp), &bytesRead, 10000000LL);
479	if (error != B_OK) {
480		TRACE("response read error\n");
481		return error;
482	}
483	TRACE("bytesRead = %lu\n", bytesRead);
484	TRACE("response: opcode = %x, response = %u\n",
485		resp.opcode, resp.response);
486	fStatusSequenceNumber = resp.statSN;
487	if (resp.response != 0) {
488		TRACE("response indicates failure.\n");
489		return B_ERROR;
490	}
491
492	fSocket->Close();
493	fConnected = false;
494	return B_OK;
495}
496
497
498status_t
499iSCSIConnection::GetText(const char* request, size_t requestLength, char** response, size_t* responseLength)
500{
501	iscsi_text_request req;
502	req.immediateDelivery = true;
503	req.final = true;
504	req.c = false;
505	req.totalAHSLength = 0;
506	req.dataSegmentLength = requestLength;
507	req.lun = 0;
508	req.initiatorTaskTag = _rand();
509	req.targetTransferTag = 0xffffffffL;
510	req.cmdSN = fSession->NextCommandSequenceNumber();
511	req.expStatSN = fStatusSequenceNumber;
512	status_t error = fSocket->Write(&req, sizeof(req));
513	if (error != B_OK) {
514		TRACE("iSCSI text request write error\n");
515		return error;
516	}
517	error = fSocket->Write(request, requestLength);
518	if (error != B_OK) {
519		TRACE("iSCSI text request data write error\n");
520		return error;
521	}
522	if (requestLength % ISCSI_ALIGNMENT != 0) {
523		char buffer[3];
524		memset(buffer, 0, 3);
525		error = fSocket->Write(buffer, ISCSI_ALIGNMENT - (requestLength % ISCSI_ALIGNMENT));
526		if (error != B_OK) {
527			TRACE("iSCSI text request padding write error\n");
528			return error;
529		}
530	}
531
532	iscsi_logout_response resp;
533	size_t bytesRead = 0;
534	error = fSocket->Read(&resp, sizeof(resp), &bytesRead, 10000000LL);
535	if (error != B_OK) {
536		TRACE("response read error\n");
537		return error;
538	}
539	TRACE("bytesRead = %lu\n", bytesRead);
540	TRACE("response: opcode = %x\n",
541		resp.opcode);
542	*response = (char*)malloc(resp.dataSegmentLength);
543	if (*response == NULL)
544		return B_NO_MEMORY;
545	error = fSocket->Read(*response, resp.dataSegmentLength, &bytesRead, 10000000LL);
546	if (error != B_OK) {
547		TRACE("response read error\n");
548		return error;
549	}
550	TRACE("bytesRead = %lu\n", bytesRead);
551	*responseLength = resp.dataSegmentLength;
552	if ((resp.dataSegmentLength % ISCSI_ALIGNMENT) != 0) {
553		error = fSocket->Read(NULL, ISCSI_PADDING(resp.dataSegmentLength), &bytesRead, 1000000LL);
554		if (error != B_OK) {
555			TRACE("response padding read error\n");
556			return error;
557		}
558	}
559	fStatusSequenceNumber = resp.statSN;
560	if (resp.opcode != ISCSI_OPCODE_TEXT_RESPONSE) {
561		PANIC("response opcode unexpected!");
562		return B_ERROR;
563	}
564
565	return B_OK;
566}
567
568
569status_t
570iSCSIConnection::SendCommand(const void* command, size_t commandSize,
571	bool r, bool w, uint32 expectedDataTransferLength,
572	void* response, uint32 responseOffset, size_t responseLength)
573{
574	TRACE("command size = %lu, offset = %lu, length = %lu\n", commandSize, responseOffset, responseLength);
575	iscsi_scsi_command req;
576	req.immediateDelivery = true;
577	req.final = true;
578	req.r = r;
579	req.w = w;
580	req.attr = 0;
581	req.totalAHSLength = 0;
582	req.dataSegmentLength = (commandSize > 16) ? commandSize - 16 : 0;
583	req.lun = 0;
584	req.initiatorTaskTag = _rand();
585	req.expectedDataTransferLength = expectedDataTransferLength;
586	req.cmdSN = fSession->NextCommandSequenceNumber();
587	req.expStatSN = fStatusSequenceNumber;
588	if (commandSize < 16)
589		memset(req.cdb, 0, 16);
590	memcpy(req.cdb, command, commandSize <= 16 ? commandSize : 16);
591	status_t error = fSocket->Write(&req, sizeof(req));
592	if (error != B_OK) {
593		TRACE("write error\n");
594		return error;
595	}
596	if (commandSize > 16) {
597		error = fSocket->Write((uint8*)command + 16, commandSize - 16);
598		if (error != B_OK) {
599			TRACE("write error\n");
600			return error;
601		}
602		if ((req.dataSegmentLength % ISCSI_ALIGNMENT) != 0) {
603			char buffer[3];
604			memset(buffer, 0, 3);
605			error = fSocket->Write(buffer, ISCSI_ALIGNMENT - (req.dataSegmentLength % ISCSI_ALIGNMENT));
606			if (error != B_OK) {
607				TRACE("request padding write error\n");
608				return error;
609			}
610		}
611	}
612
613	struct iscsi_basic_header_segment resp;
614	error = _ReadResponse(&resp);
615	if (error != B_OK) {
616		TRACE("response read error\n");
617		return error;
618	}
619	while (r && resp.opcode != ISCSI_OPCODE_SCSI_RESPONSE) {
620		if (resp.opcode != ISCSI_OPCODE_SCSI_DATA_IN)
621			PANIC("response opcode %x unexpected\n", resp.opcode);
622		iscsi_scsi_data_in* dataIn = (iscsi_scsi_data_in*)&resp;
623		TRACE("A=%u, S=%u, bufferOffset=%lu, length = %lu\n", dataIn->acknowledge, dataIn->S, dataIn->bufferOffset, resp.dataSegmentLength);
624		if (resp.dataSegmentLength > 0) {
625			size_t toRead = resp.dataSegmentLength;
626			size_t toSkip = 0;
627			if (dataIn->bufferOffset < responseOffset) {
628				toSkip = MIN(responseOffset - dataIn->bufferOffset, resp.dataSegmentLength);
629				error = _Read(NULL, toSkip);
630				if (error != B_OK) {
631					TRACE("response skip error\n");
632					return error;
633				}
634				TRACE("skipped %lu response bytes\n", toSkip);
635				toRead -= toSkip;
636			}
637			if (toRead > 0) {
638				if (dataIn->bufferOffset + resp.dataSegmentLength > responseOffset + responseLength) {
639					size_t cutOff = expectedDataTransferLength - (responseOffset + responseLength);
640					toRead = MIN(expectedDataTransferLength - cutOff - dataIn->bufferOffset, resp.dataSegmentLength) - toSkip;
641				}
642				error = _Read((uint8*)response + dataIn->bufferOffset - responseOffset, toRead);
643				if (error != B_OK) {
644					TRACE("response read error: %lx\n", error);
645					return error;
646				}
647			}
648			size_t toCutOff = resp.dataSegmentLength - toSkip - toRead;
649			if (toCutOff > 0) {
650				error = _Read(NULL, toCutOff);
651				if (error != B_OK) {
652					TRACE("response cut-off error\n");
653					return error;
654				}
655			}
656			if (toSkip + toRead + toCutOff != resp.dataSegmentLength)
657				PANIC("inconcistency while reading detected!\n");
658		}
659		if ((resp.dataSegmentLength % ISCSI_ALIGNMENT) != 0) {
660			error = _Read(NULL, ISCSI_PADDING(resp.dataSegmentLength));
661			if (error != B_OK) {
662				TRACE("response padding read error\n");
663				return error;
664			}
665		}
666		fStatusSequenceNumber = dataIn->statSN;
667		if (dataIn->S) {
668			// TODO check status
669			return B_OK;
670		}
671
672		error = _ReadResponse(&resp);
673		if (error != B_OK) {
674			TRACE("response read error\n");
675			return error;
676		}
677	}
678
679	TRACE("response: opcode = %x, AHS length = %x, data length = %lu\n",
680		resp.opcode, resp.totalAHSLength, resp.dataSegmentLength);
681	if (resp.opcode != ISCSI_OPCODE_SCSI_RESPONSE) {
682		PANIC("response opcode unexpected!\n");
683		return B_ERROR;
684	}
685	iscsi_scsi_response* scsiResp = (iscsi_scsi_response*)&resp;
686	TRACE("response: response = %x, status = %x, SNACK=%x\n",
687		scsiResp->response, scsiResp->status, scsiResp->snackTag);
688	if (resp.dataSegmentLength > 0) {
689		void* buffer = NULL;
690		if (scsiResp->status != SCSI_STATUS_GOOD)
691			buffer = malloc(resp.dataSegmentLength);
692		error = _Read(buffer, resp.dataSegmentLength);
693		if (error != B_OK) {
694			TRACE("response read error\n");
695			return error;
696		}
697#ifdef TRACE_ISCSI
698		if (scsiResp->status != SCSI_STATUS_GOOD) {
699			scsi_sense* sense = (scsi_sense*)((uint16*)buffer + 1);
700			TRACE("error code = %u, sense key = %u, ILI = %u, asc = %x\n",
701				sense->error_code, sense->sense_key, sense->ILI, (sense->asc << 8) | sense->ascq);
702		}
703#endif
704		free(buffer);
705	}
706	if ((resp.dataSegmentLength % ISCSI_ALIGNMENT) != 0) {
707		error = _Read(NULL, ISCSI_PADDING(resp.dataSegmentLength));
708		if (error != B_OK) {
709			TRACE("response padding read error\n");
710			return error;
711		}
712	}
713	fStatusSequenceNumber = scsiResp->statSN;
714	if (scsiResp->response != 0x00 || scsiResp->status != SCSI_STATUS_GOOD)
715		return B_BAD_VALUE;
716
717	return B_OK;
718}
719
720
721status_t
722iSCSIConnection::_ReadResponse(iscsi_basic_header_segment* resp, bigtime_t timeout)
723{
724	if (resp == NULL)
725		return B_BAD_VALUE;
726
727	status_t error = _Read(resp, sizeof(iscsi_basic_header_segment), timeout);
728	if (error != B_OK) {
729		TRACE("initial read failed\n");
730		return error;
731	}
732
733	if (resp->reserved == true) {
734		TRACE("reserved bit is 1!\n");
735		return B_IO_ERROR;
736	}
737	// theoretically process header checksum here, if available
738
739	// At any time a target can send a NOP-In, requiring us to answer with a NOP-Out.
740	// TODO handle asynchronous event message
741	while (resp->opcode == ISCSI_OPCODE_NOP_IN) {
742		iscsi_nop_in* nopIn = (iscsi_nop_in*)resp;
743		// A NOP-In can also be the answer to our own NOP-Out though.
744		if (nopIn->targetTransferTag == 0xffffffff)
745			return B_OK;
746		TRACE("received NOP-In (ping data length = %lu)\n", nopIn->dataSegmentLength);
747		void* pingData = NULL;
748		if (nopIn->dataSegmentLength > 0) {
749			pingData = malloc(nopIn->dataSegmentLength);
750			if (pingData == NULL)
751				return B_NO_MEMORY;
752			error = _Read(pingData, nopIn->dataSegmentLength, timeout);
753			if (error != B_OK) {
754				TRACE("NOP-In ping data read error\n");
755				free(pingData);
756				return error;
757			}
758		}
759		if ((nopIn->dataSegmentLength % ISCSI_ALIGNMENT) != 0) {
760			error = _Read(NULL, ISCSI_PADDING(nopIn->dataSegmentLength), timeout);
761			if (error != B_OK) {
762				TRACE("NOP-In padding read error\n");
763				free(pingData);
764				return error;
765			}
766		}
767		iscsi_nop_out nopOut;
768		nopOut.totalAHSLength = 0;
769		nopOut.dataSegmentLength = nopIn->dataSegmentLength;
770		nopOut.lun = nopIn->lun;
771		nopOut.initiatorTaskTag = 0xffffffff;
772		nopOut.targetTransferTag = nopIn->targetTransferTag;
773		nopOut.cmdSN = fSession->NextCommandSequenceNumber();
774		nopOut.expStatSN = fStatusSequenceNumber;
775		error = fSocket->Write(&nopOut, sizeof(nopOut));
776		if (error != B_OK) {
777			TRACE("NOP-Out write error\n");
778			free(pingData);
779			return error;
780		}
781		if (nopOut.dataSegmentLength > 0) {
782			error = fSocket->Write(pingData, nopOut.dataSegmentLength);
783			free(pingData);
784			if (error != B_OK) {
785				TRACE("NOP-Out ping data write error\n");
786				return error;
787			}
788			if ((nopOut.dataSegmentLength % ISCSI_ALIGNMENT) != 0) {
789				char buffer[3];
790				memset(buffer, 0, 3);
791				error = fSocket->Write(buffer, ISCSI_ALIGNMENT - (nopOut.dataSegmentLength % ISCSI_ALIGNMENT));
792				if (error != B_OK) {
793					TRACE("NOP-Out padding write error\n");
794					return error;
795				}
796			}
797		}
798
799		error = _Read(resp, sizeof(iscsi_basic_header_segment), timeout);
800		if (error != B_OK) {
801			TRACE("read failed\n");
802			return error;
803		}
804	}
805
806	return B_OK;
807}
808
809
810status_t
811iSCSIConnection::_Read(void* buffer, size_t bufferSize, bigtime_t timeout)
812{
813	size_t bytesRead;
814	status_t error = fSocket->Read(buffer, bufferSize, &bytesRead, timeout);
815	if (error != B_OK)
816		return error;
817	if (bytesRead < bufferSize) {
818		dprintf("not enough data available: %lu vs. %lu\n", bytesRead, bufferSize);
819		return B_ERROR;
820	}
821	return B_OK;
822}
823