1/*
2 * ESounD media addon for BeOS
3 *
4 * Copyright (c) 2006 Fran��ois Revol (revol@free.fr)
5 *
6 * Based on Multi Audio addon for Haiku,
7 * Copyright (c) 2002, 2003 Jerome Duval (jerome.duval@free.fr)
8 *
9 * All rights reserved.
10 * Redistribution and use in source and binary forms, with or without modification,
11 * are permitted provided that the following conditions are met:
12 *
13 * - Redistributions of source code must retain the above copyright notice,
14 *   this list of conditions and the following disclaimer.
15 * - Redistributions in binary form must reproduce the above copyright notice,
16 *   this list of conditions and the following disclaimer in the documentation
17 *   and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
23 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
25 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 *
30 */
31#define _ZETA_TS_FIND_DIR_ 1
32#include <FindDirectory.h>
33#include <File.h>
34#include <Path.h>
35#include <sys/socket.h>
36#include <sys/time.h>
37#include <arpa/inet.h>
38#include <netdb.h>
39#include <netinet/in.h>
40#include <netinet/tcp.h>
41#include <errno.h>
42#include <stdlib.h>
43#include "compat.h"
44//#undef DEBUG
45//#define DEBUG 4
46#include "debug.h"
47#include <Debug.h>
48#include "ESDEndpoint.h"
49
50
51ESDEndpoint::ESDEndpoint()
52 : BDataIO()
53 , fHost(NULL)
54 , fPort(ESD_DEFAULT_PORT)
55 , fSocket(-1)
56{
57	CALLED();
58	Reset();
59}
60
61
62ESDEndpoint::~ESDEndpoint()
63{
64	CALLED();
65	if (fSocket > -1)
66		closesocket(fSocket);
67	fSocket = -1;
68}
69
70
71status_t
72ESDEndpoint::InitCheck() const
73{
74	return fInitStatus;
75}
76
77
78void
79ESDEndpoint::Reset()
80{
81	fInitStatus = B_NO_INIT;
82	fDefaultCommand = ESD_PROTO_STREAM_PLAY;
83	fDefaultCommandSent = false;
84	fDefaultFormat = ESD_BITS8 | ESD_MONO;
85	fDefaultRate = ESD_DEFAULT_RATE;
86	fLatency = 0LL;
87}
88
89
90status_t
91ESDEndpoint::SendAuthKey()
92{
93	CALLED();
94	BPath kfPath;
95	status_t err;
96	off_t size;
97	char key[ESD_MAX_KEY];
98	err = find_directory(B_USER_SETTINGS_DIRECTORY, &kfPath);
99	kfPath.Append("esd_auth");
100	BFile keyFile(kfPath.Path(), B_READ_WRITE|B_CREATE_FILE);
101	err = keyFile.GetSize(&size);
102	if (err < 0)
103		return err;
104	if (size < ESD_MAX_KEY) {
105		keyFile.Seek(0LL, SEEK_SET);
106		srand(time(NULL));
107		for (int i = 0; i < ESD_MAX_KEY; i++)
108			key[i] = (char)(rand() % 256);
109		err = keyFile.Write(key, ESD_MAX_KEY);
110		if (err < 0)
111			return err;
112		if (err < ESD_MAX_KEY)
113			return EIO;
114	}
115	err = keyFile.Read(key, ESD_MAX_KEY);
116	if (err < 0)
117		return err;
118	if (err < ESD_MAX_KEY)
119		return EIO;
120	memcpy(fAuthKey, key, sizeof(esd_key_t));
121	return write(fSocket, fAuthKey, ESD_MAX_KEY);
122}
123
124
125bool
126ESDEndpoint::Connected() const
127{
128	return (fInitStatus == B_OK);
129}
130
131
132status_t
133ESDEndpoint::Connect(const char *host, uint16 port)
134{
135	status_t err;
136	// set up connection asynchronously
137	fHost = host;
138	fPort = port;
139
140	err = fConnectThread = spawn_thread(_ConnectThread, "ESDEndpoint Connection", B_LOW_PRIORITY, this);
141	if (err < B_OK)
142		return err;
143	err = resume_thread(fConnectThread);
144
145	// TODO: return now instead and move Connect() call
146	//wait_for_thread(fConnectThread, &err);
147
148	return err;
149}
150
151
152status_t
153ESDEndpoint::WaitForConnect()
154{
155	status_t err;
156	int32 ret;
157	err = wait_for_thread(fConnectThread, &ret);
158	if (err < B_OK)
159		return err;
160
161	return ret;
162}
163
164
165int32
166ESDEndpoint::_ConnectThread(void *_arg)
167{
168	ESDEndpoint *_this = (ESDEndpoint *)_arg;
169	return _this->ConnectThread();
170}
171
172int32
173ESDEndpoint::ConnectThread(void)
174{
175	const char *host = fHost.String();
176	uint16 port = fPort;
177	status_t err;
178	int flag;
179	struct timeval oldTimeout;
180	socklen_t oldTimeoutLen = sizeof(struct timeval);
181	struct timeval timeout = { 10, 0 }; // 10s should be enough on a LAN
182	CALLED();
183
184	struct hostent *he;
185	struct sockaddr_in sin;
186	he = gethostbyname(host);
187	PRINT(("gethostbyname(%s) = %p\n", host, he));
188	if (!he)
189		return ENOENT;
190	memcpy((struct in_addr *)&sin.sin_addr, he->h_addr, sizeof(struct in_addr));
191
192	/* close old connection */
193	if (fSocket > -1)
194		closesocket(fSocket);
195	Reset();
196
197	fSocket = socket(AF_INET, SOCK_STREAM, 0);
198	if (fSocket < 0)
199		return errno;
200	sin.sin_family = AF_INET;
201	sin.sin_port = htons( port );
202
203	/*
204	flag = 128*1024;
205	setsockopt(fSocket, SOL_SOCKET, SO_SNDBUF, &flag, sizeof(flag));
206	setsockopt(fSocket, SOL_SOCKET, SO_RCVBUF, &flag, sizeof(flag));
207	*/
208
209	if (getsockopt(fSocket, SOL_SOCKET, SO_RCVTIMEO, (char *)&oldTimeout, &oldTimeoutLen) >= 0) {
210		setsockopt(fSocket, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(struct timeval));
211	}
212
213	err = connect(fSocket, (struct sockaddr *) &sin, sizeof(sin));
214	PRINT(("connect: %ld, %s\n", err, strerror(errno)));
215	setsockopt(fSocket, SOL_SOCKET, SO_RCVTIMEO, (char *)&oldTimeout, sizeof(struct timeval));
216	if (err < 0)
217		return errno;
218
219	uint32 cmd;
220	uint32 result;
221/*	uint32 cmd = ESD_PROTO_CONNECT;
222	err = write(fSocket, &cmd, sizeof(cmd));
223	if (err < 0)
224		return errno;
225	if (err < sizeof(cmd))
226		return EIO;
227*/
228	/* send authentification key */
229	err = SendAuthKey();
230	if (err < 0)
231		return errno;
232
233	/* send endian tag, wait for 'ok' and measure the round-trip time
234	 * to account for the network latency
235	 */
236	bigtime_t ping = system_time();
237
238	cmd = ESD_ENDIAN_TAG;
239	err = write(fSocket, &cmd, sizeof(cmd));
240	if (err < 0)
241		return errno;
242	if ((unsigned)err < sizeof(cmd))
243		return EIO;
244
245	read(fSocket, &result, sizeof(result));
246	if (result != 1)
247		fprintf(stderr, "ESDEndpoint::Connect: didn't get ok from ESD_PROTO_INIT (%ld)!\n", result);
248
249	ping = (system_time() - ping) / 2; /* approximate one-way trip time */
250	fLatency = ping;
251
252	/* ask the server for its own latency
253	 * sadly it seems to only give a fixed value,
254	 * not counting the audio card's buffering into account.
255	 * we take another measurement of the round-trip,
256	 * and use the mean of the 2.
257	 */
258
259	ping = system_time();
260
261	cmd = ESD_PROTO_LATENCY;
262	err = write(fSocket, &cmd, sizeof(cmd));
263	if (err < 0)
264		return errno;
265	if ((unsigned)err < sizeof(cmd))
266		return EIO;
267
268	read(fSocket, &result, sizeof(result));
269	fprintf(stderr, "ESDEndpoint::Connect: ESD_PROTO_LATENCY: %ld\n", result);
270
271	ping = (system_time() - ping) / 2; /* approximate one-way trip time */
272
273	bigtime_t serverLatency = result * 1000000LL / (ESD_DEFAULT_RATE * 2/*16b*/ * 2/*stereo*/ );
274	bigtime_t netLatency = (fLatency + ping) / 2; /* mean of both */
275	fprintf(stderr, "ESDEndpoint::Connect: Latency: server: %lld, net1: %lld, net2: %lld\n", serverLatency, fLatency, ping);
276
277	fLatency = netLatency + serverLatency;
278
279
280
281	flag = 1;
282	//int len;
283	/* disable Nagle */
284	setsockopt(fSocket, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));
285	//setsockopt(fSocket, SOL_SOCKET, SO_NONBLOCK, &flag, sizeof(flag));
286	/*
287	len = sizeof(flag);
288	getsockopt(fSocket, SOL_SOCKET, SO_SNDBUF, &flag, &len);
289	fprintf(stderr, "ESDEndpoint::Connect: SNDBUF: %ld\n", flag);
290	flag = MIN(TCP_MAXWIN, MAX(flag, (4 * netLatency * (ESD_DEFAULT_RATE*2*2) / 1000000LL)));
291	fprintf(stderr, "ESDEndpoint::Connect: SNDBUF: %ld\n", flag);
292	setsockopt(fSocket, SOL_SOCKET, SO_SNDBUF, &flag, sizeof(flag));
293	*/
294
295
296
297	// TODO: get default format
298
299	fInitStatus = B_OK;
300
301	return B_OK;
302}
303
304status_t
305ESDEndpoint::Disconnect()
306{
307	CALLED();
308	if (fSocket > -1)
309		closesocket(fSocket);
310	fSocket = -1;
311	return B_OK;
312}
313
314status_t
315ESDEndpoint::SetCommand(esd_command_t cmd)
316{
317	CALLED();
318	if (fDefaultCommandSent)
319		return EALREADY;
320	fDefaultCommand = cmd;
321	return B_OK;
322}
323
324status_t
325ESDEndpoint::SetFormat(int bits, int channels, float rate)
326{
327	esd_format_t fmt = 0;
328	CALLED();
329	if (fDefaultCommandSent)
330		return EALREADY;
331	PRINT(("SetFormat(%d,%d,%f)\n", bits, channels, rate));
332	switch (bits) {
333	case 8:
334		fmt |= ESD_BITS8;
335		break;
336	case 16:
337		fmt |= ESD_BITS16;
338		break;
339	default:
340		return EINVAL;
341	}
342	switch (channels) {
343	case 1:
344		fmt |= ESD_MONO;
345		break;
346	case 2:
347		fmt |= ESD_STEREO;
348		break;
349	default:
350		return EINVAL;
351	}
352	fmt |= ESD_STREAM | ESD_FUNC_PLAY;
353	PRINT(("SetFormat: %08lx\n", (long)fmt));
354	fDefaultFormat = fmt;
355	fDefaultRate = rate;
356	return B_OK;
357}
358
359status_t
360ESDEndpoint::GetServerInfo()
361{
362	CALLED();
363	struct serverinfo {
364		uint32 ver;
365		uint32 rate;
366		uint32 fmt;
367	} si;
368	status_t err;
369	err = SendCommand(ESD_PROTO_SERVER_INFO, (const uint8 *)&si, 0, (uint8 *)&si, sizeof(si));
370	if (err < 0)
371		return err;
372	PRINT(("err 0x%08lx, version: %lu, rate: %lu, fmt: %lu\n", err, si.ver, si.rate, si.fmt));
373	return B_OK;
374}
375
376
377void
378ESDEndpoint::GetFriendlyName(BString &name)
379{
380	name = "ESounD Out";
381	name << " (" << Host();
382	name << ":" << Port() << ")";
383
384}
385
386bool
387ESDEndpoint::CanSend()
388{
389	CALLED();
390	return fDefaultCommandSent;
391}
392
393ssize_t
394ESDEndpoint::Read(void *buffer, size_t size)
395{
396	CALLED();
397	return EINVAL;
398}
399
400ssize_t
401ESDEndpoint::Write(const void *buffer, size_t size)
402{
403	status_t err = B_OK;
404	CALLED();
405	if (!fDefaultCommandSent)
406		err = SendDefaultCommand();
407	if (err < B_OK)
408		return err;
409	//PRINT(("write(fSocket, buffer, %d)\n", size));
410	//fprintf(stderr, "ESDEndpoint::Write(, %d) %s\n", size, (size%2)?"ODD BUFFER SIZE":"");
411	if (fDefaultFormat & ESD_BITS16) {
412		size /= 2;
413		size *= 2;
414	}
415	err = write(fSocket, buffer, size);
416	if ((unsigned)err != size) {
417		fprintf(stderr, "ESDEndpoint::Write: sent only %ld of %ld!\n", err, size);
418		if (err < 0)
419			fprintf(stderr, "ESDEndpoint::Write: %s\n", strerror(errno));
420	}
421	//PRINT(("write(fSocket, buffer, %d): %s\n", size, strerror(err)));
422	if (err < B_OK)
423		return errno;
424	return err;
425}
426
427status_t
428ESDEndpoint::SendCommand(esd_command_t cmd, const uint8 *obuf, size_t olen, uint8 *ibuf, size_t ilen)
429{
430	status_t err;
431	CALLED();
432	err = send(fSocket, &cmd, sizeof(cmd), 0);
433	if (err < B_OK)
434		return errno;
435	if (obuf && olen) {
436		err = send(fSocket, obuf, olen, 0);
437		if (err < B_OK)
438			return errno;
439	}
440	err = B_OK;
441	if (ibuf && ilen) {
442		err = recv(fSocket, ibuf, ilen, 0);
443		if (err < B_OK)
444			return errno;
445		/* return received len */
446	}
447	return err;
448}
449
450status_t
451ESDEndpoint::SendDefaultCommand()
452{
453	status_t err;
454	struct {
455		esd_format_t format;
456		esd_rate_t rate;
457		char name[ESD_MAX_NAME];
458	} c;
459	CALLED();
460	if (fDefaultCommandSent)
461		return EALREADY;
462	c.format = fDefaultFormat;
463	c.rate = fDefaultRate;
464	strcpy(c.name, "BeOS/Haiku/ZETA Media Kit output");
465	err = SendCommand(fDefaultCommand, (uint8 *)&c, sizeof(c), NULL, 0);
466	if (err < B_OK)
467		return err;
468	PRINT(("SendCommand: %s\n", strerror(err)));
469	fDefaultCommandSent = true;
470	return B_OK;
471}
472
473