Merge pull request #2076 from birarda/authentication

groundwork for secure node communication via data-server
This commit is contained in:
Philip Rosedale 2014-02-25 10:07:55 -08:00
commit e637e48598
54 changed files with 1641 additions and 4336 deletions

View file

@ -31,13 +31,11 @@ ENDIF(APPLE)
# targets not supported on windows
if (NOT WIN32)
add_subdirectory(animation-server)
add_subdirectory(data-server)
endif (NOT WIN32)
# targets on all platforms
add_subdirectory(assignment-client)
add_subdirectory(domain-server)
add_subdirectory(interface)
add_subdirectory(pairing-server)
add_subdirectory(tests)
add_subdirectory(voxel-edit)

View file

@ -736,12 +736,12 @@ AnimationServer::AnimationServer(int &argc, char **argv) :
::wantLocalDomain = cmdOptionExists(argc, (const char**) argv,local);
if (::wantLocalDomain) {
printf("Local Domain MODE!\n");
nodeList->setDomainIPToLocalhost();
nodeList->getDomainInfo().setIPToLocalhost();
}
const char* domainHostname = getCmdOption(argc, (const char**) argv, "--domain");
if (domainHostname) {
NodeList::getInstance()->setDomainHostname(domainHostname);
NodeList::getInstance()->getDomainInfo().setHostname(domainHostname);
}
const char* packetsPerSecondCommand = getCmdOption(argc, (const char**) argv, "--pps");

View file

@ -79,7 +79,7 @@ void Agent::run() {
// figure out the URL for the script for this agent assignment
QString scriptURLString("http://%1:8080/assignment/%2");
scriptURLString = scriptURLString.arg(NodeList::getInstance()->getDomainIP().toString(),
scriptURLString = scriptURLString.arg(NodeList::getInstance()->getDomainInfo().getIP().toString(),
uuidStringWithoutCurlyBraces(_uuid));
QNetworkAccessManager *networkManager = new QNetworkAccessManager(this);

View file

@ -6,9 +6,11 @@
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
#include <QtCore/QProcess>
#include <QtCore/QThread>
#include <QtCore/QTimer>
#include <AccountManager.h>
#include <Assignment.h>
#include <Logging.h>
#include <NodeList.h>
@ -28,6 +30,11 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) :
QCoreApplication(argc, argv),
_currentAssignment(NULL)
{
setOrganizationName("High Fidelity");
setOrganizationDomain("highfidelity.io");
setApplicationName("assignment-client");
QSettings::setDefaultFormat(QSettings::IniFormat);
// register meta type is required for queued invoke method on Assignment subclasses
// set the logging target to the the CHILD_TARGET_NAME
@ -91,6 +98,10 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) :
// connect our readPendingDatagrams method to the readyRead() signal of the socket
connect(&nodeList->getNodeSocket(), &QUdpSocket::readyRead, this, &AssignmentClient::readPendingDatagrams);
// connections to AccountManager for authentication
connect(&AccountManager::getInstance(), &AccountManager::authRequired,
this, &AssignmentClient::handleAuthenticationRequest);
}
void AssignmentClient::sendAssignmentRequest() {
@ -120,10 +131,10 @@ void AssignmentClient::readPendingDatagrams() {
// switch our nodelist domain IP and port to whoever sent us the assignment
nodeList->setDomainSockAddr(senderSockAddr);
nodeList->setSessionUUID(_currentAssignment->getUUID());
nodeList->getDomainInfo().setSockAddr(senderSockAddr);
nodeList->getDomainInfo().setAssignmentUUID(_currentAssignment->getUUID());
qDebug() << "Destination IP for assignment is" << nodeList->getDomainIP().toString();
qDebug() << "Destination IP for assignment is" << nodeList->getDomainInfo().getIP().toString();
// start the deployed assignment
QThread* workerThread = new QThread(this);
@ -158,6 +169,30 @@ void AssignmentClient::readPendingDatagrams() {
}
}
void AssignmentClient::handleAuthenticationRequest() {
const QString DATA_SERVER_USERNAME_ENV = "HIFI_AC_USERNAME";
const QString DATA_SERVER_PASSWORD_ENV = "HIFI_AC_PASSWORD";
// this node will be using an authentication server, let's make sure we have a username/password
QProcessEnvironment sysEnvironment = QProcessEnvironment::systemEnvironment();
QString username = sysEnvironment.value(DATA_SERVER_USERNAME_ENV);
QString password = sysEnvironment.value(DATA_SERVER_PASSWORD_ENV);
AccountManager& accountManager = AccountManager::getInstance();
if (!username.isEmpty() && !password.isEmpty()) {
// ask the account manager to log us in from the env variables
accountManager.requestAccessToken(username, password);
} else {
qDebug() << "Authentication was requested against" << qPrintable(accountManager.getRootURL().toString())
<< "but both or one of" << qPrintable(DATA_SERVER_USERNAME_ENV)
<< "/" << qPrintable(DATA_SERVER_PASSWORD_ENV) << "are not set. Unable to authenticate.";
return;
}
}
void AssignmentClient::assignmentCompleted() {
// reset the logging target to the the CHILD_TARGET_NAME
Logging::setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME);
@ -175,4 +210,5 @@ void AssignmentClient::assignmentCompleted() {
// reset our NodeList by switching back to unassigned and clearing the list
nodeList->setOwnerType(NodeType::Unassigned);
nodeList->reset();
nodeList->resetNodeInterestSet();
}

View file

@ -21,6 +21,7 @@ private slots:
void sendAssignmentRequest();
void readPendingDatagrams();
void assignmentCompleted();
void handleAuthenticationRequest();
private:
Assignment _requestAssignment;
ThreadedAssignment* _currentAssignment;

View file

@ -1,23 +0,0 @@
cmake_minimum_required(VERSION 2.8)
set(ROOT_DIR ..)
set(MACRO_DIR ${ROOT_DIR}/cmake/macros)
set(TARGET_NAME data-server)
find_package(Qt5Network REQUIRED)
include(${MACRO_DIR}/SetupHifiProject.cmake)
setup_hifi_project(${TARGET_NAME} TRUE)
qt5_use_modules(${TARGET_NAME} Network)
# link the shared hifi library
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR})
# add hiredis as a library
FILE(GLOB HIREDIS_SRCS external/hiredis/*.c)
add_library(hiredis ${HIREDIS_SRCS})
include_directories(external/hiredis/)
target_link_libraries(${TARGET_NAME} hiredis)

View file

@ -1,20 +0,0 @@
#ifndef __HIREDIS_FMACRO_H
#define __HIREDIS_FMACRO_H
#if !defined(_BSD_SOURCE)
#define _BSD_SOURCE
#endif
#if defined(__sun__)
#define _POSIX_C_SOURCE 200112L
#elif defined(__linux__)
#define _XOPEN_SOURCE 600
#else
#define _XOPEN_SOURCE
#endif
#if __APPLE__ && __MACH__
#define _OSX
#endif
#endif

File diff suppressed because it is too large Load diff

View file

@ -1,213 +0,0 @@
/*
* Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __HIREDIS_H
#define __HIREDIS_H
#include <stdio.h> /* for size_t */
#include <stdarg.h> /* for va_list */
#include <sys/time.h> /* for struct timeval */
#define HIREDIS_MAJOR 0
#define HIREDIS_MINOR 11
#define HIREDIS_PATCH 0
#define REDIS_ERR -1
#define REDIS_OK 0
/* When an error occurs, the err flag in a context is set to hold the type of
* error that occured. REDIS_ERR_IO means there was an I/O error and you
* should use the "errno" variable to find out what is wrong.
* For other values, the "errstr" field will hold a description. */
#define REDIS_ERR_IO 1 /* Error in read or write */
#define REDIS_ERR_EOF 3 /* End of file */
#define REDIS_ERR_PROTOCOL 4 /* Protocol error */
#define REDIS_ERR_OOM 5 /* Out of memory */
#define REDIS_ERR_OTHER 2 /* Everything else... */
/* Connection type can be blocking or non-blocking and is set in the
* least significant bit of the flags field in redisContext. */
#define REDIS_BLOCK 0x1
/* Connection may be disconnected before being free'd. The second bit
* in the flags field is set when the context is connected. */
#define REDIS_CONNECTED 0x2
/* The async API might try to disconnect cleanly and flush the output
* buffer and read all subsequent replies before disconnecting.
* This flag means no new commands can come in and the connection
* should be terminated once all replies have been read. */
#define REDIS_DISCONNECTING 0x4
/* Flag specific to the async API which means that the context should be clean
* up as soon as possible. */
#define REDIS_FREEING 0x8
/* Flag that is set when an async callback is executed. */
#define REDIS_IN_CALLBACK 0x10
/* Flag that is set when the async context has one or more subscriptions. */
#define REDIS_SUBSCRIBED 0x20
/* Flag that is set when monitor mode is active */
#define REDIS_MONITORING 0x40
#define REDIS_REPLY_STRING 1
#define REDIS_REPLY_ARRAY 2
#define REDIS_REPLY_INTEGER 3
#define REDIS_REPLY_NIL 4
#define REDIS_REPLY_STATUS 5
#define REDIS_REPLY_ERROR 6
#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */
#define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */
#ifdef __cplusplus
extern "C" {
#endif
/* This is the reply object returned by redisCommand() */
typedef struct redisReply {
int type; /* REDIS_REPLY_* */
long long integer; /* The integer when type is REDIS_REPLY_INTEGER */
int len; /* Length of string */
char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */
size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
} redisReply;
typedef struct redisReadTask {
int type;
int elements; /* number of elements in multibulk container */
int idx; /* index in parent (array) object */
void *obj; /* holds user-generated value for a read task */
struct redisReadTask *parent; /* parent task */
void *privdata; /* user-settable arbitrary field */
} redisReadTask;
typedef struct redisReplyObjectFunctions {
void *(*createString)(const redisReadTask*, char*, size_t);
void *(*createArray)(const redisReadTask*, int);
void *(*createInteger)(const redisReadTask*, long long);
void *(*createNil)(const redisReadTask*);
void (*freeObject)(void*);
} redisReplyObjectFunctions;
/* State for the protocol parser */
typedef struct redisReader {
int err; /* Error flags, 0 when there is no error */
char errstr[128]; /* String representation of error when applicable */
char *buf; /* Read buffer */
size_t pos; /* Buffer cursor */
size_t len; /* Buffer length */
size_t maxbuf; /* Max length of unused buffer */
redisReadTask rstack[9];
int ridx; /* Index of current read task */
void *reply; /* Temporary reply pointer */
redisReplyObjectFunctions *fn;
void *privdata;
} redisReader;
/* Public API for the protocol parser. */
redisReader *redisReaderCreate(void);
void redisReaderFree(redisReader *r);
int redisReaderFeed(redisReader *r, const char *buf, size_t len);
int redisReaderGetReply(redisReader *r, void **reply);
/* Backwards compatibility, can be removed on big version bump. */
#define redisReplyReaderCreate redisReaderCreate
#define redisReplyReaderFree redisReaderFree
#define redisReplyReaderFeed redisReaderFeed
#define redisReplyReaderGetReply redisReaderGetReply
#define redisReplyReaderSetPrivdata(_r, _p) (int)(((redisReader*)(_r))->privdata = (_p))
#define redisReplyReaderGetObject(_r) (((redisReader*)(_r))->reply)
#define redisReplyReaderGetError(_r) (((redisReader*)(_r))->errstr)
/* Function to free the reply objects hiredis returns by default. */
void freeReplyObject(void *reply);
/* Functions to format a command according to the protocol. */
int redisvFormatCommand(char **target, const char *format, va_list ap);
int redisFormatCommand(char **target, const char *format, ...);
int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen);
/* Context for a connection to Redis */
typedef struct redisContext {
int err; /* Error flags, 0 when there is no error */
char errstr[128]; /* String representation of error when applicable */
int fd;
int flags;
char *obuf; /* Write buffer */
redisReader *reader; /* Protocol reader */
} redisContext;
redisContext *redisConnect(const char *ip, int port);
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv);
redisContext *redisConnectNonBlock(const char *ip, int port);
redisContext *redisConnectUnix(const char *path);
redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv);
redisContext *redisConnectUnixNonBlock(const char *path);
int redisSetTimeout(redisContext *c, const struct timeval tv);
int redisEnableKeepAlive(redisContext *c);
void redisFree(redisContext *c);
int redisBufferRead(redisContext *c);
int redisBufferWrite(redisContext *c, int *done);
/* In a blocking context, this function first checks if there are unconsumed
* replies to return and returns one if so. Otherwise, it flushes the output
* buffer to the socket and reads until it has a reply. In a non-blocking
* context, it will return unconsumed replies until there are no more. */
int redisGetReply(redisContext *c, void **reply);
int redisGetReplyFromReader(redisContext *c, void **reply);
/* Write a command to the output buffer. Use these functions in blocking mode
* to get a pipeline of commands. */
int redisvAppendCommand(redisContext *c, const char *format, va_list ap);
int redisAppendCommand(redisContext *c, const char *format, ...);
int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
/* Issue a command to Redis. In a blocking context, it is identical to calling
* redisAppendCommand, followed by redisGetReply. The function will return
* NULL if there was an error in performing the request, otherwise it will
* return the reply. In a non-blocking context, it is identical to calling
* only redisAppendCommand and will always return NULL. */
void *redisvCommand(redisContext *c, const char *format, va_list ap);
void *redisCommand(redisContext *c, const char *format, ...);
void *redisCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -1,339 +0,0 @@
/* Extracted from anet.c to work properly with Hiredis error reporting.
*
* Copyright (c) 2006-2011, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "fmacros.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <netdb.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <poll.h>
#include <limits.h>
#include "net.h"
#include "sds.h"
/* Defined in hiredis.c */
void __redisSetError(redisContext *c, int type, const char *str);
static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) {
char buf[128] = { 0 };
size_t len = 0;
if (prefix != NULL)
len = snprintf(buf,sizeof(buf),"%s: ",prefix);
strerror_r(errno,buf+len,sizeof(buf)-len);
__redisSetError(c,type,buf);
}
static int redisSetReuseAddr(redisContext *c, int fd) {
int on = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
close(fd);
return REDIS_ERR;
}
return REDIS_OK;
}
static int redisCreateSocket(redisContext *c, int type) {
int s;
if ((s = socket(type, SOCK_STREAM, 0)) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
return REDIS_ERR;
}
if (type == AF_INET) {
if (redisSetReuseAddr(c,s) == REDIS_ERR) {
return REDIS_ERR;
}
}
return s;
}
static int redisSetBlocking(redisContext *c, int fd, int blocking) {
int flags;
/* Set the socket nonblocking.
* Note that fcntl(2) for F_GETFL and F_SETFL can't be
* interrupted by a signal. */
if ((flags = fcntl(fd, F_GETFL)) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)");
close(fd);
return REDIS_ERR;
}
if (blocking)
flags &= ~O_NONBLOCK;
else
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)");
close(fd);
return REDIS_ERR;
}
return REDIS_OK;
}
int redisKeepAlive(redisContext *c, int interval) {
int val = 1;
int fd = c->fd;
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
return REDIS_ERR;
}
#ifdef _OSX
val = interval;
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) {
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
return REDIS_ERR;
}
#else
val = interval;
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) {
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
return REDIS_ERR;
}
val = interval/3;
if (val == 0) val = 1;
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val)) < 0) {
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
return REDIS_ERR;
}
val = 3;
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) < 0) {
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
return REDIS_ERR;
}
#endif
return REDIS_OK;
}
static int redisSetTcpNoDelay(redisContext *c, int fd) {
int yes = 1;
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)");
close(fd);
return REDIS_ERR;
}
return REDIS_OK;
}
#define __MAX_MSEC (((LONG_MAX) - 999) / 1000)
static int redisContextWaitReady(redisContext *c, int fd, const struct timeval *timeout) {
struct pollfd wfd[1];
long msec;
msec = -1;
wfd[0].fd = fd;
wfd[0].events = POLLOUT;
/* Only use timeout when not NULL. */
if (timeout != NULL) {
if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) {
__redisSetErrorFromErrno(c, REDIS_ERR_IO, NULL);
close(fd);
return REDIS_ERR;
}
msec = (timeout->tv_sec * 1000) + ((timeout->tv_usec + 999) / 1000);
if (msec < 0 || msec > INT_MAX) {
msec = INT_MAX;
}
}
if (errno == EINPROGRESS) {
int res;
if ((res = poll(wfd, 1, msec)) == -1) {
__redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)");
close(fd);
return REDIS_ERR;
} else if (res == 0) {
errno = ETIMEDOUT;
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
close(fd);
return REDIS_ERR;
}
if (redisCheckSocketError(c, fd) != REDIS_OK)
return REDIS_ERR;
return REDIS_OK;
}
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
close(fd);
return REDIS_ERR;
}
int redisCheckSocketError(redisContext *c, int fd) {
int err = 0;
socklen_t errlen = sizeof(err);
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"getsockopt(SO_ERROR)");
close(fd);
return REDIS_ERR;
}
if (err) {
errno = err;
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
close(fd);
return REDIS_ERR;
}
return REDIS_OK;
}
int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)");
return REDIS_ERR;
}
if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,&tv,sizeof(tv)) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)");
return REDIS_ERR;
}
return REDIS_OK;
}
int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout) {
int s, rv;
char _port[6]; /* strlen("65535"); */
struct addrinfo hints, *servinfo, *p;
int blocking = (c->flags & REDIS_BLOCK);
snprintf(_port, 6, "%d", port);
memset(&hints,0,sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
/* Try with IPv6 if no IPv4 address was found. We do it in this order since
* in a Redis client you can't afford to test if you have IPv6 connectivity
* as this would add latency to every connect. Otherwise a more sensible
* route could be: Use IPv6 if both addresses are available and there is IPv6
* connectivity. */
if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) {
hints.ai_family = AF_INET6;
if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) {
__redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv));
return REDIS_ERR;
}
}
for (p = servinfo; p != NULL; p = p->ai_next) {
if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)
continue;
if (redisSetBlocking(c,s,0) != REDIS_OK)
goto error;
if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
if (errno == EHOSTUNREACH) {
close(s);
continue;
} else if (errno == EINPROGRESS && !blocking) {
/* This is ok. */
} else {
if (redisContextWaitReady(c,s,timeout) != REDIS_OK)
goto error;
}
}
if (blocking && redisSetBlocking(c,s,1) != REDIS_OK)
goto error;
if (redisSetTcpNoDelay(c,s) != REDIS_OK)
goto error;
c->fd = s;
c->flags |= REDIS_CONNECTED;
rv = REDIS_OK;
goto end;
}
if (p == NULL) {
char buf[128];
snprintf(buf,sizeof(buf),"Can't create socket: %s",strerror(errno));
__redisSetError(c,REDIS_ERR_OTHER,buf);
goto error;
}
error:
rv = REDIS_ERR;
end:
freeaddrinfo(servinfo);
return rv; // Need to return REDIS_OK if alright
}
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) {
int s;
int blocking = (c->flags & REDIS_BLOCK);
struct sockaddr_un sa;
if ((s = redisCreateSocket(c,AF_LOCAL)) < 0)
return REDIS_ERR;
if (redisSetBlocking(c,s,0) != REDIS_OK)
return REDIS_ERR;
sa.sun_family = AF_LOCAL;
strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
if (connect(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
if (errno == EINPROGRESS && !blocking) {
/* This is ok. */
} else {
if (redisContextWaitReady(c,s,timeout) != REDIS_OK)
return REDIS_ERR;
}
}
/* Reset socket to be blocking after connect(2). */
if (blocking && redisSetBlocking(c,s,1) != REDIS_OK)
return REDIS_ERR;
c->fd = s;
c->flags |= REDIS_CONNECTED;
return REDIS_OK;
}

View file

@ -1,48 +0,0 @@
/* Extracted from anet.c to work properly with Hiredis error reporting.
*
* Copyright (c) 2006-2011, Salvatore Sanfilippo <antirez at gmail dot com>
* Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __NET_H
#define __NET_H
#include "hiredis.h"
#if defined(__sun)
#define AF_LOCAL AF_UNIX
#endif
int redisCheckSocketError(redisContext *c, int fd);
int redisContextSetTimeout(redisContext *c, const struct timeval tv);
int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout);
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout);
int redisKeepAlive(redisContext *c, int interval);
#endif

View file

@ -1,606 +0,0 @@
/* SDSLib, A C dynamic strings library
*
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "sds.h"
#ifdef SDS_ABORT_ON_OOM
static void sdsOomAbort(void) {
fprintf(stderr,"SDS: Out Of Memory (SDS_ABORT_ON_OOM defined)\n");
abort();
}
#endif
sds sdsnewlen(const void *init, size_t initlen) {
struct sdshdr *sh;
sh = malloc(sizeof(struct sdshdr)+initlen+1);
#ifdef SDS_ABORT_ON_OOM
if (sh == NULL) sdsOomAbort();
#else
if (sh == NULL) return NULL;
#endif
sh->len = initlen;
sh->free = 0;
if (initlen) {
if (init) memcpy(sh->buf, init, initlen);
else memset(sh->buf,0,initlen);
}
sh->buf[initlen] = '\0';
return (char*)sh->buf;
}
sds sdsempty(void) {
return sdsnewlen("",0);
}
sds sdsnew(const char *init) {
size_t initlen = (init == NULL) ? 0 : strlen(init);
return sdsnewlen(init, initlen);
}
sds sdsdup(const sds s) {
return sdsnewlen(s, sdslen(s));
}
void sdsfree(sds s) {
if (s == NULL) return;
free(s-sizeof(struct sdshdr));
}
void sdsupdatelen(sds s) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
int reallen = strlen(s);
sh->free += (sh->len-reallen);
sh->len = reallen;
}
static sds sdsMakeRoomFor(sds s, size_t addlen) {
struct sdshdr *sh, *newsh;
size_t free = sdsavail(s);
size_t len, newlen;
if (free >= addlen) return s;
len = sdslen(s);
sh = (void*) (s-(sizeof(struct sdshdr)));
newlen = (len+addlen)*2;
newsh = realloc(sh, sizeof(struct sdshdr)+newlen+1);
#ifdef SDS_ABORT_ON_OOM
if (newsh == NULL) sdsOomAbort();
#else
if (newsh == NULL) return NULL;
#endif
newsh->free = newlen - len;
return newsh->buf;
}
/* Grow the sds to have the specified length. Bytes that were not part of
* the original length of the sds will be set to zero. */
sds sdsgrowzero(sds s, size_t len) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
size_t totlen, curlen = sh->len;
if (len <= curlen) return s;
s = sdsMakeRoomFor(s,len-curlen);
if (s == NULL) return NULL;
/* Make sure added region doesn't contain garbage */
sh = (void*)(s-(sizeof(struct sdshdr)));
memset(s+curlen,0,(len-curlen+1)); /* also set trailing \0 byte */
totlen = sh->len+sh->free;
sh->len = len;
sh->free = totlen-sh->len;
return s;
}
sds sdscatlen(sds s, const void *t, size_t len) {
struct sdshdr *sh;
size_t curlen = sdslen(s);
s = sdsMakeRoomFor(s,len);
if (s == NULL) return NULL;
sh = (void*) (s-(sizeof(struct sdshdr)));
memcpy(s+curlen, t, len);
sh->len = curlen+len;
sh->free = sh->free-len;
s[curlen+len] = '\0';
return s;
}
sds sdscat(sds s, const char *t) {
return sdscatlen(s, t, strlen(t));
}
sds sdscpylen(sds s, char *t, size_t len) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
size_t totlen = sh->free+sh->len;
if (totlen < len) {
s = sdsMakeRoomFor(s,len-sh->len);
if (s == NULL) return NULL;
sh = (void*) (s-(sizeof(struct sdshdr)));
totlen = sh->free+sh->len;
}
memcpy(s, t, len);
s[len] = '\0';
sh->len = len;
sh->free = totlen-len;
return s;
}
sds sdscpy(sds s, char *t) {
return sdscpylen(s, t, strlen(t));
}
sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
va_list cpy;
char *buf, *t;
size_t buflen = 16;
while(1) {
buf = malloc(buflen);
#ifdef SDS_ABORT_ON_OOM
if (buf == NULL) sdsOomAbort();
#else
if (buf == NULL) return NULL;
#endif
buf[buflen-2] = '\0';
va_copy(cpy,ap);
vsnprintf(buf, buflen, fmt, cpy);
va_end(cpy);
if (buf[buflen-2] != '\0') {
free(buf);
buflen *= 2;
continue;
}
break;
}
t = sdscat(s, buf);
free(buf);
return t;
}
sds sdscatprintf(sds s, const char *fmt, ...) {
va_list ap;
char *t;
va_start(ap, fmt);
t = sdscatvprintf(s,fmt,ap);
va_end(ap);
return t;
}
sds sdstrim(sds s, const char *cset) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
char *start, *end, *sp, *ep;
size_t len;
sp = start = s;
ep = end = s+sdslen(s)-1;
while(sp <= end && strchr(cset, *sp)) sp++;
while(ep > start && strchr(cset, *ep)) ep--;
len = (sp > ep) ? 0 : ((ep-sp)+1);
if (sh->buf != sp) memmove(sh->buf, sp, len);
sh->buf[len] = '\0';
sh->free = sh->free+(sh->len-len);
sh->len = len;
return s;
}
sds sdsrange(sds s, int start, int end) {
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
size_t newlen, len = sdslen(s);
if (len == 0) return s;
if (start < 0) {
start = len+start;
if (start < 0) start = 0;
}
if (end < 0) {
end = len+end;
if (end < 0) end = 0;
}
newlen = (start > end) ? 0 : (end-start)+1;
if (newlen != 0) {
if (start >= (signed)len) {
newlen = 0;
} else if (end >= (signed)len) {
end = len-1;
newlen = (start > end) ? 0 : (end-start)+1;
}
} else {
start = 0;
}
if (start && newlen) memmove(sh->buf, sh->buf+start, newlen);
sh->buf[newlen] = 0;
sh->free = sh->free+(sh->len-newlen);
sh->len = newlen;
return s;
}
void sdstolower(sds s) {
int len = sdslen(s), j;
for (j = 0; j < len; j++) s[j] = tolower(s[j]);
}
void sdstoupper(sds s) {
int len = sdslen(s), j;
for (j = 0; j < len; j++) s[j] = toupper(s[j]);
}
int sdscmp(sds s1, sds s2) {
size_t l1, l2, minlen;
int cmp;
l1 = sdslen(s1);
l2 = sdslen(s2);
minlen = (l1 < l2) ? l1 : l2;
cmp = memcmp(s1,s2,minlen);
if (cmp == 0) return l1-l2;
return cmp;
}
/* Split 's' with separator in 'sep'. An array
* of sds strings is returned. *count will be set
* by reference to the number of tokens returned.
*
* On out of memory, zero length string, zero length
* separator, NULL is returned.
*
* Note that 'sep' is able to split a string using
* a multi-character separator. For example
* sdssplit("foo_-_bar","_-_"); will return two
* elements "foo" and "bar".
*
* This version of the function is binary-safe but
* requires length arguments. sdssplit() is just the
* same function but for zero-terminated strings.
*/
sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count) {
int elements = 0, slots = 5, start = 0, j;
sds *tokens = malloc(sizeof(sds)*slots);
#ifdef SDS_ABORT_ON_OOM
if (tokens == NULL) sdsOomAbort();
#endif
if (seplen < 1 || len < 0 || tokens == NULL) return NULL;
if (len == 0) {
*count = 0;
return tokens;
}
for (j = 0; j < (len-(seplen-1)); j++) {
/* make sure there is room for the next element and the final one */
if (slots < elements+2) {
sds *newtokens;
slots *= 2;
newtokens = realloc(tokens,sizeof(sds)*slots);
if (newtokens == NULL) {
#ifdef SDS_ABORT_ON_OOM
sdsOomAbort();
#else
goto cleanup;
#endif
}
tokens = newtokens;
}
/* search the separator */
if ((seplen == 1 && *(s+j) == sep[0]) || (memcmp(s+j,sep,seplen) == 0)) {
tokens[elements] = sdsnewlen(s+start,j-start);
if (tokens[elements] == NULL) {
#ifdef SDS_ABORT_ON_OOM
sdsOomAbort();
#else
goto cleanup;
#endif
}
elements++;
start = j+seplen;
j = j+seplen-1; /* skip the separator */
}
}
/* Add the final element. We are sure there is room in the tokens array. */
tokens[elements] = sdsnewlen(s+start,len-start);
if (tokens[elements] == NULL) {
#ifdef SDS_ABORT_ON_OOM
sdsOomAbort();
#else
goto cleanup;
#endif
}
elements++;
*count = elements;
return tokens;
#ifndef SDS_ABORT_ON_OOM
cleanup:
{
int i;
for (i = 0; i < elements; i++) sdsfree(tokens[i]);
free(tokens);
return NULL;
}
#endif
}
void sdsfreesplitres(sds *tokens, int count) {
if (!tokens) return;
while(count--)
sdsfree(tokens[count]);
free(tokens);
}
sds sdsfromlonglong(long long value) {
char buf[32], *p;
unsigned long long v;
v = (value < 0) ? -value : value;
p = buf+31; /* point to the last character */
do {
*p-- = '0'+(v%10);
v /= 10;
} while(v);
if (value < 0) *p-- = '-';
p++;
return sdsnewlen(p,32-(p-buf));
}
sds sdscatrepr(sds s, char *p, size_t len) {
s = sdscatlen(s,"\"",1);
if (s == NULL) return NULL;
while(len--) {
switch(*p) {
case '\\':
case '"':
s = sdscatprintf(s,"\\%c",*p);
break;
case '\n': s = sdscatlen(s,"\\n",2); break;
case '\r': s = sdscatlen(s,"\\r",2); break;
case '\t': s = sdscatlen(s,"\\t",2); break;
case '\a': s = sdscatlen(s,"\\a",2); break;
case '\b': s = sdscatlen(s,"\\b",2); break;
default:
if (isprint(*p))
s = sdscatprintf(s,"%c",*p);
else
s = sdscatprintf(s,"\\x%02x",(unsigned char)*p);
break;
}
p++;
if (s == NULL) return NULL;
}
return sdscatlen(s,"\"",1);
}
/* Split a line into arguments, where every argument can be in the
* following programming-language REPL-alike form:
*
* foo bar "newline are supported\n" and "\xff\x00otherstuff"
*
* The number of arguments is stored into *argc, and an array
* of sds is returned. The caller should sdsfree() all the returned
* strings and finally free() the array itself.
*
* Note that sdscatrepr() is able to convert back a string into
* a quoted string in the same format sdssplitargs() is able to parse.
*/
sds *sdssplitargs(char *line, int *argc) {
char *p = line;
char *current = NULL;
char **vector = NULL, **_vector = NULL;
*argc = 0;
while(1) {
/* skip blanks */
while(*p && isspace(*p)) p++;
if (*p) {
/* get a token */
int inq=0; /* set to 1 if we are in "quotes" */
int done=0;
if (current == NULL) {
current = sdsempty();
if (current == NULL) goto err;
}
while(!done) {
if (inq) {
if (*p == '\\' && *(p+1)) {
char c;
p++;
switch(*p) {
case 'n': c = '\n'; break;
case 'r': c = '\r'; break;
case 't': c = '\t'; break;
case 'b': c = '\b'; break;
case 'a': c = '\a'; break;
default: c = *p; break;
}
current = sdscatlen(current,&c,1);
} else if (*p == '"') {
/* closing quote must be followed by a space */
if (*(p+1) && !isspace(*(p+1))) goto err;
done=1;
} else if (!*p) {
/* unterminated quotes */
goto err;
} else {
current = sdscatlen(current,p,1);
}
} else {
switch(*p) {
case ' ':
case '\n':
case '\r':
case '\t':
case '\0':
done=1;
break;
case '"':
inq=1;
break;
default:
current = sdscatlen(current,p,1);
break;
}
}
if (*p) p++;
if (current == NULL) goto err;
}
/* add the token to the vector */
_vector = realloc(vector,((*argc)+1)*sizeof(char*));
if (_vector == NULL) goto err;
vector = _vector;
vector[*argc] = current;
(*argc)++;
current = NULL;
} else {
return vector;
}
}
err:
while((*argc)--)
sdsfree(vector[*argc]);
if (vector != NULL) free(vector);
if (current != NULL) sdsfree(current);
return NULL;
}
#ifdef SDS_TEST_MAIN
#include <stdio.h>
int __failed_tests = 0;
int __test_num = 0;
#define test_cond(descr,_c) do { \
__test_num++; printf("%d - %s: ", __test_num, descr); \
if(_c) printf("PASSED\n"); else {printf("FAILED\n"); __failed_tests++;} \
} while(0);
#define test_report() do { \
printf("%d tests, %d passed, %d failed\n", __test_num, \
__test_num-__failed_tests, __failed_tests); \
if (__failed_tests) { \
printf("=== WARNING === We have failed tests here...\n"); \
} \
} while(0);
int main(void) {
{
sds x = sdsnew("foo"), y;
test_cond("Create a string and obtain the length",
sdslen(x) == 3 && memcmp(x,"foo\0",4) == 0)
sdsfree(x);
x = sdsnewlen("foo",2);
test_cond("Create a string with specified length",
sdslen(x) == 2 && memcmp(x,"fo\0",3) == 0)
x = sdscat(x,"bar");
test_cond("Strings concatenation",
sdslen(x) == 5 && memcmp(x,"fobar\0",6) == 0);
x = sdscpy(x,"a");
test_cond("sdscpy() against an originally longer string",
sdslen(x) == 1 && memcmp(x,"a\0",2) == 0)
x = sdscpy(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk");
test_cond("sdscpy() against an originally shorter string",
sdslen(x) == 33 &&
memcmp(x,"xyzxxxxxxxxxxyyyyyyyyyykkkkkkkkkk\0",33) == 0)
sdsfree(x);
x = sdscatprintf(sdsempty(),"%d",123);
test_cond("sdscatprintf() seems working in the base case",
sdslen(x) == 3 && memcmp(x,"123\0",4) ==0)
sdsfree(x);
x = sdstrim(sdsnew("xxciaoyyy"),"xy");
test_cond("sdstrim() correctly trims characters",
sdslen(x) == 4 && memcmp(x,"ciao\0",5) == 0)
y = sdsrange(sdsdup(x),1,1);
test_cond("sdsrange(...,1,1)",
sdslen(y) == 1 && memcmp(y,"i\0",2) == 0)
sdsfree(y);
y = sdsrange(sdsdup(x),1,-1);
test_cond("sdsrange(...,1,-1)",
sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0)
sdsfree(y);
y = sdsrange(sdsdup(x),-2,-1);
test_cond("sdsrange(...,-2,-1)",
sdslen(y) == 2 && memcmp(y,"ao\0",3) == 0)
sdsfree(y);
y = sdsrange(sdsdup(x),2,1);
test_cond("sdsrange(...,2,1)",
sdslen(y) == 0 && memcmp(y,"\0",1) == 0)
sdsfree(y);
y = sdsrange(sdsdup(x),1,100);
test_cond("sdsrange(...,1,100)",
sdslen(y) == 3 && memcmp(y,"iao\0",4) == 0)
sdsfree(y);
y = sdsrange(sdsdup(x),100,100);
test_cond("sdsrange(...,100,100)",
sdslen(y) == 0 && memcmp(y,"\0",1) == 0)
sdsfree(y);
sdsfree(x);
x = sdsnew("foo");
y = sdsnew("foa");
test_cond("sdscmp(foo,foa)", sdscmp(x,y) > 0)
sdsfree(y);
sdsfree(x);
x = sdsnew("bar");
y = sdsnew("bar");
test_cond("sdscmp(bar,bar)", sdscmp(x,y) == 0)
sdsfree(y);
sdsfree(x);
x = sdsnew("aar");
y = sdsnew("bar");
test_cond("sdscmp(bar,bar)", sdscmp(x,y) < 0)
}
test_report()
}
#endif

View file

@ -1,88 +0,0 @@
/* SDSLib, A C dynamic strings library
*
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __SDS_H
#define __SDS_H
#include <sys/types.h>
#include <stdarg.h>
typedef char *sds;
struct sdshdr {
int len;
int free;
char buf[];
};
static inline size_t sdslen(const sds s) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
return sh->len;
}
static inline size_t sdsavail(const sds s) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
return sh->free;
}
sds sdsnewlen(const void *init, size_t initlen);
sds sdsnew(const char *init);
sds sdsempty(void);
size_t sdslen(const sds s);
sds sdsdup(const sds s);
void sdsfree(sds s);
size_t sdsavail(sds s);
sds sdsgrowzero(sds s, size_t len);
sds sdscatlen(sds s, const void *t, size_t len);
sds sdscat(sds s, const char *t);
sds sdscpylen(sds s, char *t, size_t len);
sds sdscpy(sds s, char *t);
sds sdscatvprintf(sds s, const char *fmt, va_list ap);
#ifdef __GNUC__
sds sdscatprintf(sds s, const char *fmt, ...)
__attribute__((format(printf, 2, 3)));
#else
sds sdscatprintf(sds s, const char *fmt, ...);
#endif
sds sdstrim(sds s, const char *cset);
sds sdsrange(sds s, int start, int end);
void sdsupdatelen(sds s);
int sdscmp(sds s1, sds s2);
sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count);
void sdsfreesplitres(sds *tokens, int count);
void sdstolower(sds s);
void sdstoupper(sds s);
sds sdsfromlonglong(long long value);
sds sdscatrepr(sds s, char *p, size_t len);
sds *sdssplitargs(char *line, int *argc);
#endif

View file

@ -1,191 +0,0 @@
//
// DataServer.cpp
// hifi
//
// Created by Stephen Birarda on 1/20/2014.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
#include <QtCore/QDataStream>
#include <QtCore/QStringList>
#include <QtCore/QUuid>
#include <PacketHeaders.h>
#include <HifiSockAddr.h>
#include <UUID.h>
#include "DataServer.h"
const quint16 DATA_SERVER_LISTEN_PORT = 3282;
const char REDIS_HOSTNAME[] = "127.0.0.1";
const unsigned short REDIS_PORT = 6379;
DataServer::DataServer(int argc, char* argv[]) :
QCoreApplication(argc, argv),
_socket(),
_redis(NULL),
_uuid(QUuid::createUuid())
{
_socket.bind(QHostAddress::Any, DATA_SERVER_LISTEN_PORT);
connect(&_socket, &QUdpSocket::readyRead, this, &DataServer::readPendingDatagrams);
_redis = redisConnect(REDIS_HOSTNAME, REDIS_PORT);
if (_redis && _redis->err) {
if (_redis) {
qDebug() << "Redis connection error:" << _redis->errstr;
} else {
qDebug("Redis connection error - can't allocate redis context.");
}
// couldn't connect to redis, bail out
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
}
}
DataServer::~DataServer() {
// disconnect from redis and free the context
if (_redis) {
redisFree(_redis);
}
}
const int MAX_PACKET_SIZE = 1500;
void DataServer::readPendingDatagrams() {
QByteArray receivedPacket;
HifiSockAddr senderSockAddr;
while (_socket.hasPendingDatagrams()) {
receivedPacket.resize(_socket.pendingDatagramSize());
_socket.readDatagram(receivedPacket.data(), _socket.pendingDatagramSize(),
senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer());
PacketType requestType = packetTypeForPacket(receivedPacket);
if ((requestType == PacketTypeDataServerPut || requestType == PacketTypeDataServerGet) &&
receivedPacket[numBytesArithmeticCodingFromBuffer(receivedPacket.data())] == versionForPacketType(requestType)) {
QDataStream packetStream(receivedPacket);
int numReceivedHeaderBytes = numBytesForPacketHeader(receivedPacket);
packetStream.skipRawData(numReceivedHeaderBytes);
// pull the sequence number used for this packet
quint8 sequenceNumber = 0;
packetStream >> sequenceNumber;
// pull the UUID that we will need as part of the key
QString userString;
packetStream >> userString;
QUuid parsedUUID(userString);
if (parsedUUID.isNull()) {
// we failed to parse a UUID, this means the user has sent us a username
// ask redis for the UUID for this user
redisReply* reply = (redisReply*) redisCommand(_redis, "GET user:%s", qPrintable(userString));
if (reply->type == REDIS_REPLY_STRING) {
parsedUUID = QUuid(QString(reply->str));
}
if (!parsedUUID.isNull()) {
qDebug() << "Found UUID" << parsedUUID << "for username" << userString;
} else {
qDebug() << "Failed UUID lookup for username" << userString;
}
freeReplyObject(reply);
reply = NULL;
}
if (!parsedUUID.isNull()) {
if (requestType == PacketTypeDataServerPut) {
// pull the key and value that specifies the data the user is putting/getting
QString dataKey, dataValue;
packetStream >> dataKey >> dataValue;
qDebug("Sending command to redis: SET uuid:%s:%s %s",
qPrintable(uuidStringWithoutCurlyBraces(parsedUUID)),
qPrintable(dataKey), qPrintable(dataValue));
redisReply* reply = (redisReply*) redisCommand(_redis, "SET uuid:%s:%s %s",
qPrintable(uuidStringWithoutCurlyBraces(parsedUUID)),
qPrintable(dataKey), qPrintable(dataValue));
if (reply->type == REDIS_REPLY_STATUS && strcmp("OK", reply->str) == 0) {
// if redis stored the value successfully reply back with a confirm
// which is a reply packet with the sequence number
QByteArray replyPacket = byteArrayWithPopulatedHeader(PacketTypeDataServerConfirm, _uuid);
replyPacket.append(sequenceNumber);
_socket.writeDatagram(replyPacket, senderSockAddr.getAddress(), senderSockAddr.getPort());
}
freeReplyObject(reply);
} else {
// setup a send packet with the returned data
// leverage the packetData sent by overwriting and appending
QByteArray sendPacket = byteArrayWithPopulatedHeader(PacketTypeDataServerSend, _uuid);
QDataStream sendPacketStream(&sendPacket, QIODevice::Append);
sendPacketStream << sequenceNumber;
// pull the key list that specifies the data the user is putting/getting
QString keyListString;
packetStream >> keyListString;
if (keyListString != "uuid") {
// copy the parsed UUID
sendPacketStream << uuidStringWithoutCurlyBraces(parsedUUID);
const char MULTI_KEY_VALUE_SEPARATOR = '|';
// append the keyListString back to the sendPacket
sendPacketStream << keyListString;
QStringList keyList = keyListString.split(MULTI_KEY_VALUE_SEPARATOR);
QStringList valueList;
foreach (const QString& dataKey, keyList) {
qDebug("Sending command to redis: GET uuid:%s:%s",
qPrintable(uuidStringWithoutCurlyBraces(parsedUUID)),
qPrintable(dataKey));
redisReply* reply = (redisReply*) redisCommand(_redis, "GET uuid:%s:%s",
qPrintable(uuidStringWithoutCurlyBraces(parsedUUID)),
qPrintable(dataKey));
if (reply->len) {
// copy the value that redis returned
valueList << QString(reply->str);
} else {
// didn't find a value - insert a space
valueList << QChar(' ');
}
freeReplyObject(reply);
}
// append the value QStringList using the right separator
sendPacketStream << valueList.join(MULTI_KEY_VALUE_SEPARATOR);
} else {
// user was asking for their UUID
sendPacketStream << userString;
sendPacketStream << QString("uuid");
sendPacketStream << uuidStringWithoutCurlyBraces(parsedUUID);
}
// reply back with the send packet
_socket.writeDatagram(sendPacket, senderSockAddr.getAddress(), senderSockAddr.getPort());
}
}
}
}
}

View file

@ -1,31 +0,0 @@
//
// DataServer.h
// hifi
//
// Created by Stephen Birarda on 1/20/2014.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
#ifndef __hifi__DataServer__
#define __hifi__DataServer__
#include <QtCore/QCoreApplication>
#include <QtCore/QUuid>
#include <QtNetwork/QUdpSocket>
#include <hiredis.h>
class DataServer : public QCoreApplication {
Q_OBJECT
public:
DataServer(int argc, char* argv[]);
~DataServer();
private:
QUdpSocket _socket;
redisContext* _redis;
QUuid _uuid;
private slots:
void readPendingDatagrams();
};
#endif /* defined(__hifi__DataServer__) */

View file

@ -1,28 +0,0 @@
//
// main.cpp
// data-server
//
// Created by Stephen Birarda on 1/1/13.
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
#include <QtCore/QCoreApplication>
#include <Logging.h>
#include "DataServer.h"
const char DATA_SERVER_LOGGING_TARGET_NAME[] = "data-server";
int main(int argc, char* argv[]) {
setvbuf(stdout, NULL, _IOLBF, 0);
qInstallMessageHandler(Logging::verboseMessageHandler);
Logging::setTargetName(DATA_SERVER_LOGGING_TARGET_NAME);
DataServer dataServer(argc, argv);
return dataServer.exec();
}

View file

@ -12,10 +12,11 @@
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonArray>
#include <QtCore/QProcess>
#include <QtCore/QStandardPaths>
#include <QtCore/QStringList>
#include <QtCore/QTimer>
#include <AccountManager.h>
#include <HTTPConnection.h>
#include <PacketHeaders.h>
#include <SharedUtil.h>
@ -25,12 +26,6 @@
#include "DomainServer.h"
const int RESTART_HOLD_TIME_MSECS = 5 * 1000;
const char* VOXEL_SERVER_CONFIG = "voxelServerConfig";
const char* PARTICLE_SERVER_CONFIG = "particleServerConfig";
const char* METAVOXEL_SERVER_CONFIG = "metavoxelServerConfig";
const quint16 DOMAIN_SERVER_HTTP_PORT = 8080;
DomainServer::DomainServer(int argc, char* argv[]) :
@ -38,39 +33,131 @@ DomainServer::DomainServer(int argc, char* argv[]) :
_HTTPManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this),
_staticAssignmentHash(),
_assignmentQueue(),
_hasCompletedRestartHold(false)
_nodeAuthenticationURL(),
_redeemedTokenResponses()
{
const char CUSTOM_PORT_OPTION[] = "-p";
const char* customPortString = getCmdOption(argc, (const char**) argv, CUSTOM_PORT_OPTION);
unsigned short domainServerPort = customPortString ? atoi(customPortString) : DEFAULT_DOMAIN_SERVER_PORT;
setOrganizationName("High Fidelity");
setOrganizationDomain("highfidelity.io");
setApplicationName("domain-server");
QSettings::setDefaultFormat(QSettings::IniFormat);
QStringList argumentList = arguments();
_argumentList = arguments();
int argumentIndex = 0;
// check if this domain server should use no authentication or a custom hostname for authentication
const QString DEFAULT_AUTH_OPTION = "--defaultAuth";
const QString CUSTOM_AUTH_OPTION = "--customAuth";
if ((argumentIndex = _argumentList.indexOf(DEFAULT_AUTH_OPTION) != -1)) {
_nodeAuthenticationURL = QUrl(DEFAULT_NODE_AUTH_URL);
} else if ((argumentIndex = _argumentList.indexOf(CUSTOM_AUTH_OPTION)) != -1) {
_nodeAuthenticationURL = QUrl(_argumentList.value(argumentIndex + 1));
}
if (!_nodeAuthenticationURL.isEmpty()) {
const QString DATA_SERVER_USERNAME_ENV = "HIFI_DS_USERNAME";
const QString DATA_SERVER_PASSWORD_ENV = "HIFI_DS_PASSWORD";
// this node will be using an authentication server, let's make sure we have a username/password
QProcessEnvironment sysEnvironment = QProcessEnvironment::systemEnvironment();
QString username = sysEnvironment.value(DATA_SERVER_USERNAME_ENV);
QString password = sysEnvironment.value(DATA_SERVER_PASSWORD_ENV);
AccountManager& accountManager = AccountManager::getInstance();
accountManager.setRootURL(_nodeAuthenticationURL);
if (!username.isEmpty() && !password.isEmpty()) {
connect(&accountManager, &AccountManager::loginComplete, this, &DomainServer::requestCreationFromDataServer);
// ask the account manager to log us in from the env variables
accountManager.requestAccessToken(username, password);
} else {
qDebug() << "Authentication was requested against" << qPrintable(_nodeAuthenticationURL.toString())
<< "but both or one of" << qPrintable(DATA_SERVER_USERNAME_ENV)
<< "/" << qPrintable(DATA_SERVER_PASSWORD_ENV) << "are not set. Qutting!";
// bail out
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
return;
}
} else {
// auth is not requested for domain-server, setup NodeList and assignments now
setupNodeListAndAssignments();
}
}
void DomainServer::requestCreationFromDataServer() {
// this slot is fired when we get a valid access token from the data-server
// now let's ask it to set us up with a UUID
JSONCallbackParameters callbackParams;
callbackParams.jsonCallbackReceiver = this;
callbackParams.jsonCallbackMethod = "processCreateResponseFromDataServer";
AccountManager::getInstance().authenticatedRequest("/api/v1/domains/create",
QNetworkAccessManager::PostOperation,
callbackParams);
}
void DomainServer::processCreateResponseFromDataServer(const QJsonObject& jsonObject) {
if (jsonObject["status"].toString() == "success") {
// pull out the UUID the data-server is telling us to use, and complete our setup with it
QUuid newSessionUUID = QUuid(jsonObject["data"].toObject()["uuid"].toString());
setupNodeListAndAssignments(newSessionUUID);
}
}
void DomainServer::processTokenRedeemResponse(const QJsonObject& jsonObject) {
// pull out the registration token this is associated with
QString registrationToken = jsonObject["data"].toObject()["registration_token"].toString();
// if we have a registration token add it to our hash of redeemed token responses
if (!registrationToken.isEmpty()) {
qDebug() << "Redeemed registration token" << registrationToken;
_redeemedTokenResponses.insert(registrationToken, jsonObject);
}
}
void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
int argumentIndex = 0;
const QString CUSTOM_PORT_OPTION = "-p";
unsigned short domainServerPort = DEFAULT_DOMAIN_SERVER_PORT;
if ((argumentIndex = _argumentList.indexOf(CUSTOM_PORT_OPTION)) != -1) {
domainServerPort = _argumentList.value(argumentIndex + 1).toUShort();
}
QSet<Assignment::Type> parsedTypes(QSet<Assignment::Type>() << Assignment::AgentType);
parseCommandLineTypeConfigs(argumentList, parsedTypes);
parseCommandLineTypeConfigs(_argumentList, parsedTypes);
const QString CONFIG_FILE_OPTION = "--configFile";
if ((argumentIndex = argumentList.indexOf(CONFIG_FILE_OPTION)) != -1) {
QString configFilePath = argumentList.value(argumentIndex + 1);
if ((argumentIndex = _argumentList.indexOf(CONFIG_FILE_OPTION)) != -1) {
QString configFilePath = _argumentList.value(argumentIndex + 1);
readConfigFile(configFilePath, parsedTypes);
}
populateDefaultStaticAssignmentsExcludingTypes(parsedTypes);
NodeList* nodeList = NodeList::createInstance(NodeType::DomainServer, domainServerPort);
// create a random UUID for this session for the domain-server
nodeList->setSessionUUID(sessionUUID);
connect(nodeList, &NodeList::nodeAdded, this, &DomainServer::nodeAdded);
connect(nodeList, &NodeList::nodeKilled, this, &DomainServer::nodeKilled);
QTimer* silentNodeTimer = new QTimer(this);
connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes()));
silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000);
connect(&nodeList->getNodeSocket(), SIGNAL(readyRead()), SLOT(readAvailableDatagrams()));
// fire a single shot timer to add static assignments back into the queue after a restart
QTimer::singleShot(RESTART_HOLD_TIME_MSECS, this, SLOT(addStaticAssignmentsBackToQueueAfterRestart()));
// add whatever static assignments that have been parsed to the queue
addStaticAssignmentsToQueue();
}
void DomainServer::parseCommandLineTypeConfigs(const QStringList& argumentList, QSet<Assignment::Type>& excludedTypes) {
@ -212,24 +299,177 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet<Ass
}
}
void DomainServer::requestAuthenticationFromPotentialNode(const HifiSockAddr& senderSockAddr) {
// this is a node we do not recognize and we need authentication - ask them to do so
// by providing them the hostname they should authenticate with
QByteArray authenticationRequestPacket = byteArrayWithPopulatedHeader(PacketTypeDomainServerAuthRequest);
QDataStream authPacketStream(&authenticationRequestPacket, QIODevice::Append);
authPacketStream << _nodeAuthenticationURL;
qDebug() << "Asking node at" << senderSockAddr << "to authenticate.";
// send the authentication request back to the node
NodeList::getInstance()->getNodeSocket().writeDatagram(authenticationRequestPacket,
senderSockAddr.getAddress(), senderSockAddr.getPort());
}
const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer
<< NodeType::AvatarMixer << NodeType::VoxelServer << NodeType::ParticleServer
<< NodeType::MetavoxelServer;
void DomainServer::addNodeToNodeListAndConfirmConnection(const QByteArray& packet, const HifiSockAddr& senderSockAddr,
const QJsonObject& authJsonObject) {
NodeType_t nodeType;
HifiSockAddr publicSockAddr, localSockAddr;
int numPreInterestBytes = parseNodeDataFromByteArray(nodeType, publicSockAddr, localSockAddr, packet, senderSockAddr);
QUuid assignmentUUID = uuidFromPacketHeader(packet);
SharedAssignmentPointer matchingAssignment;
if (!assignmentUUID.isNull() && (matchingAssignment = matchingStaticAssignmentForCheckIn(assignmentUUID, nodeType))
&& matchingAssignment) {
// this is an assigned node, make sure the UUID sent is for an assignment we're actually trying to give out
// remove the matching assignment from the assignment queue so we don't take the next check in
// (if it exists)
removeMatchingAssignmentFromQueue(matchingAssignment);
} else {
assignmentUUID = QUuid();
}
// create a new session UUID for this node
QUuid nodeUUID = QUuid::createUuid();
SharedNodePointer newNode = NodeList::getInstance()->addOrUpdateNode(nodeUUID, nodeType, publicSockAddr, localSockAddr);
// when the newNode is created the linked data is also created, if this was a static assignment set the UUID
reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData())->setStaticAssignmentUUID(assignmentUUID);
if (!authJsonObject.isEmpty()) {
// pull the connection secret from the authJsonObject and set it as the connection secret for this node
QUuid connectionSecret(authJsonObject["data"].toObject()["connection_secret"].toString());
newNode->setConnectionSecret(connectionSecret);
}
// reply back to the user with a PacketTypeDomainList
sendDomainListToNode(newNode, senderSockAddr, nodeInterestListFromPacket(packet, numPreInterestBytes));
}
const int NUM_BYTES_DATA_SERVER_REGISTRATION_TOKEN = 16;
int DomainServer::parseNodeDataFromByteArray(NodeType_t& nodeType, HifiSockAddr& publicSockAddr,
HifiSockAddr& localSockAddr, const QByteArray& packet,
const HifiSockAddr& senderSockAddr) {
QDataStream packetStream(packet);
packetStream.skipRawData(numBytesForPacketHeader(packet));
if (packetTypeForPacket(packet) == PacketTypeDomainConnectRequest) {
// we need to skip a quint8 that indicates if there is a registration token
// and potentially the registration token itself
quint8 hasRegistrationToken;
packetStream >> hasRegistrationToken;
if (hasRegistrationToken) {
QByteArray registrationToken;
packetStream >> registrationToken;
}
}
packetStream >> nodeType;
packetStream >> publicSockAddr >> localSockAddr;
if (publicSockAddr.getAddress().isNull()) {
// this node wants to use us its STUN server
// so set the node public address to whatever we perceive the public address to be
// if the sender is on our box then leave its public address to 0 so that
// other users attempt to reach it on the same address they have for the domain-server
if (senderSockAddr.getAddress().isLoopback()) {
publicSockAddr.setAddress(QHostAddress());
} else {
publicSockAddr.setAddress(senderSockAddr.getAddress());
}
}
return packetStream.device()->pos();
}
NodeSet DomainServer::nodeInterestListFromPacket(const QByteArray& packet, int numPreceedingBytes) {
QDataStream packetStream(packet);
packetStream.skipRawData(numPreceedingBytes);
quint8 numInterestTypes = 0;
packetStream >> numInterestTypes;
quint8 nodeType;
NodeSet nodeInterestSet;
for (int i = 0; i < numInterestTypes; i++) {
packetStream >> nodeType;
nodeInterestSet.insert((NodeType_t) nodeType);
}
return nodeInterestSet;
}
void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr &senderSockAddr,
const NodeSet& nodeInterestList) {
QByteArray broadcastPacket = byteArrayWithPopulatedHeader(PacketTypeDomainList);
// always send the node their own UUID back
QDataStream broadcastDataStream(&broadcastPacket, QIODevice::Append);
broadcastDataStream << node->getUUID();
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
NodeList* nodeList = NodeList::getInstance();
if (nodeInterestList.size() > 0) {
// if the node has any interest types, send back those nodes as well
foreach (const SharedNodePointer& otherNode, nodeList->getNodeHash()) {
if (otherNode->getUUID() != node->getUUID() && nodeInterestList.contains(otherNode->getType())) {
// don't send avatar nodes to other avatars, that will come from avatar mixer
broadcastDataStream << *otherNode.data();
// pack the secret that these two nodes will use to communicate with each other
QUuid secretUUID = nodeData->getSessionSecretHash().value(otherNode->getUUID());
if (secretUUID.isNull()) {
// generate a new secret UUID these two nodes can use
secretUUID = QUuid::createUuid();
// set that on the current Node's sessionSecretHash
nodeData->getSessionSecretHash().insert(otherNode->getUUID(), secretUUID);
// set it on the other Node's sessionSecretHash
reinterpret_cast<DomainServerNodeData*>(otherNode->getLinkedData())
->getSessionSecretHash().insert(node->getUUID(), secretUUID);
}
broadcastDataStream << secretUUID;
}
}
}
nodeList->writeDatagram(broadcastPacket, node, senderSockAddr);
}
void DomainServer::readAvailableDatagrams() {
NodeList* nodeList = NodeList::getInstance();
AccountManager& accountManager = AccountManager::getInstance();
HifiSockAddr senderSockAddr, nodePublicAddress, nodeLocalAddress;
static QByteArray broadcastPacket = byteArrayWithPopulatedHeader(PacketTypeDomainList);
static int numBroadcastPacketHeaderBytes = broadcastPacket.size();
HifiSockAddr senderSockAddr;
static QByteArray assignmentPacket = byteArrayWithPopulatedHeader(PacketTypeCreateAssignment);
static int numAssignmentPacketHeaderBytes = assignmentPacket.size();
QByteArray receivedPacket;
NodeType_t nodeType;
while (nodeList->getNodeSocket().hasPendingDatagrams()) {
receivedPacket.resize(nodeList->getNodeSocket().pendingDatagramSize());
@ -238,111 +478,69 @@ void DomainServer::readAvailableDatagrams() {
if (nodeList->packetVersionAndHashMatch(receivedPacket)) {
PacketType requestType = packetTypeForPacket(receivedPacket);
if (requestType == PacketTypeDomainListRequest) {
// this is an RFD or domain list request packet, and there is a version match
if (requestType == PacketTypeDomainConnectRequest) {
QDataStream packetStream(receivedPacket);
packetStream.skipRawData(numBytesForPacketHeader(receivedPacket));
QUuid nodeUUID = uuidFromPacketHeader(receivedPacket);
quint8 hasRegistrationToken;
packetStream >> hasRegistrationToken;
packetStream >> nodeType;
packetStream >> nodePublicAddress >> nodeLocalAddress;
if (nodePublicAddress.getAddress().isNull()) {
// this node wants to use us its STUN server
// so set the node public address to whatever we perceive the public address to be
if (requiresAuthentication() && !hasRegistrationToken) {
// we need authentication and this node did not give us a registration token - tell it to auth
requestAuthenticationFromPotentialNode(senderSockAddr);
} else if (requiresAuthentication()) {
QByteArray registrationToken;
packetStream >> registrationToken;
// if the sender is on our box then leave its public address to 0 so that
// other users attempt to reach it on the same address they have for the domain-server
if (senderSockAddr.getAddress().isLoopback()) {
nodePublicAddress.setAddress(QHostAddress());
} else {
nodePublicAddress.setAddress(senderSockAddr.getAddress());
}
}
SharedAssignmentPointer matchingStaticAssignment;
// check if this is a non-statically assigned node, a node that is assigned and checking in for the first time
// or a node that has already checked in and is continuing to report for duty
if (!STATICALLY_ASSIGNED_NODES.contains(nodeType)
|| (matchingStaticAssignment = matchingStaticAssignmentForCheckIn(nodeUUID, nodeType))
|| nodeList->getInstance()->nodeWithUUID(nodeUUID)) {
QString registrationTokenString(registrationToken.toHex());
QJsonObject jsonForRedeemedToken = _redeemedTokenResponses.value(registrationTokenString);
if (nodeUUID.isNull()) {
// this is a check in from an unidentified node
// we need to generate a session UUID for this node
nodeUUID = QUuid::createUuid();
}
SharedNodePointer checkInNode = nodeList->addOrUpdateNode(nodeUUID,
nodeType,
nodePublicAddress,
nodeLocalAddress);
// resize our broadcast packet in preparation to set it up again
broadcastPacket.resize(numBroadcastPacketHeaderBytes);
if (matchingStaticAssignment) {
// this was a newly added node with a matching static assignment
// check if we have redeemed this token and are ready to check the node in
if (jsonForRedeemedToken.isEmpty()) {
// make a request against the data-server to get information required to connect to this node
JSONCallbackParameters tokenCallbackParams;
tokenCallbackParams.jsonCallbackReceiver = this;
tokenCallbackParams.jsonCallbackMethod = "processTokenRedeemResponse";
// remove the matching assignment from the assignment queue so we don't take the next check in
// (if it exists)
if (_hasCompletedRestartHold) {
removeMatchingAssignmentFromQueue(matchingStaticAssignment);
}
QString redeemURLString = QString("/api/v1/nodes/redeem/%1.json").arg(registrationTokenString);
accountManager.authenticatedRequest(redeemURLString, QNetworkAccessManager::GetOperation,
tokenCallbackParams);
} else if (jsonForRedeemedToken["status"].toString() != "success") {
// we redeemed the token, but it was invalid - get the node to get another
requestAuthenticationFromPotentialNode(senderSockAddr);
} else {
// we've redeemed the token for this node and are ready to start communicating with it
// add the node to our NodeList
addNodeToNodeListAndConfirmConnection(receivedPacket, senderSockAddr, jsonForRedeemedToken);
}
quint8 numInterestTypes = 0;
packetStream >> numInterestTypes;
// if it exists, remove this response from the in-memory hash
_redeemedTokenResponses.remove(registrationTokenString);
NodeType_t* nodeTypesOfInterest = reinterpret_cast<NodeType_t*>(receivedPacket.data()
+ packetStream.device()->pos());
// always send the node their own UUID back
QDataStream broadcastDataStream(&broadcastPacket, QIODevice::Append);
broadcastDataStream << checkInNode->getUUID();
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(checkInNode->getLinkedData());
if (numInterestTypes > 0) {
// if the node has any interest types, send back those nodes as well
foreach (const SharedNodePointer& otherNode, nodeList->getNodeHash()) {
if (otherNode->getUUID() != nodeUUID &&
memchr(nodeTypesOfInterest, otherNode->getType(), numInterestTypes)) {
// don't send avatar nodes to other avatars, that will come from avatar mixer
broadcastDataStream << *otherNode.data();
// pack the secret that these two nodes will use to communicate with each other
QUuid secretUUID = nodeData->getSessionSecretHash().value(otherNode->getUUID());
if (secretUUID.isNull()) {
// generate a new secret UUID these two nodes can use
secretUUID = QUuid::createUuid();
// set that on the current Node's sessionSecretHash
nodeData->getSessionSecretHash().insert(otherNode->getUUID(), secretUUID);
// set it on the other Node's sessionSecretHash
reinterpret_cast<DomainServerNodeData*>(otherNode->getLinkedData())
->getSessionSecretHash().insert(nodeUUID, secretUUID);
}
broadcastDataStream << secretUUID;
}
}
}
// update last receive to now
quint64 timeNow = usecTimestampNow();
checkInNode->setLastHeardMicrostamp(timeNow);
// send the constructed list back to this node
nodeList->getNodeSocket().writeDatagram(broadcastPacket,
senderSockAddr.getAddress(), senderSockAddr.getPort());
} else {
// we don't require authentication - add this node to our NodeList
// and send back session UUID right away
addNodeToNodeListAndConfirmConnection(receivedPacket, senderSockAddr);
}
} else if (requestType == PacketTypeDomainListRequest) {
QUuid nodeUUID = uuidFromPacketHeader(receivedPacket);
NodeType_t throwawayNodeType;
HifiSockAddr nodePublicAddress, nodeLocalAddress;
int numNodeInfoBytes = parseNodeDataFromByteArray(throwawayNodeType, nodePublicAddress, nodeLocalAddress,
receivedPacket, senderSockAddr);
SharedNodePointer checkInNode = nodeList->updateSocketsForNode(nodeUUID, nodePublicAddress, nodeLocalAddress);
// update last receive to now
quint64 timeNow = usecTimestampNow();
checkInNode->setLastHeardMicrostamp(timeNow);
sendDomainListToNode(checkInNode, senderSockAddr, nodeInterestListFromPacket(receivedPacket, numNodeInfoBytes));
} else if (requestType == PacketTypeRequestAssignment) {
// construct the requested assignment from the packet data
@ -588,43 +786,39 @@ void DomainServer::nodeAdded(SharedNodePointer node) {
}
void DomainServer::nodeKilled(SharedNodePointer node) {
// if this node's UUID matches a static assignment we need to throw it back in the assignment queue
SharedAssignmentPointer matchedAssignment = _staticAssignmentHash.value(node->getUUID());
if (matchedAssignment) {
refreshStaticAssignmentAndAddToQueue(matchedAssignment);
}
// cleanup the connection secrets that we set up for this node (on the other nodes)
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
foreach (const QUuid& otherNodeSessionUUID, nodeData->getSessionSecretHash().keys()) {
SharedNodePointer otherNode = NodeList::getInstance()->nodeWithUUID(otherNodeSessionUUID);
if (otherNode) {
reinterpret_cast<DomainServerNodeData*>(otherNode->getLinkedData())->getSessionSecretHash().remove(node->getUUID());
if (nodeData) {
// if this node's UUID matches a static assignment we need to throw it back in the assignment queue
if (!nodeData->getStaticAssignmentUUID().isNull()) {
SharedAssignmentPointer matchedAssignment = _staticAssignmentHash.value(nodeData->getStaticAssignmentUUID());
if (matchedAssignment) {
refreshStaticAssignmentAndAddToQueue(matchedAssignment);
}
}
// cleanup the connection secrets that we set up for this node (on the other nodes)
foreach (const QUuid& otherNodeSessionUUID, nodeData->getSessionSecretHash().keys()) {
SharedNodePointer otherNode = NodeList::getInstance()->nodeWithUUID(otherNodeSessionUUID);
if (otherNode) {
reinterpret_cast<DomainServerNodeData*>(otherNode->getLinkedData())->getSessionSecretHash().remove(node->getUUID());
}
}
}
}
SharedAssignmentPointer DomainServer::matchingStaticAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType) {
if (_hasCompletedRestartHold) {
// look for a match in the assignment hash
QQueue<SharedAssignmentPointer>::iterator i = _assignmentQueue.begin();
while (i != _assignmentQueue.end()) {
if (i->data()->getType() == Assignment::typeForNodeType(nodeType) && i->data()->getUUID() == checkInUUID) {
return _assignmentQueue.takeAt(i - _assignmentQueue.begin());
} else {
++i;
}
}
} else {
SharedAssignmentPointer matchingStaticAssignment = _staticAssignmentHash.value(checkInUUID);
if (matchingStaticAssignment && matchingStaticAssignment->getType() == nodeType) {
return matchingStaticAssignment;
QQueue<SharedAssignmentPointer>::iterator i = _assignmentQueue.begin();
while (i != _assignmentQueue.end()) {
if (i->data()->getType() == Assignment::typeForNodeType(nodeType) && i->data()->getUUID() == checkInUUID) {
return _assignmentQueue.takeAt(i - _assignmentQueue.begin());
} else {
++i;
}
}
return SharedAssignmentPointer();
}
@ -680,8 +874,7 @@ void DomainServer::removeMatchingAssignmentFromQueue(const SharedAssignmentPoint
}
}
void DomainServer::addStaticAssignmentsBackToQueueAfterRestart() {
_hasCompletedRestartHold = true;
void DomainServer::addStaticAssignmentsToQueue() {
// if the domain-server has just restarted,
// check if there are static assignments that we need to throw into the assignment queue

View file

@ -11,8 +11,11 @@
#include <QtCore/QCoreApplication>
#include <QtCore/QHash>
#include <QtCore/QJsonObject>
#include <QtCore/QQueue>
#include <QtCore/QSharedPointer>
#include <QtCore/QStringList>
#include <QtCore/QUrl>
#include <Assignment.h>
#include <HTTPManager.h>
@ -25,6 +28,8 @@ class DomainServer : public QCoreApplication, public HTTPRequestHandler {
public:
DomainServer(int argc, char* argv[]);
bool requiresAuthentication() const { return !_nodeAuthenticationURL.isEmpty(); }
bool handleHTTPRequest(HTTPConnection* connection, const QString& path);
void exit(int retCode = 0);
@ -36,6 +41,17 @@ public slots:
void nodeKilled(SharedNodePointer node);
private:
void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid());
void requestAuthenticationFromPotentialNode(const HifiSockAddr& senderSockAddr);
void addNodeToNodeListAndConfirmConnection(const QByteArray& packet, const HifiSockAddr& senderSockAddr,
const QJsonObject& authJsonObject = QJsonObject());
int parseNodeDataFromByteArray(NodeType_t& nodeType, HifiSockAddr& publicSockAddr,
HifiSockAddr& localSockAddr, const QByteArray& packet, const HifiSockAddr& senderSockAddr);
NodeSet nodeInterestListFromPacket(const QByteArray& packet, int numPreceedingBytes);
void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr,
const NodeSet& nodeInterestList);
void parseCommandLineTypeConfigs(const QStringList& argumentList, QSet<Assignment::Type>& excludedTypes);
void readConfigFile(const QString& path, QSet<Assignment::Type>& excludedTypes);
QString readServerAssignmentConfig(const QJsonObject& jsonObject, const QString& nodeName);
@ -47,6 +63,7 @@ private:
SharedAssignmentPointer deployableAssignmentForRequest(const Assignment& requestAssignment);
void removeMatchingAssignmentFromQueue(const SharedAssignmentPointer& removableAssignment);
void refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment);
void addStaticAssignmentsToQueue();
QJsonObject jsonForSocket(const HifiSockAddr& socket);
QJsonObject jsonObjectForNode(const SharedNodePointer& node);
@ -56,10 +73,17 @@ private:
QHash<QUuid, SharedAssignmentPointer> _staticAssignmentHash;
QQueue<SharedAssignmentPointer> _assignmentQueue;
bool _hasCompletedRestartHold;
QUrl _nodeAuthenticationURL;
QStringList _argumentList;
QHash<QString, QJsonObject> _redeemedTokenResponses;
private slots:
void requestCreationFromDataServer();
void processCreateResponseFromDataServer(const QJsonObject& jsonObject);
void processTokenRedeemResponse(const QJsonObject& jsonObject);
void readAvailableDatagrams();
void addStaticAssignmentsBackToQueueAfterRestart();
};
#endif /* defined(__hifi__DomainServer__) */

View file

@ -0,0 +1,16 @@
//
// DomainServerNodeData.cpp
// hifi
//
// Created by Stephen Birarda on 2/6/2014.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
#include "DomainServerNodeData.h"
DomainServerNodeData::DomainServerNodeData() :
_sessionSecretHash(),
_staticAssignmentUUID()
{
}

View file

@ -10,17 +10,22 @@
#define __hifi__DomainServerNodeData__
#include <QtCore/QHash>
#include <QtCore/QUuid>
#include <NodeData.h>
class DomainServerNodeData : public NodeData {
public:
DomainServerNodeData() : _sessionSecretHash() {};
DomainServerNodeData();
int parseData(const QByteArray& packet) { return 0; }
void setStaticAssignmentUUID(const QUuid& staticAssignmentUUID) { _staticAssignmentUUID = staticAssignmentUUID; }
const QUuid& getStaticAssignmentUUID() const { return _staticAssignmentUUID; }
QHash<QUuid, QUuid>& getSessionSecretHash() { return _sessionSecretHash; }
private:
QHash<QUuid, QUuid> _sessionSecretHash;
QUuid _staticAssignmentUUID;
};
#endif /* defined(__hifi__DomainServerNodeData__) */

View file

@ -1,4 +1,4 @@
[INFO]
name=Interface
organizationName=High Fidelity
organizationDomain=highfidelity.io
organizationDomain=highfidelity.io

View file

@ -51,11 +51,11 @@
#include <QMimeData>
#include <QMessageBox>
#include <AccountManager.h>
#include <AudioInjector.h>
#include <Logging.h>
#include <OctalCode.h>
#include <PacketHeaders.h>
#include <PairingHandler.h>
#include <ParticlesScriptingInterface.h>
#include <PerfStat.h>
#include <UUID.h>
@ -63,7 +63,6 @@
#include "Application.h"
#include "ClipboardScriptingInterface.h"
#include "DataServerClient.h"
#include "InterfaceVersion.h"
#include "Menu.h"
#include "Swatch.h"
@ -129,7 +128,6 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
_wantToKillLocalVoxels(false),
_audioScope(256, 200, true),
_myAvatar(),
_profile(QString()),
_mirrorViewRect(QRect(MIRROR_VIEW_LEFT_PADDING, MIRROR_VIEW_TOP_PADDING, MIRROR_VIEW_WIDTH, MIRROR_VIEW_HEIGHT)),
_mouseX(0),
_mouseY(0),
@ -161,11 +159,27 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
_pasteMode(false),
_logger(new FileLogger(this))
{
switchToResourcesParentIfRequired();
// read the ApplicationInfo.ini file for Name/Version/Domain information
QSettings applicationInfo("resources/info/ApplicationInfo.ini", QSettings::IniFormat);
// set the associated application properties
applicationInfo.beginGroup("INFO");
qDebug() << "[VERSION] Build sequence: " << qPrintable(applicationVersion());
setApplicationName(applicationInfo.value("name").toString());
setApplicationVersion(BUILD_VERSION);
setOrganizationName(applicationInfo.value("organizationName").toString());
setOrganizationDomain(applicationInfo.value("organizationDomain").toString());
QSettings::setDefaultFormat(QSettings::IniFormat);
_myAvatar = _avatarManager.getMyAvatar();
_applicationStartupTime = startup_time;
switchToResourcesParentIfRequired();
QFontDatabase::addApplicationFont("resources/styles/Inconsolata.otf");
_window->setWindowTitle("Interface");
@ -203,27 +217,21 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
connect(audioThread, SIGNAL(started()), &_audio, SLOT(start()));
audioThread->start();
connect(nodeList, SIGNAL(domainChanged(const QString&)), SLOT(domainChanged(const QString&)));
connect(&nodeList->getDomainInfo(), SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&)));
connect(&nodeList->getDomainInfo(), SIGNAL(connectedToDomain(const QString&)), SLOT(connectedToDomain(const QString&)));
connect(nodeList, &NodeList::nodeAdded, this, &Application::nodeAdded);
connect(nodeList, &NodeList::nodeKilled, this, &Application::nodeKilled);
connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer)));
connect(nodeList, SIGNAL(nodeAdded(SharedNodePointer)), &_voxels, SLOT(nodeAdded(SharedNodePointer)));
connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), &_voxels, SLOT(nodeKilled(SharedNodePointer)));
connect(nodeList, &NodeList::uuidChanged, this, &Application::updateWindowTitle);
// read the ApplicationInfo.ini file for Name/Version/Domain information
QSettings applicationInfo("resources/info/ApplicationInfo.ini", QSettings::IniFormat);
// set the associated application properties
applicationInfo.beginGroup("INFO");
setApplicationName(applicationInfo.value("name").toString());
setApplicationVersion(BUILD_VERSION);
setOrganizationName(applicationInfo.value("organizationName").toString());
setOrganizationDomain(applicationInfo.value("organizationDomain").toString());
qDebug() << "[VERSION] Build sequence: " << qPrintable(applicationVersion());
// connect to appropriate slots on AccountManager
AccountManager& accountManager = AccountManager::getInstance();
connect(&accountManager, &AccountManager::authRequired, Menu::getInstance(), &Menu::loginForCurrentDomain);
connect(&accountManager, &AccountManager::usernameChanged, this, &Application::updateWindowTitle);
_settings = new QSettings(this);
@ -601,13 +609,6 @@ void Application::updateProjectionMatrix(Camera& camera, bool updateViewFrustum)
glMatrixMode(GL_MODELVIEW);
}
void Application::resetProfile(const QString& username) {
// call the destructor on the old profile and construct a new one
(&_profile)->~Profile();
new (&_profile) Profile(username);
updateWindowTitle();
}
void Application::controlledBroadcastToNodes(const QByteArray& packet, const NodeSet& destinationNodeTypes) {
foreach(NodeType_t type, destinationNodeTypes) {
// Intercept data to voxel server when voxels are disabled
@ -1427,12 +1428,7 @@ void Application::timer() {
// ask the node list to check in with the domain server
NodeList::getInstance()->sendDomainServerCheckIn();
// send unmatched DataServerClient packets
DataServerClient::resendUnmatchedPackets();
// give the MyAvatar object position, orientation to the Profile so it can propagate to the data-server
_profile.updatePosition(_myAvatar->getPosition());
_profile.updateOrientation(_myAvatar->getOrientation());
}
static glm::vec3 getFaceVector(BoxFace face) {
@ -2942,9 +2938,6 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
}
}
// render transmitter pick ray, if non-empty
_myAvatar->renderTransmitterPickRay();
// give external parties a change to hook in
emit renderingInWorldInterface();
@ -3010,8 +3003,6 @@ void Application::displayOverlay() {
_myAvatar->renderHeadMouse();
}
_myAvatar->renderTransmitterLevels(_glWidget->width(), _glWidget->height());
// Display stats and log text onscreen
glLineWidth(1.0f);
glPointSize(1.0f);
@ -3990,17 +3981,15 @@ void Application::updateWindowTitle(){
QString buildVersion = " (build " + applicationVersion() + ")";
NodeList* nodeList = NodeList::getInstance();
QString title = QString() + _profile.getUsername() + " " + nodeList->getSessionUUID().toString()
+ " @ " + nodeList->getDomainHostname() + buildVersion;
QString username = AccountManager::getInstance().getUsername();
QString title = QString() + (!username.isEmpty() ? username + " " : QString()) + nodeList->getSessionUUID().toString()
+ " @ " + nodeList->getDomainInfo().getHostname() + buildVersion;
qDebug("Application title set to: %s", title.toStdString().c_str());
_window->setWindowTitle(title);
}
void Application::domainChanged(const QString& domainHostname) {
// update the user's last domain in their Profile (which will propagate to data-server)
_profile.updateDomain(domainHostname);
updateWindowTitle();
// reset the environment so that we don't erroneously end up with multiple
@ -4015,6 +4004,19 @@ void Application::domainChanged(const QString& domainHostname) {
_particles.clear();
}
void Application::connectedToDomain(const QString& hostname) {
AccountManager& accountManager = AccountManager::getInstance();
if (accountManager.isLoggedIn()) {
// update our domain-server with the data-server we're logged in with
QString domainPutJsonString = "{\"location\":{\"domain\":\"" + hostname + "\"}}";
accountManager.authenticatedRequest("/api/v1/users/location", QNetworkAccessManager::PutOperation,
JSONCallbackParameters(), domainPutJsonString.toUtf8());
}
}
void Application::nodeAdded(SharedNodePointer node) {
if (node->getType() == NodeType::AvatarMixer) {
// new avatar mixer, send off our identity packet right away
@ -4402,6 +4404,5 @@ void Application::takeSnapshot() {
player->setMedia(QUrl::fromLocalFile(inf.absoluteFilePath()));
player->play();
Snapshot::saveSnapshot(_glWidget, &_profile, _myAvatar);
Snapshot::saveSnapshot(_glWidget, _myAvatar);
}

View file

@ -53,7 +53,6 @@
#include "avatar/Avatar.h"
#include "avatar/AvatarManager.h"
#include "avatar/MyAvatar.h"
#include "avatar/Profile.h"
#include "devices/Faceshift.h"
#include "devices/SixenseManager.h"
#include "devices/Visage.h"
@ -183,7 +182,6 @@ public:
ControllerScriptingInterface* getControllerScriptingInterface() { return &_controllerScriptingInterface; }
AvatarManager& getAvatarManager() { return _avatarManager; }
Profile* getProfile() { return &_profile; }
void resetProfile(const QString& username);
void controlledBroadcastToNodes(const QByteArray& packet, const NodeSet& destinationNodeTypes);
@ -267,6 +265,8 @@ public slots:
private slots:
void timer();
void idle();
void connectedToDomain(const QString& hostname);
void setFullscreen(bool fullscreen);
void setEnable3DTVMode(bool enable3DTVMode);
@ -407,7 +407,6 @@ private:
AvatarManager _avatarManager;
MyAvatar* _myAvatar; // TODO: move this and relevant code to AvatarManager (or MyAvatar as the case may be)
Profile _profile; // The data-server linked profile for this user
Faceshift _faceshift;
Visage _visage;

View file

@ -43,12 +43,6 @@ void DatagramProcessor::processDatagrams() {
if (nodeList->packetVersionAndHashMatch(incomingPacket)) {
// only process this packet if we have a match on the packet version
switch (packetTypeForPacket(incomingPacket)) {
case PacketTypeTransmitterData:
// V2 = IOS transmitter app
application->getAvatar()->getTransmitter().processIncomingData(reinterpret_cast<unsigned char*>(incomingPacket.data()),
incomingPacket.size());
break;
case PacketTypeMixedAudio:
QMetaObject::invokeMethod(&application->_audio, "addReceivedAudioToBuffer", Qt::QueuedConnection,
Q_ARG(QByteArray, incomingPacket));
@ -115,12 +109,6 @@ void DatagramProcessor::processDatagrams() {
application->_bandwidthMeter.inputStream(BandwidthMeter::AVATARS).updateValue(incomingPacket.size());
break;
}
case PacketTypeDataServerGet:
case PacketTypeDataServerPut:
case PacketTypeDataServerSend:
case PacketTypeDataServerConfirm:
DataServerClient::processMessageFromDataServer(incomingPacket);
break;
default:
nodeList->processNodeData(senderSockAddr, incomingPacket);
break;

View file

@ -24,11 +24,10 @@
#include <QUuid>
#include <QWindow>
#include <AccountManager.h>
#include <UUID.h>
#include "Application.h"
#include "DataServerClient.h"
#include "PairingHandler.h"
#include "Menu.h"
#include "Util.h"
#include "InfoView.h"
@ -71,7 +70,8 @@ Menu::Menu() :
_voxelSizeScale(DEFAULT_OCTREE_SIZE_SCALE),
_boundaryLevelAdjust(0),
_maxVoxelPacketsPerSecond(DEFAULT_MAX_VOXEL_PPS),
_lastAdjust(usecTimestampNow())
_lastAdjust(usecTimestampNow()),
_loginAction(NULL)
{
Application *appInstance = Application::getInstance();
@ -86,11 +86,17 @@ Menu::Menu() :
QAction::AboutRole);
#endif
(addActionToQMenuAndActionHash(fileMenu,
MenuOption::Login,
0,
this,
SLOT(login())));
AccountManager& accountManager = AccountManager::getInstance();
_loginAction = addActionToQMenuAndActionHash(fileMenu, MenuOption::Logout);
// call our toggle login function now so the menu option is setup properly
toggleLoginMenuItem();
// connect to the appropriate slots of the AccountManager so that we can change the Login/Logout menu item
connect(&accountManager, &AccountManager::loginComplete, this, &Menu::toggleLoginMenuItem);
connect(&accountManager, &AccountManager::logoutComplete, this, &Menu::toggleLoginMenuItem);
connect(&accountManager, &AccountManager::authEndpointChanged, this, &Menu::toggleLoginMenuItem);
addDisabledActionAndSeparator(fileMenu, "Scripts");
addActionToQMenuAndActionHash(fileMenu, MenuOption::LoadScript, Qt::CTRL | Qt::Key_O, appInstance, SLOT(loadDialog()));
@ -129,10 +135,6 @@ Menu::Menu() :
addActionToQMenuAndActionHash(fileMenu, MenuOption::SettingsImport, 0, this, SLOT(importSettings()));
addActionToQMenuAndActionHash(fileMenu, MenuOption::SettingsExport, 0, this, SLOT(exportSettings()));
addDisabledActionAndSeparator(fileMenu, "Devices");
addActionToQMenuAndActionHash(fileMenu, MenuOption::Pair, 0, PairingHandler::getInstance(), SLOT(sendPairRequest()));
addCheckableActionToQMenuAndActionHash(fileMenu, MenuOption::TransmitterDrive, 0, true);
addActionToQMenuAndActionHash(fileMenu,
MenuOption::Quit,
Qt::CTRL | Qt::Key_Q,
@ -528,7 +530,6 @@ void Menu::loadSettings(QSettings* settings) {
scanMenuBar(&loadAction, settings);
Application::getInstance()->getAvatar()->loadData(settings);
Application::getInstance()->getSwatch()->loadData(settings);
Application::getInstance()->getProfile()->loadData(settings);
Application::getInstance()->updateWindowTitle();
NodeList::getInstance()->loadData(settings);
@ -561,7 +562,6 @@ void Menu::saveSettings(QSettings* settings) {
scanMenuBar(&saveAction, settings);
Application::getInstance()->getAvatar()->saveData(settings);
Application::getInstance()->getSwatch()->saveData(settings);
Application::getInstance()->getProfile()->saveData(settings);
NodeList::getInstance()->saveData(settings);
}
@ -757,24 +757,38 @@ void sendFakeEnterEvent() {
const int QLINE_MINIMUM_WIDTH = 400;
const float DIALOG_RATIO_OF_WINDOW = 0.30f;
void Menu::login() {
QInputDialog loginDialog(Application::getInstance()->getWindow());
void Menu::loginForCurrentDomain() {
QDialog loginDialog(Application::getInstance()->getWindow());
loginDialog.setWindowTitle("Login");
loginDialog.setLabelText("Username:");
QString username = Application::getInstance()->getProfile()->getUsername();
loginDialog.setTextValue(username);
QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom);
loginDialog.setLayout(layout);
loginDialog.setWindowFlags(Qt::Sheet);
loginDialog.resize(loginDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, loginDialog.size().height());
QFormLayout* form = new QFormLayout();
layout->addLayout(form, 1);
QLineEdit* loginLineEdit = new QLineEdit();
loginLineEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH);
form->addRow("Login:", loginLineEdit);
QLineEdit* passwordLineEdit = new QLineEdit();
passwordLineEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH);
passwordLineEdit->setEchoMode(QLineEdit::Password);
form->addRow("Password:", passwordLineEdit);
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
loginDialog.connect(buttons, SIGNAL(accepted()), SLOT(accept()));
loginDialog.connect(buttons, SIGNAL(rejected()), SLOT(reject()));
layout->addWidget(buttons);
int dialogReturn = loginDialog.exec();
if (dialogReturn == QDialog::Accepted && !loginDialog.textValue().isEmpty() && loginDialog.textValue() != username) {
// there has been a username change
// ask for a profile reset with the new username
Application::getInstance()->resetProfile(loginDialog.textValue());
if (dialogReturn == QDialog::Accepted && !loginLineEdit->text().isEmpty() && !passwordLineEdit->text().isEmpty()) {
// attempt to get an access token given this username and password
AccountManager::getInstance().requestAccessToken(loginLineEdit->text(), passwordLineEdit->text());
}
sendFakeEnterEvent();
}
@ -783,9 +797,10 @@ void Menu::editPreferences() {
QDialog dialog(applicationInstance->getWindow());
dialog.setWindowTitle("Interface Preferences");
QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom);
dialog.setLayout(layout);
QFormLayout* form = new QFormLayout();
layout->addLayout(form, 1);
@ -916,23 +931,23 @@ void Menu::editPreferences() {
}
void Menu::goToDomain(const QString newDomain) {
if (NodeList::getInstance()->getDomainHostname() != newDomain) {
if (NodeList::getInstance()->getDomainInfo().getHostname() != newDomain) {
// send a node kill request, indicating to other clients that they should play the "disappeared" effect
Application::getInstance()->getAvatar()->sendKillAvatar();
// give our nodeList the new domain-server hostname
NodeList::getInstance()->setDomainHostname(newDomain);
NodeList::getInstance()->getDomainInfo().setHostname(newDomain);
}
}
void Menu::goToDomainDialog() {
QString currentDomainHostname = NodeList::getInstance()->getDomainHostname();
QString currentDomainHostname = NodeList::getInstance()->getDomainInfo().getHostname();
if (NodeList::getInstance()->getDomainPort() != DEFAULT_DOMAIN_SERVER_PORT) {
if (NodeList::getInstance()->getDomainInfo().getPort() != DEFAULT_DOMAIN_SERVER_PORT) {
// add the port to the currentDomainHostname string if it is custom
currentDomainHostname.append(QString(":%1").arg(NodeList::getInstance()->getDomainPort()));
currentDomainHostname.append(QString(":%1").arg(NodeList::getInstance()->getDomainInfo().getPort()));
}
QInputDialog domainDialog(Application::getInstance()->getWindow());
@ -1026,7 +1041,7 @@ void Menu::goTo() {
QInputDialog gotoDialog(Application::getInstance()->getWindow());
gotoDialog.setWindowTitle("Go to");
gotoDialog.setLabelText("Destination:");
QString destination = Application::getInstance()->getProfile()->getUsername();
QString destination = QString();
gotoDialog.setTextValue(destination);
gotoDialog.setWindowFlags(Qt::Sheet);
gotoDialog.resize(gotoDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, gotoDialog.size().height());
@ -1038,14 +1053,14 @@ void Menu::goTo() {
// go to coordinate destination or to Username
if (!goToDestination(destination)) {
// there's a username entered by the user, make a request to the data-server
DataServerClient::getValuesForKeysAndUserString(
QStringList()
<< DataServerKey::Domain
<< DataServerKey::Position
<< DataServerKey::Orientation,
destination, Application::getInstance()->getProfile());
JSONCallbackParameters callbackParams;
callbackParams.jsonCallbackReceiver = Application::getInstance()->getAvatar();
callbackParams.jsonCallbackMethod = "goToLocationFromResponse";
// there's a username entered by the user, make a request to the data-server for the associated location
AccountManager::getInstance().authenticatedRequest("/api/v1/users/" + gotoDialog.textValue() + "/location",
QNetworkAccessManager::GetOperation,
callbackParams);
}
}
@ -1101,6 +1116,32 @@ void Menu::pasteToVoxel() {
sendFakeEnterEvent();
}
void Menu::toggleLoginMenuItem() {
AccountManager& accountManager = AccountManager::getInstance();
disconnect(_loginAction, 0, 0, 0);
if (accountManager.isLoggedIn()) {
// change the menu item to logout
_loginAction->setText("Logout " + accountManager.getUsername());
connect(_loginAction, &QAction::triggered, &accountManager, &AccountManager::logout);
_loginAction->setEnabled(true);
} else {
// change the menu item to login
_loginAction->setText("Login");
// if we don't have a rootURL in the AccountManager we're in a domain that doesn't use auth
// so setup the menu item according to the presence of that root URL
if (accountManager.hasAuthEndpoint()) {
connect(_loginAction, &QAction::triggered, this, &Menu::loginForCurrentDomain);
_loginAction->setEnabled(true);
} else {
_loginAction->setEnabled(false);
}
}
}
void Menu::bandwidthDetails() {
if (! _bandwidthDialog) {
_bandwidthDialog = new BandwidthDialog(Application::getInstance()->getGLWidget(),

View file

@ -76,7 +76,6 @@ public:
int getMaxVoxels() const { return _maxVoxels; }
QAction* getUseVoxelShader() const { return _useVoxelShader; }
void handleViewFrustumOffsetKeyModifier(int key);
// User Tweakable LOD Items
@ -102,6 +101,7 @@ public:
void goToDomain(const QString newDomain);
public slots:
void loginForCurrentDomain();
void bandwidthDetails();
void voxelStatsDetails();
void lodTools();
@ -111,10 +111,11 @@ public slots:
void exportSettings();
void goTo();
void pasteToVoxel();
void toggleLoginMenuItem();
private slots:
void aboutApp();
void login();
void editPreferences();
void goToDomainDialog();
void goToLocation();
@ -173,6 +174,7 @@ private:
QMenu* _activeScriptsMenu;
QString replaceLastOccurrence(QChar search, QChar replace, QString string);
quint64 _lastAdjust;
QAction* _loginAction;
};
namespace MenuOption {
@ -247,6 +249,7 @@ namespace MenuOption {
const QString LodTools = "LOD Tools";
const QString Log = "Log";
const QString Login = "Login";
const QString Logout = "Logout";
const QString LookAtVectors = "Look-at Vectors";
const QString MetavoxelEditor = "Metavoxel Editor...";
const QString Metavoxels = "Metavoxels";

View file

@ -1,59 +0,0 @@
//
// PairingHandler.cpp
// hifi
//
// Created by Stephen Birarda on 5/13/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
#ifndef _WIN32
#include <arpa/inet.h> // not available on windows
#endif
#include <string.h>
#include <stdio.h>
#include <QtNetwork/QHostInfo>
#include <HifiSockAddr.h>
#include <NodeList.h>
#include "PairingHandler.h"
const char PAIRING_SERVER_HOSTNAME[] = "pairing.highfidelity.io";
const int PAIRING_SERVER_PORT = 7247;
PairingHandler* PairingHandler::getInstance() {
static PairingHandler* instance = NULL;
if (!instance) {
instance = new PairingHandler();
}
return instance;
}
void PairingHandler::sendPairRequest() {
// prepare the pairing request packet
NodeList* nodeList = NodeList::getInstance();
// use the getLocalAddress helper to get this client's listening address
quint32 localAddress = htonl(getHostOrderLocalAddress());
char pairPacket[24] = {};
sprintf(pairPacket, "Find %d.%d.%d.%d:%hu",
localAddress & 0xFF,
(localAddress >> 8) & 0xFF,
(localAddress >> 16) & 0xFF,
(localAddress >> 24) & 0xFF,
NodeList::getInstance()->getNodeSocket().localPort());
qDebug("Sending pair packet: %s", pairPacket);
HifiSockAddr pairingServerSocket(PAIRING_SERVER_HOSTNAME, PAIRING_SERVER_PORT);
// send the pair request to the pairing server
nodeList->getNodeSocket().writeDatagram((char*) pairPacket, strlen(pairPacket),
pairingServerSocket.getAddress(), pairingServerSocket.getPort());
}

View file

@ -1,22 +0,0 @@
//
// PairingHandler.h
// hifi
//
// Created by Stephen Birarda on 5/13/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
#ifndef __hifi__PairingHandler__
#define __hifi__PairingHandler__
#include <QtCore/QObject>
class PairingHandler : public QObject {
Q_OBJECT
public:
static PairingHandler* getInstance();
public slots:
void sendPairRequest();
};
#endif /* defined(__hifi__PairingHandler__) */

View file

@ -19,7 +19,6 @@
#include "Application.h"
#include "Avatar.h"
#include "DataServerClient.h"
#include "Hand.h"
#include "Head.h"
#include "Menu.h"

View file

@ -14,7 +14,6 @@
#include <QtCore/QSharedPointer>
#include <AvatarHashMap.h>
#include <DataServerClient.h>
#include "Avatar.h"

View file

@ -13,13 +13,15 @@
#include <glm/gtx/vector_angle.hpp>
#include <QtCore/QTimer>
#include <AccountManager.h>
#include <NodeList.h>
#include <PacketHeaders.h>
#include <SharedUtil.h>
#include "Application.h"
#include "Audio.h"
#include "DataServerClient.h"
#include "Environment.h"
#include "Menu.h"
#include "MyAvatar.h"
@ -42,6 +44,8 @@ const bool USING_HEAD_LEAN = false;
const float SKIN_COLOR[] = {1.0f, 0.84f, 0.66f};
const float DARK_SKIN_COLOR[] = {0.9f, 0.78f, 0.63f};
const float DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * 1000;
MyAvatar::MyAvatar() :
Avatar(),
_mousePressed(false),
@ -66,6 +70,11 @@ MyAvatar::MyAvatar() :
for (int i = 0; i < MAX_DRIVE_KEYS; i++) {
_driveKeys[i] = 0.0f;
}
// update our location every 5 seconds in the data-server, assuming that we are authenticated with one
QTimer* locationUpdateTimer = new QTimer(this);
connect(locationUpdateTimer, &QTimer::timeout, this, &MyAvatar::updateLocationInDataServer);
locationUpdateTimer->start(DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS);
}
MyAvatar::~MyAvatar() {
@ -81,7 +90,6 @@ void MyAvatar::reset() {
setVelocity(glm::vec3(0,0,0));
setThrust(glm::vec3(0,0,0));
_transmitter.resetLevels();
}
void MyAvatar::setMoveTarget(const glm::vec3 moveTarget) {
@ -89,32 +97,7 @@ void MyAvatar::setMoveTarget(const glm::vec3 moveTarget) {
_moveTargetStepCounter = 0;
}
void MyAvatar::updateTransmitter(float deltaTime) {
// no transmitter drive implies transmitter pick
if (!Menu::getInstance()->isOptionChecked(MenuOption::TransmitterDrive) && _transmitter.isConnected()) {
_transmitterPickStart = getChestPosition();
glm::vec3 direction = getOrientation() * glm::quat(glm::radians(_transmitter.getEstimatedRotation())) * IDENTITY_FRONT;
// check against voxels, avatars
const float MAX_PICK_DISTANCE = 100.0f;
float minDistance = MAX_PICK_DISTANCE;
VoxelDetail detail;
float distance;
BoxFace face;
VoxelSystem* voxels = Application::getInstance()->getVoxels();
if (voxels->findRayIntersection(_transmitterPickStart, direction, detail, distance, face)) {
minDistance = min(minDistance, distance);
}
_transmitterPickEnd = _transmitterPickStart + direction * minDistance;
} else {
_transmitterPickStart = _transmitterPickEnd = glm::vec3();
}
}
void MyAvatar::update(float deltaTime) {
updateTransmitter(deltaTime);
updateFromGyros(deltaTime);
// Update head mouse from faceshift if active
@ -570,35 +553,6 @@ void MyAvatar::renderHeadMouse() const {
*/
}
void MyAvatar::renderTransmitterPickRay() const {
if (_transmitterPickStart != _transmitterPickEnd) {
Glower glower;
const float TRANSMITTER_PICK_COLOR[] = { 1.0f, 1.0f, 0.0f };
glColor3fv(TRANSMITTER_PICK_COLOR);
glLineWidth(3.0f);
glBegin(GL_LINES);
glVertex3f(_transmitterPickStart.x, _transmitterPickStart.y, _transmitterPickStart.z);
glVertex3f(_transmitterPickEnd.x, _transmitterPickEnd.y, _transmitterPickEnd.z);
glEnd();
glLineWidth(1.0f);
glPushMatrix();
glTranslatef(_transmitterPickEnd.x, _transmitterPickEnd.y, _transmitterPickEnd.z);
const float PICK_END_RADIUS = 0.025f;
glutSolidSphere(PICK_END_RADIUS, 8, 8);
glPopMatrix();
}
}
void MyAvatar::renderTransmitterLevels(int width, int height) const {
// Show hand transmitter data if detected
if (_transmitter.isConnected()) {
_transmitter.renderLevels(width, height);
}
}
void MyAvatar::saveData(QSettings* settings) {
settings->beginGroup("Avatar");
@ -780,36 +734,6 @@ void MyAvatar::updateThrust(float deltaTime) {
_shouldJump = false;
}
// Add thrusts from Transmitter
if (Menu::getInstance()->isOptionChecked(MenuOption::TransmitterDrive) && _transmitter.isConnected()) {
_transmitter.checkForLostTransmitter();
glm::vec3 rotation = _transmitter.getEstimatedRotation();
const float TRANSMITTER_MIN_RATE = 1.f;
const float TRANSMITTER_MIN_YAW_RATE = 4.f;
const float TRANSMITTER_LATERAL_FORCE_SCALE = 5.f;
const float TRANSMITTER_FWD_FORCE_SCALE = 25.f;
const float TRANSMITTER_UP_FORCE_SCALE = 100.f;
const float TRANSMITTER_YAW_SCALE = 10.0f;
const float TRANSMITTER_LIFT_SCALE = 3.f;
const float TOUCH_POSITION_RANGE_HALF = 32767.f;
if (fabs(rotation.z) > TRANSMITTER_MIN_RATE) {
_thrust += rotation.z * TRANSMITTER_LATERAL_FORCE_SCALE * deltaTime * right;
}
if (fabs(rotation.x) > TRANSMITTER_MIN_RATE) {
_thrust += -rotation.x * TRANSMITTER_FWD_FORCE_SCALE * deltaTime * front;
}
if (fabs(rotation.y) > TRANSMITTER_MIN_YAW_RATE) {
_bodyYawDelta += rotation.y * TRANSMITTER_YAW_SCALE * deltaTime;
}
if (_transmitter.getTouchState()->state == 'D') {
_thrust += TRANSMITTER_UP_FORCE_SCALE *
(float)(_transmitter.getTouchState()->y - TOUCH_POSITION_RANGE_HALF) / TOUCH_POSITION_RANGE_HALF *
TRANSMITTER_LIFT_SCALE *
deltaTime *
up;
}
}
// Update speed brake status
const float MIN_SPEED_BRAKE_VELOCITY = _scale * 0.4f;
if ((glm::length(_thrust) == 0.0f) && _isThrustOn && (glm::length(_velocity) > MIN_SPEED_BRAKE_VELOCITY)) {
@ -1197,3 +1121,62 @@ void MyAvatar::resetSize() {
qDebug("Reseted scale to %f", _targetScale);
}
static QByteArray createByteArray(const glm::vec3& vector) {
return QByteArray::number(vector.x) + ',' + QByteArray::number(vector.y) + ',' + QByteArray::number(vector.z);
}
void MyAvatar::updateLocationInDataServer() {
// TODO: don't re-send this when it hasn't change or doesn't change by some threshold
// This will required storing the last sent values and clearing them when the AccountManager rootURL changes
AccountManager& accountManager = AccountManager::getInstance();
if (accountManager.isLoggedIn()) {
QString positionString(createByteArray(_position));
QString orientationString(createByteArray(safeEulerAngles(getOrientation())));
// construct the json to put the user's location
QString locationPutJson = QString() + "{\"location\":{\"position\":\""
+ positionString + "\", \"orientation\":\"" + orientationString + "\"}}";
accountManager.authenticatedRequest("/api/v1/users/location", QNetworkAccessManager::PutOperation,
JSONCallbackParameters(), locationPutJson.toUtf8());
}
}
void MyAvatar::goToLocationFromResponse(const QJsonObject& jsonObject) {
if (jsonObject["status"].toString() == "success") {
// send a node kill request, indicating to other clients that they should play the "disappeared" effect
sendKillAvatar();
QJsonObject locationObject = jsonObject["data"].toObject()["location"].toObject();
QString positionString = locationObject["position"].toString();
QString orientationString = locationObject["orientation"].toString();
QString domainHostnameString = locationObject["domain"].toString();
qDebug() << "Changing domain to" << domainHostnameString <<
", position to" << positionString <<
", and orientation to" << orientationString;
QStringList coordinateItems = positionString.split(',');
QStringList orientationItems = orientationString.split(',');
NodeList::getInstance()->getDomainInfo().setHostname(domainHostnameString);
// orient the user to face the target
glm::quat newOrientation = glm::quat(glm::radians(glm::vec3(orientationItems[0].toFloat(),
orientationItems[1].toFloat(),
orientationItems[2].toFloat())))
* glm::angleAxis(180.0f, 0.0f, 1.0f, 0.0f);
setOrientation(newOrientation);
// move the user a couple units away
const float DISTANCE_TO_USER = 2.0f;
glm::vec3 newPosition = glm::vec3(coordinateItems[0].toFloat(), coordinateItems[1].toFloat(),
coordinateItems[2].toFloat()) - newOrientation * IDENTITY_FRONT * DISTANCE_TO_USER;
setPosition(newPosition);
}
}

View file

@ -11,8 +11,6 @@
#include <QSettings>
#include <devices/Transmitter.h>
#include "Avatar.h"
enum AvatarHandState
@ -36,13 +34,10 @@ public:
void update(float deltaTime);
void simulate(float deltaTime);
void updateFromGyros(float deltaTime);
void updateTransmitter(float deltaTime);
void render(bool forceRenderHead);
void renderDebugBodyPoints();
void renderHeadMouse() const;
void renderTransmitterPickRay() const;
void renderTransmitterLevels(int width, int height) const;
// setters
void setMousePressed(bool mousePressed) { _mousePressed = mousePressed; }
@ -63,7 +58,6 @@ public:
float getAbsoluteHeadYaw() const;
const glm::vec3& getMouseRayOrigin() const { return _mouseRayOrigin; }
const glm::vec3& getMouseRayDirection() const { return _mouseRayDirection; }
Transmitter& getTransmitter() { return _transmitter; }
glm::vec3 getGravity() const { return _gravity; }
glm::vec3 getUprightHeadPosition() const;
bool getShouldRenderLocally() const { return _shouldRender; }
@ -81,21 +75,22 @@ public:
static void sendKillAvatar();
void orbit(const glm::vec3& position, int deltaX, int deltaY);
AvatarData* getLookAtTargetAvatar() const { return _lookAtTargetAvatar.data(); }
void updateLookAtTargetAvatar();
void clearLookAtTargetAvatar();
virtual void setFaceModelURL(const QUrl& faceModelURL);
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
public slots:
void goHome();
void increaseSize();
void decreaseSize();
void resetSize();
void updateLocationInDataServer();
void goToLocationFromResponse(const QJsonObject& jsonObject);
// Set/Get update the thrust that will move the avatar around
void addThrust(glm::vec3 newThrust) { _thrust += newThrust; };
@ -122,10 +117,6 @@ private:
QWeakPointer<AvatarData> _lookAtTargetAvatar;
bool _shouldRender;
Transmitter _transmitter; // Gets UDP data from transmitter app used to animate the avatar
glm::vec3 _transmitterPickStart;
glm::vec3 _transmitterPickEnd;
bool _billboardValid;
// private methods

View file

@ -1,167 +0,0 @@
//
// Profile.cpp
// hifi
//
// Created by Stephen Birarda on 10/8/13.
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
#include <QtCore/QSettings>
#include <NodeList.h>
#include <UUID.h>
#include "Application.h"
#include "Profile.h"
#include "Util.h"
Profile::Profile(const QString &username) :
_username(),
_uuid(),
_lastDomain(),
_lastPosition(0.0, 0.0, 0.0),
_lastOrientationSend(0)
{
if (!username.isEmpty()) {
setUsername(username);
// we've been given a new username, ask the data-server for profile
DataServerClient::getValueForKeyAndUserString(DataServerKey::UUID, getUserString(), this);
// send our current domain server to the data-server
updateDomain(NodeList::getInstance()->getDomainHostname());
}
}
QString Profile::getUserString() const {
if (_uuid.isNull()) {
return _username;
} else {
return uuidStringWithoutCurlyBraces(_uuid);
}
}
void Profile::updateDomain(const QString& domain) {
if (_lastDomain != domain) {
_lastDomain = domain;
// send the changed domain to the data-server
DataServerClient::putValueForKeyAndUserString(DataServerKey::Domain, domain, getUserString());
}
}
static QByteArray createByteArray(const glm::vec3& vector) {
return QByteArray::number(vector.x) + ',' + QByteArray::number(vector.y) + ',' + QByteArray::number(vector.z);
}
void Profile::updatePosition(const glm::vec3 position) {
if (_lastPosition != position) {
static timeval lastPositionSend = {};
const quint64 DATA_SERVER_POSITION_UPDATE_INTERVAL_USECS = 5 * 1000 * 1000;
const float DATA_SERVER_POSITION_CHANGE_THRESHOLD_METERS = 1;
if (usecTimestampNow() - usecTimestamp(&lastPositionSend) >= DATA_SERVER_POSITION_UPDATE_INTERVAL_USECS &&
(fabsf(_lastPosition.x - position.x) >= DATA_SERVER_POSITION_CHANGE_THRESHOLD_METERS ||
fabsf(_lastPosition.y - position.y) >= DATA_SERVER_POSITION_CHANGE_THRESHOLD_METERS ||
fabsf(_lastPosition.z - position.z) >= DATA_SERVER_POSITION_CHANGE_THRESHOLD_METERS)) {
// if it has been 5 seconds since the last position change and the user has moved >= the threshold
// in at least one of the axis then send the position update to the data-server
_lastPosition = position;
// update the lastPositionSend to now
gettimeofday(&lastPositionSend, NULL);
// send the changed position to the data-server
DataServerClient::putValueForKeyAndUserString(DataServerKey::Position,
QString(createByteArray(position)), getUserString());
}
}
}
void Profile::updateOrientation(const glm::quat& orientation) {
glm::vec3 eulerAngles = safeEulerAngles(orientation);
if (_lastOrientation == eulerAngles) {
return;
}
const quint64 DATA_SERVER_ORIENTATION_UPDATE_INTERVAL_USECS = 5 * 1000 * 1000;
const float DATA_SERVER_ORIENTATION_CHANGE_THRESHOLD_DEGREES = 5.0f;
quint64 now = usecTimestampNow();
if (now - _lastOrientationSend >= DATA_SERVER_ORIENTATION_UPDATE_INTERVAL_USECS &&
glm::distance(_lastOrientation, eulerAngles) >= DATA_SERVER_ORIENTATION_CHANGE_THRESHOLD_DEGREES) {
DataServerClient::putValueForKeyAndUserString(DataServerKey::Orientation, QString(createByteArray(eulerAngles)),
getUserString());
_lastOrientation = eulerAngles;
_lastOrientationSend = now;
}
}
void Profile::saveData(QSettings* settings) {
settings->beginGroup("Profile");
settings->setValue("username", _username);
settings->setValue("UUID", _uuid);
settings->endGroup();
}
void Profile::loadData(QSettings* settings) {
settings->beginGroup("Profile");
setUsername(settings->value("username").toString());
this->setUUID(settings->value("UUID").toUuid());
settings->endGroup();
}
void Profile::processDataServerResponse(const QString& userString, const QStringList& keyList, const QStringList& valueList) {
for (int i = 0; i < keyList.size(); i++) {
if (valueList[i] != " ") {
if (keyList[i] == DataServerKey::Domain && keyList[i + 1] == DataServerKey::Position &&
keyList[i + 2] == DataServerKey::Orientation && valueList[i] != " " &&
valueList[i + 1] != " " && valueList[i + 2] != " ") {
QStringList coordinateItems = valueList[i + 1].split(',');
QStringList orientationItems = valueList[i + 2].split(',');
if (coordinateItems.size() == 3 && orientationItems.size() == 3) {
// send a node kill request, indicating to other clients that they should play the "disappeared" effect
MyAvatar::sendKillAvatar();
qDebug() << "Changing domain to" << valueList[i].toLocal8Bit().constData() <<
", position to" << valueList[i + 1].toLocal8Bit().constData() <<
", and orientation to" << valueList[i + 2].toLocal8Bit().constData() <<
"to go to" << userString;
NodeList::getInstance()->setDomainHostname(valueList[i]);
// orient the user to face the target
glm::quat newOrientation = glm::quat(glm::radians(glm::vec3(orientationItems[0].toFloat(),
orientationItems[1].toFloat(),
orientationItems[2].toFloat()))) *
glm::angleAxis(180.0f, 0.0f, 1.0f, 0.0f);
Application::getInstance()->getAvatar()->setOrientation(newOrientation);
// move the user a couple units away
const float DISTANCE_TO_USER = 2.0f;
glm::vec3 newPosition = glm::vec3(coordinateItems[0].toFloat(), coordinateItems[1].toFloat(),
coordinateItems[2].toFloat()
) - newOrientation * IDENTITY_FRONT * DISTANCE_TO_USER;
Application::getInstance()->getAvatar()->setPosition(newPosition);
}
} else if (keyList[i] == DataServerKey::UUID) {
// this is the user's UUID - set it on the profile
setUUID(QUuid(valueList[i]));
}
}
}
}
void Profile::setUsername(const QString& username) {
_username = username;
}

View file

@ -1,59 +0,0 @@
//
// Profile.h
// hifi
//
// Created by Stephen Birarda on 10/8/13.
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
#ifndef __hifi__Profile__
#define __hifi__Profile__
#include <stdint.h>
#include <QtCore/QString>
#include <QtCore/QUrl>
#include <QtCore/QUuid>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include "DataServerClient.h"
class Profile : public DataServerCallbackObject {
public:
Profile(const QString& username);
QString getUserString() const;
const QString& getUsername() const { return _username; }
void setUUID(const QUuid& uuid) { _uuid = uuid; }
const QUuid& getUUID() { return _uuid; }
void updateDomain(const QString& domain);
void updatePosition(const glm::vec3 position);
void updateOrientation(const glm::quat& orientation);
QString getLastDomain() const { return _lastDomain; }
const glm::vec3& getLastPosition() const { return _lastPosition; }
void saveData(QSettings* settings);
void loadData(QSettings* settings);
void processDataServerResponse(const QString& userString, const QStringList& keyList, const QStringList& valueList);
private:
void setUsername(const QString& username);
void setDisplayName(const QString& displaName);
QString _username;
QUuid _uuid;
QString _lastDomain;
glm::vec3 _lastPosition;
glm::vec3 _lastOrientation;
quint64 _lastOrientationSend;
};
#endif /* defined(__hifi__Profile__) */

View file

@ -1,167 +0,0 @@
//
// Transmitter.cpp
// hifi
//
// Created by Philip Rosedale on 5/20/13.
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
#ifdef WIN32
#define WANT_TIMEVAL
#include <Systime.h>
#endif
#include <cstring>
#include <glm/glm.hpp>
#include <PacketHeaders.h>
#include "InterfaceConfig.h"
#include "Transmitter.h"
#include "Util.h"
const float DELTA_TIME = 1.f / 60.f;
const float DECAY_RATE = 0.15f;
Transmitter::Transmitter() :
_isConnected(false),
_lastRotationRate(0,0,0),
_lastAcceleration(0,0,0),
_estimatedRotation(0,0,0),
_lastReceivedPacket(NULL)
{
}
Transmitter::~Transmitter() {
if (_lastReceivedPacket) {
delete _lastReceivedPacket;
}
}
void Transmitter::checkForLostTransmitter() {
// If we are in motion, check for loss of transmitter packets
if (glm::length(_estimatedRotation) > 0.f) {
timeval now;
gettimeofday(&now, NULL);
const int TIME_TO_ASSUME_LOST_MSECS = 2000;
int msecsSinceLast = diffclock(_lastReceivedPacket, &now);
if (msecsSinceLast > TIME_TO_ASSUME_LOST_MSECS) {
resetLevels();
qDebug("Transmitter signal lost.");
}
}
}
void Transmitter::resetLevels() {
_lastRotationRate *= 0.f;
_estimatedRotation *= 0.f;
}
void Transmitter::processIncomingData(unsigned char* packetData, int numBytes) {
// Packet's first byte is 'T'
int numBytesPacketHeader = numBytesForPacketHeader(reinterpret_cast<const char*>(packetData));
const int ROTATION_MARKER_SIZE = 1; // 'R' = Rotation (clockwise about x,y,z)
const int ACCELERATION_MARKER_SIZE = 1; // 'A' = Acceleration (x,y,z)
if (!_lastReceivedPacket) {
_lastReceivedPacket = new timeval;
}
gettimeofday(_lastReceivedPacket, NULL);
if (numBytes == numBytesPacketHeader + ROTATION_MARKER_SIZE + ACCELERATION_MARKER_SIZE
+ sizeof(_lastRotationRate) + sizeof(_lastAcceleration)
+ sizeof(_touchState.x) + sizeof(_touchState.y) + sizeof(_touchState.state)) {
unsigned char* packetDataPosition = packetData + numBytesPacketHeader + ROTATION_MARKER_SIZE;
memcpy(&_lastRotationRate, packetDataPosition, sizeof(_lastRotationRate));
packetDataPosition += sizeof(_lastRotationRate) + ACCELERATION_MARKER_SIZE;
memcpy(&_lastAcceleration, packetDataPosition, sizeof(_lastAcceleration));
packetDataPosition += sizeof(_lastAcceleration);
memcpy(&_touchState.state, packetDataPosition, sizeof(_touchState.state));
packetDataPosition += sizeof(_touchState.state);
memcpy(&_touchState.x, packetDataPosition, sizeof(_touchState.x));
packetDataPosition += sizeof(_touchState.x);
memcpy(&_touchState.y, packetDataPosition, sizeof(_touchState.y));
packetDataPosition += sizeof(_touchState.y);
// Update estimated absolute position from rotation rates
_estimatedRotation += _lastRotationRate * DELTA_TIME;
// Sensor Fusion! Slowly adjust estimated rotation to be relative to gravity (average acceleration)
const float GRAVITY_FOLLOW_RATE = 1.f;
float rollAngle = angleBetween(glm::vec3(_lastAcceleration.x, _lastAcceleration.y, 0.f), glm::vec3(0,-1,0)) *
((_lastAcceleration.x < 0.f) ? -1.f : 1.f);
float pitchAngle = angleBetween(glm::vec3(0.f, _lastAcceleration.y, _lastAcceleration.z), glm::vec3(0,-1,0)) *
((_lastAcceleration.z < 0.f) ? 1.f : -1.f);
_estimatedRotation.x = (1.f - GRAVITY_FOLLOW_RATE * DELTA_TIME) * _estimatedRotation.x +
GRAVITY_FOLLOW_RATE * DELTA_TIME * pitchAngle;
_estimatedRotation.z = (1.f - GRAVITY_FOLLOW_RATE * DELTA_TIME) * _estimatedRotation.z +
GRAVITY_FOLLOW_RATE * DELTA_TIME * rollAngle;
// Can't apply gravity fusion to Yaw, so decay estimated yaw to zero,
// presuming that the average yaw direction is toward screen
_estimatedRotation.y *= (1.f - DECAY_RATE * DELTA_TIME);
if (!_isConnected) {
qDebug("Transmitter Connected.");
_isConnected = true;
_estimatedRotation *= 0.0;
}
} else {
qDebug("Transmitter packet read error, %d bytes.", numBytes);
}
}
void Transmitter::renderLevels(int width, int height) const {
char val[50];
const int LEVEL_CORNER_X = 10;
const int LEVEL_CORNER_Y = 400;
// Draw the numeric degree/sec values from the gyros
sprintf(val, "Pitch Rate %4.1f", _lastRotationRate.x);
drawtext(LEVEL_CORNER_X, LEVEL_CORNER_Y, 0.10f, 0, 1.0f, 1, val, 0, 1, 0);
sprintf(val, "Yaw Rate %4.1f", _lastRotationRate.y);
drawtext(LEVEL_CORNER_X, LEVEL_CORNER_Y + 15, 0.10f, 0, 1.0f, 1, val, 0, 1, 0);
sprintf(val, "Roll Rate %4.1f", _lastRotationRate.z);
drawtext(LEVEL_CORNER_X, LEVEL_CORNER_Y + 30, 0.10f, 0, 1.0f, 1, val, 0, 1, 0);
sprintf(val, "Pitch %4.3f", _estimatedRotation.x);
drawtext(LEVEL_CORNER_X, LEVEL_CORNER_Y + 45, 0.10f, 0, 1.0f, 1, val, 0, 1, 0);
sprintf(val, "Yaw %4.3f", _estimatedRotation.y);
drawtext(LEVEL_CORNER_X, LEVEL_CORNER_Y + 60, 0.10f, 0, 1.0f, 1, val, 0, 1, 0);
sprintf(val, "Roll %4.3f", _estimatedRotation.z);
drawtext(LEVEL_CORNER_X, LEVEL_CORNER_Y + 75, 0.10f, 0, 1.0f, 1, val, 0, 1, 0);
// Draw the levels as horizontal lines
const int LEVEL_CENTER = 150;
const float ACCEL_VIEW_SCALING = 1.f;
glLineWidth(2.0);
glColor4f(1, 1, 1, 1);
glBegin(GL_LINES);
// Gyro rates
glVertex2f(LEVEL_CORNER_X + LEVEL_CENTER, LEVEL_CORNER_Y - 3);
glVertex2f(LEVEL_CORNER_X + LEVEL_CENTER + _lastRotationRate.x, LEVEL_CORNER_Y - 3);
glVertex2f(LEVEL_CORNER_X + LEVEL_CENTER, LEVEL_CORNER_Y + 12);
glVertex2f(LEVEL_CORNER_X + LEVEL_CENTER + _lastRotationRate.y, LEVEL_CORNER_Y + 12);
glVertex2f(LEVEL_CORNER_X + LEVEL_CENTER, LEVEL_CORNER_Y + 27);
glVertex2f(LEVEL_CORNER_X + LEVEL_CENTER + _lastRotationRate.z, LEVEL_CORNER_Y + 27);
// Acceleration
glVertex2f(LEVEL_CORNER_X + LEVEL_CENTER, LEVEL_CORNER_Y + 42);
glVertex2f(LEVEL_CORNER_X + LEVEL_CENTER + (int)(_estimatedRotation.x * ACCEL_VIEW_SCALING),
LEVEL_CORNER_Y + 42);
glVertex2f(LEVEL_CORNER_X + LEVEL_CENTER, LEVEL_CORNER_Y + 57);
glVertex2f(LEVEL_CORNER_X + LEVEL_CENTER + (int)(_estimatedRotation.y * ACCEL_VIEW_SCALING),
LEVEL_CORNER_Y + 57);
glVertex2f(LEVEL_CORNER_X + LEVEL_CENTER, LEVEL_CORNER_Y + 72);
glVertex2f(LEVEL_CORNER_X + LEVEL_CENTER + (int)(_estimatedRotation.z * ACCEL_VIEW_SCALING),
LEVEL_CORNER_Y + 72);
glEnd();
// Draw green vertical centerline
glColor4f(0, 1, 0, 0.5);
glBegin(GL_LINES);
glVertex2f(LEVEL_CORNER_X + LEVEL_CENTER, LEVEL_CORNER_Y - 6);
glVertex2f(LEVEL_CORNER_X + LEVEL_CENTER, LEVEL_CORNER_Y + 30);
glEnd();
}

View file

@ -1,50 +0,0 @@
//
// Transmitter.h
// hifi
//
// Created by Philip Rosedale on 5/20/13.
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
#ifndef __hifi__Transmitter__
#define __hifi__Transmitter__
#include <iostream>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
#include <cstring>
#include "world.h"
#include <stdint.h>
struct TouchState {
uint16_t x, y;
char state;
};
class Transmitter
{
public:
Transmitter();
~Transmitter();
void render();
void checkForLostTransmitter();
void resetLevels();
void renderLevels(int width, int height) const;
bool isConnected() const { return _isConnected; };
const glm::vec3 getLastRotationRate() const { return _lastRotationRate; };
const glm::vec3 getLastAcceleration() const { return _lastRotationRate; };
const glm::vec3 getEstimatedRotation() const { return _estimatedRotation; };
const TouchState* getTouchState() const { return &_touchState; };
void processIncomingData(unsigned char* packetData, int numBytes);
private:
bool _isConnected;
glm::vec3 _lastRotationRate;
glm::vec3 _lastAcceleration;
glm::vec3 _estimatedRotation;
TouchState _touchState;
timeval* _lastReceivedPacket;
};
#endif /* defined(__hifi__Transmitter__) */

View file

@ -38,6 +38,7 @@ int main(int argc, const char * argv[]) {
int exitCode;
{
QSettings::setDefaultFormat(QSettings::IniFormat);
Application app(argc, const_cast<char**>(argv), startup_time);
qDebug( "Created QT Application.");

View file

@ -3,16 +3,17 @@
// hifi
//
// Created by Stojce Slavkovski on 1/26/14.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
//
#include "Snapshot.h"
#include <FileUtils.h>
#include <QDateTime>
#include <QFileInfo>
#include <AccountManager.h>
#include <FileUtils.h>
#include "Snapshot.h"
// filename format: hifi-snap-by-%username%-on-%date%_%time%_@-%location%.jpg
// %1 <= username, %2 <= date and time, %3 <= current location
const QString FILENAME_PATH_FORMAT = "hifi-snap-by-%1-on-%2@%3.jpg";
@ -31,7 +32,6 @@ const QString ORIENTATION_W = "orientation-w";
const QString DOMAIN_KEY = "domain";
SnapshotMetaData* Snapshot::parseSnapshotData(QString snapshotPath) {
if (!QFile(snapshotPath).exists()) {
@ -60,7 +60,7 @@ SnapshotMetaData* Snapshot::parseSnapshotData(QString snapshotPath) {
return data;
}
void Snapshot::saveSnapshot(QGLWidget* widget, Profile* profile, Avatar* avatar) {
void Snapshot::saveSnapshot(QGLWidget* widget, Avatar* avatar) {
QImage shot = widget->grabFrameBuffer();
glm::vec3 location = avatar->getPosition();
@ -76,13 +76,13 @@ void Snapshot::saveSnapshot(QGLWidget* widget, Profile* profile, Avatar* avatar)
shot.setText(ORIENTATION_Z, QString::number(orientation.z));
shot.setText(ORIENTATION_W, QString::number(orientation.w));
shot.setText(DOMAIN_KEY, profile->getLastDomain());
shot.setText(DOMAIN_KEY, NodeList::getInstance()->getDomainInfo().getHostname());
QString formattedLocation = QString("%1_%2_%3").arg(location.x).arg(location.y).arg(location.z);
// replace decimal . with '-'
formattedLocation.replace('.', '-');
QString username = profile->getUsername();
QString username = AccountManager::getInstance().getUsername();
// normalize username, replace all non alphanumeric with '-'
username.replace(QRegExp("[^A-Za-z0-9_]"), "-");

View file

@ -11,12 +11,11 @@
#include "InterfaceConfig.h"
#include <QString>
#include <QImage>
#include <QGLWidget>
#include <QString>
#include "avatar/Avatar.h"
#include "avatar/Profile.h"
class SnapshotMetaData {
public:
@ -39,7 +38,7 @@ private:
class Snapshot {
public:
static void saveSnapshot(QGLWidget* widget, Profile* profile, Avatar* avatar);
static void saveSnapshot(QGLWidget* widget, Avatar* avatar);
static SnapshotMetaData* parseSnapshotData(QString snapshotPath);
};

View file

@ -0,0 +1,291 @@
//
// AccountManager.cpp
// hifi
//
// Created by Stephen Birarda on 2/18/2014.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
#include <QtCore/QDataStream>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <QtCore/QMap>
#include <QtCore/QStringList>
#include <QtCore/QUrlQuery>
#include <QtNetwork/QNetworkRequest>
#include "NodeList.h"
#include "PacketHeaders.h"
#include "AccountManager.h"
const bool VERBOSE_HTTP_REQUEST_DEBUGGING = false;
AccountManager& AccountManager::getInstance() {
static AccountManager sharedInstance;
return sharedInstance;
}
Q_DECLARE_METATYPE(OAuthAccessToken)
Q_DECLARE_METATYPE(DataServerAccountInfo)
Q_DECLARE_METATYPE(QNetworkAccessManager::Operation)
Q_DECLARE_METATYPE(JSONCallbackParameters)
const QString ACCOUNTS_GROUP = "accounts";
AccountManager::AccountManager() :
_rootURL(),
_networkAccessManager(),
_pendingCallbackMap(),
_accounts()
{
qRegisterMetaType<OAuthAccessToken>("OAuthAccessToken");
qRegisterMetaTypeStreamOperators<OAuthAccessToken>("OAuthAccessToken");
qRegisterMetaType<DataServerAccountInfo>("DataServerAccountInfo");
qRegisterMetaTypeStreamOperators<DataServerAccountInfo>("DataServerAccountInfo");
qRegisterMetaType<QNetworkAccessManager::Operation>("QNetworkAccessManager::Operation");
qRegisterMetaType<JSONCallbackParameters>("JSONCallbackParameters");
// check if there are existing access tokens to load from settings
QSettings settings;
settings.beginGroup(ACCOUNTS_GROUP);
foreach(const QString& key, settings.allKeys()) {
// take a key copy to perform the double slash replacement
QString keyCopy(key);
QUrl keyURL(keyCopy.replace("slashslash", "//"));
// pull out the stored access token and put it in our in memory array
_accounts.insert(keyURL, settings.value(key).value<DataServerAccountInfo>());
qDebug() << "Found a data-server access token for" << qPrintable(keyURL.toString());
}
}
const QString DOUBLE_SLASH_SUBSTITUTE = "slashslash";
void AccountManager::logout() {
// a logout means we want to delete the DataServerAccountInfo we currently have for this URL, in-memory and in file
_accounts.remove(_rootURL);
QSettings settings;
settings.beginGroup(ACCOUNTS_GROUP);
QString keyURLString(_rootURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE));
settings.remove(keyURLString);
qDebug() << "Removed account info for" << _rootURL << "from in-memory accounts and .ini file";
emit logoutComplete();
// the username has changed to blank
emit usernameChanged(QString());
}
void AccountManager::setRootURL(const QUrl& rootURL) {
if (_rootURL != rootURL) {
_rootURL = rootURL;
qDebug() << "URL for node authentication has been changed to" << qPrintable(_rootURL.toString());
qDebug() << "Re-setting authentication flow.";
// tell listeners that the auth endpoint has changed
emit authEndpointChanged();
}
}
void AccountManager::authenticatedRequest(const QString& path, QNetworkAccessManager::Operation operation,
const JSONCallbackParameters& callbackParams, const QByteArray& dataByteArray) {
QMetaObject::invokeMethod(this, "invokedRequest",
Q_ARG(const QString&, path),
Q_ARG(QNetworkAccessManager::Operation, operation),
Q_ARG(const JSONCallbackParameters&, callbackParams),
Q_ARG(const QByteArray&, dataByteArray));
}
void AccountManager::invokedRequest(const QString& path, QNetworkAccessManager::Operation operation,
const JSONCallbackParameters& callbackParams, const QByteArray& dataByteArray) {
if (hasValidAccessToken()) {
QNetworkRequest authenticatedRequest;
QUrl requestURL = _rootURL;
requestURL.setPath(path);
requestURL.setQuery("access_token=" + _accounts.value(_rootURL).getAccessToken().token);
authenticatedRequest.setUrl(requestURL);
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
qDebug() << "Making an authenticated request to" << qPrintable(requestURL.toString());
if (!dataByteArray.isEmpty()) {
qDebug() << "The POST/PUT body -" << QString(dataByteArray);
}
}
QNetworkReply* networkReply = NULL;
switch (operation) {
case QNetworkAccessManager::GetOperation:
networkReply = _networkAccessManager.get(authenticatedRequest);
break;
case QNetworkAccessManager::PostOperation:
case QNetworkAccessManager::PutOperation:
authenticatedRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
if (operation == QNetworkAccessManager::PostOperation) {
networkReply = _networkAccessManager.post(authenticatedRequest, dataByteArray);
} else {
networkReply = _networkAccessManager.put(authenticatedRequest, dataByteArray);
}
break;
default:
// other methods not yet handled
break;
}
if (networkReply) {
if (!callbackParams.isEmpty()) {
// if we have information for a callback, insert the callbackParams into our local map
_pendingCallbackMap.insert(networkReply, callbackParams);
}
// if we ended up firing of a request, hook up to it now
connect(networkReply, SIGNAL(finished()), this, SLOT(passSuccessToCallback()));
connect(networkReply, SIGNAL(error(QNetworkReply::NetworkError)),
this, SLOT(passErrorToCallback(QNetworkReply::NetworkError)));
}
}
}
void AccountManager::passSuccessToCallback() {
QNetworkReply* requestReply = reinterpret_cast<QNetworkReply*>(sender());
QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll());
JSONCallbackParameters callbackParams = _pendingCallbackMap.value(requestReply);
if (callbackParams.jsonCallbackReceiver) {
// invoke the right method on the callback receiver
QMetaObject::invokeMethod(callbackParams.jsonCallbackReceiver, qPrintable(callbackParams.jsonCallbackMethod),
Q_ARG(const QJsonObject&, jsonResponse.object()));
// remove the related reply-callback group from the map
_pendingCallbackMap.remove(requestReply);
} else {
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
qDebug() << "Received JSON response from data-server that has no matching callback.";
qDebug() << jsonResponse;
}
}
}
void AccountManager::passErrorToCallback(QNetworkReply::NetworkError errorCode) {
QNetworkReply* requestReply = reinterpret_cast<QNetworkReply*>(sender());
JSONCallbackParameters callbackParams = _pendingCallbackMap.value(requestReply);
if (callbackParams.errorCallbackReceiver) {
// invoke the right method on the callback receiver
QMetaObject::invokeMethod(callbackParams.errorCallbackReceiver, qPrintable(callbackParams.errorCallbackMethod),
Q_ARG(QNetworkReply::NetworkError, errorCode),
Q_ARG(const QString&, requestReply->errorString()));
// remove the related reply-callback group from the map
_pendingCallbackMap.remove(requestReply);
} else {
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
qDebug() << "Received error response from data-server that has no matching callback.";
qDebug() << "Error" << errorCode << "-" << requestReply->errorString();
}
}
}
bool AccountManager::hasValidAccessToken() {
DataServerAccountInfo accountInfo = _accounts.value(_rootURL);
if (accountInfo.getAccessToken().token.isEmpty() || accountInfo.getAccessToken().isExpired()) {
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
qDebug() << "An access token is required for requests to" << qPrintable(_rootURL.toString());
}
return false;
} else {
return true;
}
}
bool AccountManager::checkAndSignalForAccessToken() {
bool hasToken = hasValidAccessToken();
if (!hasToken) {
// emit a signal so somebody can call back to us and request an access token given a username and password
emit authRequired();
}
return hasToken;
}
void AccountManager::requestAccessToken(const QString& login, const QString& password) {
QNetworkRequest request;
QUrl grantURL = _rootURL;
grantURL.setPath("/oauth/token");
QByteArray postData;
postData.append("grant_type=password&");
postData.append("username=" + login + "&");
postData.append("password=" + password);
request.setUrl(grantURL);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QNetworkReply* requestReply = _networkAccessManager.post(request, postData);
connect(requestReply, &QNetworkReply::finished, this, &AccountManager::requestFinished);
connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestError(QNetworkReply::NetworkError)));
}
void AccountManager::requestFinished() {
QNetworkReply* requestReply = reinterpret_cast<QNetworkReply*>(sender());
QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll());
const QJsonObject& rootObject = jsonResponse.object();
if (!rootObject.contains("error")) {
// construct an OAuthAccessToken from the json object
if (!rootObject.contains("access_token") || !rootObject.contains("expires_in")
|| !rootObject.contains("token_type")) {
// TODO: error handling - malformed token response
qDebug() << "Received a response for password grant that is missing one or more expected values.";
} else {
// clear the path from the response URL so we have the right root URL for this access token
QUrl rootURL = requestReply->url();
rootURL.setPath("");
qDebug() << "Storing an account with access-token for" << qPrintable(rootURL.toString());
DataServerAccountInfo freshAccountInfo(rootObject);
_accounts.insert(rootURL, freshAccountInfo);
emit loginComplete(rootURL);
// the username has changed to whatever came back
emit usernameChanged(freshAccountInfo.getUsername());
// store this access token into the local settings
QSettings localSettings;
localSettings.beginGroup(ACCOUNTS_GROUP);
localSettings.setValue(rootURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE),
QVariant::fromValue(freshAccountInfo));
}
} else {
// TODO: error handling
qDebug() << "Error in response for password grant -" << rootObject["error_description"].toString();
}
}
void AccountManager::requestError(QNetworkReply::NetworkError error) {
// TODO: error handling
qDebug() << "AccountManager requestError - " << error;
}

View file

@ -0,0 +1,84 @@
//
// AccountManager.h
// hifi
//
// Created by Stephen Birarda on 2/18/2014.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
#ifndef __hifi__AccountManager__
#define __hifi__AccountManager__
#include <QtCore/QByteArray>
#include <QtCore/QObject>
#include <QtCore/QUrl>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply>
#include "DataServerAccountInfo.h"
class JSONCallbackParameters {
public:
JSONCallbackParameters() :
jsonCallbackReceiver(NULL), jsonCallbackMethod(),
errorCallbackReceiver(NULL), errorCallbackMethod() {};
bool isEmpty() const { return jsonCallbackReceiver == NULL && errorCallbackReceiver == NULL; }
QObject* jsonCallbackReceiver;
QString jsonCallbackMethod;
QObject* errorCallbackReceiver;
QString errorCallbackMethod;
};
class AccountManager : public QObject {
Q_OBJECT
public:
static AccountManager& getInstance();
void authenticatedRequest(const QString& path,
QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation,
const JSONCallbackParameters& callbackParams = JSONCallbackParameters(),
const QByteArray& dataByteArray = QByteArray());
const QUrl& getRootURL() const { return _rootURL; }
void setRootURL(const QUrl& rootURL);
bool hasAuthEndpoint() { return !_rootURL.isEmpty(); }
bool isLoggedIn() { return !_rootURL.isEmpty() && hasValidAccessToken(); }
bool hasValidAccessToken();
bool checkAndSignalForAccessToken();
void requestAccessToken(const QString& login, const QString& password);
QString getUsername() const { return _accounts[_rootURL].getUsername(); }
public slots:
void requestFinished();
void requestError(QNetworkReply::NetworkError error);
void logout();
signals:
void authRequired();
void authEndpointChanged();
void usernameChanged(const QString& username);
void loginComplete(const QUrl& rootURL);
void logoutComplete();
private slots:
void passSuccessToCallback();
void passErrorToCallback(QNetworkReply::NetworkError errorCode);
private:
AccountManager();
AccountManager(AccountManager const& other); // not implemented
void operator=(AccountManager const& other); // not implemented
Q_INVOKABLE void invokedRequest(const QString& path, QNetworkAccessManager::Operation operation,
const JSONCallbackParameters& callbackParams, const QByteArray& dataByteArray);
QUrl _rootURL;
QNetworkAccessManager _networkAccessManager;
QMap<QNetworkReply*, JSONCallbackParameters> _pendingCallbackMap;
QMap<QUrl, DataServerAccountInfo> _accounts;
};
#endif /* defined(__hifi__AccountManager__) */

View file

@ -0,0 +1,61 @@
//
// DataServerAccountInfo.cpp
// hifi
//
// Created by Stephen Birarda on 2/18/2014.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
#include <QtCore/QDebug>
#include "DataServerAccountInfo.h"
DataServerAccountInfo::DataServerAccountInfo() :
_accessToken(),
_username()
{
}
DataServerAccountInfo::DataServerAccountInfo(const QJsonObject& jsonObject) :
_accessToken(jsonObject),
_username()
{
setUsername(jsonObject["user"].toObject()["username"].toString());
}
DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherInfo) {
_accessToken = otherInfo._accessToken;
_username = otherInfo._username;
}
DataServerAccountInfo& DataServerAccountInfo::operator=(const DataServerAccountInfo& otherInfo) {
DataServerAccountInfo temp(otherInfo);
swap(temp);
return *this;
}
void DataServerAccountInfo::swap(DataServerAccountInfo& otherInfo) {
using std::swap;
swap(_accessToken, otherInfo._accessToken);
swap(_username, otherInfo._username);
}
void DataServerAccountInfo::setUsername(const QString& username) {
if (_username != username) {
_username = username;
qDebug() << "Username changed to" << username;
}
}
QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info) {
out << info._accessToken << info._username;
return out;
}
QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info) {
in >> info._accessToken >> info._username;
return in;
}

View file

@ -0,0 +1,38 @@
//
// DataServerAccountInfo.h
// hifi
//
// Created by Stephen Birarda on 2/21/2014.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
#ifndef __hifi__DataServerAccountInfo__
#define __hifi__DataServerAccountInfo__
#include <QtCore/QObject>
#include "OAuthAccessToken.h"
class DataServerAccountInfo : public QObject {
Q_OBJECT
public:
DataServerAccountInfo();
DataServerAccountInfo(const QJsonObject& jsonObject);
DataServerAccountInfo(const DataServerAccountInfo& otherInfo);
DataServerAccountInfo& operator=(const DataServerAccountInfo& otherInfo);
const OAuthAccessToken& getAccessToken() const { return _accessToken; }
const QString& getUsername() const { return _username; }
void setUsername(const QString& username);
friend QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info);
friend QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info);
private:
void swap(DataServerAccountInfo& otherInfo);
OAuthAccessToken _accessToken;
QString _username;
};
#endif /* defined(__hifi__DataServerAccountInfo__) */

View file

@ -1,148 +0,0 @@
//
// DataServerClient.cpp
// hifi
//
// Created by Stephen Birarda on 10/7/13.
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
#include <QtCore/QUrl>
#include <QtNetwork/QUdpSocket>
#include "NodeList.h"
#include "PacketHeaders.h"
#include "UUID.h"
#include "DataServerClient.h"
QMap<quint8, QByteArray> DataServerClient::_unmatchedPackets;
QMap<quint8, DataServerCallbackObject*> DataServerClient::_callbackObjects;
quint8 DataServerClient::_sequenceNumber = 0;
const char MULTI_KEY_VALUE_SEPARATOR = '|';
const char DATA_SERVER_HOSTNAME[] = "data.highfidelity.io";
const unsigned short DATA_SERVER_PORT = 3282;
const HifiSockAddr& DataServerClient::dataServerSockAddr() {
static HifiSockAddr dsSockAddr = HifiSockAddr(DATA_SERVER_HOSTNAME, DATA_SERVER_PORT);
return dsSockAddr;
}
void DataServerClient::putValueForKeyAndUserString(const QString& key, const QString& value, const QString& userString) {
// setup the header for this packet and push packetStream to desired spot
QByteArray putPacket = byteArrayWithPopulatedHeader(PacketTypeDataServerPut);
QDataStream packetStream(&putPacket, QIODevice::Append);
// pack our data for the put packet
packetStream << _sequenceNumber << userString << key << value;
// add the putPacket to our vector of unconfirmed packets, will be deleted once put is confirmed
_unmatchedPackets.insert(_sequenceNumber, putPacket);
// send this put request to the data server
NodeList::getInstance()->getNodeSocket().writeDatagram(putPacket, dataServerSockAddr().getAddress(),
dataServerSockAddr().getPort());
// push the sequence number forwards
_sequenceNumber++;
}
void DataServerClient::putValueForKeyAndUUID(const QString& key, const QString& value, const QUuid& uuid) {
putValueForKeyAndUserString(key, value, uuidStringWithoutCurlyBraces(uuid));
}
void DataServerClient::getValueForKeyAndUUID(const QString& key, const QUuid &uuid, DataServerCallbackObject* callbackObject) {
getValuesForKeysAndUUID(QStringList(key), uuid, callbackObject);
}
void DataServerClient::getValuesForKeysAndUUID(const QStringList& keys, const QUuid& uuid,
DataServerCallbackObject* callbackObject) {
if (!uuid.isNull()) {
getValuesForKeysAndUserString(keys, uuidStringWithoutCurlyBraces(uuid), callbackObject);
}
}
void DataServerClient::getValuesForKeysAndUserString(const QStringList& keys, const QString& userString,
DataServerCallbackObject* callbackObject) {
if (!userString.isEmpty() && keys.size() <= UCHAR_MAX) {
QByteArray getPacket = byteArrayWithPopulatedHeader(PacketTypeDataServerGet);
QDataStream packetStream(&getPacket, QIODevice::Append);
// pack our data for the getPacket
packetStream << _sequenceNumber << userString << keys.join(MULTI_KEY_VALUE_SEPARATOR);
// add the getPacket to our map of unconfirmed packets, will be deleted once we get a response from the nameserver
_unmatchedPackets.insert(_sequenceNumber, getPacket);
_callbackObjects.insert(_sequenceNumber, callbackObject);
// send the get to the data server
NodeList::getInstance()->getNodeSocket().writeDatagram(getPacket, dataServerSockAddr().getAddress(),
dataServerSockAddr().getPort());
_sequenceNumber++;
}
}
void DataServerClient::getValueForKeyAndUserString(const QString& key, const QString& userString,
DataServerCallbackObject* callbackObject) {
getValuesForKeysAndUserString(QStringList(key), userString, callbackObject);
}
void DataServerClient::processConfirmFromDataServer(const QByteArray& packet) {
removeMatchedPacketFromMap(packet);
}
void DataServerClient::processSendFromDataServer(const QByteArray& packet) {
// pull the user string from the packet so we know who to associate this with
QDataStream packetStream(packet);
packetStream.skipRawData(numBytesForPacketHeader(packet));
quint8 sequenceNumber = 0;
packetStream >> sequenceNumber;
if (_callbackObjects.find(sequenceNumber) != _callbackObjects.end()) {
// remove the packet from our two maps, it's matched
DataServerCallbackObject* callbackObject = _callbackObjects.take(sequenceNumber);
_unmatchedPackets.remove(sequenceNumber);
QString userString, keyListString, valueListString;
packetStream >> userString >> keyListString >> valueListString;
callbackObject->processDataServerResponse(userString, keyListString.split(MULTI_KEY_VALUE_SEPARATOR),
valueListString.split(MULTI_KEY_VALUE_SEPARATOR));
}
}
void DataServerClient::processMessageFromDataServer(const QByteArray& packet) {
switch (packetTypeForPacket(packet)) {
case PacketTypeDataServerSend:
processSendFromDataServer(packet);
break;
case PacketTypeDataServerConfirm:
processConfirmFromDataServer(packet);
break;
default:
break;
}
}
void DataServerClient::removeMatchedPacketFromMap(const QByteArray& packet) {
quint8 sequenceNumber = packet[numBytesForPacketHeader(packet)];
// attempt to remove a packet with this sequence number from the QMap of unmatched packets
_unmatchedPackets.remove(sequenceNumber);
}
void DataServerClient::resendUnmatchedPackets() {
if (_unmatchedPackets.size() > 0) {
qDebug() << "Resending" << _unmatchedPackets.size() << "packets to the data server.";
foreach (const QByteArray& packet, _unmatchedPackets) {
// send the unmatched packet to the data server
NodeList::getInstance()->getNodeSocket().writeDatagram(packet.data(), packet.size(),
dataServerSockAddr().getAddress(),
dataServerSockAddr().getPort());
}
}
}

View file

@ -1,60 +0,0 @@
//
// DataServerClient.h
// hifi
//
// Created by Stephen Birarda on 10/7/13.
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
#ifndef __hifi__DataServerClient__
#define __hifi__DataServerClient__
#include <map>
#include <QtCore/QMap>
#include <QtCore/QPointer>
#include <QtCore/QStringList>
#include <QtCore/QUuid>
#include "HifiSockAddr.h"
class DataServerCallbackObject {
public:
virtual void processDataServerResponse(const QString& userString, const QStringList& keyList, const QStringList& valueList) = 0;
};
class DataServerClient {
public:
static const HifiSockAddr& dataServerSockAddr();
static void putValueForKeyAndUserString(const QString& key, const QString& value, const QString& userString);
static void putValueForKeyAndUUID(const QString& key, const QString& value, const QUuid& uuid);
static void getValueForKeyAndUserString(const QString& key, const QString& userString,
DataServerCallbackObject* callbackObject);
static void getValueForKeyAndUUID(const QString& key, const QUuid& uuid, DataServerCallbackObject* callbackObject);
static void getValuesForKeysAndUUID(const QStringList& keys, const QUuid& uuid, DataServerCallbackObject* callbackObject);
static void getValuesForKeysAndUserString(const QStringList& keys, const QString& userString,
DataServerCallbackObject* callbackObject);
static void processMessageFromDataServer(const QByteArray& packet);
static void resendUnmatchedPackets();
private:
static void processConfirmFromDataServer(const QByteArray& packet);
static void processSendFromDataServer(const QByteArray& packet);
static void removeMatchedPacketFromMap(const QByteArray& packet);
static QMap<quint8, QByteArray> _unmatchedPackets;
static QMap<quint8, DataServerCallbackObject*> _callbackObjects;
static quint8 _sequenceNumber;
};
namespace DataServerKey {
const QString Domain = "domain";
const QString Position = "position";
const QString Orientation = "orientation";
const QString UUID = "uuid";
}
#endif /* defined(__hifi__DataServerClient__) */

View file

@ -0,0 +1,126 @@
//
// DomainInfo.cpp
// hifi
//
// Created by Stephen Birarda on 2/18/2014.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
#include <QtCore/QJsonObject>
#include "AccountManager.h"
#include "DomainInfo.h"
DomainInfo::DomainInfo() :
_uuid(),
_sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)),
_assignmentUUID(),
_connectionSecret(),
_registrationToken(),
_rootAuthenticationURL(),
_publicKey(),
_isConnected(false)
{
// clear appropriate variables after a domain-server logout
connect(&AccountManager::getInstance(), &AccountManager::logoutComplete, this, &DomainInfo::logout);
}
void DomainInfo::clearConnectionInfo() {
_uuid = QUuid();
_connectionSecret = QUuid();
_registrationToken = QByteArray();
_rootAuthenticationURL = QUrl();
_publicKey = QString();
_isConnected = false;
}
void DomainInfo::reset() {
clearConnectionInfo();
_hostname = QString();
_sockAddr.setAddress(QHostAddress::Null);
}
void DomainInfo::parseAuthInformationFromJsonObject(const QJsonObject& jsonObject) {
QJsonObject dataObject = jsonObject["data"].toObject();
_connectionSecret = QUuid(dataObject["connection_secret"].toString());
_registrationToken = QByteArray::fromHex(dataObject["registration_token"].toString().toUtf8());
_publicKey = dataObject["public_key"].toString();
}
void DomainInfo::setSockAddr(const HifiSockAddr& sockAddr) {
if (_sockAddr != sockAddr) {
// we should reset on a sockAddr change
reset();
// change the sockAddr
_sockAddr = sockAddr;
}
}
void DomainInfo::setHostname(const QString& hostname) {
if (hostname != _hostname) {
// re-set the domain info so that auth information is reloaded
reset();
int colonIndex = hostname.indexOf(':');
if (colonIndex > 0) {
// the user has included a custom DS port with the hostname
// the new hostname is everything up to the colon
_hostname = hostname.left(colonIndex);
// grab the port by reading the string after the colon
_sockAddr.setPort(atoi(hostname.mid(colonIndex + 1, hostname.size()).toLocal8Bit().constData()));
qDebug() << "Updated hostname to" << _hostname << "and port to" << _sockAddr.getPort();
} else {
// no port included with the hostname, simply set the member variable and reset the domain server port to default
_hostname = hostname;
_sockAddr.setPort(DEFAULT_DOMAIN_SERVER_PORT);
}
// re-set the sock addr to null and fire off a lookup of the IP address for this domain-server's hostname
qDebug("Looking up DS hostname %s.", _hostname.toLocal8Bit().constData());
QHostInfo::lookupHost(_hostname, this, SLOT(completedHostnameLookup(const QHostInfo&)));
emit hostnameChanged(_hostname);
}
}
void DomainInfo::completedHostnameLookup(const QHostInfo& hostInfo) {
for (int i = 0; i < hostInfo.addresses().size(); i++) {
if (hostInfo.addresses()[i].protocol() == QAbstractSocket::IPv4Protocol) {
_sockAddr.setAddress(hostInfo.addresses()[i]);
qDebug("DS at %s is at %s", _hostname.toLocal8Bit().constData(),
_sockAddr.getAddress().toString().toLocal8Bit().constData());
return;
}
}
// if we got here then we failed to lookup the address
qDebug("Failed domain server lookup");
}
void DomainInfo::setIsConnected(bool isConnected) {
if (_isConnected != isConnected) {
_isConnected = isConnected;
if (_isConnected) {
emit connectedToDomain(_hostname);
}
}
}
void DomainInfo::logout() {
// clear any information related to auth for this domain, assuming it had requested auth
if (!_rootAuthenticationURL.isEmpty()) {
_rootAuthenticationURL = QUrl();
_connectionSecret = QUuid();
_registrationToken = QByteArray();
_publicKey = QString();
_isConnected = false;
}
}

View file

@ -0,0 +1,80 @@
//
// DomainInfo.h
// hifi
//
// Created by Stephen Birarda on 2/18/2014.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
#ifndef __hifi__DomainInfo__
#define __hifi__DomainInfo__
#include <QtCore/QObject>
#include <QtCore/QUuid>
#include <QtCore/QUrl>
#include <QtNetwork/QHostInfo>
#include "HifiSockAddr.h"
const QString DEFAULT_DOMAIN_HOSTNAME = "root.highfidelity.io";
const unsigned short DEFAULT_DOMAIN_SERVER_PORT = 40102;
class DomainInfo : public QObject {
Q_OBJECT
public:
DomainInfo();
void clearConnectionInfo();
void parseAuthInformationFromJsonObject(const QJsonObject& jsonObject);
const QUuid& getUUID() const { return _uuid; }
void setUUID(const QUuid& uuid) { _uuid = uuid; }
const QString& getHostname() const { return _hostname; }
void setHostname(const QString& hostname);
const QHostAddress& getIP() const { return _sockAddr.getAddress(); }
void setIPToLocalhost() { _sockAddr.setAddress(QHostAddress(QHostAddress::LocalHost)); }
const HifiSockAddr& getSockAddr() { return _sockAddr; }
void setSockAddr(const HifiSockAddr& sockAddr);
unsigned short getPort() const { return _sockAddr.getPort(); }
const QUuid& getAssignmentUUID() const { return _assignmentUUID; }
void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; }
const QUuid& getConnectionSecret() const { return _connectionSecret; }
void setConnectionSecret(const QUuid& connectionSecret) { _connectionSecret = connectionSecret; }
const QByteArray& getRegistrationToken() const { return _registrationToken; }
const QUrl& getRootAuthenticationURL() const { return _rootAuthenticationURL; }
void setRootAuthenticationURL(const QUrl& rootAuthenticationURL) { _rootAuthenticationURL = rootAuthenticationURL; }
bool isConnected() const { return _isConnected; }
void setIsConnected(bool isConnected);
private slots:
void completedHostnameLookup(const QHostInfo& hostInfo);
void logout();
signals:
void hostnameChanged(const QString& hostname);
void connectedToDomain(const QString& hostname);
private:
void reset();
QUuid _uuid;
QString _hostname;
HifiSockAddr _sockAddr;
QUuid _assignmentUUID;
QUuid _connectionSecret;
QByteArray _registrationToken;
QUrl _rootAuthenticationURL;
QString _publicKey;
bool _isConnected;
};
#endif /* defined(__hifi__DomainInfo__) */

View file

@ -12,8 +12,11 @@
#include <QtCore/QDataStream>
#include <QtCore/QDebug>
#include <QtCore/QJsonDocument>
#include <QtCore/QUrl>
#include <QtNetwork/QHostInfo>
#include "AccountManager.h"
#include "Assignment.h"
#include "HifiSockAddr.h"
#include "Logging.h"
@ -27,8 +30,7 @@ const char SOLO_NODE_TYPES[2] = {
NodeType::AudioMixer
};
const QString DEFAULT_DOMAIN_HOSTNAME = "root.highfidelity.io";
const unsigned short DEFAULT_DOMAIN_SERVER_PORT = 40102;
const QUrl DEFAULT_NODE_AUTH_URL = QUrl("https://data-web.highfidelity.io");
NodeList* NodeList::_sharedInstance = NULL;
@ -59,8 +61,6 @@ NodeList* NodeList::getInstance() {
NodeList::NodeList(char newOwnerType, unsigned short int newSocketListenPort) :
_nodeHash(),
_nodeHashMutex(QMutex::Recursive),
_domainHostname(),
_domainSockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)),
_nodeSocket(this),
_ownerType(newOwnerType),
_nodeTypesOfInterest(),
@ -73,16 +73,18 @@ NodeList::NodeList(char newOwnerType, unsigned short int newSocketListenPort) :
{
_nodeSocket.bind(QHostAddress::AnyIPv4, newSocketListenPort);
qDebug() << "NodeList socket is listening on" << _nodeSocket.localPort();
}
NodeList::~NodeList() {
clear();
// clear our NodeList when the domain changes
connect(&_domainInfo, &DomainInfo::hostnameChanged, this, &NodeList::reset);
// clear our NodeList when logout is requested
connect(&AccountManager::getInstance(), &AccountManager::logoutComplete , this, &NodeList::reset);
}
bool NodeList::packetVersionAndHashMatch(const QByteArray& packet) {
if (packet[1] != versionForPacketType(packetTypeForPacket(packet))
&& packetTypeForPacket(packet) != PacketTypeStunResponse) {
PacketType checkType = packetTypeForPacket(packet);
if (packet[1] != versionForPacketType(checkType)
&& checkType != PacketTypeStunResponse) {
PacketType mismatchType = packetTypeForPacket(packet);
int numPacketTypeBytes = numBytesArithmeticCodingFromBuffer(packet.data());
@ -91,12 +93,13 @@ bool NodeList::packetVersionAndHashMatch(const QByteArray& packet) {
<< qPrintable(QString::number(versionForPacketType(mismatchType))) << "expected.";
}
const QSet<PacketType> NON_VERIFIED_PACKETS = QSet<PacketType>() << PacketTypeDomainList
<< PacketTypeDomainListRequest << PacketTypeStunResponse << PacketTypeDataServerConfirm
const QSet<PacketType> NON_VERIFIED_PACKETS = QSet<PacketType>()
<< PacketTypeDomainServerAuthRequest << PacketTypeDomainConnectRequest
<< PacketTypeStunResponse << PacketTypeDataServerConfirm
<< PacketTypeDataServerGet << PacketTypeDataServerPut << PacketTypeDataServerSend
<< PacketTypeCreateAssignment << PacketTypeRequestAssignment;
if (!NON_VERIFIED_PACKETS.contains(packetTypeForPacket(packet))) {
if (!NON_VERIFIED_PACKETS.contains(checkType)) {
// figure out which node this is from
SharedNodePointer sendingNode = sendingNodeForPacket(packet);
if (sendingNode) {
@ -104,11 +107,36 @@ bool NodeList::packetVersionAndHashMatch(const QByteArray& packet) {
if (hashFromPacketHeader(packet) == hashForPacketAndConnectionUUID(packet, sendingNode->getConnectionSecret())) {
return true;
} else {
qDebug() << "Packet hash mismatch on" << packetTypeForPacket(packet) << "- Sender"
qDebug() << "Packet hash mismatch on" << checkType << "- Sender"
<< uuidFromPacketHeader(packet);
}
} else {
qDebug() << "Packet of type" << packetTypeForPacket(packet) << "received from unknown node with UUID"
if (checkType == PacketTypeDomainList) {
if (_domainInfo.getRootAuthenticationURL().isEmpty() && _domainInfo.getUUID().isNull()) {
// if this is a domain-server that doesn't require auth,
// pull the UUID from this packet and set it as our domain-server UUID
_domainInfo.setUUID(uuidFromPacketHeader(packet));
// we also know this domain-server requires no authentication
// so set the account manager root URL empty
AccountManager::getInstance().setRootURL(QUrl());
}
if (_domainInfo.getUUID() == uuidFromPacketHeader(packet)) {
if (hashForPacketAndConnectionUUID(packet, _domainInfo.getConnectionSecret()) == hashFromPacketHeader(packet)) {
// this is a packet from the domain-server (PacketTypeDomainServerListRequest)
// and the sender UUID matches the UUID we expect for the domain
return true;
} else {
// this is a packet from the domain-server but there is a hash mismatch
qDebug() << "Packet hash mismatch on" << checkType << "from domain-server at" << _domainInfo.getHostname();
return false;
}
}
}
qDebug() << "Packet of type" << checkType << "received from unknown node with UUID"
<< uuidFromPacketHeader(packet);
}
} else {
@ -118,6 +146,17 @@ bool NodeList::packetVersionAndHashMatch(const QByteArray& packet) {
return false;
}
qint64 NodeList::writeDatagram(const QByteArray& datagram, const HifiSockAddr& destinationSockAddr,
const QUuid& connectionSecret) {
QByteArray datagramCopy = datagram;
// setup the MD5 hash for source verification in the header
replaceHashInPacketGivenConnectionUUID(datagramCopy, connectionSecret);
return _nodeSocket.writeDatagram(datagramCopy, destinationSockAddr.getAddress(), destinationSockAddr.getPort());
}
qint64 NodeList::writeDatagram(const QByteArray& datagram, const SharedNodePointer& destinationNode,
const HifiSockAddr& overridenSockAddr) {
if (destinationNode) {
@ -133,11 +172,7 @@ qint64 NodeList::writeDatagram(const QByteArray& datagram, const SharedNodePoint
}
}
QByteArray datagramCopy = datagram;
// setup the MD5 hash for source verification in the header
replaceHashInPacketGivenConnectionUUID(datagramCopy, destinationNode->getConnectionSecret());
return _nodeSocket.writeDatagram(datagramCopy, destinationSockAddr->getAddress(), destinationSockAddr->getPort());
writeDatagram(datagram, *destinationSockAddr, destinationNode->getConnectionSecret());
}
// didn't have a destinationNode to send to, return 0
@ -149,37 +184,6 @@ qint64 NodeList::writeDatagram(const char* data, qint64 size, const SharedNodePo
return writeDatagram(QByteArray(data, size), destinationNode, overridenSockAddr);
}
void NodeList::setDomainHostname(const QString& domainHostname) {
if (domainHostname != _domainHostname) {
int colonIndex = domainHostname.indexOf(':');
if (colonIndex > 0) {
// the user has included a custom DS port with the hostname
// the new hostname is everything up to the colon
_domainHostname = domainHostname.left(colonIndex);
// grab the port by reading the string after the colon
_domainSockAddr.setPort(atoi(domainHostname.mid(colonIndex + 1, domainHostname.size()).toLocal8Bit().constData()));
qDebug() << "Updated hostname to" << _domainHostname << "and port to" << _domainSockAddr.getPort();
} else {
// no port included with the hostname, simply set the member variable and reset the domain server port to default
_domainHostname = domainHostname;
_domainSockAddr.setPort(DEFAULT_DOMAIN_SERVER_PORT);
}
// clear the NodeList so nodes from this domain are killed
clear();
// reset our _domainIP to the null address so that a lookup happens on next check in
_domainSockAddr.setAddress(QHostAddress::Null);
emit domainChanged(_domainHostname);
}
}
void NodeList::timePingReply(const QByteArray& packet, const SharedNodePointer& sendingNode) {
QDataStream packetStream(packet);
packetStream.skipRawData(numBytesForPacketHeader(packet));
@ -218,11 +222,13 @@ void NodeList::timePingReply(const QByteArray& packet, const SharedNodePointer&
void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteArray& packet) {
switch (packetTypeForPacket(packet)) {
case PacketTypeDomainList: {
// only process the DS if this is our current domain server
if (_domainSockAddr == senderSockAddr) {
processDomainServerList(packet);
}
processDomainServerList(packet);
break;
}
case PacketTypeDomainServerAuthRequest: {
// the domain-server has asked us to auth via a data-server
processDomainServerAuthRequest(packet);
break;
}
case PacketTypePing: {
@ -320,10 +326,11 @@ void NodeList::reset() {
clear();
_numNoReplyDomainCheckIns = 0;
_nodeTypesOfInterest.clear();
// refresh the owner UUID
_sessionUUID = QUuid::createUuid();
// refresh the owner UUID to the NULL UUID
setSessionUUID(QUuid());
// clear the domain connection information
_domainInfo.clearConnectionInfo();
}
void NodeList::addNodeTypeToInterestSet(NodeType_t nodeTypeToAdd) {
@ -495,66 +502,60 @@ void NodeList::processKillNode(const QByteArray& dataByteArray) {
}
void NodeList::sendDomainServerCheckIn() {
static bool printedDomainServerIP = false;
// Lookup the IP address of the domain server if we need to
if (_domainSockAddr.getAddress().isNull() && !_domainHostname.isEmpty()) {
qDebug("Looking up DS hostname %s.", _domainHostname.toLocal8Bit().constData());
QHostInfo domainServerHostInfo = QHostInfo::fromName(_domainHostname);
for (int i = 0; i < domainServerHostInfo.addresses().size(); i++) {
if (domainServerHostInfo.addresses()[i].protocol() == QAbstractSocket::IPv4Protocol) {
_domainSockAddr.setAddress(domainServerHostInfo.addresses()[i]);
qDebug("DS at %s is at %s", _domainHostname.toLocal8Bit().constData(),
_domainSockAddr.getAddress().toString().toLocal8Bit().constData());
printedDomainServerIP = true;
break;
}
// if we got here without a break out of the for loop then we failed to lookup the address
if (i == domainServerHostInfo.addresses().size() - 1) {
qDebug("Failed domain server lookup");
}
}
} else if (!printedDomainServerIP) {
qDebug("Domain Server IP: %s", _domainSockAddr.getAddress().toString().toLocal8Bit().constData());
printedDomainServerIP = true;
}
if (_publicSockAddr.isNull() && !_hasCompletedInitialSTUNFailure) {
// we don't know our public socket and we need to send it to the domain server
// send a STUN request to figure it out
sendSTUNRequest();
} else if (!_domainSockAddr.getAddress().isNull()) {
// construct the DS check in packet if we can
// check in packet has header, optional UUID, node type, port, IP, node types of interest, null termination
QByteArray domainServerPacket = byteArrayWithPopulatedHeader(PacketTypeDomainListRequest);
QDataStream packetStream(&domainServerPacket, QIODevice::Append);
// pack our data to send to the domain-server
packetStream << _ownerType << _publicSockAddr
<< HifiSockAddr(QHostAddress(getHostOrderLocalAddress()), _nodeSocket.localPort())
<< (quint8) _nodeTypesOfInterest.size();
// copy over the bytes for node types of interest, if required
foreach (NodeType_t nodeTypeOfInterest, _nodeTypesOfInterest) {
packetStream << nodeTypeOfInterest;
} else if (!_domainInfo.getIP().isNull()) {
if (_domainInfo.getRootAuthenticationURL().isEmpty()
|| !_sessionUUID.isNull()
|| !_domainInfo.getRegistrationToken().isEmpty() ) {
// construct the DS check in packet
PacketType domainPacketType = _sessionUUID.isNull() ? PacketTypeDomainConnectRequest : PacketTypeDomainListRequest;
QUuid packetUUID = (domainPacketType == PacketTypeDomainListRequest)
? _sessionUUID : _domainInfo.getAssignmentUUID();
QByteArray domainServerPacket = byteArrayWithPopulatedHeader(domainPacketType, packetUUID);
QDataStream packetStream(&domainServerPacket, QIODevice::Append);
if (domainPacketType == PacketTypeDomainConnectRequest) {
// we may need a registration token to present to the domain-server
packetStream << (quint8) !_domainInfo.getRegistrationToken().isEmpty();
if (!_domainInfo.getRegistrationToken().isEmpty()) {
// if we have a registration token send that along in the request
packetStream << _domainInfo.getRegistrationToken();
}
}
// pack our data to send to the domain-server
packetStream << _ownerType << _publicSockAddr
<< HifiSockAddr(QHostAddress(getHostOrderLocalAddress()), _nodeSocket.localPort())
<< (quint8) _nodeTypesOfInterest.size();
// copy over the bytes for node types of interest, if required
foreach (NodeType_t nodeTypeOfInterest, _nodeTypesOfInterest) {
packetStream << nodeTypeOfInterest;
}
writeDatagram(domainServerPacket, _domainInfo.getSockAddr(), _domainInfo.getConnectionSecret());
const int NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST = 5;
static unsigned int numDomainCheckins = 0;
// send a STUN request every Nth domain server check in so we update our public socket, if required
if (numDomainCheckins++ % NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST == 0) {
sendSTUNRequest();
}
// increment the count of un-replied check-ins
_numNoReplyDomainCheckIns++;
} else if (AccountManager::getInstance().hasValidAccessToken()) {
// we have an access token we can use for the authentication server the domain-server requested
// so ask that server to provide us with information to connect to the domain-server
requestAuthForDomainServer();
}
_nodeSocket.writeDatagram(domainServerPacket, _domainSockAddr.getAddress(), _domainSockAddr.getPort());
const int NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST = 5;
static unsigned int numDomainCheckins = 0;
// send a STUN request every Nth domain server check in so we update our public socket, if required
if (numDomainCheckins++ % NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST == 0) {
sendSTUNRequest();
}
// increment the count of un-replied check-ins
_numNoReplyDomainCheckIns++;
}
}
@ -563,7 +564,8 @@ void NodeList::setSessionUUID(const QUuid& sessionUUID) {
_sessionUUID = sessionUUID;
if (sessionUUID != oldUUID) {
qDebug() << "NodeList UUID changed from" << oldUUID << "to" << _sessionUUID;
qDebug() << "NodeList UUID changed from" << uuidStringWithoutCurlyBraces(oldUUID)
<< "to" << uuidStringWithoutCurlyBraces(_sessionUUID);
emit uuidChanged(sessionUUID);
}
}
@ -571,6 +573,9 @@ void NodeList::setSessionUUID(const QUuid& sessionUUID) {
int NodeList::processDomainServerList(const QByteArray& packet) {
// this is a packet from the domain server, reset the count of un-replied check-ins
_numNoReplyDomainCheckIns = 0;
// if this was the first domain-server list from this domain, we've now connected
_domainInfo.setIsConnected(true);
int readNodes = 0;
@ -597,7 +602,7 @@ int NodeList::processDomainServerList(const QByteArray& packet) {
// if the public socket address is 0 then it's reachable at the same IP
// as the domain server
if (nodePublicSocket.getAddress().isNull()) {
nodePublicSocket.setAddress(_domainSockAddr.getAddress());
nodePublicSocket.setAddress(_domainInfo.getIP());
}
SharedNodePointer node = addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket, nodeLocalSocket);
@ -613,6 +618,41 @@ int NodeList::processDomainServerList(const QByteArray& packet) {
return readNodes;
}
void NodeList::domainServerAuthReply(const QJsonObject& jsonObject) {
_domainInfo.parseAuthInformationFromJsonObject(jsonObject);
}
void NodeList::requestAuthForDomainServer() {
JSONCallbackParameters callbackParams;
callbackParams.jsonCallbackReceiver = this;
callbackParams.jsonCallbackMethod = "domainServerAuthReply";
AccountManager::getInstance().authenticatedRequest("/api/v1/domains/"
+ uuidStringWithoutCurlyBraces(_domainInfo.getUUID()) + "/auth.json",
QNetworkAccessManager::GetOperation,
callbackParams);
}
void NodeList::processDomainServerAuthRequest(const QByteArray& packet) {
QDataStream authPacketStream(packet);
authPacketStream.skipRawData(numBytesForPacketHeader(packet));
_domainInfo.setUUID(uuidFromPacketHeader(packet));
AccountManager& accountManager = AccountManager::getInstance();
// grab the hostname this domain-server wants us to authenticate with
QUrl authenticationRootURL;
authPacketStream >> authenticationRootURL;
accountManager.setRootURL(authenticationRootURL);
_domainInfo.setRootAuthenticationURL(authenticationRootURL);
if (AccountManager::getInstance().checkAndSignalForAccessToken()) {
// request a domain-server auth
requestAuthForDomainServer();
}
}
void NodeList::sendAssignment(Assignment& assignment) {
PacketType assignmentPacketType = assignment.getCommand() == Assignment::CreateCommand
@ -676,9 +716,8 @@ SharedNodePointer NodeList::addOrUpdateNode(const QUuid& uuid, char nodeType,
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) {
_nodeHashMutex.lock();
SharedNodePointer matchingNode = _nodeHash.value(uuid);
if (!matchingNode) {
if (!_nodeHash.contains(uuid)) {
// we didn't have this node, so add them
Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket);
SharedNodePointer newNodeSharedPointer(newNode, &QObject::deleteLater);
@ -695,29 +734,32 @@ SharedNodePointer NodeList::addOrUpdateNode(const QUuid& uuid, char nodeType,
} else {
_nodeHashMutex.unlock();
return updateSocketsForNode(uuid, publicSocket, localSocket);
}
}
SharedNodePointer NodeList::updateSocketsForNode(const QUuid& uuid,
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) {
SharedNodePointer matchingNode = nodeWithUUID(uuid);
if (matchingNode) {
// perform appropriate updates to this node
QMutexLocker locker(&matchingNode->getMutex());
if (matchingNode->getType() == NodeType::AudioMixer ||
matchingNode->getType() == NodeType::VoxelServer ||
matchingNode->getType() == NodeType::MetavoxelServer) {
// until the Audio class also uses our nodeList, we need to update
// the lastRecvTimeUsecs for the audio mixer so it doesn't get killed and re-added continously
matchingNode->setLastHeardMicrostamp(usecTimestampNow());
}
// check if we need to change this node's public or local sockets
if (publicSocket != matchingNode->getPublicSocket()) {
matchingNode->setPublicSocket(publicSocket);
qDebug() << "Public socket change for node" << *matchingNode;
}
if (localSocket != matchingNode->getLocalSocket()) {
matchingNode->setLocalSocket(localSocket);
qDebug() << "Local socket change for node" << *matchingNode;
}
// we had this node already, do nothing for now
return matchingNode;
}
return matchingNode;
}
unsigned NodeList::broadcastToNodes(const QByteArray& packet, const NodeSet& destinationNodeTypes) {
@ -806,21 +848,20 @@ void NodeList::loadData(QSettings *settings) {
QString domainServerHostname = settings->value(DOMAIN_SERVER_SETTING_KEY).toString();
if (domainServerHostname.size() > 0) {
_domainHostname = domainServerHostname;
_domainInfo.setHostname(domainServerHostname);
} else {
_domainHostname = DEFAULT_DOMAIN_HOSTNAME;
_domainInfo.setHostname(DEFAULT_DOMAIN_HOSTNAME);
}
emit domainChanged(_domainHostname);
settings->endGroup();
}
void NodeList::saveData(QSettings* settings) {
settings->beginGroup(DOMAIN_SERVER_SETTING_KEY);
if (_domainHostname != DEFAULT_DOMAIN_HOSTNAME) {
if (_domainInfo.getHostname() != DEFAULT_DOMAIN_HOSTNAME) {
// the user is using a different hostname, store it
settings->setValue(DOMAIN_SERVER_SETTING_KEY, QVariant(_domainHostname));
settings->setValue(DOMAIN_SERVER_SETTING_KEY, QVariant(_domainInfo.getHostname()));
} else {
// the user has switched back to default, remove the current setting
settings->remove(DOMAIN_SERVER_SETTING_KEY);

View file

@ -28,6 +28,7 @@
#include <QtNetwork/QHostAddress>
#include <QtNetwork/QUdpSocket>
#include "DomainInfo.h"
#include "Node.h"
const quint64 NODE_SILENCE_THRESHOLD_USECS = 2 * 1000 * 1000;
@ -36,8 +37,7 @@ const quint64 PING_INACTIVE_NODE_INTERVAL_USECS = 1 * 1000 * 1000;
extern const char SOLO_NODE_TYPES[2];
extern const QString DEFAULT_DOMAIN_HOSTNAME;
extern const unsigned short DEFAULT_DOMAIN_SERVER_PORT;
extern const QUrl DEFAULT_NODE_AUTH_URL;
const char DEFAULT_ASSIGNMENT_SERVER_HOSTNAME[] = "localhost";
@ -67,16 +67,6 @@ public:
NodeType_t getOwnerType() const { return _ownerType; }
void setOwnerType(NodeType_t ownerType) { _ownerType = ownerType; }
const QString& getDomainHostname() const { return _domainHostname; }
void setDomainHostname(const QString& domainHostname);
const QHostAddress& getDomainIP() const { return _domainSockAddr.getAddress(); }
void setDomainIPToLocalhost() { _domainSockAddr.setAddress(QHostAddress(INADDR_LOOPBACK)); }
void setDomainSockAddr(const HifiSockAddr& domainSockAddr) { _domainSockAddr = domainSockAddr; }
unsigned short getDomainPort() const { return _domainSockAddr.getPort(); }
const QUuid& getSessionUUID() const { return _sessionUUID; }
void setSessionUUID(const QUuid& sessionUUID);
@ -95,12 +85,12 @@ public:
int size() const { return _nodeHash.size(); }
int getNumNoReplyDomainCheckIns() const { return _numNoReplyDomainCheckIns; }
void reset();
DomainInfo& getDomainInfo() { return _domainInfo; }
const NodeSet& getNodeInterestSet() const { return _nodeTypesOfInterest; }
void addNodeTypeToInterestSet(NodeType_t nodeTypeToAdd);
void addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes);
void resetNodeInterestSet() { _nodeTypesOfInterest.clear(); }
int processDomainServerList(const QByteArray& packet);
@ -116,6 +106,8 @@ public:
SharedNodePointer addOrUpdateNode(const QUuid& uuid, char nodeType,
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket);
SharedNodePointer updateSocketsForNode(const QUuid& uuid,
const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket);
void processNodeData(const HifiSockAddr& senderSockAddr, const QByteArray& packet);
void processKillNode(const QByteArray& datagram);
@ -129,35 +121,44 @@ public:
void loadData(QSettings* settings);
void saveData(QSettings* settings);
public slots:
void reset();
void sendDomainServerCheckIn();
void pingInactiveNodes();
void removeSilentNodes();
void killNodeWithUUID(const QUuid& nodeUUID);
signals:
void domainChanged(const QString& domainHostname);
void uuidChanged(const QUuid& ownerUUID);
void nodeAdded(SharedNodePointer);
void nodeKilled(SharedNodePointer);
private slots:
void domainServerAuthReply(const QJsonObject& jsonObject);
private:
static NodeList* _sharedInstance;
NodeList(char ownerType, unsigned short int socketListenPort);
~NodeList();
NodeList(NodeList const&); // Don't implement, needed to avoid copies of singleton
void operator=(NodeList const&); // Don't implement, needed to avoid copies of singleton
void sendSTUNRequest();
void processSTUNResponse(const QByteArray& packet);
qint64 writeDatagram(const QByteArray& datagram, const HifiSockAddr& destinationSockAddr,
const QUuid& connectionSecret);
NodeHash::iterator killNodeAtHashIterator(NodeHash::iterator& nodeItemToKill);
void clear();
void processDomainServerAuthRequest(const QByteArray& packet);
void requestAuthForDomainServer();
NodeHash _nodeHash;
QMutex _nodeHashMutex;
QString _domainHostname;
HifiSockAddr _domainSockAddr;
QUdpSocket _nodeSocket;
NodeType_t _ownerType;
NodeSet _nodeTypesOfInterest;
DomainInfo _domainInfo;
QUuid _sessionUUID;
int _numNoReplyDomainCheckIns;
HifiSockAddr _assignmentServerSocket;
@ -166,10 +167,7 @@ private:
unsigned int _stunRequestsSinceSuccess;
void activateSocketFromNodeCommunication(const QByteArray& packet, const SharedNodePointer& sendingNode);
void timePingReply(const QByteArray& packet, const SharedNodePointer& sendingNode);
void resetDomainData(char domainField[], const char* domainData);
void domainLookup();
void clear();
void timePingReply(const QByteArray& packet, const SharedNodePointer& sendingNode);
};
#endif /* defined(__hifi__NodeList__) */

View file

@ -0,0 +1,61 @@
//
// OAuthAccessToken.cpp
// hifi
//
// Created by Stephen Birarda on 2/18/2014.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
#include <QtCore/QDataStream>
#include "OAuthAccessToken.h"
OAuthAccessToken::OAuthAccessToken() :
token(),
refreshToken(),
expiryTimestamp(),
tokenType()
{
}
OAuthAccessToken::OAuthAccessToken(const QJsonObject& jsonObject) :
token(jsonObject["access_token"].toString()),
refreshToken(jsonObject["refresh_token"].toString()),
expiryTimestamp(QDateTime::currentMSecsSinceEpoch() + jsonObject["expires_in"].toDouble()),
tokenType(jsonObject["token_type"].toString())
{
}
OAuthAccessToken::OAuthAccessToken(const OAuthAccessToken& otherToken) {
token = otherToken.token;
refreshToken = otherToken.refreshToken;
expiryTimestamp = otherToken.expiryTimestamp;
tokenType = otherToken.tokenType;
}
OAuthAccessToken& OAuthAccessToken::operator=(const OAuthAccessToken& otherToken) {
OAuthAccessToken temp(otherToken);
swap(temp);
return *this;
}
void OAuthAccessToken::swap(OAuthAccessToken& otherToken) {
using std::swap;
swap(token, otherToken.token);
swap(refreshToken, otherToken.refreshToken);
swap(expiryTimestamp, otherToken.expiryTimestamp);
swap(tokenType, otherToken.tokenType);
}
QDataStream& operator<<(QDataStream &out, const OAuthAccessToken& token) {
out << token.token << token.expiryTimestamp << token.tokenType << token.refreshToken;
return out;
}
QDataStream& operator>>(QDataStream &in, OAuthAccessToken& token) {
in >> token.token >> token.expiryTimestamp >> token.tokenType >> token.refreshToken;
return in;
}

View file

@ -0,0 +1,37 @@
//
// OAuthAccessToken.h
// hifi
//
// Created by Stephen Birarda on 2/18/2014.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
#ifndef __hifi__OAuthAccessToken__
#define __hifi__OAuthAccessToken__
#include <QtCore/QObject>
#include <QtCore/QDateTime>
#include <QtCore/QJsonObject>
class OAuthAccessToken : public QObject {
Q_OBJECT
public:
OAuthAccessToken();
OAuthAccessToken(const QJsonObject& jsonObject);
OAuthAccessToken(const OAuthAccessToken& otherToken);
OAuthAccessToken& operator=(const OAuthAccessToken& otherToken);
bool isExpired() const { return expiryTimestamp <= QDateTime::currentMSecsSinceEpoch(); }
QString token;
QString refreshToken;
qlonglong expiryTimestamp;
QString tokenType;
friend QDataStream& operator<<(QDataStream &out, const OAuthAccessToken& token);
friend QDataStream& operator>>(QDataStream &in, OAuthAccessToken& token);
private:
void swap(OAuthAccessToken& otherToken);
};
#endif /* defined(__hifi__OAuthAccessToken__) */

View file

@ -17,6 +17,8 @@
#include "UUID.h"
// NOTE: if adding a new packet type, you can replace one marked usable or add at the end
enum PacketType {
PacketTypeUnknown,
PacketTypeStunResponse,
@ -30,7 +32,7 @@ enum PacketType {
PacketTypeMicrophoneAudioNoEcho,
PacketTypeMicrophoneAudioWithEcho,
PacketTypeBulkAvatarData,
PacketTypeTransmitterData,
PacketTypeTransmitterData, // usable
PacketTypeEnvironmentData,
PacketTypeDomainListRequest,
PacketTypeRequestAssignment,
@ -54,7 +56,9 @@ enum PacketType {
PacketTypeParticleAddResponse,
PacketTypeMetavoxelData,
PacketTypeAvatarIdentity,
PacketTypeAvatarBillboard
PacketTypeAvatarBillboard,
PacketTypeDomainConnectRequest,
PacketTypeDomainServerAuthRequest
};
typedef char PacketVersion;

View file

@ -1,17 +0,0 @@
cmake_minimum_required(VERSION 2.8)
set(ROOT_DIR ..)
set(MACRO_DIR ${ROOT_DIR}/cmake/macros)
set(TARGET_NAME pairing-server)
find_package(Qt5Network REQUIRED)
include(${MACRO_DIR}/SetupHifiProject.cmake)
setup_hifi_project(${TARGET_NAME} TRUE)
qt5_use_modules(${TARGET_NAME} Network)
# link the shared hifi library
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR})

View file

@ -1,135 +0,0 @@
//
// main.cpp
// pairing-server
//
// Created by Stephen Birarda on 5/1/13.
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
#include <iostream>
#include <vector>
#include <cstdio>
#include <cstring>
#ifndef _WIN32
#include <arpa/inet.h> // not available on windows
#endif
const int INET_ADDR_STRLEN = 16;
#include <QtNetwork/QUdpSocket>
#include <HifiSockAddr.h>
const int PAIRING_SERVER_LISTEN_PORT = 7247;
const int MAX_PACKET_SIZE_BYTES = 1400;
struct PairableDevice {
char identifier[64];
char name[64];
HifiSockAddr sendingSocket;
HifiSockAddr localSocket;
};
struct RequestingClient {
char address[INET_ADDR_STRLEN];
int port;
};
QUdpSocket serverSocket;
PairableDevice* lastDevice = NULL;
RequestingClient* lastClient = NULL;
int indexOfFirstOccurenceOfCharacter(char* haystack, char needle) {
int currentIndex = 0;
while (haystack[currentIndex] != '\0' && haystack[currentIndex] != needle) {
currentIndex++;
}
return currentIndex;
}
void sendLastClientToLastDevice() {
char pairData[INET_ADDR_STRLEN + 6] = {};
int bytesWritten = sprintf(pairData, "%s:%d", ::lastClient->address, ::lastClient->port);
::serverSocket.writeDatagram(pairData, bytesWritten,
::lastDevice->sendingSocket.getAddress(), ::lastDevice->sendingSocket.getPort());
}
int main(int argc, const char* argv[]) {
serverSocket.bind(QHostAddress::LocalHost, PAIRING_SERVER_LISTEN_PORT);
HifiSockAddr senderSockAddr;
char senderData[MAX_PACKET_SIZE_BYTES] = {};
while (true) {
if (::serverSocket.hasPendingDatagrams()
&& ::serverSocket.readDatagram(senderData, MAX_PACKET_SIZE_BYTES,
senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer())) {
if (senderData[0] == 'A') {
// this is a device reporting itself as available
PairableDevice tempDevice = {};
char deviceAddress[INET_ADDR_STRLEN] = {};
int socketPort = 0;
int numMatches = sscanf(senderData, "Available %s %[^:]:%d %s",
tempDevice.identifier,
deviceAddress,
&socketPort,
tempDevice.name);
if (numMatches >= 3) {
// if we have fewer than 3 matches the packet wasn't properly formatted
// setup the localSocket for the pairing device
tempDevice.localSocket.setAddress(QHostAddress(deviceAddress));
tempDevice.localSocket.setPort(socketPort);
// store this device's sending socket so we can talk back to it
tempDevice.sendingSocket = senderSockAddr;
// push this new device into the vector
printf("New last device is %s (%s) at %s:%d\n",
tempDevice.identifier,
tempDevice.name,
deviceAddress,
socketPort);
// copy the tempDevice to the persisting lastDevice
::lastDevice = new PairableDevice(tempDevice);
if (::lastClient) {
sendLastClientToLastDevice();
}
}
} else if (senderData[0] == 'F') {
// this is a client looking to pair with a device
// send the most recent device this address so it can attempt to pair
RequestingClient tempClient = {};
int requestorMatches = sscanf(senderData, "Find %[^:]:%d",
tempClient.address,
&tempClient.port);
if (requestorMatches == 2) {
// good data, copy the tempClient to the persisting lastInterfaceClient
::lastClient = new RequestingClient(tempClient);
printf("New last client is at %s:%d\n",
::lastClient->address,
::lastClient->port);
if (::lastDevice) {
sendLastClientToLastDevice();
}
}
}
}
}
}