Merge pull request #3229 from birarda/domain-server-auth

introduce the option to require payment for voxels
This commit is contained in:
Philip Rosedale 2014-07-31 11:30:13 -07:00
commit 204595a742
35 changed files with 955 additions and 206 deletions

View file

@ -395,41 +395,25 @@ void AudioMixer::run() {
nodeList->linkedDataCreateCallback = attachNewNodeDataToNode;
// setup a NetworkAccessManager to ask the domain-server for our settings
NetworkAccessManager& networkManager = NetworkAccessManager::getInstance();
// wait until we have the domain-server settings, otherwise we bail
DomainHandler& domainHandler = nodeList->getDomainHandler();
QUrl settingsJSONURL;
settingsJSONURL.setScheme("http");
settingsJSONURL.setHost(nodeList->getDomainHandler().getHostname());
settingsJSONURL.setPort(DOMAIN_SERVER_HTTP_PORT);
settingsJSONURL.setPath("/settings.json");
settingsJSONURL.setQuery(QString("type=%1").arg(_type));
qDebug() << "Waiting for domain settings from domain-server.";
QNetworkReply *reply = NULL;
// block until we get the settingsRequestComplete signal
QEventLoop loop;
connect(&domainHandler, &DomainHandler::settingsReceived, &loop, &QEventLoop::quit);
connect(&domainHandler, &DomainHandler::settingsReceiveFail, &loop, &QEventLoop::quit);
domainHandler.requestDomainSettings();
loop.exec();
int failedAttempts = 0;
const int MAX_SETTINGS_REQUEST_FAILED_ATTEMPTS = 5;
qDebug() << "Requesting settings for assignment from domain-server at" << settingsJSONURL.toString();
while (!reply || reply->error() != QNetworkReply::NoError) {
reply = networkManager.get(QNetworkRequest(settingsJSONURL));
QEventLoop loop;
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
loop.exec();
++failedAttempts;
if (failedAttempts == MAX_SETTINGS_REQUEST_FAILED_ATTEMPTS) {
qDebug() << "Failed to get settings from domain-server. Bailing on assignment.";
setFinished(true);
return;
}
if (domainHandler.getSettingsObject().isEmpty()) {
qDebug() << "Failed to retreive settings object from domain-server. Bailing on assignment.";
setFinished(true);
return;
}
QJsonObject settingsObject = QJsonDocument::fromJson(reply->readAll()).object();
const QJsonObject& settingsObject = domainHandler.getSettingsObject();
// check the settings object to see if we have anything we can parse out
const QString AUDIO_GROUP_KEY = "audio";

View file

@ -9,11 +9,14 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QJsonDocument>
#include <QJsonObject>
#include <QTimer>
#include <QUuid>
#include <time.h>
#include <AccountManager.h>
#include <HTTPConnection.h>
#include <Logging.h>
#include <UUID.h>
@ -244,6 +247,10 @@ OctreeServer::OctreeServer(const QByteArray& packet) :
_averageLoopTime.updateAverage(0);
qDebug() << "Octree server starting... [" << this << "]";
// make sure the AccountManager has an Auth URL for payment redemptions
AccountManager::getInstance().setAuthURL(DEFAULT_NODE_AUTH_URL);
}
OctreeServer::~OctreeServer() {
@ -857,6 +864,8 @@ void OctreeServer::readPendingDatagrams() {
}
} else if (packetType == PacketTypeJurisdictionRequest) {
_jurisdictionSender->queueReceivedPacket(matchingNode, receivedPacket);
} else if (packetType == PacketTypeSignedTransactionPayment) {
handleSignedTransactionPayment(packetType, receivedPacket);
} else if (_octreeInboundPacketProcessor && getOctree()->handlesEditPacketType(packetType)) {
_octreeInboundPacketProcessor->queueReceivedPacket(matchingNode, receivedPacket);
} else {
@ -1204,6 +1213,51 @@ QString OctreeServer::getStatusLink() {
return result;
}
void OctreeServer::handleSignedTransactionPayment(PacketType packetType, const QByteArray& datagram) {
// for now we're not verifying that this is actual payment for any octree edits
// just use the AccountManager to send it up to the data server and have it redeemed
AccountManager& accountManager = AccountManager::getInstance();
const int NUM_BYTES_SIGNED_TRANSACTION_BINARY_MESSAGE = 72;
const int NUM_BYTES_SIGNED_TRANSACTION_BINARY_SIGNATURE = 256;
int numBytesPacketHeader = numBytesForPacketHeaderGivenPacketType(packetType);
// pull out the transaction message in binary
QByteArray messageHex = datagram.mid(numBytesPacketHeader, NUM_BYTES_SIGNED_TRANSACTION_BINARY_MESSAGE).toHex();
// pull out the binary signed message digest
QByteArray signatureHex = datagram.mid(numBytesPacketHeader + NUM_BYTES_SIGNED_TRANSACTION_BINARY_MESSAGE,
NUM_BYTES_SIGNED_TRANSACTION_BINARY_SIGNATURE).toHex();
// setup the QJSONObject we are posting
QJsonObject postObject;
const QString TRANSACTION_OBJECT_MESSAGE_KEY = "message";
const QString TRANSACTION_OBJECT_SIGNATURE_KEY = "signature";
const QString POST_OBJECT_TRANSACTION_KEY = "transaction";
QJsonObject transactionObject;
transactionObject.insert(TRANSACTION_OBJECT_MESSAGE_KEY, QString(messageHex));
transactionObject.insert(TRANSACTION_OBJECT_SIGNATURE_KEY, QString(signatureHex));
postObject.insert(POST_OBJECT_TRANSACTION_KEY, transactionObject);
// setup our callback params
JSONCallbackParameters callbackParameters;
callbackParameters.jsonCallbackReceiver = this;
callbackParameters.jsonCallbackMethod = "handleSignedTransactionPaymentResponse";
accountManager.unauthenticatedRequest("/api/v1/transactions/redeem", QNetworkAccessManager::PostOperation,
callbackParameters, QJsonDocument(postObject).toJson());
}
void OctreeServer::handleSignedTransactionPaymentResponse(const QJsonObject& jsonObject) {
// pull the ID to debug the transaction
QString transactionIDString = jsonObject["data"].toObject()["transaction"].toObject()["id"].toString();
qDebug() << "Redeemed transaction with ID" << transactionIDString << "successfully.";
}
void OctreeServer::sendStatsPacket() {
// TODO: we have too many stats to fit in a single MTU... so for now, we break it into multiple JSON objects and
// send them separately. What we really should do is change the NodeList::sendStatsToDomainServer() to handle the

View file

@ -127,6 +127,8 @@ public slots:
void nodeAdded(SharedNodePointer node);
void nodeKilled(SharedNodePointer node);
void sendStatsPacket();
void handleSignedTransactionPaymentResponse(const QJsonObject& jsonObject);
protected:
void parsePayload();
@ -137,6 +139,8 @@ protected:
QString getConfiguration();
QString getStatusLink();
void handleSignedTransactionPayment(PacketType packetType, const QByteArray& datagram);
int _argc;
const char** _argv;
char** _parsedArgV;

View file

@ -10,6 +10,7 @@
<th>Type</th>
<th>UUID</th>
<th>Pool</th>
<th>Username</th>
<th>Public</th>
<th>Local</th>
<th>Uptime (s)</th>
@ -24,8 +25,9 @@
<td><%- node.type %></td>
<td><a href="stats/?uuid=<%- node.uuid %>"><%- node.uuid %></a></td>
<td><%- node.pool %></td>
<td><%- node.public.ip %><span class='port'><%- node.public.port %></span></td>
<td><%- node.local.ip %><span class='port'><%- node.local.port %></span></td>
<td><%- node.username %></td>
<td><%- node.public.ip %><span class='port'>:<%- node.public.port %></span></td>
<td><%- node.local.ip %><span class='port'>:<%- node.local.port %></span></td>
<td><%- ((Date.now() - node.wake_timestamp) / 1000).toLocaleString() %></td>
<td><%- (typeof node.pending_credits == 'number' ? node.pending_credits.toLocaleString() : 'N/A') %></td>
<td><span class='glyphicon glyphicon-remove' data-uuid="<%- node.uuid %>"></span></td>

View file

@ -28,5 +28,33 @@
"default": ""
}
}
},
"voxels": {
"label": "Voxels",
"assignment-types": [2,3],
"settings": {
"voxel-wallet": {
"label": "Destination Wallet ID",
"help": "Wallet to be paid for voxel changes",
"placeholder": "00000000-0000-0000-0000-000000000000",
"default": ""
},
"per-voxel-credits": {
"type": "double",
"label": "Per Voxel Cost",
"help": "Credit cost to change each voxel",
"placeholder": "0.0",
"default": "0.0",
"input_addon": "₵"
},
"per-meter-cubed-credits": {
"type": "double",
"label": "Per Meter Cubed Cost",
"help": "Credit cost to change each cubed meter of voxel space",
"placeholder": "0.0",
"default": "0.0",
"input_addon": "₵"
}
}
}
}

View file

@ -16,16 +16,22 @@
<% var setting_id = group_key + "." + setting_key %>
<label for="<%- setting_id %>" class="col-sm-2 control-label"><%- setting.label %></label>
<div class="col-sm-10">
<% if(setting.type) %>
<% if (setting.type === "checkbox") { %>
<% var checked_box = (values[group_key] || {})[setting_key] || setting.default %>
<input type="checkbox" id="<%- setting_id %>" <%- checked_box ? "checked" : "" %>>
<% } else { %>
<% if (setting.type === "checkbox") { %>
<% var checked_box = (values[group_key] || {})[setting_key] || setting.default %>
<input type="checkbox" id="<%- setting_id %>" <%- checked_box ? "checked" : "" %>>
<% } else { %>
<% if (setting.input_addon) { %>
<div class="input-group">
<div class="input-group-addon"><%- setting.input_addon %></div>
<% } %>
<input type="text" class="form-control" id="<%- setting_id %>"
placeholder="<%- setting.placeholder %>"
value="<%- (values[group_key] || {})[setting_key] %>">
<% if (setting.input_addon) { %>
</div>
<% } %>
<% } %>
</div>
<p class="help-block col-sm-offset-2 col-sm-10"><%- setting.help %></p>
</div>

View file

@ -42,12 +42,17 @@ DomainServer::DomainServer(int argc, char* argv[]) :
_hostname(),
_networkReplyUUIDMap(),
_sessionAuthenticationHash(),
_webAuthenticationStateSet(),
_cookieSessionHash(),
_settingsManager()
{
setOrganizationName("High Fidelity");
setOrganizationDomain("highfidelity.io");
setApplicationName("domain-server");
QSettings::setDefaultFormat(QSettings::IniFormat);
qRegisterMetaType<DomainServerWebSessionData>("DomainServerWebSessionData");
qRegisterMetaTypeStreamOperators<DomainServerWebSessionData>("DomainServerWebSessionData");
_argumentVariantMap = HifiConfigVariantMap::mergeCLParametersWithJSONConfig(arguments());
@ -57,6 +62,8 @@ DomainServer::DomainServer(int argc, char* argv[]) :
qDebug() << "Setting up LimitedNodeList and assignments.";
setupNodeListAndAssignments();
loadExistingSessionsFromSettings();
}
}
@ -426,11 +433,14 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
}
}
QString connectedUsername;
if (!isAssignment && !_oauthProviderURL.isEmpty() && _argumentVariantMap.contains(ALLOWED_ROLES_CONFIG_KEY)) {
// this is an Agent, and we require authentication so we can compare the user's roles to our list of allowed ones
if (_sessionAuthenticationHash.contains(packetUUID)) {
if (!_sessionAuthenticationHash.value(packetUUID)) {
connectedUsername = _sessionAuthenticationHash.take(packetUUID);
if (connectedUsername.isEmpty()) {
// we've decided this is a user that isn't allowed in, return out
// TODO: provide information to the user so they know why they can't connect
return;
@ -473,6 +483,9 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
// now that we've pulled the wallet UUID and added the node to our list, delete the pending assignee data
delete pendingAssigneeData;
}
// if we have a username from an OAuth connect request, set it on the DomainServerNodeData
nodeData->setUsername(connectedUsername);
nodeData->setSendingSockAddr(senderSockAddr);
@ -848,7 +861,7 @@ QJsonObject DomainServer::jsonForSocket(const HifiSockAddr& socket) {
QJsonObject socketJSON;
socketJSON["ip"] = socket.getAddress().toString();
socketJSON["port"] = ntohs(socket.getPort());
socketJSON["port"] = socket.getPort();
return socketJSON;
}
@ -860,6 +873,7 @@ const char JSON_KEY_LOCAL_SOCKET[] = "local";
const char JSON_KEY_POOL[] = "pool";
const char JSON_KEY_PENDING_CREDITS[] = "pending_credits";
const char JSON_KEY_WAKE_TIMESTAMP[] = "wake_timestamp";
const char JSON_KEY_USERNAME[] = "username";
QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) {
QJsonObject nodeJson;
@ -884,6 +898,10 @@ QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) {
// if the node has pool information, add it
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
// add the node username, if it exists
nodeJson[JSON_KEY_USERNAME] = nodeData->getUsername();
SharedAssignmentPointer matchingAssignment = _allAssignments.value(nodeData->getAssignmentUUID());
if (matchingAssignment) {
nodeJson[JSON_KEY_POOL] = matchingAssignment->getPool();
@ -921,7 +939,19 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
const QString URI_NODES = "/nodes";
const QString UUID_REGEX_STRING = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
// allow sub-handlers to handle requests that do not require authentication
if (_settingsManager.handlePublicHTTPRequest(connection, url)) {
return true;
}
// all requests below require a cookie to prove authentication so check that first
if (!isAuthenticatedRequest(connection, url)) {
// this is not an authenticated request
// return true from the handler since it was handled with a 401 or re-direct to auth
return true;
}
if (connection->requestOperation() == QNetworkAccessManager::GetOperation) {
if (url.path() == "/assignments.json") {
// user is asking for json list of assignments
@ -1162,9 +1192,11 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
}
// didn't process the request, let our DomainServerSettingsManager or HTTPManager handle
return _settingsManager.handleHTTPRequest(connection, url);
return _settingsManager.handleAuthenticatedHTTPRequest(connection, url);
}
const QString HIFI_SESSION_COOKIE_KEY = "DS_WEB_SESSION_UUID";
bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &url) {
const QString URI_OAUTH = "/oauth";
qDebug() << "HTTPS request received at" << url.toString();
@ -1178,7 +1210,6 @@ bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &u
const QString STATE_QUERY_KEY = "state";
QUuid stateUUID = QUuid(codeURLQuery.queryItemValue(STATE_QUERY_KEY));
if (!authorizationCode.isEmpty() && !stateUUID.isNull()) {
// fire off a request with this code and state to get an access token for the user
@ -1193,15 +1224,45 @@ bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &u
QNetworkRequest tokenRequest(tokenRequestUrl);
tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QNetworkReply* tokenReply = NetworkAccessManager::getInstance().post(tokenRequest, tokenPostBody.toLocal8Bit());
qDebug() << "Requesting a token for user with session UUID" << uuidStringWithoutCurlyBraces(stateUUID);
// insert this to our pending token replies so we can associate the returned access token with the right UUID
_networkReplyUUIDMap.insert(tokenReply, stateUUID);
connect(tokenReply, &QNetworkReply::finished, this, &DomainServer::handleTokenRequestFinished);
if (_webAuthenticationStateSet.remove(stateUUID)) {
// this is a web user who wants to auth to access web interface
// we hold the response back to them until we get their profile information
// and can decide if they are let in or not
QEventLoop loop;
connect(tokenReply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
// start the loop for the token request
loop.exec();
QNetworkReply* profileReply = profileRequestGivenTokenReply(tokenReply);
// stop the loop once the profileReply is complete
connect(profileReply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
// restart the loop for the profile request
loop.exec();
// call helper method to get cookieHeaders
Headers cookieHeaders = setupCookieHeadersFromProfileReply(profileReply);
connection->respond(HTTPConnection::StatusCode302, QByteArray(),
HTTPConnection::DefaultContentType, cookieHeaders);
// we've redirected the user back to our homepage
return true;
} else {
qDebug() << "Requesting a token for user with session UUID" << uuidStringWithoutCurlyBraces(stateUUID);
// insert this to our pending token replies so we can associate the returned access token with the right UUID
_networkReplyUUIDMap.insert(tokenReply, stateUUID);
connect(tokenReply, &QNetworkReply::finished, this, &DomainServer::handleTokenRequestFinished);
}
}
// respond with a 200 code indicating that login is complete
@ -1213,6 +1274,77 @@ bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &u
}
}
bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl& url) {
const QByteArray HTTP_COOKIE_HEADER_KEY = "Cookie";
const QString ADMIN_USERS_CONFIG_KEY = "admin-users";
const QString ADMIN_ROLES_CONFIG_KEY = "admin-roles";
if (!_oauthProviderURL.isEmpty()
&& (_argumentVariantMap.contains(ADMIN_USERS_CONFIG_KEY) || _argumentVariantMap.contains(ADMIN_ROLES_CONFIG_KEY))) {
QString cookieString = connection->requestHeaders().value(HTTP_COOKIE_HEADER_KEY);
const QString COOKIE_UUID_REGEX_STRING = HIFI_SESSION_COOKIE_KEY + "=([\\d\\w-]+)($|;)";
QRegExp cookieUUIDRegex(COOKIE_UUID_REGEX_STRING);
QUuid cookieUUID;
if (cookieString.indexOf(cookieUUIDRegex) != -1) {
cookieUUID = cookieUUIDRegex.cap(1);
}
if (!cookieUUID.isNull() && _cookieSessionHash.contains(cookieUUID)) {
// pull the QJSONObject for the user with this cookie UUID
DomainServerWebSessionData sessionData = _cookieSessionHash.value(cookieUUID);
QString profileUsername = sessionData.getUsername();
if (_argumentVariantMap.value(ADMIN_USERS_CONFIG_KEY).toJsonValue().toArray().contains(profileUsername)) {
// this is an authenticated user
return true;
}
// loop the roles of this user and see if they are in the admin-roles array
QJsonArray adminRolesArray = _argumentVariantMap.value(ADMIN_ROLES_CONFIG_KEY).toJsonValue().toArray();
if (!adminRolesArray.isEmpty()) {
foreach(const QString& userRole, sessionData.getRoles()) {
if (adminRolesArray.contains(userRole)) {
// this user has a role that allows them to administer the domain-server
return true;
}
}
}
QString unauthenticatedRequest = "You do not have permission to access this domain-server.";
connection->respond(HTTPConnection::StatusCode401, unauthenticatedRequest.toUtf8());
// the user does not have allowed username or role, return 401
return false;
} else {
// re-direct this user to OAuth page
// generate a random state UUID to use
QUuid stateUUID = QUuid::createUuid();
// add it to the set so we can handle the callback from the OAuth provider
_webAuthenticationStateSet.insert(stateUUID);
QUrl oauthRedirectURL = oauthAuthorizationURL(stateUUID);
Headers redirectHeaders;
redirectHeaders.insert("Location", oauthRedirectURL.toEncoded());
connection->respond(HTTPConnection::StatusCode302,
QByteArray(), HTTPConnection::DefaultContentType, redirectHeaders);
// we don't know about this user yet, so they are not yet authenticated
return false;
}
} else {
// we don't have an OAuth URL + admin roles/usernames, so all users are authenticated
return true;
}
}
const QString OAUTH_JSON_ACCESS_TOKEN_KEY = "access_token";
void DomainServer::handleTokenRequestFinished() {
@ -1220,20 +1352,11 @@ void DomainServer::handleTokenRequestFinished() {
QUuid matchingSessionUUID = _networkReplyUUIDMap.take(networkReply);
if (!matchingSessionUUID.isNull() && networkReply->error() == QNetworkReply::NoError) {
// pull the access token from the returned JSON and store it with the matching session UUID
QJsonDocument returnedJSON = QJsonDocument::fromJson(networkReply->readAll());
QString accessToken = returnedJSON.object()[OAUTH_JSON_ACCESS_TOKEN_KEY].toString();
qDebug() << "Received access token for user with UUID" << uuidStringWithoutCurlyBraces(matchingSessionUUID);
// fire off a request to get this user's identity so we can see if we will let them in
QUrl profileURL = _oauthProviderURL;
profileURL.setPath("/api/v1/users/profile");
profileURL.setQuery(QString("%1=%2").arg(OAUTH_JSON_ACCESS_TOKEN_KEY, accessToken));
QNetworkReply* profileReply = NetworkAccessManager::getInstance().get(QNetworkRequest(profileURL));
qDebug() << "Requesting access token for user with session UUID" << uuidStringWithoutCurlyBraces(matchingSessionUUID);
qDebug() << "Received access token for user with UUID" << uuidStringWithoutCurlyBraces(matchingSessionUUID)
<< "-" << "requesting profile.";
QNetworkReply* profileReply = profileRequestGivenTokenReply(networkReply);
connect(profileReply, &QNetworkReply::finished, this, &DomainServer::handleProfileRequestFinished);
@ -1241,6 +1364,19 @@ void DomainServer::handleTokenRequestFinished() {
}
}
QNetworkReply* DomainServer::profileRequestGivenTokenReply(QNetworkReply* tokenReply) {
// pull the access token from the returned JSON and store it with the matching session UUID
QJsonDocument returnedJSON = QJsonDocument::fromJson(tokenReply->readAll());
QString accessToken = returnedJSON.object()[OAUTH_JSON_ACCESS_TOKEN_KEY].toString();
// fire off a request to get this user's identity so we can see if we will let them in
QUrl profileURL = _oauthProviderURL;
profileURL.setPath("/api/v1/users/profile");
profileURL.setQuery(QString("%1=%2").arg(OAUTH_JSON_ACCESS_TOKEN_KEY, accessToken));
return NetworkAccessManager::getInstance().get(QNetworkRequest(profileURL));
}
void DomainServer::handleProfileRequestFinished() {
QNetworkReply* networkReply = reinterpret_cast<QNetworkReply*>(sender());
QUuid matchingSessionUUID = _networkReplyUUIDMap.take(networkReply);
@ -1254,26 +1390,83 @@ void DomainServer::handleProfileRequestFinished() {
QJsonArray allowedRolesArray = _argumentVariantMap.value(ALLOWED_ROLES_CONFIG_KEY).toJsonValue().toArray();
bool shouldAllowUserToConnect = false;
QString connectableUsername;
QString profileUsername = profileJSON.object()["data"].toObject()["user"].toObject()["username"].toString();
foreach(const QJsonValue& roleValue, userRolesArray) {
if (allowedRolesArray.contains(roleValue)) {
// the user has a role that lets them in
// set the bool to true and break
shouldAllowUserToConnect = true;
connectableUsername = profileUsername;
break;
}
}
qDebug() << "Confirmed authentication state for user" << uuidStringWithoutCurlyBraces(matchingSessionUUID)
<< "-" << shouldAllowUserToConnect;
if (connectableUsername.isEmpty()) {
qDebug() << "User" << profileUsername << "with session UUID"
<< uuidStringWithoutCurlyBraces(matchingSessionUUID)
<< "does not have an allowable role. Refusing connection.";
} else {
qDebug() << "User" << profileUsername << "with session UUID"
<< uuidStringWithoutCurlyBraces(matchingSessionUUID)
<< "has an allowable role. Can connect.";
}
// insert this UUID and a flag that indicates if they are allowed to connect
_sessionAuthenticationHash.insert(matchingSessionUUID, shouldAllowUserToConnect);
_sessionAuthenticationHash.insert(matchingSessionUUID, connectableUsername);
}
}
}
const QString DS_SETTINGS_SESSIONS_GROUP = "web-sessions";
Headers DomainServer::setupCookieHeadersFromProfileReply(QNetworkReply* profileReply) {
Headers cookieHeaders;
// create a UUID for this cookie
QUuid cookieUUID = QUuid::createUuid();
QJsonDocument profileDocument = QJsonDocument::fromJson(profileReply->readAll());
QJsonObject userObject = profileDocument.object()["data"].toObject()["user"].toObject();
// add the profile to our in-memory data structure so we know who the user is when they send us their cookie
DomainServerWebSessionData sessionData(userObject);
_cookieSessionHash.insert(cookieUUID, sessionData);
// persist the cookie to settings file so we can get it back on DS relaunch
QSettings localSettings;
localSettings.beginGroup(DS_SETTINGS_SESSIONS_GROUP);
QVariant sessionVariant = QVariant::fromValue(sessionData);
localSettings.setValue(cookieUUID.toString(), QVariant::fromValue(sessionData));
// setup expiry for cookie to 1 month from today
QDateTime cookieExpiry = QDateTime::currentDateTimeUtc().addMonths(1);
QString cookieString = HIFI_SESSION_COOKIE_KEY + "=" + uuidStringWithoutCurlyBraces(cookieUUID.toString());
cookieString += "; expires=" + cookieExpiry.toString("ddd, dd MMM yyyy HH:mm:ss") + " GMT";
cookieString += "; domain=" + _hostname + "; path=/";
cookieHeaders.insert("Set-Cookie", cookieString.toUtf8());
// redirect the user back to the homepage so they can present their cookie and be authenticated
QString redirectString = "http://" + _hostname + ":" + QString::number(_httpManager.serverPort());
cookieHeaders.insert("Location", redirectString.toUtf8());
return cookieHeaders;
}
void DomainServer::loadExistingSessionsFromSettings() {
// read data for existing web sessions into memory so existing sessions can be leveraged
QSettings domainServerSettings;
domainServerSettings.beginGroup(DS_SETTINGS_SESSIONS_GROUP);
foreach(const QString& uuidKey, domainServerSettings.childKeys()) {
_cookieSessionHash.insert(QUuid(uuidKey), domainServerSettings.value(uuidKey).value<DomainServerWebSessionData>());
qDebug() << "Pulled web session from settings - cookie UUID is" << uuidKey;
}
}
void DomainServer::refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment) {
QUuid oldUUID = assignment->getUUID();
assignment->resetUUID();

View file

@ -25,6 +25,7 @@
#include <LimitedNodeList.h>
#include "DomainServerSettingsManager.h"
#include "DomainServerWebSessionData.h"
#include "WalletTransaction.h"
#include "PendingAssignedNodeData.h"
@ -85,8 +86,14 @@ private:
QUrl oauthRedirectURL();
QUrl oauthAuthorizationURL(const QUuid& stateUUID = QUuid::createUuid());
bool isAuthenticatedRequest(HTTPConnection* connection, const QUrl& url);
void handleTokenRequestFinished();
QNetworkReply* profileRequestGivenTokenReply(QNetworkReply* tokenReply);
void handleProfileRequestFinished();
Headers setupCookieHeadersFromProfileReply(QNetworkReply* profileReply);
void loadExistingSessionsFromSettings();
QJsonObject jsonForSocket(const HifiSockAddr& socket);
QJsonObject jsonObjectForNode(const SharedNodePointer& node);
@ -108,7 +115,10 @@ private:
QString _oauthClientSecret;
QString _hostname;
QMap<QNetworkReply*, QUuid> _networkReplyUUIDMap;
QHash<QUuid, bool> _sessionAuthenticationHash;
QHash<QUuid, QString> _sessionAuthenticationHash;
QSet<QUuid> _webAuthenticationStateSet;
QHash<QUuid, DomainServerWebSessionData> _cookieSessionHash;
DomainServerSettingsManager _settingsManager;
};

View file

@ -21,6 +21,7 @@ DomainServerNodeData::DomainServerNodeData() :
_sessionSecretHash(),
_assignmentUUID(),
_walletUUID(),
_username(),
_paymentIntervalTimer(),
_statsJSONObject(),
_sendingSockAddr(),

View file

@ -35,6 +35,9 @@ public:
void setWalletUUID(const QUuid& walletUUID) { _walletUUID = walletUUID; }
const QUuid& getWalletUUID() const { return _walletUUID; }
void setUsername(const QString& username) { _username = username; }
const QString& getUsername() const { return _username; }
QElapsedTimer& getPaymentIntervalTimer() { return _paymentIntervalTimer; }
void setSendingSockAddr(const HifiSockAddr& sendingSockAddr) { _sendingSockAddr = sendingSockAddr; }
@ -50,6 +53,7 @@ private:
QHash<QUuid, QUuid> _sessionSecretHash;
QUuid _assignmentUUID;
QUuid _walletUUID;
QString _username;
QElapsedTimer _paymentIntervalTimer;
QJsonObject _statsJSONObject;
HifiSockAddr _sendingSockAddr;

View file

@ -47,24 +47,8 @@ DomainServerSettingsManager::DomainServerSettingsManager() :
const QString DESCRIPTION_SETTINGS_KEY = "settings";
const QString SETTING_DEFAULT_KEY = "default";
bool DomainServerSettingsManager::handleHTTPRequest(HTTPConnection* connection, const QUrl &url) {
if (connection->requestOperation() == QNetworkAccessManager::PostOperation && url.path() == "/settings.json") {
// this is a POST operation to change one or more settings
QJsonDocument postedDocument = QJsonDocument::fromJson(connection->requestContent());
QJsonObject postedObject = postedDocument.object();
// we recurse one level deep below each group for the appropriate setting
recurseJSONObjectAndOverwriteSettings(postedObject, _settingsMap, _descriptionObject);
// store whatever the current _settingsMap is to file
persistToFile();
// return success to the caller
QString jsonSuccess = "{\"status\": \"success\"}";
connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json");
return true;
} else if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == "/settings.json") {
bool DomainServerSettingsManager::handlePublicHTTPRequest(HTTPConnection* connection, const QUrl &url) {
if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == "/settings.json") {
// this is a GET operation for our settings
// check if there is a query parameter for settings affecting a particular type of assignment
@ -135,6 +119,30 @@ bool DomainServerSettingsManager::handleHTTPRequest(HTTPConnection* connection,
return false;
}
bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection *connection, const QUrl &url) {
if (connection->requestOperation() == QNetworkAccessManager::PostOperation && url.path() == "/settings.json") {
// this is a POST operation to change one or more settings
QJsonDocument postedDocument = QJsonDocument::fromJson(connection->requestContent());
QJsonObject postedObject = postedDocument.object();
// we recurse one level deep below each group for the appropriate setting
recurseJSONObjectAndOverwriteSettings(postedObject, _settingsMap, _descriptionObject);
// store whatever the current _settingsMap is to file
persistToFile();
// return success to the caller
QString jsonSuccess = "{\"status\": \"success\"}";
connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json");
return true;
}
return false;
}
const QString SETTING_DESCRIPTION_TYPE_KEY = "type";
void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject,
QVariantMap& settingsVariant,
QJsonObject descriptionObject) {
@ -145,7 +153,17 @@ void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
// we don't continue if this key is not present in our descriptionObject
if (descriptionObject.contains(key)) {
if (rootValue.isString()) {
settingsVariant[key] = rootValue.toString();
if (rootValue.toString().isEmpty()) {
// this is an empty value, clear it in settings variant so the default is sent
settingsVariant.remove(key);
} else {
if (descriptionObject[key].toObject().contains(SETTING_DESCRIPTION_TYPE_KEY)) {
// for now this means that this is a double, so set it as a double
settingsVariant[key] = rootValue.toString().toDouble();
} else {
settingsVariant[key] = rootValue.toString();
}
}
} else if (rootValue.isBool()) {
settingsVariant[key] = rootValue.toBool();
} else if (rootValue.isObject()) {
@ -158,9 +176,16 @@ void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
settingsVariant[key] = QVariantMap();
}
QVariantMap& thisMap = *reinterpret_cast<QVariantMap*>(settingsVariant[key].data());
recurseJSONObjectAndOverwriteSettings(rootValue.toObject(),
*reinterpret_cast<QVariantMap*>(settingsVariant[key].data()),
thisMap,
nextDescriptionObject[DESCRIPTION_SETTINGS_KEY].toObject());
if (thisMap.isEmpty()) {
// we've cleared all of the settings below this value, so remove this one too
settingsVariant.remove(key);
}
}
}
}

View file

@ -16,11 +16,12 @@
#include <HTTPManager.h>
class DomainServerSettingsManager : public QObject, HTTPRequestHandler {
class DomainServerSettingsManager : public QObject {
Q_OBJECT
public:
DomainServerSettingsManager();
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url);
bool handlePublicHTTPRequest(HTTPConnection* connection, const QUrl& url);
bool handleAuthenticatedHTTPRequest(HTTPConnection* connection, const QUrl& url);
QByteArray getJSONSettingsMap() const;
private:

View file

@ -0,0 +1,63 @@
//
// DomainServerWebSessionData.cpp
// domain-server/src
//
// Created by Stephen Birarda on 2014-07-21.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QtCore/QDebug>
#include <QtCore/QJsonArray>
#include <QtCore/QJsonObject>
#include "DomainServerWebSessionData.h"
DomainServerWebSessionData::DomainServerWebSessionData() :
_username(),
_roles()
{
}
DomainServerWebSessionData::DomainServerWebSessionData(const QJsonObject& userObject) :
_roles()
{
_username = userObject["username"].toString();
// pull each of the roles and throw them into our set
foreach(const QJsonValue& rolesValue, userObject["roles"].toArray()) {
_roles.insert(rolesValue.toString());
}
}
DomainServerWebSessionData::DomainServerWebSessionData(const DomainServerWebSessionData& otherSessionData) {
_username = otherSessionData._username;
_roles = otherSessionData._roles;
}
DomainServerWebSessionData& DomainServerWebSessionData::operator=(const DomainServerWebSessionData& otherSessionData) {
DomainServerWebSessionData temp(otherSessionData);
swap(temp);
return *this;
}
void DomainServerWebSessionData::swap(DomainServerWebSessionData& otherSessionData) {
using std::swap;
swap(_username, otherSessionData._username);
swap(_roles, otherSessionData._roles);
}
QDataStream& operator<<(QDataStream &out, const DomainServerWebSessionData& session) {
out << session._username << session._roles;
return out;
}
QDataStream& operator>>(QDataStream &in, DomainServerWebSessionData& session) {
in >> session._username >> session._roles;
return in;
}

View file

@ -0,0 +1,41 @@
//
// DomainServerWebSessionData.h
// domain-server/src
//
// Created by Stephen Birarda on 2014-07-21.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_DomainServerWebSessionData_h
#define hifi_DomainServerWebSessionData_h
#include <QtCore/QObject>
#include <QtCore/QSet>
class DomainServerWebSessionData : public QObject {
Q_OBJECT
public:
DomainServerWebSessionData();
DomainServerWebSessionData(const QJsonObject& userObject);
DomainServerWebSessionData(const DomainServerWebSessionData& otherSessionData);
DomainServerWebSessionData& operator=(const DomainServerWebSessionData& otherSessionData);
const QString& getUsername() const { return _username; }
const QSet<QString>& getRoles() const { return _roles; }
friend QDataStream& operator<<(QDataStream &out, const DomainServerWebSessionData& session);
friend QDataStream& operator>>(QDataStream &in, DomainServerWebSessionData& session);
private:
void swap(DomainServerWebSessionData& otherSessionData);
QString _username;
QSet<QString> _roles;
};
Q_DECLARE_METATYPE(DomainServerWebSessionData)
#endif // hifi_DomainServerWebSessionData_h

View file

@ -69,6 +69,7 @@
#include "InterfaceVersion.h"
#include "Menu.h"
#include "ModelUploader.h"
#include "PaymentManager.h"
#include "Util.h"
#include "devices/MIDIManager.h"
#include "devices/OculusManager.h"
@ -233,9 +234,17 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
connect(audioThread, SIGNAL(started()), &_audio, SLOT(start()));
audioThread->start();
const DomainHandler& domainHandler = nodeList->getDomainHandler();
connect(&nodeList->getDomainHandler(), SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&)));
connect(&nodeList->getDomainHandler(), SIGNAL(connectedToDomain(const QString&)), SLOT(connectedToDomain(const QString&)));
connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&)));
connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(connectedToDomain(const QString&)));
connect(&domainHandler, &DomainHandler::settingsReceived, this, &Application::domainSettingsReceived);
// hookup VoxelEditSender to PaymentManager so we can pay for octree edits
const PaymentManager& paymentManager = PaymentManager::getInstance();
connect(&_voxelEditSender, &VoxelEditPacketSender::octreePaymentRequired,
&paymentManager, &PaymentManager::sendSignedPayment);
// update our location every 5 seconds in the data-server, assuming that we are authenticated with one
const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * 1000;
@ -3791,6 +3800,38 @@ void Application::uploadAttachment() {
uploadModel(ATTACHMENT_MODEL);
}
void Application::domainSettingsReceived(const QJsonObject& domainSettingsObject) {
// from the domain-handler, figure out the satoshi cost per voxel and per meter cubed
const QString VOXEL_SETTINGS_KEY = "voxels";
const QString PER_VOXEL_COST_KEY = "per-voxel-credits";
const QString PER_METER_CUBED_COST_KEY = "per-meter-cubed-credits";
const QString VOXEL_WALLET_UUID = "voxel-wallet";
const QJsonObject& voxelObject = domainSettingsObject[VOXEL_SETTINGS_KEY].toObject();
qint64 satoshisPerVoxel = 0;
qint64 satoshisPerMeterCubed = 0;
QUuid voxelWalletUUID;
if (!domainSettingsObject.isEmpty()) {
float perVoxelCredits = (float) voxelObject[PER_VOXEL_COST_KEY].toDouble();
float perMeterCubedCredits = (float) voxelObject[PER_METER_CUBED_COST_KEY].toDouble();
satoshisPerVoxel = (qint64) floorf(perVoxelCredits * SATOSHIS_PER_CREDIT);
satoshisPerMeterCubed = (qint64) floorf(perMeterCubedCredits * SATOSHIS_PER_CREDIT);
voxelWalletUUID = QUuid(voxelObject[VOXEL_WALLET_UUID].toString());
}
qDebug() << "Voxel costs are" << satoshisPerVoxel << "per voxel and" << satoshisPerMeterCubed << "per meter cubed";
qDebug() << "Destination wallet UUID for voxel payments is" << voxelWalletUUID;
_voxelEditSender.setSatoshisPerVoxel(satoshisPerVoxel);
_voxelEditSender.setSatoshisPerMeterCubed(satoshisPerMeterCubed);
_voxelEditSender.setDestinationWalletUUID(voxelWalletUUID);
}
QString Application::getPreviousScriptLocation() {
QString suggestedName;
if (_previousScriptLocation.isEmpty()) {

View file

@ -342,6 +342,8 @@ public slots:
void uploadAttachment();
void bumpSettings() { ++_numChangedSettings; }
void domainSettingsReceived(const QJsonObject& domainSettingsObject);
private slots:
void timer();

View file

@ -0,0 +1,48 @@
//
// PaymentManager.cpp
// interface/src
//
// Created by Stephen Birarda on 2014-07-30.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QtCore/QDateTime>
#include <QtCore/QDebug>
#include <QtCore/QUuid>
#include <NodeList.h>
#include <PacketHeaders.h>
#include "SignedWalletTransaction.h"
#include "PaymentManager.h"
PaymentManager& PaymentManager::getInstance() {
static PaymentManager sharedInstance;
return sharedInstance;
}
void PaymentManager::sendSignedPayment(qint64 satoshiAmount, const QUuid& nodeUUID, const QUuid& destinationWalletUUID) {
// setup a signed wallet transaction
const qint64 DEFAULT_TRANSACTION_EXPIRY_SECONDS = 60;
qint64 currentTimestamp = QDateTime::currentDateTimeUtc().toTime_t();
SignedWalletTransaction newTransaction(destinationWalletUUID, satoshiAmount,
currentTimestamp, DEFAULT_TRANSACTION_EXPIRY_SECONDS);
// send the signed transaction to the redeeming node
QByteArray transactionByteArray = byteArrayWithPopulatedHeader(PacketTypeSignedTransactionPayment);
// append the binary message and the signed message digest
transactionByteArray.append(newTransaction.binaryMessage());
transactionByteArray.append(newTransaction.signedMessageDigest());
qDebug() << "Paying" << satoshiAmount << "satoshis to" << destinationWalletUUID << "via" << nodeUUID;
// use the NodeList to send that to the right node
NodeList* nodeList = NodeList::getInstance();
nodeList->writeDatagram(transactionByteArray, nodeList->nodeWithUUID(nodeUUID));
}

View file

@ -0,0 +1,25 @@
//
// PaymentManager.h
// interface/src
//
// Created by Stephen Birarda on 2014-07-30.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_PaymentManager_h
#define hifi_PaymentManager_h
#include <QtCore/QObject>
class PaymentManager : public QObject {
Q_OBJECT
public:
static PaymentManager& getInstance();
public slots:
void sendSignedPayment(qint64 satoshiAmount, const QUuid& nodeUUID, const QUuid& destinationWalletUUID);
};
#endif // hifi_PaymentManager_h

View file

@ -32,7 +32,7 @@ SignedWalletTransaction::SignedWalletTransaction(const QUuid& destinationUUID, q
}
QByteArray SignedWalletTransaction::hexMessage() {
QByteArray SignedWalletTransaction::binaryMessage() {
// build the message using the components of this transaction
// UUID, source UUID, destination UUID, message timestamp, expiry delta, amount
@ -49,7 +49,11 @@ QByteArray SignedWalletTransaction::hexMessage() {
messageBinary.append(reinterpret_cast<const char*>(&_amount), sizeof(_amount));
return messageBinary.toHex();
return messageBinary;
}
QByteArray SignedWalletTransaction::hexMessage() {
return binaryMessage().toHex();
}
QByteArray SignedWalletTransaction::messageDigest() {

View file

@ -19,6 +19,7 @@ class SignedWalletTransaction : public WalletTransaction {
public:
SignedWalletTransaction(const QUuid& destinationUUID, qint64 amount, qint64 messageTimestamp, qint64 expiryDelta);
QByteArray binaryMessage();
QByteArray hexMessage();
QByteArray messageDigest();
QByteArray signedMessageDigest();

View file

@ -21,6 +21,7 @@ const char* HTTPConnection::StatusCode200 = "200 OK";
const char* HTTPConnection::StatusCode301 = "301 Moved Permanently";
const char* HTTPConnection::StatusCode302 = "302 Found";
const char* HTTPConnection::StatusCode400 = "400 Bad Request";
const char* HTTPConnection::StatusCode401 = "401 Unauthorized";
const char* HTTPConnection::StatusCode404 = "404 Not Found";
const char* HTTPConnection::DefaultContentType = "text/plain; charset=ISO-8859-1";
@ -42,7 +43,8 @@ HTTPConnection::HTTPConnection (QTcpSocket* socket, HTTPManager* parentManager)
HTTPConnection::~HTTPConnection() {
// log the destruction
if (_socket->error() != QAbstractSocket::UnknownSocketError) {
if (_socket->error() != QAbstractSocket::UnknownSocketError
&& _socket->error() != QAbstractSocket::RemoteHostClosedError) {
qDebug() << _socket->errorString() << "-" << _socket->error();
}
}

View file

@ -46,6 +46,7 @@ public:
static const char* StatusCode301;
static const char* StatusCode302;
static const char* StatusCode400;
static const char* StatusCode401;
static const char* StatusCode404;
static const char* DefaultContentType;

View file

@ -15,8 +15,8 @@
#include <QtCore/QMap>
#include <QtCore/QStringList>
#include <QtCore/QUrlQuery>
#include <QtNetwork/QHttpMultiPart>
#include <QtNetwork/QNetworkRequest>
#include <QHttpMultiPart>
#include "NodeList.h"
#include "PacketHeaders.h"
@ -62,6 +62,8 @@ AccountManager::AccountManager() :
qRegisterMetaType<QNetworkAccessManager::Operation>("QNetworkAccessManager::Operation");
qRegisterMetaType<JSONCallbackParameters>("JSONCallbackParameters");
qRegisterMetaType<QHttpMultiPart*>("QHttpMultiPart*");
connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged);
}
@ -142,90 +144,113 @@ void AccountManager::authenticatedRequest(const QString& path, QNetworkAccessMan
const JSONCallbackParameters& callbackParams,
const QByteArray& dataByteArray,
QHttpMultiPart* dataMultiPart) {
QMetaObject::invokeMethod(this, "invokedRequest",
Q_ARG(const QString&, path),
Q_ARG(bool, true),
Q_ARG(QNetworkAccessManager::Operation, operation),
Q_ARG(const JSONCallbackParameters&, callbackParams),
Q_ARG(const QByteArray&, dataByteArray),
Q_ARG(QHttpMultiPart*, dataMultiPart));
}
void AccountManager::invokedRequest(const QString& path, QNetworkAccessManager::Operation operation,
void AccountManager::unauthenticatedRequest(const QString& path, QNetworkAccessManager::Operation operation,
const JSONCallbackParameters& callbackParams,
const QByteArray& dataByteArray,
QHttpMultiPart* dataMultiPart) {
QMetaObject::invokeMethod(this, "invokedRequest",
Q_ARG(const QString&, path),
Q_ARG(bool, false),
Q_ARG(QNetworkAccessManager::Operation, operation),
Q_ARG(const JSONCallbackParameters&, callbackParams),
Q_ARG(const QByteArray&, dataByteArray),
Q_ARG(QHttpMultiPart*, dataMultiPart));
}
void AccountManager::invokedRequest(const QString& path,
bool requiresAuthentication,
QNetworkAccessManager::Operation operation,
const JSONCallbackParameters& callbackParams,
const QByteArray& dataByteArray, QHttpMultiPart* dataMultiPart) {
NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
if (hasValidAccessToken()) {
QNetworkRequest authenticatedRequest;
QUrl requestURL = _authURL;
if (path.startsWith("/")) {
requestURL.setPath(path);
QNetworkRequest networkRequest;
QUrl requestURL = _authURL;
if (path.startsWith("/")) {
requestURL.setPath(path);
} else {
requestURL.setPath("/" + path);
}
if (requiresAuthentication) {
if (hasValidAccessToken()) {
requestURL.setQuery("access_token=" + _accountInfo.getAccessToken().token);
} else {
requestURL.setPath("/" + path);
qDebug() << "No valid access token present. Bailing on authenticated invoked request.";
return;
}
requestURL.setQuery("access_token=" + _accountInfo.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);
}
}
networkRequest.setUrl(requestURL);
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
qDebug() << "Making a 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:
if (dataMultiPart) {
if (operation == QNetworkAccessManager::PostOperation) {
networkReply = networkAccessManager.post(authenticatedRequest, dataMultiPart);
} else {
networkReply = networkAccessManager.put(authenticatedRequest, dataMultiPart);
}
dataMultiPart->setParent(networkReply);
}
QNetworkReply* networkReply = NULL;
switch (operation) {
case QNetworkAccessManager::GetOperation:
networkReply = networkAccessManager.get(networkRequest);
break;
case QNetworkAccessManager::PostOperation:
case QNetworkAccessManager::PutOperation:
if (dataMultiPart) {
if (operation == QNetworkAccessManager::PostOperation) {
networkReply = networkAccessManager.post(networkRequest, dataMultiPart);
} else {
authenticatedRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
if (operation == QNetworkAccessManager::PostOperation) {
networkReply = networkAccessManager.post(authenticatedRequest, dataByteArray);
} else {
networkReply = networkAccessManager.put(authenticatedRequest, dataByteArray);
}
networkReply = networkAccessManager.put(networkRequest, dataMultiPart);
}
break;
case QNetworkAccessManager::DeleteOperation:
networkReply = networkAccessManager.sendCustomRequest(authenticatedRequest, "DELETE");
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 (callbackParams.updateReciever && !callbackParams.updateSlot.isEmpty()) {
callbackParams.updateReciever->connect(networkReply, SIGNAL(uploadProgress(qint64, qint64)),
callbackParams.updateSlot.toStdString().c_str());
dataMultiPart->setParent(networkReply);
} else {
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
if (operation == QNetworkAccessManager::PostOperation) {
networkReply = networkAccessManager.post(networkRequest, dataByteArray);
} else {
networkReply = networkAccessManager.put(networkRequest, dataByteArray);
}
}
// if we ended up firing of a request, hook up to it now
connect(networkReply, SIGNAL(finished()), SLOT(processReply()));
break;
case QNetworkAccessManager::DeleteOperation:
networkReply = networkAccessManager.sendCustomRequest(networkRequest, "DELETE");
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 (callbackParams.updateReciever && !callbackParams.updateSlot.isEmpty()) {
callbackParams.updateReciever->connect(networkReply, SIGNAL(uploadProgress(qint64, qint64)),
callbackParams.updateSlot.toStdString().c_str());
}
}
// if we ended up firing of a request, hook up to it now
connect(networkReply, SIGNAL(finished()), SLOT(processReply()));
}
}
@ -276,6 +301,7 @@ void AccountManager::passErrorToCallback(QNetworkReply* requestReply) {
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
qDebug() << "Received error response from data-server that has no matching callback.";
qDebug() << "Error" << requestReply->error() << "-" << requestReply->errorString();
qDebug() << requestReply->readAll();
}
}
}

View file

@ -47,6 +47,12 @@ public:
const JSONCallbackParameters& callbackParams = JSONCallbackParameters(),
const QByteArray& dataByteArray = QByteArray(),
QHttpMultiPart* dataMultiPart = NULL);
void unauthenticatedRequest(const QString& path,
QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation,
const JSONCallbackParameters& callbackParams = JSONCallbackParameters(),
const QByteArray& dataByteArray = QByteArray(),
QHttpMultiPart* dataMultiPart = NULL);
const QUrl& getAuthURL() const { return _authURL; }
void setAuthURL(const QUrl& authURL);
@ -88,7 +94,9 @@ private:
void passSuccessToCallback(QNetworkReply* reply);
void passErrorToCallback(QNetworkReply* reply);
Q_INVOKABLE void invokedRequest(const QString& path, QNetworkAccessManager::Operation operation,
Q_INVOKABLE void invokedRequest(const QString& path,
bool requiresAuthentication,
QNetworkAccessManager::Operation operation,
const JSONCallbackParameters& callbackParams,
const QByteArray& dataByteArray,
QHttpMultiPart* dataMultiPart);

View file

@ -9,6 +9,11 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <math.h>
#include <QtCore/QJsonDocument>
#include "Assignment.h"
#include "NodeList.h"
#include "PacketHeaders.h"
#include "UserActivityLogger.h"
@ -21,7 +26,9 @@ DomainHandler::DomainHandler(QObject* parent) :
_sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)),
_assignmentUUID(),
_isConnected(false),
_handshakeTimer(NULL)
_handshakeTimer(NULL),
_settingsObject(),
_failedSettingsRequests(0)
{
}
@ -37,8 +44,14 @@ void DomainHandler::clearConnectionInfo() {
}
}
void DomainHandler::clearSettings() {
_settingsObject = QJsonObject();
_failedSettingsRequests = 0;
}
void DomainHandler::reset() {
clearConnectionInfo();
clearSettings();
_hostname = QString();
_sockAddr.setAddress(QHostAddress::Null);
}
@ -109,10 +122,64 @@ void DomainHandler::setIsConnected(bool isConnected) {
if (_isConnected) {
emit connectedToDomain(_hostname);
// we've connected to new domain - time to ask it for global settings
requestDomainSettings();
}
}
}
void DomainHandler::requestDomainSettings() const {
if (_settingsObject.isEmpty()) {
// setup the URL required to grab settings JSON
QUrl settingsJSONURL;
settingsJSONURL.setScheme("http");
settingsJSONURL.setHost(_hostname);
settingsJSONURL.setPort(DOMAIN_SERVER_HTTP_PORT);
settingsJSONURL.setPath("/settings.json");
Assignment::Type assignmentType = Assignment::typeForNodeType(NodeList::getInstance()->getOwnerType());
settingsJSONURL.setQuery(QString("type=%1").arg(assignmentType));
qDebug() << "Requesting domain-server settings at" << settingsJSONURL.toString();
QNetworkReply* reply = NetworkAccessManager::getInstance().get(QNetworkRequest(settingsJSONURL));
connect(reply, &QNetworkReply::finished, this, &DomainHandler::settingsRequestFinished);
}
}
const int MAX_SETTINGS_REQUEST_FAILED_ATTEMPTS = 5;
void DomainHandler::settingsRequestFinished() {
QNetworkReply* settingsReply = reinterpret_cast<QNetworkReply*>(sender());
int replyCode = settingsReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (settingsReply->error() == QNetworkReply::NoError && replyCode != 301 && replyCode != 302) {
// parse the JSON to a QJsonObject and save it
_settingsObject = QJsonDocument::fromJson(settingsReply->readAll()).object();
qDebug() << "Received domain settings.";
emit settingsReceived(_settingsObject);
// reset failed settings requests to 0, we got them
_failedSettingsRequests = 0;
} else {
// error grabbing the settings - in some cases this means we are stuck
// so we should retry until we get it
qDebug() << "Error getting domain settings -" << settingsReply->errorString() << "- retrying";
if (++_failedSettingsRequests >= MAX_SETTINGS_REQUEST_FAILED_ATTEMPTS) {
qDebug() << "Failed to retreive domain-server settings" << MAX_SETTINGS_REQUEST_FAILED_ATTEMPTS
<< "times. Re-setting connection to domain.";
clearSettings();
clearConnectionInfo();
emit settingsReceiveFail();
} else {
requestDomainSettings();
}
}
}
void DomainHandler::parseDTLSRequirementPacket(const QByteArray& dtlsRequirementPacket) {
// figure out the port that the DS wants us to use for us to talk to them with DTLS
int numBytesPacketHeader = numBytesForPacketHeader(dtlsRequirementPacket);

View file

@ -12,6 +12,7 @@
#ifndef hifi_DomainHandler_h
#define hifi_DomainHandler_h
#include <QtCore/QJsonObject>
#include <QtCore/QObject>
#include <QtCore/QTimer>
#include <QtCore/QUuid>
@ -33,6 +34,7 @@ public:
DomainHandler(QObject* parent = 0);
void clearConnectionInfo();
void clearSettings();
const QUuid& getUUID() const { return _uuid; }
void setUUID(const QUuid& uuid) { _uuid = uuid; }
@ -54,14 +56,22 @@ public:
bool isConnected() const { return _isConnected; }
void setIsConnected(bool isConnected);
bool hasSettings() const { return !_settingsObject.isEmpty(); }
void requestDomainSettings() const;
const QJsonObject& getSettingsObject() const { return _settingsObject; }
void parseDTLSRequirementPacket(const QByteArray& dtlsRequirementPacket);
private slots:
void completedHostnameLookup(const QHostInfo& hostInfo);
void settingsRequestFinished();
signals:
void hostnameChanged(const QString& hostname);
void connectedToDomain(const QString& hostname);
void settingsReceived(const QJsonObject& domainSettingsObject);
void settingsReceiveFail();
private:
void reset();
@ -71,6 +81,8 @@ private:
QUuid _assignmentUUID;
bool _isConnected;
QTimer* _handshakeTimer;
QJsonObject _settingsObject;
int _failedSettingsRequests;
};
#endif // hifi_DomainHandler_h

View file

@ -70,6 +70,7 @@ enum PacketType {
PacketTypeVoxelEditNack,
PacketTypeParticleEditNack,
PacketTypeModelEditNack,
PacketTypeSignedTransactionPayment
};
typedef char PacketVersion;

View file

@ -70,7 +70,7 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall
}
void UserActivityLogger::requestFinished(const QJsonObject& object) {
qDebug() << object;
// qDebug() << object;
}
void UserActivityLogger::requestError(QNetworkReply::NetworkError error,const QString& string) {

View file

@ -0,0 +1,30 @@
//
// EditPacketBuffer.cpp
// libraries/octree/src
//
// Created by Stephen Birarda on 2014-07-30.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "EditPacketBuffer.h"
EditPacketBuffer::EditPacketBuffer() :
_nodeUUID(),
_currentType(PacketTypeUnknown),
_currentSize(0),
_satoshiCost(0)
{
}
EditPacketBuffer::EditPacketBuffer(PacketType type, unsigned char* buffer, ssize_t length, qint64 satoshiCost, QUuid nodeUUID) :
_nodeUUID(nodeUUID),
_currentType(type),
_currentSize(length),
_satoshiCost(satoshiCost)
{
memcpy(_currentBuffer, buffer, length);
};

View file

@ -0,0 +1,34 @@
//
// EditPacketBuffer.h
// libraries/octree/src
//
// Created by Stephen Birarda on 2014-07-30.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_EditPacketBuffer_h
#define hifi_EditPacketBuffer_h
#include <QtCore/QUuid>
#include <LimitedNodeList.h>
#include <PacketHeaders.h>
/// Used for construction of edit packets
class EditPacketBuffer {
public:
EditPacketBuffer();
EditPacketBuffer(PacketType type, unsigned char* codeColorBuffer, ssize_t length,
qint64 satoshiCost = 0, const QUuid nodeUUID = QUuid());
QUuid _nodeUUID;
PacketType _currentType;
unsigned char _currentBuffer[MAX_PACKET_SIZE];
ssize_t _currentSize;
qint64 _satoshiCost;
};
#endif // hifi_EditPacketBuffer_h

View file

@ -17,14 +17,6 @@
#include <PacketHeaders.h>
#include "OctreeEditPacketSender.h"
EditPacketBuffer::EditPacketBuffer(PacketType type, unsigned char* buffer, ssize_t length, QUuid nodeUUID) :
_nodeUUID(nodeUUID),
_currentType(type),
_currentSize(length)
{
memcpy(_currentBuffer, buffer, length);
};
const int OctreeEditPacketSender::DEFAULT_MAX_PENDING_MESSAGES = PacketSender::DEFAULT_PACKETS_PER_SECOND;
@ -34,7 +26,10 @@ OctreeEditPacketSender::OctreeEditPacketSender() :
_maxPendingMessages(DEFAULT_MAX_PENDING_MESSAGES),
_releaseQueuedMessagesPending(false),
_serverJurisdictions(NULL),
_maxPacketSize(MAX_PACKET_SIZE) {
_maxPacketSize(MAX_PACKET_SIZE),
_destinationWalletUUID()
{
}
OctreeEditPacketSender::~OctreeEditPacketSender() {
@ -87,7 +82,8 @@ bool OctreeEditPacketSender::serversExist() const {
// This method is called when the edit packet layer has determined that it has a fully formed packet destined for
// a known nodeID.
void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, unsigned char* buffer, ssize_t length) {
void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, unsigned char* buffer,
ssize_t length, qint64 satoshiCost) {
NodeList* nodeList = NodeList::getInstance();
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
@ -101,10 +97,16 @@ void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, unsigned c
unsigned char* sequenceAt = buffer + numBytesPacketHeader;
quint16 sequence = _outgoingSequenceNumbers[nodeUUID]++;
memcpy(sequenceAt, &sequence, sizeof(quint16));
// send packet
QByteArray packet(reinterpret_cast<const char*>(buffer), length);
queuePacketForSending(node, packet);
if (hasDestinationWalletUUID() && satoshiCost > 0) {
// if we have a destination wallet UUID and a cost associated with this packet, signal that it
// needs to be sent
emit octreePaymentRequired(satoshiCost, nodeUUID, _destinationWalletUUID);
}
// add packet to history
_sentPacketHistories[nodeUUID].packetSent(sequence, packet);
@ -120,6 +122,7 @@ void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, unsigned c
qDebug() << "OctreeEditPacketSender::queuePacketToNode() queued " << buffer[0] <<
" - command to node bytes=" << length <<
" satoshiCost=" << satoshiCost <<
" sequence=" << sequence <<
" transitTimeSoFar=" << transitTime << " usecs";
}
@ -135,7 +138,7 @@ void OctreeEditPacketSender::processPreServerExistsPackets() {
_pendingPacketsLock.lock();
while (!_preServerSingleMessagePackets.empty()) {
EditPacketBuffer* packet = _preServerSingleMessagePackets.front();
queuePacketToNodes(&packet->_currentBuffer[0], packet->_currentSize);
queuePacketToNodes(&packet->_currentBuffer[0], packet->_currentSize, packet->_satoshiCost);
delete packet;
_preServerSingleMessagePackets.erase(_preServerSingleMessagePackets.begin());
}
@ -157,11 +160,12 @@ void OctreeEditPacketSender::processPreServerExistsPackets() {
}
}
void OctreeEditPacketSender::queuePendingPacketToNodes(PacketType type, unsigned char* buffer, ssize_t length) {
void OctreeEditPacketSender::queuePendingPacketToNodes(PacketType type, unsigned char* buffer,
ssize_t length, qint64 satoshiCost) {
// If we're asked to save messages while waiting for voxel servers to arrive, then do so...
if (_maxPendingMessages > 0) {
EditPacketBuffer* packet = new EditPacketBuffer(type, buffer, length);
EditPacketBuffer* packet = new EditPacketBuffer(type, buffer, length, satoshiCost);
_pendingPacketsLock.lock();
_preServerSingleMessagePackets.push_back(packet);
// if we've saved MORE than our max, then clear out the oldest packet...
@ -175,7 +179,7 @@ void OctreeEditPacketSender::queuePendingPacketToNodes(PacketType type, unsigned
}
}
void OctreeEditPacketSender::queuePacketToNodes(unsigned char* buffer, ssize_t length) {
void OctreeEditPacketSender::queuePacketToNodes(unsigned char* buffer, ssize_t length, qint64 satoshiCost) {
if (!_shouldSend) {
return; // bail early
}
@ -202,7 +206,7 @@ void OctreeEditPacketSender::queuePacketToNodes(unsigned char* buffer, ssize_t l
isMyJurisdiction = (map.isMyJurisdiction(octCode, CHECK_NODE_ONLY) == JurisdictionMap::WITHIN);
_serverJurisdictions->unlock();
if (isMyJurisdiction) {
queuePacketToNode(nodeUUID, buffer, length);
queuePacketToNode(nodeUUID, buffer, length, satoshiCost);
}
}
}
@ -210,7 +214,8 @@ void OctreeEditPacketSender::queuePacketToNodes(unsigned char* buffer, ssize_t l
// NOTE: codeColorBuffer - is JUST the octcode/color and does not contain the packet header!
void OctreeEditPacketSender::queueOctreeEditMessage(PacketType type, unsigned char* codeColorBuffer, ssize_t length) {
void OctreeEditPacketSender::queueOctreeEditMessage(PacketType type, unsigned char* codeColorBuffer,
ssize_t length, qint64 satoshiCost) {
if (!_shouldSend) {
return; // bail early
@ -285,6 +290,7 @@ void OctreeEditPacketSender::queueOctreeEditMessage(PacketType type, unsigned ch
memcpy(&packetBuffer._currentBuffer[packetBuffer._currentSize], codeColorBuffer, length);
packetBuffer._currentSize += length;
packetBuffer._satoshiCost += satoshiCost;
}
}
}
@ -306,7 +312,8 @@ void OctreeEditPacketSender::releaseQueuedMessages() {
void OctreeEditPacketSender::releaseQueuedPacket(EditPacketBuffer& packetBuffer) {
_releaseQueuedPacketMutex.lock();
if (packetBuffer._currentSize > 0 && packetBuffer._currentType != PacketTypeUnknown) {
queuePacketToNode(packetBuffer._nodeUUID, &packetBuffer._currentBuffer[0], packetBuffer._currentSize);
queuePacketToNode(packetBuffer._nodeUUID, &packetBuffer._currentBuffer[0],
packetBuffer._currentSize, packetBuffer._satoshiCost);
packetBuffer._currentSize = 0;
packetBuffer._currentType = PacketTypeUnknown;
}
@ -326,6 +333,9 @@ void OctreeEditPacketSender::initializePacket(EditPacketBuffer& packetBuffer, Pa
packetBuffer._currentSize += sizeof(quint64); // nudge past timestamp
packetBuffer._currentType = type;
// reset cost for packet to 0
packetBuffer._satoshiCost = 0;
}
bool OctreeEditPacketSender::process() {

View file

@ -15,20 +15,11 @@
#include <qqueue.h>
#include <PacketSender.h>
#include <PacketHeaders.h>
#include "EditPacketBuffer.h"
#include "JurisdictionMap.h"
#include "SentPacketHistory.h"
/// Used for construction of edit packets
class EditPacketBuffer {
public:
EditPacketBuffer() : _nodeUUID(), _currentType(PacketTypeUnknown), _currentSize(0) { }
EditPacketBuffer(PacketType type, unsigned char* codeColorBuffer, ssize_t length, const QUuid nodeUUID = QUuid());
QUuid _nodeUUID;
PacketType _currentType;
unsigned char _currentBuffer[MAX_PACKET_SIZE];
ssize_t _currentSize;
};
/// Utility for processing, packing, queueing and sending of outbound edit messages.
class OctreeEditPacketSender : public PacketSender {
Q_OBJECT
@ -39,7 +30,7 @@ public:
/// Queues a single edit message. Will potentially send a pending multi-command packet. Determines which server
/// node or nodes the packet should be sent to. Can be called even before servers are known, in which case up to
/// MaxPendingMessages will be buffered and processed when servers are known.
void queueOctreeEditMessage(PacketType type, unsigned char* buffer, ssize_t length);
void queueOctreeEditMessage(PacketType type, unsigned char* buffer, ssize_t length, qint64 satoshiCost = 0);
/// Releases all queued messages even if those messages haven't filled an MTU packet. This will move the packed message
/// packets onto the send queue. If running in threaded mode, the caller does not need to do any further processing to
@ -91,18 +82,24 @@ public:
// you must override these...
virtual char getMyNodeType() const = 0;
virtual void adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssize_t length, int clockSkew) { };
bool hasDestinationWalletUUID() const { return !_destinationWalletUUID.isNull(); }
void setDestinationWalletUUID(const QUuid& destinationWalletUUID) { _destinationWalletUUID = destinationWalletUUID; }
const QUuid& getDestinationWalletUUID() { return _destinationWalletUUID; }
void processNackPacket(const QByteArray& packet);
public slots:
void nodeKilled(SharedNodePointer node);
public:
void processNackPacket(const QByteArray& packet);
signals:
void octreePaymentRequired(qint64 satoshiAmount, const QUuid& nodeUUID, const QUuid& destinationWalletUUID);
protected:
bool _shouldSend;
void queuePacketToNode(const QUuid& nodeID, unsigned char* buffer, ssize_t length);
void queuePendingPacketToNodes(PacketType type, unsigned char* buffer, ssize_t length);
void queuePacketToNodes(unsigned char* buffer, ssize_t length);
void queuePacketToNode(const QUuid& nodeID, unsigned char* buffer, ssize_t length, qint64 satoshiCost = 0);
void queuePendingPacketToNodes(PacketType type, unsigned char* buffer, ssize_t length, qint64 satoshiCost = 0);
void queuePacketToNodes(unsigned char* buffer, ssize_t length, qint64 satoshiCost = 0);
void initializePacket(EditPacketBuffer& packetBuffer, PacketType type);
void releaseQueuedPacket(EditPacketBuffer& packetBuffer); // releases specific queued packet
@ -127,5 +124,7 @@ protected:
// TODO: add locks for this and _pendingEditPackets
QHash<QUuid, SentPacketHistory> _sentPacketHistories;
QHash<QUuid, quint16> _outgoingSequenceNumbers;
QUuid _destinationWalletUUID;
};
#endif // hifi_OctreeEditPacketSender_h

View file

@ -117,9 +117,9 @@ void VoxelEditPacketSender::sendVoxelEditMessage(PacketType type, const VoxelDet
// If we don't have voxel jurisdictions, then we will simply queue up these packets and wait till we have
// jurisdictions for processing
if (!voxelServersExist()) {
queuePendingPacketToNodes(type, bufferOut, sizeOut);
queuePendingPacketToNodes(type, bufferOut, sizeOut, satoshiCostForMessage(detail));
} else {
queuePacketToNodes(bufferOut, sizeOut);
queuePacketToNodes(bufferOut, sizeOut, satoshiCostForMessage(detail));
}
// either way, clean up the created buffer
@ -138,7 +138,20 @@ void VoxelEditPacketSender::queueVoxelEditMessages(PacketType type, int numberOf
int sizeOut = 0;
if (encodeVoxelEditMessageDetails(type, 1, &details[i], &bufferOut[0], _maxPacketSize, sizeOut)) {
queueOctreeEditMessage(type, bufferOut, sizeOut);
queueOctreeEditMessage(type, bufferOut, sizeOut, satoshiCostForMessage(details[i]));
}
}
}
qint64 VoxelEditPacketSender::satoshiCostForMessage(const VoxelDetail& details) {
const DomainHandler& domainHandler = NodeList::getInstance()->getDomainHandler();
if (_satoshisPerVoxel == 0 && _satoshisPerMeterCubed == 0) {
return 0;
} else {
float meterScale = details.s * TREE_SCALE;
float totalVolume = meterScale * meterScale * meterScale;
return _satoshisPerVoxel + (qint64) floorf(totalVolume * _satoshisPerMeterCubed);
}
}

View file

@ -15,6 +15,7 @@
#define hifi_VoxelEditPacketSender_h
#include <OctreeEditPacketSender.h>
#include "VoxelDetail.h"
/// Utility for processing, packing, queueing and sending of outbound edit voxel messages.
@ -49,5 +50,14 @@ public:
// My server type is the voxel server
virtual char getMyNodeType() const { return NodeType::VoxelServer; }
void setSatoshisPerVoxel(qint64 satoshisPerVoxel) { _satoshisPerVoxel = satoshisPerVoxel; }
void setSatoshisPerMeterCubed(qint64 satoshisPerMeterCubed) { _satoshisPerMeterCubed = satoshisPerMeterCubed; }
qint64 satoshiCostForMessage(const VoxelDetail& details);
private:
qint64 _satoshisPerVoxel;
qint64 _satoshisPerMeterCubed;
};
#endif // hifi_VoxelEditPacketSender_h

View file

@ -71,7 +71,6 @@ void VoxelsScriptingInterface::setVoxel(float x, float y, float z, float scale,
VoxelDetail addVoxelDetail = {x / (float)TREE_SCALE, y / (float)TREE_SCALE, z / (float)TREE_SCALE,
scale / (float)TREE_SCALE, red, green, blue};
// handle the local tree also...
if (_tree) {
if (_undoStack) {