#define _ZETA_TS_FIND_DIR_ 1
#include <FindDirectory.h>
#include <File.h>
#include <Path.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <errno.h>
#include <stdlib.h>
#include "compat.h"
#include "debug.h"
#include <Debug.h>
#include "ESDEndpoint.h"
ESDEndpoint::ESDEndpoint()
: BDataIO()
, fHost(NULL)
, fPort(ESD_DEFAULT_PORT)
, fSocket(-1)
{
CALLED();
Reset();
}
ESDEndpoint::~ESDEndpoint()
{
CALLED();
if (fSocket > -1)
closesocket(fSocket);
fSocket = -1;
}
status_t
ESDEndpoint::InitCheck() const
{
return fInitStatus;
}
void
ESDEndpoint::Reset()
{
fInitStatus = B_NO_INIT;
fDefaultCommand = ESD_PROTO_STREAM_PLAY;
fDefaultCommandSent = false;
fDefaultFormat = ESD_BITS8 | ESD_MONO;
fDefaultRate = ESD_DEFAULT_RATE;
fLatency = 0LL;
}
status_t
ESDEndpoint::SendAuthKey()
{
CALLED();
BPath kfPath;
status_t err;
off_t size;
char key[ESD_MAX_KEY];
err = find_directory(B_USER_SETTINGS_DIRECTORY, &kfPath);
kfPath.Append("esd_auth");
BFile keyFile(kfPath.Path(), B_READ_WRITE|B_CREATE_FILE);
err = keyFile.GetSize(&size);
if (err < 0)
return err;
if (size < ESD_MAX_KEY) {
keyFile.Seek(0LL, SEEK_SET);
srand(time(NULL));
for (int i = 0; i < ESD_MAX_KEY; i++)
key[i] = (char)(rand() % 256);
err = keyFile.Write(key, ESD_MAX_KEY);
if (err < 0)
return err;
if (err < ESD_MAX_KEY)
return EIO;
}
err = keyFile.Read(key, ESD_MAX_KEY);
if (err < 0)
return err;
if (err < ESD_MAX_KEY)
return EIO;
memcpy(fAuthKey, key, sizeof(esd_key_t));
return write(fSocket, fAuthKey, ESD_MAX_KEY);
}
bool
ESDEndpoint::Connected() const
{
return (fInitStatus == B_OK);
}
status_t
ESDEndpoint::Connect(const char *host, uint16 port)
{
status_t err;
fHost = host;
fPort = port;
err = fConnectThread = spawn_thread(_ConnectThread, "ESDEndpoint Connection", B_LOW_PRIORITY, this);
if (err < B_OK)
return err;
err = resume_thread(fConnectThread);
return err;
}
status_t
ESDEndpoint::WaitForConnect()
{
status_t err;
int32 ret;
err = wait_for_thread(fConnectThread, &ret);
if (err < B_OK)
return err;
return ret;
}
int32
ESDEndpoint::_ConnectThread(void *_arg)
{
ESDEndpoint *_this = (ESDEndpoint *)_arg;
return _this->ConnectThread();
}
int32
ESDEndpoint::ConnectThread(void)
{
const char *host = fHost.String();
uint16 port = fPort;
status_t err;
int flag;
struct timeval oldTimeout;
socklen_t oldTimeoutLen = sizeof(struct timeval);
struct timeval timeout = { 10, 0 };
CALLED();
struct hostent *he;
struct sockaddr_in sin;
he = gethostbyname(host);
PRINT(("gethostbyname(%s) = %p\n", host, he));
if (!he)
return ENOENT;
memcpy((struct in_addr *)&sin.sin_addr, he->h_addr, sizeof(struct in_addr));
if (fSocket > -1)
closesocket(fSocket);
Reset();
fSocket = socket(AF_INET, SOCK_STREAM, 0);
if (fSocket < 0)
return errno;
sin.sin_family = AF_INET;
sin.sin_port = htons( port );
if (getsockopt(fSocket, SOL_SOCKET, SO_RCVTIMEO, (char *)&oldTimeout, &oldTimeoutLen) >= 0) {
setsockopt(fSocket, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(struct timeval));
}
err = connect(fSocket, (struct sockaddr *) &sin, sizeof(sin));
PRINT(("connect: %ld, %s\n", err, strerror(errno)));
setsockopt(fSocket, SOL_SOCKET, SO_RCVTIMEO, (char *)&oldTimeout, sizeof(struct timeval));
if (err < 0)
return errno;
uint32 cmd;
uint32 result;
err = SendAuthKey();
if (err < 0)
return errno;
bigtime_t ping = system_time();
cmd = ESD_ENDIAN_TAG;
err = write(fSocket, &cmd, sizeof(cmd));
if (err < 0)
return errno;
if ((unsigned)err < sizeof(cmd))
return EIO;
read(fSocket, &result, sizeof(result));
if (result != 1)
fprintf(stderr, "ESDEndpoint::Connect: didn't get ok from ESD_PROTO_INIT (%ld)!\n", result);
ping = (system_time() - ping) / 2;
fLatency = ping;
ping = system_time();
cmd = ESD_PROTO_LATENCY;
err = write(fSocket, &cmd, sizeof(cmd));
if (err < 0)
return errno;
if ((unsigned)err < sizeof(cmd))
return EIO;
read(fSocket, &result, sizeof(result));
fprintf(stderr, "ESDEndpoint::Connect: ESD_PROTO_LATENCY: %ld\n", result);
ping = (system_time() - ping) / 2;
bigtime_t serverLatency = result * 1000000LL / (ESD_DEFAULT_RATE * 2 * 2 );
bigtime_t netLatency = (fLatency + ping) / 2;
fprintf(stderr, "ESDEndpoint::Connect: Latency: server: %lld, net1: %lld, net2: %lld\n", serverLatency, fLatency, ping);
fLatency = netLatency + serverLatency;
flag = 1;
setsockopt(fSocket, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));
fInitStatus = B_OK;
return B_OK;
}
status_t
ESDEndpoint::Disconnect()
{
CALLED();
if (fSocket > -1)
closesocket(fSocket);
fSocket = -1;
return B_OK;
}
status_t
ESDEndpoint::SetCommand(esd_command_t cmd)
{
CALLED();
if (fDefaultCommandSent)
return EALREADY;
fDefaultCommand = cmd;
return B_OK;
}
status_t
ESDEndpoint::SetFormat(int bits, int channels, float rate)
{
esd_format_t fmt = 0;
CALLED();
if (fDefaultCommandSent)
return EALREADY;
PRINT(("SetFormat(%d,%d,%f)\n", bits, channels, rate));
switch (bits) {
case 8:
fmt |= ESD_BITS8;
break;
case 16:
fmt |= ESD_BITS16;
break;
default:
return EINVAL;
}
switch (channels) {
case 1:
fmt |= ESD_MONO;
break;
case 2:
fmt |= ESD_STEREO;
break;
default:
return EINVAL;
}
fmt |= ESD_STREAM | ESD_FUNC_PLAY;
PRINT(("SetFormat: %08lx\n", (long)fmt));
fDefaultFormat = fmt;
fDefaultRate = rate;
return B_OK;
}
status_t
ESDEndpoint::GetServerInfo()
{
CALLED();
struct serverinfo {
uint32 ver;
uint32 rate;
uint32 fmt;
} si;
status_t err;
err = SendCommand(ESD_PROTO_SERVER_INFO, (const uint8 *)&si, 0, (uint8 *)&si, sizeof(si));
if (err < 0)
return err;
PRINT(("err 0x%08lx, version: %lu, rate: %lu, fmt: %lu\n", err, si.ver, si.rate, si.fmt));
return B_OK;
}
void
ESDEndpoint::GetFriendlyName(BString &name)
{
name = "ESounD Out";
name << " (" << Host();
name << ":" << Port() << ")";
}
bool
ESDEndpoint::CanSend()
{
CALLED();
return fDefaultCommandSent;
}
ssize_t
ESDEndpoint::Read(void *buffer, size_t size)
{
CALLED();
return EINVAL;
}
ssize_t
ESDEndpoint::Write(const void *buffer, size_t size)
{
status_t err = B_OK;
CALLED();
if (!fDefaultCommandSent)
err = SendDefaultCommand();
if (err < B_OK)
return err;
if (fDefaultFormat & ESD_BITS16) {
size /= 2;
size *= 2;
}
err = write(fSocket, buffer, size);
if ((unsigned)err != size) {
fprintf(stderr, "ESDEndpoint::Write: sent only %ld of %ld!\n", err, size);
if (err < 0)
fprintf(stderr, "ESDEndpoint::Write: %s\n", strerror(errno));
}
if (err < B_OK)
return errno;
return err;
}
status_t
ESDEndpoint::SendCommand(esd_command_t cmd, const uint8 *obuf, size_t olen, uint8 *ibuf, size_t ilen)
{
status_t err;
CALLED();
err = send(fSocket, &cmd, sizeof(cmd), 0);
if (err < B_OK)
return errno;
if (obuf && olen) {
err = send(fSocket, obuf, olen, 0);
if (err < B_OK)
return errno;
}
err = B_OK;
if (ibuf && ilen) {
err = recv(fSocket, ibuf, ilen, 0);
if (err < B_OK)
return errno;
}
return err;
}
status_t
ESDEndpoint::SendDefaultCommand()
{
status_t err;
struct {
esd_format_t format;
esd_rate_t rate;
char name[ESD_MAX_NAME];
} c;
CALLED();
if (fDefaultCommandSent)
return EALREADY;
c.format = fDefaultFormat;
c.rate = fDefaultRate;
strcpy(c.name, "BeOS/Haiku/ZETA Media Kit output");
err = SendCommand(fDefaultCommand, (uint8 *)&c, sizeof(c), NULL, 0);
if (err < B_OK)
return err;
PRINT(("SendCommand: %s\n", strerror(err)));
fDefaultCommandSent = true;
return B_OK;
}