mirror of
https://github.com/overte-org/overte.git
synced 2025-08-09 10:29:01 +02:00
Merge remote-tracking branch 'upstream/master' into HEAD
This commit is contained in:
commit
544c54b8ee
48 changed files with 944 additions and 569 deletions
|
@ -50,9 +50,9 @@ static const int INTERFACE_RUNNING_CHECK_FREQUENCY_MS = 1000;
|
||||||
|
|
||||||
const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server";
|
const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server";
|
||||||
|
|
||||||
static const QStringList BAKEABLE_MODEL_EXTENSIONS = {"fbx"};
|
static const QStringList BAKEABLE_MODEL_EXTENSIONS = { "fbx" };
|
||||||
static QStringList BAKEABLE_TEXTURE_EXTENSIONS;
|
static QStringList BAKEABLE_TEXTURE_EXTENSIONS;
|
||||||
static const QStringList BAKEABLE_SCRIPT_EXTENSIONS = {"js"};
|
static const QStringList BAKEABLE_SCRIPT_EXTENSIONS = {};
|
||||||
static const QString BAKED_MODEL_SIMPLE_NAME = "asset.fbx";
|
static const QString BAKED_MODEL_SIMPLE_NAME = "asset.fbx";
|
||||||
static const QString BAKED_TEXTURE_SIMPLE_NAME = "texture.ktx";
|
static const QString BAKED_TEXTURE_SIMPLE_NAME = "texture.ktx";
|
||||||
static const QString BAKED_SCRIPT_SIMPLE_NAME = "asset.js";
|
static const QString BAKED_SCRIPT_SIMPLE_NAME = "asset.js";
|
||||||
|
|
|
@ -41,8 +41,15 @@ EntityServer::EntityServer(ReceivedMessage& message) :
|
||||||
DependencyManager::set<ScriptCache>();
|
DependencyManager::set<ScriptCache>();
|
||||||
|
|
||||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||||
packetReceiver.registerListenerForTypes({ PacketType::EntityAdd, PacketType::EntityEdit, PacketType::EntityErase, PacketType::EntityPhysics, PacketType::ChallengeOwnership },
|
packetReceiver.registerListenerForTypes({ PacketType::EntityAdd,
|
||||||
this, "handleEntityPacket");
|
PacketType::EntityEdit,
|
||||||
|
PacketType::EntityErase,
|
||||||
|
PacketType::EntityPhysics,
|
||||||
|
PacketType::ChallengeOwnership,
|
||||||
|
PacketType::ChallengeOwnershipRequest,
|
||||||
|
PacketType::ChallengeOwnershipReply },
|
||||||
|
this,
|
||||||
|
"handleEntityPacket");
|
||||||
|
|
||||||
connect(&_dynamicDomainVerificationTimer, &QTimer::timeout, this, &EntityServer::startDynamicDomainVerification);
|
connect(&_dynamicDomainVerificationTimer, &QTimer::timeout, this, &EntityServer::startDynamicDomainVerification);
|
||||||
_dynamicDomainVerificationTimer.setSingleShot(true);
|
_dynamicDomainVerificationTimer.setSingleShot(true);
|
||||||
|
@ -459,7 +466,7 @@ void EntityServer::startDynamicDomainVerification() {
|
||||||
EntityItemPointer entity = tree->findEntityByEntityItemID(i.value());
|
EntityItemPointer entity = tree->findEntityByEntityItemID(i.value());
|
||||||
|
|
||||||
if (entity) {
|
if (entity) {
|
||||||
if (!entity->verifyStaticCertificateProperties()) {
|
if (!entity->getProperties().verifyStaticCertificateProperties()) {
|
||||||
qCDebug(entities) << "During Dynamic Domain Verification, a certified entity with ID" << i.value() << "failed"
|
qCDebug(entities) << "During Dynamic Domain Verification, a certified entity with ID" << i.value() << "failed"
|
||||||
<< "static certificate verification.";
|
<< "static certificate verification.";
|
||||||
// Delete the entity if it doesn't pass static certificate verification
|
// Delete the entity if it doesn't pass static certificate verification
|
||||||
|
|
|
@ -96,6 +96,14 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer<ReceivedMessage>
|
||||||
_myServer->getOctree()->withWriteLock([&] {
|
_myServer->getOctree()->withWriteLock([&] {
|
||||||
_myServer->getOctree()->processChallengeOwnershipPacket(*message, sendingNode);
|
_myServer->getOctree()->processChallengeOwnershipPacket(*message, sendingNode);
|
||||||
});
|
});
|
||||||
|
} else if (packetType == PacketType::ChallengeOwnershipRequest) {
|
||||||
|
_myServer->getOctree()->withWriteLock([&] {
|
||||||
|
_myServer->getOctree()->processChallengeOwnershipRequestPacket(*message, sendingNode);
|
||||||
|
});
|
||||||
|
} else if (packetType == PacketType::ChallengeOwnershipReply) {
|
||||||
|
_myServer->getOctree()->withWriteLock([&] {
|
||||||
|
_myServer->getOctree()->processChallengeOwnershipReplyPacket(*message, sendingNode);
|
||||||
|
});
|
||||||
} else if (_myServer->getOctree()->handlesEditPacketType(packetType)) {
|
} else if (_myServer->getOctree()->handlesEditPacketType(packetType)) {
|
||||||
PerformanceWarning warn(debugProcessPacket, "processPacket KNOWN TYPE", debugProcessPacket);
|
PerformanceWarning warn(debugProcessPacket, "processPacket KNOWN TYPE", debugProcessPacket);
|
||||||
_receivedPacketCount++;
|
_receivedPacketCount++;
|
||||||
|
|
4
cmake/externals/wasapi/CMakeLists.txt
vendored
4
cmake/externals/wasapi/CMakeLists.txt
vendored
|
@ -6,8 +6,8 @@ if (WIN32)
|
||||||
include(ExternalProject)
|
include(ExternalProject)
|
||||||
ExternalProject_Add(
|
ExternalProject_Add(
|
||||||
${EXTERNAL_NAME}
|
${EXTERNAL_NAME}
|
||||||
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi9.zip
|
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi10.zip
|
||||||
URL_MD5 94f4765bdbcd53cd099f349ae031e769
|
URL_MD5 4f40e49715a420fb67b45b9cee19052c
|
||||||
CONFIGURE_COMMAND ""
|
CONFIGURE_COMMAND ""
|
||||||
BUILD_COMMAND ""
|
BUILD_COMMAND ""
|
||||||
INSTALL_COMMAND ""
|
INSTALL_COMMAND ""
|
||||||
|
|
|
@ -60,7 +60,7 @@ if (WIN32 AND NOT CYGWIN)
|
||||||
select_library_configurations(LIB_EAY)
|
select_library_configurations(LIB_EAY)
|
||||||
select_library_configurations(SSL_EAY)
|
select_library_configurations(SSL_EAY)
|
||||||
set(OPENSSL_LIBRARIES ${SSL_EAY_LIBRARY} ${LIB_EAY_LIBRARY})
|
set(OPENSSL_LIBRARIES ${SSL_EAY_LIBRARY} ${LIB_EAY_LIBRARY})
|
||||||
find_path(OPENSSL_DLL_PATH NAMES ssleay32.dll PATH_SUFFIXES "bin" ${_OPENSSL_ROOT_HINTS_AND_PATHS})
|
find_path(OPENSSL_DLL_PATH NAMES ssleay32.dll PATH_SUFFIXES "bin" HINTS ${_OPENSSL_ROOT_HINTS_AND_PATHS} NO_DEFAULT_PATH)
|
||||||
endif()
|
endif()
|
||||||
else()
|
else()
|
||||||
|
|
||||||
|
|
|
@ -146,187 +146,256 @@ function sendUpdatePlaceRequest(id, path, domainID, clearDomainID, onSuccess, on
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var pendingDomainRequest = null;
|
||||||
|
function getDomainFromAPI(callback) {
|
||||||
|
if (pendingDomainRequest !== null) {
|
||||||
|
pendingDomainRequest.success(callback);
|
||||||
|
pendingDomainRequest.error(function() { callback({ status: 'fail' }) });
|
||||||
|
return pendingDomainRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callback === undefined) {
|
||||||
|
callback = function() {};
|
||||||
|
}
|
||||||
|
|
||||||
|
var domainID = Settings.data.values.metaverse.id;
|
||||||
|
if (domainID === null || domainID === undefined || domainID === '') {
|
||||||
|
callback({ status: 'fail' });
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingDomainRequest = $.ajax({
|
||||||
|
url: "/api/domains/" + domainID,
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(data) {
|
||||||
|
pendingDomainRequest = null;
|
||||||
|
|
||||||
|
if (data.status === 'success') {
|
||||||
|
DomainInfo = data.domain;
|
||||||
|
} else {
|
||||||
|
DomainInfo = null;
|
||||||
|
}
|
||||||
|
callback(data);
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
pendingDomainRequest = null;
|
||||||
|
|
||||||
|
DomainInfo = null;
|
||||||
|
callback({ status: 'fail' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return pendingDomainRequest;
|
||||||
|
}
|
||||||
|
|
||||||
function chooseFromHighFidelityPlaces(accessToken, forcePathTo, onSuccessfullyAdded) {
|
function chooseFromHighFidelityPlaces(accessToken, forcePathTo, onSuccessfullyAdded) {
|
||||||
if (accessToken) {
|
if (accessToken) {
|
||||||
|
|
||||||
var loadingDialog = showLoadingDialog(Strings.ADD_PLACE_LOADING_DIALOG);
|
var loadingDialog = showLoadingDialog(Strings.ADD_PLACE_LOADING_DIALOG);
|
||||||
|
|
||||||
$.ajax("/api/places", {
|
function loadPlaces() {
|
||||||
dataType: 'json',
|
$.ajax("/api/places", {
|
||||||
jsonp: false,
|
dataType: 'json',
|
||||||
success: function(data) {
|
jsonp: false,
|
||||||
if (data.status == 'success') {
|
success: function(data) {
|
||||||
var modal_buttons = {
|
if (data.status == 'success') {
|
||||||
cancel: {
|
var modal_buttons = {
|
||||||
label: Strings.ADD_PLACE_CANCEL_BUTTON,
|
cancel: {
|
||||||
className: 'add-place-cancel-button btn-default'
|
label: Strings.ADD_PLACE_CANCEL_BUTTON,
|
||||||
}
|
className: 'add-place-cancel-button btn-default'
|
||||||
};
|
|
||||||
|
|
||||||
var dialog;
|
|
||||||
var modal_body;
|
|
||||||
|
|
||||||
if (data.data.places.length) {
|
|
||||||
var places_by_id = {};
|
|
||||||
|
|
||||||
modal_body = $('<div>');
|
|
||||||
|
|
||||||
modal_body.append($("<p>Choose a place name that you own or <a href='" + URLs.METAVERSE_URL + "/user/places' target='_blank'>register a new place name</a></p>"));
|
|
||||||
|
|
||||||
var currentDomainIDType = getCurrentDomainIDType();
|
|
||||||
if (currentDomainIDType === DOMAIN_ID_TYPE_TEMP) {
|
|
||||||
var warning = "<div class='domain-loading-error alert alert-warning'>";
|
|
||||||
warning += "If you choose a place name it will replace your current temporary place name.";
|
|
||||||
warning += "</div>";
|
|
||||||
modal_body.append(warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
// setup a select box for the returned places
|
|
||||||
modal_body.append($("<label for='place-name-select'>Places</label>"));
|
|
||||||
place_select = $("<select id='place-name-select' class='form-control'></select>");
|
|
||||||
_.each(data.data.places, function(place) {
|
|
||||||
places_by_id[place.id] = place;
|
|
||||||
place_select.append("<option value='" + place.id + "'>" + place.name + "</option>");
|
|
||||||
})
|
|
||||||
modal_body.append(place_select);
|
|
||||||
modal_body.append($("<p id='place-name-warning' class='warning-text' style='display: none'>This place name already points to a place or path. Saving this would overwrite the previous settings associated with it.</p>"));
|
|
||||||
|
|
||||||
if (forcePathTo === undefined || forcePathTo === null) {
|
|
||||||
var path = "<div class='form-group'>";
|
|
||||||
path += "<label for='place-path-input' class='control-label'>Path</label>";
|
|
||||||
path += "<input type='text' id='place-path-input' class='form-control' value='/'>";
|
|
||||||
path += "</div>";
|
|
||||||
modal_body.append($(path));
|
|
||||||
}
|
|
||||||
|
|
||||||
var place_select = modal_body.find("#place-name-select")
|
|
||||||
place_select.change(function(ev) {
|
|
||||||
var warning = modal_body.find("#place-name-warning");
|
|
||||||
var place = places_by_id[$(this).val()];
|
|
||||||
if (place === undefined || place.pointee === null) {
|
|
||||||
warning.hide();
|
|
||||||
} else {
|
|
||||||
warning.show();
|
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
place_select.trigger('change');
|
|
||||||
|
|
||||||
modal_buttons["success"] = {
|
var dialog;
|
||||||
label: Strings.ADD_PLACE_CONFIRM_BUTTON,
|
var modal_body;
|
||||||
className: 'add-place-confirm-button btn btn-primary',
|
|
||||||
callback: function() {
|
|
||||||
var placeID = $('#place-name-select').val();
|
|
||||||
// set the place ID on the form
|
|
||||||
$(Settings.place_ID_SELECTOR).val(placeID).change();
|
|
||||||
|
|
||||||
if (forcePathTo === undefined || forcePathTo === null) {
|
if (data.data.places.length) {
|
||||||
var placePath = $('#place-path-input').val();
|
var places_by_id = {};
|
||||||
|
|
||||||
|
modal_body = $('<div>');
|
||||||
|
|
||||||
|
modal_body.append($("<p>Choose a place name that you own or <a href='" + URLs.METAVERSE_URL + "/user/places' target='_blank'>register a new place name</a></p>"));
|
||||||
|
|
||||||
|
var currentDomainIDType = getCurrentDomainIDType();
|
||||||
|
if (currentDomainIDType === DOMAIN_ID_TYPE_TEMP) {
|
||||||
|
var warning = "<div class='domain-loading-error alert alert-warning'>";
|
||||||
|
warning += "If you choose a place name it will replace your current temporary place name.";
|
||||||
|
warning += "</div>";
|
||||||
|
modal_body.append(warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup a select box for the returned places
|
||||||
|
modal_body.append($("<label for='place-name-select'>Places</label>"));
|
||||||
|
place_select = $("<select id='place-name-select' class='form-control'></select>");
|
||||||
|
_.each(data.data.places, function(place) {
|
||||||
|
places_by_id[place.id] = place;
|
||||||
|
place_select.append("<option value='" + place.id + "'>" + place.name + "</option>");
|
||||||
|
})
|
||||||
|
modal_body.append(place_select);
|
||||||
|
modal_body.append($("<p id='place-name-warning' class='warning-text' style='display: none'>This place name already points to a place or path. Saving this would overwrite the previous settings associated with it.</p>"));
|
||||||
|
|
||||||
|
if (forcePathTo === undefined || forcePathTo === null) {
|
||||||
|
var path = "<div class='form-group'>";
|
||||||
|
path += "<label for='place-path-input' class='control-label'>Path</label>";
|
||||||
|
path += "<input type='text' id='place-path-input' class='form-control' value='/'>";
|
||||||
|
path += "</div>";
|
||||||
|
modal_body.append($(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
var place_select = modal_body.find("#place-name-select")
|
||||||
|
place_select.change(function(ev) {
|
||||||
|
var warning = modal_body.find("#place-name-warning");
|
||||||
|
var place = places_by_id[$(this).val()];
|
||||||
|
if (place === undefined || place.pointee === null) {
|
||||||
|
warning.hide();
|
||||||
} else {
|
} else {
|
||||||
var placePath = forcePathTo;
|
warning.show();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
place_select.trigger('change');
|
||||||
|
|
||||||
$('.add-place-confirm-button').attr('disabled', 'disabled');
|
modal_buttons["success"] = {
|
||||||
$('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON_PENDING);
|
label: Strings.ADD_PLACE_CONFIRM_BUTTON,
|
||||||
$('.add-place-cancel-button').attr('disabled', 'disabled');
|
className: 'add-place-confirm-button btn btn-primary',
|
||||||
|
callback: function() {
|
||||||
|
var placeID = $('#place-name-select').val();
|
||||||
|
// set the place ID on the form
|
||||||
|
$(Settings.place_ID_SELECTOR).val(placeID).change();
|
||||||
|
|
||||||
function finalizeSaveDomainID(domainID) {
|
if (forcePathTo === undefined || forcePathTo === null) {
|
||||||
var jsonSettings = {
|
var placePath = $('#place-path-input').val();
|
||||||
metaverse: {
|
} else {
|
||||||
id: domainID
|
var placePath = forcePathTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.add-place-confirm-button').attr('disabled', 'disabled');
|
||||||
|
$('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON_PENDING);
|
||||||
|
$('.add-place-cancel-button').attr('disabled', 'disabled');
|
||||||
|
|
||||||
|
function finalizeSaveDomainID(domainID) {
|
||||||
|
var jsonSettings = {
|
||||||
|
metaverse: {
|
||||||
|
id: domainID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var dialog = showLoadingDialog("Waiting for Domain Server to restart...");
|
||||||
|
$.ajax('/settings.json', {
|
||||||
|
data: JSON.stringify(jsonSettings),
|
||||||
|
contentType: 'application/json',
|
||||||
|
type: 'POST'
|
||||||
|
}).done(function(data) {
|
||||||
|
if (data.status == "success") {
|
||||||
|
waitForDomainServerRestart(function() {
|
||||||
|
dialog.modal('hide');
|
||||||
|
if (onSuccessfullyAdded) {
|
||||||
|
onSuccessfullyAdded(places_by_id[placeID].name, domainID);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
bootbox.alert("Failed to add place");
|
||||||
|
}
|
||||||
|
}).fail(function() {
|
||||||
|
bootbox.alert("Failed to add place");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// If domainID is not specified, the current domain id will be used.
|
||||||
|
function finishSettingUpPlace(domainID) {
|
||||||
|
sendUpdatePlaceRequest(
|
||||||
|
placeID,
|
||||||
|
placePath,
|
||||||
|
domainID,
|
||||||
|
false,
|
||||||
|
function(data) {
|
||||||
|
dialog.modal('hide')
|
||||||
|
if (domainID) {
|
||||||
|
$(Settings.DOMAIN_ID_SELECTOR).val(domainID).change();
|
||||||
|
finalizeSaveDomainID(domainID);
|
||||||
|
} else {
|
||||||
|
if (onSuccessfullyAdded) {
|
||||||
|
onSuccessfullyAdded(places_by_id[placeID].name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function(data) {
|
||||||
|
$('.add-place-confirm-button').removeAttr('disabled');
|
||||||
|
$('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON);
|
||||||
|
$('.add-place-cancel-button').removeAttr('disabled');
|
||||||
|
bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function maybeCreateNewDomainID() {
|
||||||
|
console.log("Maybe creating domain id", currentDomainIDType)
|
||||||
|
if (currentDomainIDType === DOMAIN_ID_TYPE_FULL) {
|
||||||
|
finishSettingUpPlace();
|
||||||
|
} else {
|
||||||
|
sendCreateDomainRequest(function(domainID) {
|
||||||
|
console.log("Created domain", domainID);
|
||||||
|
finishSettingUpPlace(domainID);
|
||||||
|
}, function() {
|
||||||
|
$('.add-place-confirm-button').removeAttr('disabled');
|
||||||
|
$('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON);
|
||||||
|
$('.add-place-cancel-button').removeAttr('disabled');
|
||||||
|
bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR);
|
||||||
|
bootbox.alert("FAIL");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var dialog = showLoadingDialog("Waiting for Domain Server to restart...");
|
|
||||||
$.ajax('/settings.json', {
|
|
||||||
data: JSON.stringify(jsonSettings),
|
|
||||||
contentType: 'application/json',
|
|
||||||
type: 'POST'
|
|
||||||
}).done(function(data) {
|
|
||||||
if (data.status == "success") {
|
|
||||||
waitForDomainServerRestart(function() {
|
|
||||||
dialog.modal('hide');
|
|
||||||
if (onSuccessfullyAdded) {
|
|
||||||
onSuccessfullyAdded(places_by_id[placeID].name, domainID);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
bootbox.alert("Failed to add place");
|
|
||||||
}
|
|
||||||
}).fail(function() {
|
|
||||||
bootbox.alert("Failed to add place");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function finishSettingUpPlace(domainID) {
|
maybeCreateNewDomainID();
|
||||||
sendUpdatePlaceRequest(
|
|
||||||
placeID,
|
|
||||||
placePath,
|
|
||||||
domainID,
|
|
||||||
false,
|
|
||||||
function(data) {
|
|
||||||
$(Settings.DOMAIN_ID_SELECTOR).val(domainID).change();
|
|
||||||
dialog.modal('hide')
|
|
||||||
if (domainID) {
|
|
||||||
finalizeSaveDomainID(domainID);
|
|
||||||
} else {
|
|
||||||
if (onSuccessfullyAdded) {
|
|
||||||
onSuccessfullyAdded(places_by_id[placeID].name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
function(data) {
|
|
||||||
$('.add-place-confirm-button').removeAttr('disabled');
|
|
||||||
$('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON);
|
|
||||||
$('.add-place-cancel-button').removeAttr('disabled');
|
|
||||||
bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentDomainIDType === DOMAIN_ID_TYPE_FULL) {
|
return false;
|
||||||
finishSettingUpPlace();
|
|
||||||
} else {
|
|
||||||
sendCreateDomainRequest(function(domainID) {
|
|
||||||
console.log("Created domain", domainID);
|
|
||||||
finishSettingUpPlace(domainID);
|
|
||||||
}, function() {
|
|
||||||
$('.add-place-confirm-button').removeAttr('disabled');
|
|
||||||
$('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON);
|
|
||||||
$('.add-place-cancel-button').removeAttr('disabled');
|
|
||||||
bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR);
|
|
||||||
bootbox.alert("FAIL");
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
modal_buttons["success"] = {
|
||||||
|
label: Strings.ADD_PLACE_NO_PLACES_BUTTON,
|
||||||
|
callback: function() {
|
||||||
|
window.open(URLs.METAVERSE_URL + "/user/places", '_blank');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
modal_body = Strings.ADD_PLACE_NO_PLACES_MESSAGE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dialog = bootbox.dialog({
|
||||||
|
title: Strings.ADD_PLACE_TITLE,
|
||||||
|
message: modal_body,
|
||||||
|
closeButton: false,
|
||||||
|
buttons: modal_buttons
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
modal_buttons["success"] = {
|
bootbox.alert(Strings.ADD_PLACE_UNABLE_TO_LOAD_ERROR);
|
||||||
label: Strings.ADD_PLACE_NO_PLACES_BUTTON,
|
|
||||||
callback: function() {
|
|
||||||
window.open(URLs.METAVERSE_URL + "/user/places", '_blank');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
modal_body = Strings.ADD_PLACE_NO_PLACES_MESSAGE;
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
dialog = bootbox.dialog({
|
error: function() {
|
||||||
title: Strings.ADD_PLACE_TITLE,
|
|
||||||
message: modal_body,
|
|
||||||
closeButton: false,
|
|
||||||
buttons: modal_buttons
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
bootbox.alert(Strings.ADD_PLACE_UNABLE_TO_LOAD_ERROR);
|
bootbox.alert(Strings.ADD_PLACE_UNABLE_TO_LOAD_ERROR);
|
||||||
|
},
|
||||||
|
complete: function() {
|
||||||
|
loadingDialog.modal('hide');
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
error: function() {
|
}
|
||||||
bootbox.alert(Strings.ADD_PLACE_UNABLE_TO_LOAD_ERROR);
|
|
||||||
},
|
var domainType = getCurrentDomainIDType();
|
||||||
complete: function() {
|
if (domainType !== DOMAIN_ID_TYPE_UNKNOWN) {
|
||||||
loadingDialog.modal('hide');
|
loadPlaces();
|
||||||
}
|
} else {
|
||||||
});
|
getDomainFromAPI(function(data) {
|
||||||
|
if (data.status === 'success') {
|
||||||
|
var domainType = getCurrentDomainIDType();
|
||||||
|
loadPlaces();
|
||||||
|
} else {
|
||||||
|
loadingDialog.modal('hide');
|
||||||
|
bootbox.confirm("We were not able to load your domain information from the Metaverse. Would you like to retry?", function(response) {
|
||||||
|
if (response) {
|
||||||
|
chooseFromHighFidelityPlaces(accessToken, forcePathTo, onSuccessfullyAdded);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
bootbox.alert({
|
bootbox.alert({
|
||||||
|
|
|
@ -980,20 +980,6 @@ function placeTableRowForPlaceObject(place) {
|
||||||
return placeTableRow(place.name, placePathOrIndex, false, place.id);
|
return placeTableRow(place.name, placePathOrIndex, false, place.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDomainFromAPI(callback) {
|
|
||||||
var domainID = Settings.data.values.metaverse.id;
|
|
||||||
$.ajax({
|
|
||||||
url: "/api/domains/" + domainID,
|
|
||||||
dataType: 'json',
|
|
||||||
success: function(data) {
|
|
||||||
callback(data);
|
|
||||||
},
|
|
||||||
error: function() {
|
|
||||||
callback({ status: 'fail' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function reloadDomainInfo() {
|
function reloadDomainInfo() {
|
||||||
$('#' + Settings.PLACES_TABLE_ID + " tbody tr").not('.headers').remove();
|
$('#' + Settings.PLACES_TABLE_ID + " tbody tr").not('.headers').remove();
|
||||||
|
|
||||||
|
@ -1010,7 +996,6 @@ function reloadDomainInfo() {
|
||||||
// check if we have owner_places (for a real domain) or a name (for a temporary domain)
|
// check if we have owner_places (for a real domain) or a name (for a temporary domain)
|
||||||
if (data.status == "success") {
|
if (data.status == "success") {
|
||||||
$('.domain-loading-hide').show();
|
$('.domain-loading-hide').show();
|
||||||
DomainInfo = data.domain;
|
|
||||||
if (data.domain.owner_places) {
|
if (data.domain.owner_places) {
|
||||||
// add a table row for each of these names
|
// add a table row for each of these names
|
||||||
_.each(data.domain.owner_places, function(place){
|
_.each(data.domain.owner_places, function(place){
|
||||||
|
@ -1043,7 +1028,6 @@ function reloadDomainInfo() {
|
||||||
appendAddButtonToPlacesTable();
|
appendAddButtonToPlacesTable();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
DomainInfo = null;
|
|
||||||
$('.domain-loading-error').show();
|
$('.domain-loading-error').show();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -58,6 +58,7 @@ $(document).ready(function(){
|
||||||
|
|
||||||
reloadSettings(function(success) {
|
reloadSettings(function(success) {
|
||||||
if (success) {
|
if (success) {
|
||||||
|
getDomainFromAPI();
|
||||||
setupWizardSteps();
|
setupWizardSteps();
|
||||||
updatePlaceNameDisplay();
|
updatePlaceNameDisplay();
|
||||||
updateUsernameDisplay();
|
updateUsernameDisplay();
|
||||||
|
|
|
@ -830,26 +830,6 @@ void DomainServer::setupICEHeartbeatForFullNetworking() {
|
||||||
void DomainServer::updateICEServerAddresses() {
|
void DomainServer::updateICEServerAddresses() {
|
||||||
if (_iceAddressLookupID == INVALID_ICE_LOOKUP_ID) {
|
if (_iceAddressLookupID == INVALID_ICE_LOOKUP_ID) {
|
||||||
_iceAddressLookupID = QHostInfo::lookupHost(_iceServerAddr, this, SLOT(handleICEHostInfo(QHostInfo)));
|
_iceAddressLookupID = QHostInfo::lookupHost(_iceServerAddr, this, SLOT(handleICEHostInfo(QHostInfo)));
|
||||||
|
|
||||||
// there seems to be a 5.9 bug where lookupHost never calls our slot
|
|
||||||
// so we add a single shot manual "timeout" to fire it off again if it hasn't called back yet
|
|
||||||
static const int ICE_ADDRESS_LOOKUP_TIMEOUT_MS = 5000;
|
|
||||||
QTimer::singleShot(ICE_ADDRESS_LOOKUP_TIMEOUT_MS, this, &DomainServer::timeoutICEAddressLookup);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DomainServer::timeoutICEAddressLookup() {
|
|
||||||
if (_iceAddressLookupID != INVALID_ICE_LOOKUP_ID) {
|
|
||||||
// we waited 5s and didn't hear back for our ICE DNS lookup
|
|
||||||
// so time that one out and kick off another
|
|
||||||
|
|
||||||
qDebug() << "IP address lookup timed out for" << _iceServerAddr << "- retrying";
|
|
||||||
|
|
||||||
QHostInfo::abortHostLookup(_iceAddressLookupID);
|
|
||||||
|
|
||||||
_iceAddressLookupID = INVALID_ICE_LOOKUP_ID;
|
|
||||||
|
|
||||||
updateICEServerAddresses();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3007,9 +2987,20 @@ void DomainServer::handleKeypairChange() {
|
||||||
|
|
||||||
void DomainServer::handleICEHostInfo(const QHostInfo& hostInfo) {
|
void DomainServer::handleICEHostInfo(const QHostInfo& hostInfo) {
|
||||||
// clear the ICE address lookup ID so that it can fire again
|
// clear the ICE address lookup ID so that it can fire again
|
||||||
_iceAddressLookupID = -1;
|
_iceAddressLookupID = INVALID_ICE_LOOKUP_ID;
|
||||||
|
|
||||||
if (hostInfo.error() != QHostInfo::NoError) {
|
// enumerate the returned addresses and collect only valid IPv4 addresses
|
||||||
|
QList<QHostAddress> sanitizedAddresses = hostInfo.addresses();
|
||||||
|
auto it = sanitizedAddresses.begin();
|
||||||
|
while (it != sanitizedAddresses.end()) {
|
||||||
|
if (!it->isNull() && it->protocol() == QAbstractSocket::IPv4Protocol) {
|
||||||
|
++it;
|
||||||
|
} else {
|
||||||
|
it = sanitizedAddresses.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hostInfo.error() != QHostInfo::NoError || sanitizedAddresses.empty()) {
|
||||||
qWarning() << "IP address lookup failed for" << _iceServerAddr << ":" << hostInfo.errorString();
|
qWarning() << "IP address lookup failed for" << _iceServerAddr << ":" << hostInfo.errorString();
|
||||||
|
|
||||||
// if we don't have an ICE server to use yet, trigger a retry
|
// if we don't have an ICE server to use yet, trigger a retry
|
||||||
|
@ -3022,7 +3013,7 @@ void DomainServer::handleICEHostInfo(const QHostInfo& hostInfo) {
|
||||||
} else {
|
} else {
|
||||||
int countBefore = _iceServerAddresses.count();
|
int countBefore = _iceServerAddresses.count();
|
||||||
|
|
||||||
_iceServerAddresses = hostInfo.addresses();
|
_iceServerAddresses = sanitizedAddresses;
|
||||||
|
|
||||||
if (countBefore == 0) {
|
if (countBefore == 0) {
|
||||||
qInfo() << "Found" << _iceServerAddresses.count() << "ice-server IP addresses for" << _iceServerAddr;
|
qInfo() << "Found" << _iceServerAddresses.count() << "ice-server IP addresses for" << _iceServerAddr;
|
||||||
|
|
|
@ -116,8 +116,6 @@ private slots:
|
||||||
void tokenGrantFinished();
|
void tokenGrantFinished();
|
||||||
void profileRequestFinished();
|
void profileRequestFinished();
|
||||||
|
|
||||||
void timeoutICEAddressLookup();
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void iceServerChanged();
|
void iceServerChanged();
|
||||||
void userConnected();
|
void userConnected();
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
#include <NLPacketList.h>
|
#include <NLPacketList.h>
|
||||||
#include <NumericalConstants.h>
|
#include <NumericalConstants.h>
|
||||||
#include <SettingHandle.h>
|
#include <SettingHandle.h>
|
||||||
|
#include <SettingHelpers.h>
|
||||||
#include <AvatarData.h> //for KillAvatarReason
|
#include <AvatarData.h> //for KillAvatarReason
|
||||||
#include <FingerprintUtils.h>
|
#include <FingerprintUtils.h>
|
||||||
#include "DomainServerNodeData.h"
|
#include "DomainServerNodeData.h"
|
||||||
|
@ -43,12 +44,7 @@ const QString DESCRIPTION_COLUMNS_KEY = "columns";
|
||||||
|
|
||||||
const QString SETTINGS_VIEWPOINT_KEY = "viewpoint";
|
const QString SETTINGS_VIEWPOINT_KEY = "viewpoint";
|
||||||
|
|
||||||
static Setting::Handle<double> JSON_SETTING_VERSION("json-settings/version", 0.0);
|
DomainServerSettingsManager::DomainServerSettingsManager() {
|
||||||
|
|
||||||
DomainServerSettingsManager::DomainServerSettingsManager() :
|
|
||||||
_descriptionArray(),
|
|
||||||
_configMap()
|
|
||||||
{
|
|
||||||
// load the description object from the settings description
|
// load the description object from the settings description
|
||||||
QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH);
|
QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH);
|
||||||
descriptionFile.open(QIODevice::ReadOnly);
|
descriptionFile.open(QIODevice::ReadOnly);
|
||||||
|
@ -100,12 +96,34 @@ void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer<Re
|
||||||
void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) {
|
void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) {
|
||||||
_argumentList = argumentList;
|
_argumentList = argumentList;
|
||||||
|
|
||||||
// after 1.7 we no longer use the master or merged configs - this is kept in place for migration
|
_configMap.loadConfig(_argumentList);
|
||||||
_configMap.loadMasterAndUserConfig(_argumentList);
|
|
||||||
|
static const auto VERSION_SETTINGS_KEYPATH = "version";
|
||||||
|
QVariant* versionVariant = _configMap.valueForKeyPath(VERSION_SETTINGS_KEYPATH);
|
||||||
|
|
||||||
|
if (!versionVariant) {
|
||||||
|
versionVariant = _configMap.valueForKeyPath(VERSION_SETTINGS_KEYPATH, true);
|
||||||
|
*versionVariant = _descriptionVersion;
|
||||||
|
persistToFile();
|
||||||
|
qDebug() << "No version in config file, setting to current version" << _descriptionVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Backward compatibility migration code
|
||||||
|
// The config version used to be stored in a different file
|
||||||
|
// This moves it to the actual config file.
|
||||||
|
Setting::Handle<double> JSON_SETTING_VERSION("json-settings/version", 0.0);
|
||||||
|
if (JSON_SETTING_VERSION.isSet()) {
|
||||||
|
auto version = JSON_SETTING_VERSION.get();
|
||||||
|
*versionVariant = version;
|
||||||
|
persistToFile();
|
||||||
|
QFile::remove(settingsFilename());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// What settings version were we before and what are we using now?
|
// What settings version were we before and what are we using now?
|
||||||
// Do we need to do any re-mapping?
|
// Do we need to do any re-mapping?
|
||||||
double oldVersion = JSON_SETTING_VERSION.get();
|
double oldVersion = versionVariant->toDouble();
|
||||||
|
|
||||||
if (oldVersion != _descriptionVersion) {
|
if (oldVersion != _descriptionVersion) {
|
||||||
const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users";
|
const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users";
|
||||||
|
@ -137,12 +155,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
|
||||||
QVariant* restrictedAccess = _configMap.valueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH, true);
|
QVariant* restrictedAccess = _configMap.valueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH, true);
|
||||||
|
|
||||||
*restrictedAccess = QVariant(true);
|
*restrictedAccess = QVariant(true);
|
||||||
|
|
||||||
// write the new settings to the json file
|
|
||||||
persistToFile();
|
|
||||||
|
|
||||||
// reload the master and user config so that the merged config is right
|
|
||||||
_configMap.loadMasterAndUserConfig(_argumentList);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,12 +184,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
|
||||||
|
|
||||||
*entityServerVariant = entityServerMap;
|
*entityServerVariant = entityServerMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
// write the new settings to the json file
|
|
||||||
persistToFile();
|
|
||||||
|
|
||||||
// reload the master and user config so that the merged config is right
|
|
||||||
_configMap.loadMasterAndUserConfig(_argumentList);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -195,12 +201,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
|
||||||
qDebug() << "Migrating plaintext password to SHA256 hash in domain-server settings.";
|
qDebug() << "Migrating plaintext password to SHA256 hash in domain-server settings.";
|
||||||
|
|
||||||
*passwordVariant = QCryptographicHash::hash(plaintextPassword.toUtf8(), QCryptographicHash::Sha256).toHex();
|
*passwordVariant = QCryptographicHash::hash(plaintextPassword.toUtf8(), QCryptographicHash::Sha256).toHex();
|
||||||
|
|
||||||
// write the new settings to file
|
|
||||||
persistToFile();
|
|
||||||
|
|
||||||
// reload the master and user config so the merged config is correct
|
|
||||||
_configMap.loadMasterAndUserConfig(_argumentList);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,19 +283,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
|
||||||
packPermissions();
|
packPermissions();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldVersion < 1.7) {
|
|
||||||
// This was prior to the removal of the master config file
|
|
||||||
// So we write the merged config to the user config file, and stop reading from the user config file
|
|
||||||
|
|
||||||
qDebug() << "Migrating merged config to user config file. The master config file is deprecated.";
|
|
||||||
|
|
||||||
// replace the user config by the merged config
|
|
||||||
_configMap.getConfig() = _configMap.getMergedConfig();
|
|
||||||
|
|
||||||
// persist the new config so the user config file has the correctly merged config
|
|
||||||
persistToFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < 1.8) {
|
if (oldVersion < 1.8) {
|
||||||
unpackPermissions();
|
unpackPermissions();
|
||||||
// This was prior to addition of domain content replacement, add that to localhost permissions by default
|
// This was prior to addition of domain content replacement, add that to localhost permissions by default
|
||||||
|
@ -316,16 +303,16 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
|
||||||
QVariant* wizardCompletedOnce = _configMap.valueForKeyPath(WIZARD_COMPLETED_ONCE, true);
|
QVariant* wizardCompletedOnce = _configMap.valueForKeyPath(WIZARD_COMPLETED_ONCE, true);
|
||||||
|
|
||||||
*wizardCompletedOnce = QVariant(true);
|
*wizardCompletedOnce = QVariant(true);
|
||||||
|
|
||||||
// write the new settings to the json file
|
|
||||||
persistToFile();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// write the current description version to our settings
|
||||||
|
*versionVariant = _descriptionVersion;
|
||||||
|
|
||||||
|
// write the new settings to the json file
|
||||||
|
persistToFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
unpackPermissions();
|
unpackPermissions();
|
||||||
|
|
||||||
// write the current description version to our settings
|
|
||||||
JSON_SETTING_VERSION.set(_descriptionVersion);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariantMap& DomainServerSettingsManager::getDescriptorsMap() {
|
QVariantMap& DomainServerSettingsManager::getDescriptorsMap() {
|
||||||
|
@ -1289,9 +1276,6 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// re-merge the user and master configs after a settings change
|
|
||||||
_configMap.mergeMasterAndUserConfigs();
|
|
||||||
|
|
||||||
return needRestart;
|
return needRestart;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -432,7 +432,8 @@ Item {
|
||||||
anchors.verticalCenter: nameCardRemoveConnectionImage.verticalCenter
|
anchors.verticalCenter: nameCardRemoveConnectionImage.verticalCenter
|
||||||
x: 240
|
x: 240
|
||||||
onClicked: {
|
onClicked: {
|
||||||
AddressManager.goToUser(thisNameCard.userName);
|
console.log("Vist user button clicked."); // Remove after debugging.
|
||||||
|
AddressManager.goToUser(thisNameCard.userName, false);
|
||||||
UserActivityLogger.palAction("go_to_user", thisNameCard.userName);
|
UserActivityLogger.palAction("go_to_user", thisNameCard.userName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -594,7 +595,10 @@ Item {
|
||||||
// the avatar goes into fly mode rather than falling. However, that is not exposed to Javascript right now.
|
// the avatar goes into fly mode rather than falling. However, that is not exposed to Javascript right now.
|
||||||
// FIXME: it would be nice if this used the same teleport steps and smoothing as in the teleport.js script.
|
// FIXME: it would be nice if this used the same teleport steps and smoothing as in the teleport.js script.
|
||||||
// Note, however, that this script allows teleporting to a person in the air, while teleport.js is going to a grounded target.
|
// Note, however, that this script allows teleporting to a person in the air, while teleport.js is going to a grounded target.
|
||||||
|
// Position avatar 2 metres from the target in the direction that target avatar was facing.
|
||||||
MyAvatar.position = Vec3.sum(avatar.position, Vec3.multiplyQbyV(avatar.orientation, {x: 0, y: 0, z: -2}));
|
MyAvatar.position = Vec3.sum(avatar.position, Vec3.multiplyQbyV(avatar.orientation, {x: 0, y: 0, z: -2}));
|
||||||
MyAvatar.orientation = Quat.multiply(avatar.orientation, {y: 1});
|
|
||||||
|
// Rotate avatar on Y axis to face target avatar and cancel out any inherited roll and pitch.
|
||||||
|
MyAvatar.orientation = Quat.cancelOutRollAndPitch(Quat.multiply(avatar.orientation, {y: 1}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -827,7 +827,7 @@ Rectangle {
|
||||||
hoverEnabled: enabled
|
hoverEnabled: enabled
|
||||||
enabled: connectionsNameCard.selected && pal.activeTab == "connectionsTab"
|
enabled: connectionsNameCard.selected && pal.activeTab == "connectionsTab"
|
||||||
onClicked: {
|
onClicked: {
|
||||||
AddressManager.goToUser(model.userName);
|
AddressManager.goToUser(model.userName, false);
|
||||||
UserActivityLogger.palAction("go_to_user", model.userName);
|
UserActivityLogger.palAction("go_to_user", model.userName);
|
||||||
}
|
}
|
||||||
onEntered: connectionsLocationData.color = hifi.colors.blueHighlight;
|
onEntered: connectionsLocationData.color = hifi.colors.blueHighlight;
|
||||||
|
|
|
@ -213,8 +213,8 @@ Rectangle {
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
peak: model.peak;
|
peak: model.peak;
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: (bar.currentIndex === 1 && selectedHMD && isVR) ||
|
visible: ((bar.currentIndex === 1 && isVR) ||
|
||||||
(bar.currentIndex === 0 && selectedDesktop && !isVR) &&
|
(bar.currentIndex === 0 && !isVR)) &&
|
||||||
Audio.devices.input.peakValuesAvailable;
|
Audio.devices.input.peakValuesAvailable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ Rectangle {
|
||||||
property string dateOfPurchase: "--";
|
property string dateOfPurchase: "--";
|
||||||
property bool isLightbox: false;
|
property bool isLightbox: false;
|
||||||
property bool isMyCert: false;
|
property bool isMyCert: false;
|
||||||
|
property bool isCertificateInvalid: false;
|
||||||
// Style
|
// Style
|
||||||
color: hifi.colors.faintGray;
|
color: hifi.colors.faintGray;
|
||||||
Hifi.QmlCommerce {
|
Hifi.QmlCommerce {
|
||||||
|
@ -44,10 +45,11 @@ Rectangle {
|
||||||
} else {
|
} else {
|
||||||
root.marketplaceUrl = result.data.marketplace_item_url;
|
root.marketplaceUrl = result.data.marketplace_item_url;
|
||||||
root.isMyCert = result.isMyCert ? result.isMyCert : false;
|
root.isMyCert = result.isMyCert ? result.isMyCert : false;
|
||||||
root.itemOwner = root.isMyCert ? Account.username :
|
root.itemOwner = root.isCertificateInvalid ? "--" : (root.isMyCert ? Account.username :
|
||||||
"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
|
"\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022");
|
||||||
root.itemEdition = result.data.edition_number + "/" + (result.data.limited_run === -1 ? "\u221e" : result.data.limited_run);
|
root.itemEdition = root.isCertificateInvalid ? "Uncertified Copy" :
|
||||||
root.dateOfPurchase = getFormattedDate(result.data.transfer_created_at * 1000);
|
(result.data.edition_number + "/" + (result.data.limited_run === -1 ? "\u221e" : result.data.limited_run));
|
||||||
|
root.dateOfPurchase = root.isCertificateInvalid ? "" : getFormattedDate(result.data.transfer_created_at * 1000);
|
||||||
root.itemName = result.data.marketplace_item_name;
|
root.itemName = result.data.marketplace_item_name;
|
||||||
|
|
||||||
if (result.data.invalid_reason || result.data.transfer_status[0] === "failed") {
|
if (result.data.invalid_reason || result.data.transfer_status[0] === "failed") {
|
||||||
|
@ -65,6 +67,44 @@ Rectangle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onUpdateCertificateStatus: {
|
||||||
|
if (certStatus === 1) { // CERTIFICATE_STATUS_VERIFICATION_SUCCESS
|
||||||
|
// NOP
|
||||||
|
} else if (certStatus === 2) { // CERTIFICATE_STATUS_VERIFICATION_TIMEOUT
|
||||||
|
root.isCertificateInvalid = true;
|
||||||
|
errorText.text = "Verification of this certificate timed out.";
|
||||||
|
errorText.color = hifi.colors.redHighlight;
|
||||||
|
} else if (certStatus === 3) { // CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED
|
||||||
|
root.isCertificateInvalid = true;
|
||||||
|
titleBarText.text = "Invalid Certificate";
|
||||||
|
titleBarText.color = hifi.colors.redHighlight;
|
||||||
|
|
||||||
|
popText.text = "";
|
||||||
|
root.itemOwner = "";
|
||||||
|
dateOfPurchaseHeader.text = "";
|
||||||
|
root.dateOfPurchase = "";
|
||||||
|
root.itemEdition = "Uncertified Copy";
|
||||||
|
|
||||||
|
errorText.text = "The information associated with this item has been modified and it no longer matches the original certified item.";
|
||||||
|
errorText.color = hifi.colors.baseGray;
|
||||||
|
} else if (certStatus === 4) { // CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED
|
||||||
|
root.isCertificateInvalid = true;
|
||||||
|
titleBarText.text = "Invalid Certificate";
|
||||||
|
titleBarText.color = hifi.colors.redHighlight;
|
||||||
|
|
||||||
|
popText.text = "";
|
||||||
|
root.itemOwner = "";
|
||||||
|
dateOfPurchaseHeader.text = "";
|
||||||
|
root.dateOfPurchase = "";
|
||||||
|
root.itemEdition = "Uncertified Copy";
|
||||||
|
|
||||||
|
errorText.text = "The avatar who rezzed this item doesn't own it.";
|
||||||
|
errorText.color = hifi.colors.baseGray;
|
||||||
|
} else {
|
||||||
|
console.log("Unknown certificate status received from ledger signal!");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onCertificateIdChanged: {
|
onCertificateIdChanged: {
|
||||||
|
@ -216,7 +256,7 @@ Rectangle {
|
||||||
}
|
}
|
||||||
AnonymousProRegular {
|
AnonymousProRegular {
|
||||||
id: isMyCertText;
|
id: isMyCertText;
|
||||||
visible: root.isMyCert;
|
visible: root.isMyCert && !root.isCertificateInvalid;
|
||||||
text: "(Private)";
|
text: "(Private)";
|
||||||
size: 18;
|
size: 18;
|
||||||
// Anchors
|
// Anchors
|
||||||
|
|
|
@ -179,6 +179,12 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
||||||
const AvatarPriority& sortData = sortedAvatars.top();
|
const AvatarPriority& sortData = sortedAvatars.top();
|
||||||
const auto& avatar = std::static_pointer_cast<Avatar>(sortData.avatar);
|
const auto& avatar = std::static_pointer_cast<Avatar>(sortData.avatar);
|
||||||
|
|
||||||
|
bool ignoring = DependencyManager::get<NodeList>()->isPersonalMutingNode(avatar->getID());
|
||||||
|
if (ignoring) {
|
||||||
|
sortedAvatars.pop();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// for ALL avatars...
|
// for ALL avatars...
|
||||||
if (_shouldRender) {
|
if (_shouldRender) {
|
||||||
avatar->ensureInScene(avatar, qApp->getMain3DScene());
|
avatar->ensureInScene(avatar, qApp->getMain3DScene());
|
||||||
|
|
|
@ -90,7 +90,7 @@ void Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, cons
|
||||||
signedSend("transaction", transactionString, hfc_key, "buy", "buySuccess", "buyFailure", controlled_failure);
|
signedSend("transaction", transactionString, hfc_key, "buy", "buySuccess", "buyFailure", controlled_failure);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Ledger::receiveAt(const QString& hfc_key, const QString& old_key) {
|
bool Ledger::receiveAt(const QString& hfc_key, const QString& old_key, const QString& machine_fingerprint) {
|
||||||
auto accountManager = DependencyManager::get<AccountManager>();
|
auto accountManager = DependencyManager::get<AccountManager>();
|
||||||
if (!accountManager->isLoggedIn()) {
|
if (!accountManager->isLoggedIn()) {
|
||||||
qCWarning(commerce) << "Cannot set receiveAt when not logged in.";
|
qCWarning(commerce) << "Cannot set receiveAt when not logged in.";
|
||||||
|
@ -99,7 +99,13 @@ bool Ledger::receiveAt(const QString& hfc_key, const QString& old_key) {
|
||||||
return false; // We know right away that we will fail, so tell the caller.
|
return false; // We know right away that we will fail, so tell the caller.
|
||||||
}
|
}
|
||||||
|
|
||||||
signedSend("public_key", hfc_key.toUtf8(), old_key, "receive_at", "receiveAtSuccess", "receiveAtFailure");
|
QJsonObject transaction;
|
||||||
|
transaction["hfc_key"] = hfc_key;
|
||||||
|
transaction["machine_fingerprint"] = machine_fingerprint;
|
||||||
|
QJsonDocument transactionDoc{ transaction };
|
||||||
|
auto transactionString = transactionDoc.toJson(QJsonDocument::Compact);
|
||||||
|
|
||||||
|
signedSend("transaction", transactionString, old_key, "receive_at", "receiveAtSuccess", "receiveAtFailure");
|
||||||
return true; // Note that there may still be an asynchronous signal of failure that callers might be interested in.
|
return true; // Note that there may still be an asynchronous signal of failure that callers might be interested in.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ class Ledger : public QObject, public Dependency {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure = false);
|
void buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure = false);
|
||||||
bool receiveAt(const QString& hfc_key, const QString& old_key);
|
bool receiveAt(const QString& hfc_key, const QString& old_key, const QString& machine_fingerprint);
|
||||||
void balance(const QStringList& keys);
|
void balance(const QStringList& keys);
|
||||||
void inventory(const QStringList& keys);
|
void inventory(const QStringList& keys);
|
||||||
void history(const QStringList& keys);
|
void history(const QStringList& keys);
|
||||||
|
@ -35,6 +35,14 @@ public:
|
||||||
void updateLocation(const QString& asset_id, const QString location, const bool controlledFailure = false);
|
void updateLocation(const QString& asset_id, const QString location, const bool controlledFailure = false);
|
||||||
void certificateInfo(const QString& certificateId);
|
void certificateInfo(const QString& certificateId);
|
||||||
|
|
||||||
|
enum CertificateStatus {
|
||||||
|
CERTIFICATE_STATUS_UNKNOWN = 0,
|
||||||
|
CERTIFICATE_STATUS_VERIFICATION_SUCCESS,
|
||||||
|
CERTIFICATE_STATUS_VERIFICATION_TIMEOUT,
|
||||||
|
CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED,
|
||||||
|
CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED,
|
||||||
|
};
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void buyResult(QJsonObject result);
|
void buyResult(QJsonObject result);
|
||||||
void receiveAtResult(QJsonObject result);
|
void receiveAtResult(QJsonObject result);
|
||||||
|
@ -45,6 +53,8 @@ signals:
|
||||||
void locationUpdateResult(QJsonObject result);
|
void locationUpdateResult(QJsonObject result);
|
||||||
void certificateInfoResult(QJsonObject result);
|
void certificateInfoResult(QJsonObject result);
|
||||||
|
|
||||||
|
void updateCertificateStatus(const QString& certID, uint certStatus);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void buySuccess(QNetworkReply& reply);
|
void buySuccess(QNetworkReply& reply);
|
||||||
void buyFailure(QNetworkReply& reply);
|
void buyFailure(QNetworkReply& reply);
|
||||||
|
|
|
@ -30,6 +30,12 @@ QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) {
|
||||||
connect(ledger.data(), &Ledger::accountResult, this, &QmlCommerce::accountResult);
|
connect(ledger.data(), &Ledger::accountResult, this, &QmlCommerce::accountResult);
|
||||||
connect(wallet.data(), &Wallet::walletStatusResult, this, &QmlCommerce::walletStatusResult);
|
connect(wallet.data(), &Wallet::walletStatusResult, this, &QmlCommerce::walletStatusResult);
|
||||||
connect(ledger.data(), &Ledger::certificateInfoResult, this, &QmlCommerce::certificateInfoResult);
|
connect(ledger.data(), &Ledger::certificateInfoResult, this, &QmlCommerce::certificateInfoResult);
|
||||||
|
connect(ledger.data(), &Ledger::updateCertificateStatus, this, &QmlCommerce::updateCertificateStatus);
|
||||||
|
|
||||||
|
auto accountManager = DependencyManager::get<AccountManager>();
|
||||||
|
connect(accountManager.data(), &AccountManager::usernameChanged, [&]() {
|
||||||
|
setPassphrase("");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void QmlCommerce::getWalletStatus() {
|
void QmlCommerce::getWalletStatus() {
|
||||||
|
|
|
@ -45,6 +45,8 @@ signals:
|
||||||
void accountResult(QJsonObject result);
|
void accountResult(QJsonObject result);
|
||||||
void certificateInfoResult(QJsonObject result);
|
void certificateInfoResult(QJsonObject result);
|
||||||
|
|
||||||
|
void updateCertificateStatus(const QString& certID, uint certStatus);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Q_INVOKABLE void getWalletStatus();
|
Q_INVOKABLE void getWalletStatus();
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include "ui/ImageProvider.h"
|
#include "ui/ImageProvider.h"
|
||||||
#include "scripting/HMDScriptingInterface.h"
|
#include "scripting/HMDScriptingInterface.h"
|
||||||
|
|
||||||
|
#include <FingerprintUtils.h>
|
||||||
#include <PathUtils.h>
|
#include <PathUtils.h>
|
||||||
#include <OffscreenUi.h>
|
#include <OffscreenUi.h>
|
||||||
#include <AccountManager.h>
|
#include <AccountManager.h>
|
||||||
|
@ -319,6 +320,7 @@ Wallet::Wallet() {
|
||||||
auto& packetReceiver = nodeList->getPacketReceiver();
|
auto& packetReceiver = nodeList->getPacketReceiver();
|
||||||
|
|
||||||
packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnershipPacket");
|
packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnershipPacket");
|
||||||
|
packetReceiver.registerListener(PacketType::ChallengeOwnershipRequest, this, "handleChallengeOwnershipPacket");
|
||||||
|
|
||||||
connect(ledger.data(), &Ledger::accountResult, this, [&]() {
|
connect(ledger.data(), &Ledger::accountResult, this, [&]() {
|
||||||
auto wallet = DependencyManager::get<Wallet>();
|
auto wallet = DependencyManager::get<Wallet>();
|
||||||
|
@ -540,7 +542,8 @@ bool Wallet::generateKeyPair() {
|
||||||
// 2. It is maximally private, and we can step back from that later if desired.
|
// 2. It is maximally private, and we can step back from that later if desired.
|
||||||
// 3. It maximally exercises all the machinery, so we are most likely to surface issues now.
|
// 3. It maximally exercises all the machinery, so we are most likely to surface issues now.
|
||||||
auto ledger = DependencyManager::get<Ledger>();
|
auto ledger = DependencyManager::get<Ledger>();
|
||||||
return ledger->receiveAt(key, oldKey);
|
QString machineFingerprint = uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint());
|
||||||
|
return ledger->receiveAt(key, oldKey, machineFingerprint);
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList Wallet::listPublicKeys() {
|
QStringList Wallet::listPublicKeys() {
|
||||||
|
@ -717,50 +720,86 @@ bool Wallet::changePassphrase(const QString& newPassphrase) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Wallet::handleChallengeOwnershipPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
|
void Wallet::handleChallengeOwnershipPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
|
||||||
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
|
||||||
|
bool challengeOriginatedFromClient = packet->getType() == PacketType::ChallengeOwnershipRequest;
|
||||||
unsigned char decryptedText[64];
|
unsigned char decryptedText[64];
|
||||||
int certIDByteArraySize;
|
int certIDByteArraySize;
|
||||||
int encryptedTextByteArraySize;
|
int encryptedTextByteArraySize;
|
||||||
|
int challengingNodeUUIDByteArraySize;
|
||||||
|
|
||||||
packet->readPrimitive(&certIDByteArraySize);
|
packet->readPrimitive(&certIDByteArraySize);
|
||||||
packet->readPrimitive(&encryptedTextByteArraySize);
|
packet->readPrimitive(&encryptedTextByteArraySize);
|
||||||
|
if (challengeOriginatedFromClient) {
|
||||||
|
packet->readPrimitive(&challengingNodeUUIDByteArraySize);
|
||||||
|
}
|
||||||
|
|
||||||
QByteArray certID = packet->read(certIDByteArraySize);
|
QByteArray certID = packet->read(certIDByteArraySize);
|
||||||
QByteArray encryptedText = packet->read(encryptedTextByteArraySize);
|
QByteArray encryptedText = packet->read(encryptedTextByteArraySize);
|
||||||
|
QByteArray challengingNodeUUID;
|
||||||
|
if (challengeOriginatedFromClient) {
|
||||||
|
challengingNodeUUID = packet->read(challengingNodeUUIDByteArraySize);
|
||||||
|
}
|
||||||
|
|
||||||
RSA* rsa = readKeys(keyFilePath().toStdString().c_str());
|
RSA* rsa = readKeys(keyFilePath().toStdString().c_str());
|
||||||
|
int decryptionStatus = -1;
|
||||||
|
|
||||||
if (rsa) {
|
if (rsa) {
|
||||||
const int decryptionStatus = RSA_private_decrypt(encryptedTextByteArraySize,
|
ERR_clear_error();
|
||||||
|
decryptionStatus = RSA_private_decrypt(encryptedTextByteArraySize,
|
||||||
reinterpret_cast<const unsigned char*>(encryptedText.constData()),
|
reinterpret_cast<const unsigned char*>(encryptedText.constData()),
|
||||||
decryptedText,
|
decryptedText,
|
||||||
rsa,
|
rsa,
|
||||||
RSA_PKCS1_OAEP_PADDING);
|
RSA_PKCS1_OAEP_PADDING);
|
||||||
|
|
||||||
RSA_free(rsa);
|
RSA_free(rsa);
|
||||||
|
|
||||||
if (decryptionStatus != -1) {
|
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
|
||||||
|
|
||||||
QByteArray decryptedTextByteArray = QByteArray(reinterpret_cast<const char*>(decryptedText), decryptionStatus);
|
|
||||||
int decryptedTextByteArraySize = decryptedTextByteArray.size();
|
|
||||||
int certIDSize = certID.size();
|
|
||||||
// setup the packet
|
|
||||||
auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnership, certIDSize + decryptedTextByteArraySize + 2 * sizeof(int), true);
|
|
||||||
|
|
||||||
decryptedTextPacket->writePrimitive(certIDSize);
|
|
||||||
decryptedTextPacket->writePrimitive(decryptedTextByteArraySize);
|
|
||||||
decryptedTextPacket->write(certID);
|
|
||||||
decryptedTextPacket->write(decryptedTextByteArray);
|
|
||||||
|
|
||||||
qCDebug(commerce) << "Sending ChallengeOwnership Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID;
|
|
||||||
|
|
||||||
nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode);
|
|
||||||
} else {
|
|
||||||
qCDebug(commerce) << "During entity ownership challenge, decrypting the encrypted text failed.";
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
qCDebug(commerce) << "During entity ownership challenge, creating the RSA object failed.";
|
qCDebug(commerce) << "During entity ownership challenge, creating the RSA object failed.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QByteArray decryptedTextByteArray;
|
||||||
|
if (decryptionStatus > -1) {
|
||||||
|
decryptedTextByteArray = QByteArray(reinterpret_cast<const char*>(decryptedText), decryptionStatus);
|
||||||
|
}
|
||||||
|
int decryptedTextByteArraySize = decryptedTextByteArray.size();
|
||||||
|
int certIDSize = certID.size();
|
||||||
|
// setup the packet
|
||||||
|
if (challengeOriginatedFromClient) {
|
||||||
|
auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnershipReply,
|
||||||
|
certIDSize + decryptedTextByteArraySize + challengingNodeUUIDByteArraySize + 3 * sizeof(int),
|
||||||
|
true);
|
||||||
|
|
||||||
|
decryptedTextPacket->writePrimitive(certIDSize);
|
||||||
|
decryptedTextPacket->writePrimitive(decryptedTextByteArraySize);
|
||||||
|
decryptedTextPacket->writePrimitive(challengingNodeUUIDByteArraySize);
|
||||||
|
decryptedTextPacket->write(certID);
|
||||||
|
decryptedTextPacket->write(decryptedTextByteArray);
|
||||||
|
decryptedTextPacket->write(challengingNodeUUID);
|
||||||
|
|
||||||
|
qCDebug(commerce) << "Sending ChallengeOwnershipReply Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID;
|
||||||
|
|
||||||
|
nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode);
|
||||||
|
} else {
|
||||||
|
auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnership, certIDSize + decryptedTextByteArraySize + 2 * sizeof(int), true);
|
||||||
|
|
||||||
|
decryptedTextPacket->writePrimitive(certIDSize);
|
||||||
|
decryptedTextPacket->writePrimitive(decryptedTextByteArraySize);
|
||||||
|
decryptedTextPacket->write(certID);
|
||||||
|
decryptedTextPacket->write(decryptedTextByteArray);
|
||||||
|
|
||||||
|
qCDebug(commerce) << "Sending ChallengeOwnership Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID;
|
||||||
|
|
||||||
|
nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decryptionStatus == -1) {
|
||||||
|
qCDebug(commerce) << "During entity ownership challenge, decrypting the encrypted text failed.";
|
||||||
|
long error = ERR_get_error();
|
||||||
|
if (error != 0) {
|
||||||
|
const char* error_str = ERR_error_string(error, NULL);
|
||||||
|
qCWarning(entities) << "RSA error:" << error_str;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Wallet::account() {
|
void Wallet::account() {
|
||||||
|
|
|
@ -14,6 +14,10 @@
|
||||||
|
|
||||||
#include <EntityTreeRenderer.h>
|
#include <EntityTreeRenderer.h>
|
||||||
#include <NetworkingConstants.h>
|
#include <NetworkingConstants.h>
|
||||||
|
#include <NetworkAccessManager.h>
|
||||||
|
#include <QtNetwork/QNetworkRequest>
|
||||||
|
#include <QtNetwork/QNetworkReply>
|
||||||
|
#include <commerce/Ledger.h>
|
||||||
|
|
||||||
#ifndef MIN
|
#ifndef MIN
|
||||||
#define MIN(a,b) ((a) < (b) ? (a) : (b))
|
#define MIN(a,b) ((a) < (b) ? (a) : (b))
|
||||||
|
@ -42,6 +46,8 @@ ContextOverlayInterface::ContextOverlayInterface() {
|
||||||
_entityPropertyFlags += PROP_DIMENSIONS;
|
_entityPropertyFlags += PROP_DIMENSIONS;
|
||||||
_entityPropertyFlags += PROP_REGISTRATION_POINT;
|
_entityPropertyFlags += PROP_REGISTRATION_POINT;
|
||||||
_entityPropertyFlags += PROP_CERTIFICATE_ID;
|
_entityPropertyFlags += PROP_CERTIFICATE_ID;
|
||||||
|
_entityPropertyFlags += PROP_CLIENT_ONLY;
|
||||||
|
_entityPropertyFlags += PROP_OWNING_AVATAR_ID;
|
||||||
|
|
||||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>().data();
|
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>().data();
|
||||||
connect(entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity, this, &ContextOverlayInterface::createOrDestroyContextOverlay);
|
connect(entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity, this, &ContextOverlayInterface::createOrDestroyContextOverlay);
|
||||||
|
@ -66,6 +72,11 @@ ContextOverlayInterface::ContextOverlayInterface() {
|
||||||
connect(&qApp->getOverlays(), &Overlays::hoverLeaveOverlay, this, &ContextOverlayInterface::contextOverlays_hoverLeaveOverlay);
|
connect(&qApp->getOverlays(), &Overlays::hoverLeaveOverlay, this, &ContextOverlayInterface::contextOverlays_hoverLeaveOverlay);
|
||||||
|
|
||||||
connect(_selectionScriptingInterface.data(), &SelectionScriptingInterface::selectedItemsListChanged, &_selectionToSceneHandler, &SelectionToSceneHandler::selectedItemsListChanged);
|
connect(_selectionScriptingInterface.data(), &SelectionScriptingInterface::selectedItemsListChanged, &_selectionToSceneHandler, &SelectionToSceneHandler::selectedItemsListChanged);
|
||||||
|
|
||||||
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
auto& packetReceiver = nodeList->getPacketReceiver();
|
||||||
|
packetReceiver.registerListener(PacketType::ChallengeOwnershipReply, this, "handleChallengeOwnershipReplyPacket");
|
||||||
|
_challengeOwnershipTimeoutTimer.setSingleShot(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const uint32_t MOUSE_HW_ID = 0;
|
static const uint32_t MOUSE_HW_ID = 0;
|
||||||
|
@ -260,6 +271,89 @@ void ContextOverlayInterface::openInspectionCertificate() {
|
||||||
auto tablet = dynamic_cast<TabletProxy*>(_tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
|
auto tablet = dynamic_cast<TabletProxy*>(_tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
|
||||||
tablet->loadQMLSource(INSPECTION_CERTIFICATE_QML_PATH);
|
tablet->loadQMLSource(INSPECTION_CERTIFICATE_QML_PATH);
|
||||||
_hmdScriptingInterface->openTablet();
|
_hmdScriptingInterface->openTablet();
|
||||||
|
|
||||||
|
setLastInspectedEntity(_currentEntityWithContextOverlay);
|
||||||
|
|
||||||
|
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags);
|
||||||
|
|
||||||
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
|
||||||
|
if (entityProperties.getClientOnly()) {
|
||||||
|
if (entityProperties.verifyStaticCertificateProperties()) {
|
||||||
|
SharedNodePointer entityServer = nodeList->soloNodeOfType(NodeType::EntityServer);
|
||||||
|
|
||||||
|
if (entityServer) {
|
||||||
|
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||||
|
QNetworkRequest networkRequest;
|
||||||
|
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||||
|
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL;
|
||||||
|
requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/transfer");
|
||||||
|
QJsonObject request;
|
||||||
|
request["certificate_id"] = entityProperties.getCertificateID();
|
||||||
|
networkRequest.setUrl(requestURL);
|
||||||
|
|
||||||
|
QNetworkReply* networkReply = NULL;
|
||||||
|
networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson());
|
||||||
|
|
||||||
|
connect(networkReply, &QNetworkReply::finished, [=]() {
|
||||||
|
QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object();
|
||||||
|
jsonObject = jsonObject["data"].toObject();
|
||||||
|
|
||||||
|
if (networkReply->error() == QNetworkReply::NoError) {
|
||||||
|
if (!jsonObject["invalid_reason"].toString().isEmpty()) {
|
||||||
|
qCDebug(entities) << "invalid_reason not empty";
|
||||||
|
} else if (jsonObject["transfer_status"].toArray().first().toString() == "failed") {
|
||||||
|
qCDebug(entities) << "'transfer_status' is 'failed'";
|
||||||
|
} else if (jsonObject["transfer_status"].toArray().first().toString() == "pending") {
|
||||||
|
qCDebug(entities) << "'transfer_status' is 'pending'";
|
||||||
|
} else {
|
||||||
|
QString ownerKey = jsonObject["transfer_recipient_key"].toString();
|
||||||
|
|
||||||
|
QByteArray certID = entityProperties.getCertificateID().toUtf8();
|
||||||
|
QByteArray encryptedText = DependencyManager::get<EntityTreeRenderer>()->getTree()->computeEncryptedNonce(certID, ownerKey);
|
||||||
|
QByteArray nodeToChallengeByteArray = entityProperties.getOwningAvatarID().toRfc4122();
|
||||||
|
|
||||||
|
int certIDByteArraySize = certID.length();
|
||||||
|
int encryptedTextByteArraySize = encryptedText.length();
|
||||||
|
int nodeToChallengeByteArraySize = nodeToChallengeByteArray.length();
|
||||||
|
|
||||||
|
auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest,
|
||||||
|
certIDByteArraySize + encryptedTextByteArraySize + nodeToChallengeByteArraySize + 3 * sizeof(int),
|
||||||
|
true);
|
||||||
|
challengeOwnershipPacket->writePrimitive(certIDByteArraySize);
|
||||||
|
challengeOwnershipPacket->writePrimitive(encryptedTextByteArraySize);
|
||||||
|
challengeOwnershipPacket->writePrimitive(nodeToChallengeByteArraySize);
|
||||||
|
challengeOwnershipPacket->write(certID);
|
||||||
|
challengeOwnershipPacket->write(encryptedText);
|
||||||
|
challengeOwnershipPacket->write(nodeToChallengeByteArray);
|
||||||
|
nodeList->sendPacket(std::move(challengeOwnershipPacket), *entityServer);
|
||||||
|
|
||||||
|
// Kickoff a 10-second timeout timer that marks the cert if we don't get an ownership response in time
|
||||||
|
if (thread() != QThread::currentThread()) {
|
||||||
|
QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
startChallengeOwnershipTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() <<
|
||||||
|
"More info:" << networkReply->readAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
networkReply->deleteLater();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
qCWarning(context_overlay) << "Couldn't get Entity Server!";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
auto ledger = DependencyManager::get<Ledger>();
|
||||||
|
_challengeOwnershipTimeoutTimer.stop();
|
||||||
|
emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED));
|
||||||
|
qCDebug(context_overlay) << "Entity" << _lastInspectedEntity << "failed static certificate verification!";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,3 +387,39 @@ void ContextOverlayInterface::deletingEntity(const EntityItemID& entityID) {
|
||||||
destroyContextOverlay(_currentEntityWithContextOverlay, PointerEvent());
|
destroyContextOverlay(_currentEntityWithContextOverlay, PointerEvent());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ContextOverlayInterface::startChallengeOwnershipTimer() {
|
||||||
|
auto ledger = DependencyManager::get<Ledger>();
|
||||||
|
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags);
|
||||||
|
|
||||||
|
connect(&_challengeOwnershipTimeoutTimer, &QTimer::timeout, this, [=]() {
|
||||||
|
qCDebug(entities) << "Ownership challenge timed out for" << _lastInspectedEntity;
|
||||||
|
emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_TIMEOUT));
|
||||||
|
});
|
||||||
|
|
||||||
|
_challengeOwnershipTimeoutTimer.start(5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContextOverlayInterface::handleChallengeOwnershipReplyPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
|
||||||
|
auto ledger = DependencyManager::get<Ledger>();
|
||||||
|
|
||||||
|
_challengeOwnershipTimeoutTimer.stop();
|
||||||
|
|
||||||
|
int certIDByteArraySize;
|
||||||
|
int decryptedTextByteArraySize;
|
||||||
|
|
||||||
|
packet->readPrimitive(&certIDByteArraySize);
|
||||||
|
packet->readPrimitive(&decryptedTextByteArraySize);
|
||||||
|
|
||||||
|
QString certID(packet->read(certIDByteArraySize));
|
||||||
|
QString decryptedText(packet->read(decryptedTextByteArraySize));
|
||||||
|
|
||||||
|
EntityItemID id;
|
||||||
|
bool verificationSuccess = DependencyManager::get<EntityTreeRenderer>()->getTree()->verifyDecryptedNonce(certID, decryptedText, id);
|
||||||
|
|
||||||
|
if (verificationSuccess) {
|
||||||
|
emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS));
|
||||||
|
} else {
|
||||||
|
emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -51,6 +51,7 @@ public:
|
||||||
|
|
||||||
Q_INVOKABLE QUuid getCurrentEntityWithContextOverlay() { return _currentEntityWithContextOverlay; }
|
Q_INVOKABLE QUuid getCurrentEntityWithContextOverlay() { return _currentEntityWithContextOverlay; }
|
||||||
void setCurrentEntityWithContextOverlay(const QUuid& entityID) { _currentEntityWithContextOverlay = entityID; }
|
void setCurrentEntityWithContextOverlay(const QUuid& entityID) { _currentEntityWithContextOverlay = entityID; }
|
||||||
|
void setLastInspectedEntity(const QUuid& entityID) { _challengeOwnershipTimeoutTimer.stop(); _lastInspectedEntity = entityID; }
|
||||||
void setEnabled(bool enabled);
|
void setEnabled(bool enabled);
|
||||||
bool getEnabled() { return _enabled; }
|
bool getEnabled() { return _enabled; }
|
||||||
bool getIsInMarketplaceInspectionMode() { return _isInMarketplaceInspectionMode; }
|
bool getIsInMarketplaceInspectionMode() { return _isInMarketplaceInspectionMode; }
|
||||||
|
@ -70,10 +71,14 @@ public slots:
|
||||||
void contextOverlays_hoverLeaveEntity(const EntityItemID& entityID, const PointerEvent& event);
|
void contextOverlays_hoverLeaveEntity(const EntityItemID& entityID, const PointerEvent& event);
|
||||||
bool contextOverlayFilterPassed(const EntityItemID& entityItemID);
|
bool contextOverlayFilterPassed(const EntityItemID& entityItemID);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void handleChallengeOwnershipReplyPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool _verboseLogging { true };
|
bool _verboseLogging { true };
|
||||||
bool _enabled { true };
|
bool _enabled { true };
|
||||||
QUuid _currentEntityWithContextOverlay{};
|
EntityItemID _currentEntityWithContextOverlay{};
|
||||||
|
EntityItemID _lastInspectedEntity{};
|
||||||
QString _entityMarketplaceID;
|
QString _entityMarketplaceID;
|
||||||
bool _contextOverlayJustClicked { false };
|
bool _contextOverlayJustClicked { false };
|
||||||
|
|
||||||
|
@ -87,6 +92,9 @@ private:
|
||||||
void deletingEntity(const EntityItemID& entityItemID);
|
void deletingEntity(const EntityItemID& entityItemID);
|
||||||
|
|
||||||
SelectionToSceneHandler _selectionToSceneHandler;
|
SelectionToSceneHandler _selectionToSceneHandler;
|
||||||
|
|
||||||
|
Q_INVOKABLE void startChallengeOwnershipTimer();
|
||||||
|
QTimer _challengeOwnershipTimeoutTimer;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_ContextOverlayInterface_h
|
#endif // hifi_ContextOverlayInterface_h
|
||||||
|
|
|
@ -215,6 +215,7 @@ void RenderableModelEntityItem::updateModelBounds() {
|
||||||
model->setScaleToFit(true, getDimensions());
|
model->setScaleToFit(true, getDimensions());
|
||||||
model->setSnapModelToRegistrationPoint(true, getRegistrationPoint());
|
model->setSnapModelToRegistrationPoint(true, getRegistrationPoint());
|
||||||
updateRenderItems = true;
|
updateRenderItems = true;
|
||||||
|
model->scaleToFit();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool success;
|
bool success;
|
||||||
|
|
|
@ -93,27 +93,41 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type,
|
||||||
|
|
||||||
QByteArray bufferOut(NLPacket::maxPayloadSize(type), 0);
|
QByteArray bufferOut(NLPacket::maxPayloadSize(type), 0);
|
||||||
|
|
||||||
bool success;
|
OctreeElement::AppendState encodeResult = OctreeElement::PARTIAL; // start the loop assuming there's more to send
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
|
||||||
|
EntityPropertyFlags didntFitProperties;
|
||||||
|
EntityItemProperties propertiesCopy = properties;
|
||||||
|
|
||||||
if (properties.parentIDChanged() && properties.getParentID() == AVATAR_SELF_ID) {
|
if (properties.parentIDChanged() && properties.getParentID() == AVATAR_SELF_ID) {
|
||||||
EntityItemProperties propertiesCopy = properties;
|
|
||||||
const QUuid myNodeID = nodeList->getSessionUUID();
|
const QUuid myNodeID = nodeList->getSessionUUID();
|
||||||
propertiesCopy.setParentID(myNodeID);
|
propertiesCopy.setParentID(myNodeID);
|
||||||
success = EntityItemProperties::encodeEntityEditPacket(type, entityItemID, propertiesCopy, bufferOut);
|
|
||||||
} else {
|
|
||||||
success = EntityItemProperties::encodeEntityEditPacket(type, entityItemID, properties, bufferOut);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (success) {
|
EntityPropertyFlags requestedProperties = propertiesCopy.getChangedProperties();
|
||||||
#ifdef WANT_DEBUG
|
|
||||||
qCDebug(entities) << "calling queueOctreeEditMessage()...";
|
while (encodeResult == OctreeElement::PARTIAL) {
|
||||||
qCDebug(entities) << " id:" << entityItemID;
|
encodeResult = EntityItemProperties::encodeEntityEditPacket(type, entityItemID, propertiesCopy, bufferOut, requestedProperties, didntFitProperties);
|
||||||
qCDebug(entities) << " properties:" << properties;
|
|
||||||
#endif
|
if (encodeResult != OctreeElement::NONE) {
|
||||||
queueOctreeEditMessage(type, bufferOut);
|
#ifdef WANT_DEBUG
|
||||||
if (type == PacketType::EntityAdd && !properties.getCertificateID().isEmpty()) {
|
qCDebug(entities) << "calling queueOctreeEditMessage()...";
|
||||||
emit addingEntityWithCertificate(properties.getCertificateID(), DependencyManager::get<AddressManager>()->getPlaceName());
|
qCDebug(entities) << " id:" << entityItemID;
|
||||||
|
qCDebug(entities) << " properties:" << properties;
|
||||||
|
#endif
|
||||||
|
queueOctreeEditMessage(type, bufferOut);
|
||||||
|
if (type == PacketType::EntityAdd && !properties.getCertificateID().isEmpty()) {
|
||||||
|
emit addingEntityWithCertificate(properties.getCertificateID(), DependencyManager::get<AddressManager>()->getPlaceName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if we still have properties to send, switch the message type to edit, and request only the packets that didn't fit
|
||||||
|
if (encodeResult != OctreeElement::COMPLETED) {
|
||||||
|
type = PacketType::EntityEdit;
|
||||||
|
requestedProperties = didntFitProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferOut.resize(NLPacket::maxPayloadSize(type)); // resize our output buffer for the next packet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,10 +14,6 @@
|
||||||
#include <QtCore/QObject>
|
#include <QtCore/QObject>
|
||||||
#include <QtEndian>
|
#include <QtEndian>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <openssl/err.h>
|
|
||||||
#include <openssl/rsa.h>
|
|
||||||
#include <openssl/pem.h>
|
|
||||||
#include <openssl/x509.h>
|
|
||||||
#include <NetworkingConstants.h>
|
#include <NetworkingConstants.h>
|
||||||
#include <NetworkAccessManager.h>
|
#include <NetworkAccessManager.h>
|
||||||
#include <QtNetwork/QNetworkReply>
|
#include <QtNetwork/QNetworkReply>
|
||||||
|
@ -87,7 +83,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
|
||||||
requestedProperties += PROP_ANGULAR_VELOCITY;
|
requestedProperties += PROP_ANGULAR_VELOCITY;
|
||||||
requestedProperties += PROP_ACCELERATION;
|
requestedProperties += PROP_ACCELERATION;
|
||||||
|
|
||||||
requestedProperties += PROP_DIMENSIONS; // NOTE: PROP_RADIUS obsolete
|
requestedProperties += PROP_DIMENSIONS;
|
||||||
requestedProperties += PROP_DENSITY;
|
requestedProperties += PROP_DENSITY;
|
||||||
requestedProperties += PROP_GRAVITY;
|
requestedProperties += PROP_GRAVITY;
|
||||||
requestedProperties += PROP_DAMPING;
|
requestedProperties += PROP_DAMPING;
|
||||||
|
@ -245,7 +241,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
|
||||||
APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, getLocalAngularVelocity());
|
APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, getLocalAngularVelocity());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_ACCELERATION, getAcceleration());
|
APPEND_ENTITY_PROPERTY(PROP_ACCELERATION, getAcceleration());
|
||||||
|
|
||||||
APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, getDimensions()); // NOTE: PROP_RADIUS obsolete
|
APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, getDimensions());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_DENSITY, getDensity());
|
APPEND_ENTITY_PROPERTY(PROP_DENSITY, getDensity());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_GRAVITY, getGravity());
|
APPEND_ENTITY_PROPERTY(PROP_GRAVITY, getGravity());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_DAMPING, getDamping());
|
APPEND_ENTITY_PROPERTY(PROP_DAMPING, getDamping());
|
||||||
|
@ -1575,116 +1571,6 @@ float EntityItem::getRadius() const {
|
||||||
return 0.5f * glm::length(getDimensions());
|
return 0.5f * glm::length(getDimensions());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checking Certifiable Properties
|
|
||||||
#define ADD_STRING_PROPERTY(n, N) if (!propertySet.get##N().isEmpty()) json[#n] = propertySet.get##N()
|
|
||||||
#define ADD_ENUM_PROPERTY(n, N) json[#n] = propertySet.get##N##AsString()
|
|
||||||
#define ADD_INT_PROPERTY(n, N) if (propertySet.get##N() != 0) json[#n] = (propertySet.get##N() == (quint32) -1) ? -1.0 : ((double) propertySet.get##N())
|
|
||||||
QByteArray EntityItem::getStaticCertificateJSON() const {
|
|
||||||
// Produce a compact json of every non-default static certificate property, with the property names in alphabetical order.
|
|
||||||
// The static certificate properties include all an only those properties that cannot be changed without altering the identity
|
|
||||||
// of the entity as reviewed during the certification submission.
|
|
||||||
|
|
||||||
QJsonObject json;
|
|
||||||
EntityItemProperties propertySet = getProperties(); // Note: neither EntityItem nor EntityitemProperties "properties" are QObject "properties"!
|
|
||||||
// It is important that this be reproducible in the same order each time. Since we also generate these on the server, we do it alphabetically
|
|
||||||
// to help maintainence in two different code bases.
|
|
||||||
if (!propertySet.getAnimation().getURL().isEmpty()) {
|
|
||||||
json["animationURL"] = propertySet.getAnimation().getURL();
|
|
||||||
}
|
|
||||||
ADD_STRING_PROPERTY(collisionSoundURL, CollisionSoundURL);
|
|
||||||
ADD_STRING_PROPERTY(compoundShapeURL, CompoundShapeURL);
|
|
||||||
ADD_INT_PROPERTY(editionNumber, EditionNumber);
|
|
||||||
ADD_INT_PROPERTY(instanceNumber, EntityInstanceNumber);
|
|
||||||
ADD_STRING_PROPERTY(itemArtist, ItemArtist);
|
|
||||||
ADD_STRING_PROPERTY(itemCategories, ItemCategories);
|
|
||||||
ADD_STRING_PROPERTY(itemDescription, ItemDescription);
|
|
||||||
ADD_STRING_PROPERTY(itemLicenseUrl, ItemLicense);
|
|
||||||
ADD_STRING_PROPERTY(itemName, ItemName);
|
|
||||||
ADD_INT_PROPERTY(limitedRun, LimitedRun);
|
|
||||||
ADD_STRING_PROPERTY(marketplaceID, MarketplaceID);
|
|
||||||
ADD_STRING_PROPERTY(modelURL, ModelURL);
|
|
||||||
ADD_STRING_PROPERTY(script, Script);
|
|
||||||
ADD_ENUM_PROPERTY(shapeType, ShapeType);
|
|
||||||
json["type"] = EntityTypes::getEntityTypeName(propertySet.getType());
|
|
||||||
|
|
||||||
return QJsonDocument(json).toJson(QJsonDocument::Compact);
|
|
||||||
}
|
|
||||||
QByteArray EntityItem::getStaticCertificateHash() const {
|
|
||||||
return QCryptographicHash::hash(getStaticCertificateJSON(), QCryptographicHash::Sha256);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EntityItem::verifyStaticCertificateProperties() {
|
|
||||||
// True IIF a non-empty certificateID matches the static certificate json.
|
|
||||||
// I.e., if we can verify that the certificateID was produced by High Fidelity signing the static certificate hash.
|
|
||||||
|
|
||||||
if (getCertificateID().isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QByteArray marketplacePublicKeyByteArray = EntityItem::_marketplacePublicKey.toUtf8();
|
|
||||||
const unsigned char* marketplacePublicKey = reinterpret_cast<const unsigned char*>(marketplacePublicKeyByteArray.constData());
|
|
||||||
int marketplacePublicKeyLength = marketplacePublicKeyByteArray.length();
|
|
||||||
|
|
||||||
BIO *bio = BIO_new_mem_buf((void*)marketplacePublicKey, marketplacePublicKeyLength);
|
|
||||||
EVP_PKEY* evp_key = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
|
|
||||||
if (evp_key) {
|
|
||||||
RSA* rsa = EVP_PKEY_get1_RSA(evp_key);
|
|
||||||
if (rsa) {
|
|
||||||
const QByteArray digestByteArray = getStaticCertificateHash();
|
|
||||||
const unsigned char* digest = reinterpret_cast<const unsigned char*>(digestByteArray.constData());
|
|
||||||
int digestLength = digestByteArray.length();
|
|
||||||
|
|
||||||
const QByteArray signatureByteArray = QByteArray::fromBase64(getCertificateID().toUtf8());
|
|
||||||
const unsigned char* signature = reinterpret_cast<const unsigned char*>(signatureByteArray.constData());
|
|
||||||
int signatureLength = signatureByteArray.length();
|
|
||||||
|
|
||||||
ERR_clear_error();
|
|
||||||
bool answer = RSA_verify(NID_sha256,
|
|
||||||
digest,
|
|
||||||
digestLength,
|
|
||||||
signature,
|
|
||||||
signatureLength,
|
|
||||||
rsa);
|
|
||||||
long error = ERR_get_error();
|
|
||||||
if (error != 0) {
|
|
||||||
const char* error_str = ERR_error_string(error, NULL);
|
|
||||||
qCWarning(entities) << "ERROR while verifying static certificate properties! RSA error:" << error_str
|
|
||||||
<< "\nStatic Cert JSON:" << getStaticCertificateJSON()
|
|
||||||
<< "\nKey:" << EntityItem::_marketplacePublicKey << "\nKey Length:" << marketplacePublicKeyLength
|
|
||||||
<< "\nDigest:" << digest << "\nDigest Length:" << digestLength
|
|
||||||
<< "\nSignature:" << signature << "\nSignature Length:" << signatureLength;
|
|
||||||
}
|
|
||||||
RSA_free(rsa);
|
|
||||||
if (bio) {
|
|
||||||
BIO_free(bio);
|
|
||||||
}
|
|
||||||
if (evp_key) {
|
|
||||||
EVP_PKEY_free(evp_key);
|
|
||||||
}
|
|
||||||
return answer;
|
|
||||||
} else {
|
|
||||||
if (bio) {
|
|
||||||
BIO_free(bio);
|
|
||||||
}
|
|
||||||
if (evp_key) {
|
|
||||||
EVP_PKEY_free(evp_key);
|
|
||||||
}
|
|
||||||
long error = ERR_get_error();
|
|
||||||
const char* error_str = ERR_error_string(error, NULL);
|
|
||||||
qCWarning(entities) << "Failed to verify static certificate properties! RSA error:" << error_str;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (bio) {
|
|
||||||
BIO_free(bio);
|
|
||||||
}
|
|
||||||
long error = ERR_get_error();
|
|
||||||
const char* error_str = ERR_error_string(error, NULL);
|
|
||||||
qCWarning(entities) << "Failed to verify static certificate properties! RSA error:" << error_str;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EntityItem::adjustShapeInfoByRegistration(ShapeInfo& info) const {
|
void EntityItem::adjustShapeInfoByRegistration(ShapeInfo& info) const {
|
||||||
if (_registrationPoint != ENTITY_ITEM_DEFAULT_REGISTRATION_POINT) {
|
if (_registrationPoint != ENTITY_ITEM_DEFAULT_REGISTRATION_POINT) {
|
||||||
glm::mat4 scale = glm::scale(getDimensions());
|
glm::mat4 scale = glm::scale(getDimensions());
|
||||||
|
|
|
@ -328,9 +328,6 @@ public:
|
||||||
void setEntityInstanceNumber(const quint32&);
|
void setEntityInstanceNumber(const quint32&);
|
||||||
QString getCertificateID() const;
|
QString getCertificateID() const;
|
||||||
void setCertificateID(const QString& value);
|
void setCertificateID(const QString& value);
|
||||||
QByteArray getStaticCertificateJSON() const;
|
|
||||||
QByteArray getStaticCertificateHash() const;
|
|
||||||
bool verifyStaticCertificateProperties();
|
|
||||||
|
|
||||||
// TODO: get rid of users of getRadius()...
|
// TODO: get rid of users of getRadius()...
|
||||||
float getRadius() const;
|
float getRadius() const;
|
||||||
|
|
|
@ -14,6 +14,15 @@
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QtCore/QJsonDocument>
|
#include <QtCore/QJsonDocument>
|
||||||
|
|
||||||
|
#include <openssl/err.h>
|
||||||
|
#include <openssl/rsa.h>
|
||||||
|
#include <openssl/pem.h>
|
||||||
|
#include <openssl/x509.h>
|
||||||
|
#include <NetworkingConstants.h>
|
||||||
|
#include <NetworkAccessManager.h>
|
||||||
|
#include <QtNetwork/QNetworkReply>
|
||||||
|
#include <QtNetwork/QNetworkRequest>
|
||||||
|
|
||||||
#include <ByteCountCoding.h>
|
#include <ByteCountCoding.h>
|
||||||
#include <GLMHelpers.h>
|
#include <GLMHelpers.h>
|
||||||
#include <RegisteredMetaTypes.h>
|
#include <RegisteredMetaTypes.h>
|
||||||
|
@ -1212,8 +1221,9 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
|
||||||
//
|
//
|
||||||
// TODO: Implement support for script and visible properties.
|
// TODO: Implement support for script and visible properties.
|
||||||
//
|
//
|
||||||
bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties,
|
OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties,
|
||||||
QByteArray& buffer) {
|
QByteArray& buffer, EntityPropertyFlags requestedProperties, EntityPropertyFlags& didntFitProperties) {
|
||||||
|
|
||||||
OctreePacketData ourDataPacket(false, buffer.size()); // create a packetData object to add out packet details too.
|
OctreePacketData ourDataPacket(false, buffer.size()); // create a packetData object to add out packet details too.
|
||||||
OctreePacketData* packetData = &ourDataPacket; // we want a pointer to this so we can use our APPEND_ENTITY_PROPERTY macro
|
OctreePacketData* packetData = &ourDataPacket; // we want a pointer to this so we can use our APPEND_ENTITY_PROPERTY macro
|
||||||
|
|
||||||
|
@ -1255,17 +1265,8 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
|
||||||
QByteArray encodedUpdateDelta = updateDeltaCoder;
|
QByteArray encodedUpdateDelta = updateDeltaCoder;
|
||||||
|
|
||||||
EntityPropertyFlags propertyFlags(PROP_LAST_ITEM);
|
EntityPropertyFlags propertyFlags(PROP_LAST_ITEM);
|
||||||
EntityPropertyFlags requestedProperties = properties.getChangedProperties();
|
|
||||||
EntityPropertyFlags propertiesDidntFit = requestedProperties;
|
EntityPropertyFlags propertiesDidntFit = requestedProperties;
|
||||||
|
|
||||||
// TODO: we need to handle the multi-pass form of this, similar to how we handle entity data
|
|
||||||
//
|
|
||||||
// If we are being called for a subsequent pass at appendEntityData() that failed to completely encode this item,
|
|
||||||
// then our modelTreeElementExtraEncodeData should include data about which properties we need to append.
|
|
||||||
//if (modelTreeElementExtraEncodeData && modelTreeElementExtraEncodeData->includedItems.contains(getEntityItemID())) {
|
|
||||||
// requestedProperties = modelTreeElementExtraEncodeData->includedItems.value(getEntityItemID());
|
|
||||||
//}
|
|
||||||
|
|
||||||
LevelDetails entityLevel = packetData->startLevel();
|
LevelDetails entityLevel = packetData->startLevel();
|
||||||
|
|
||||||
// Last Edited quint64 always first, before any other details, which allows us easy access to adjusting this
|
// Last Edited quint64 always first, before any other details, which allows us easy access to adjusting this
|
||||||
|
@ -1293,7 +1294,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
|
||||||
int propertyCount = 0;
|
int propertyCount = 0;
|
||||||
|
|
||||||
bool headerFits = successIDFits && successTypeFits && successLastEditedFits
|
bool headerFits = successIDFits && successTypeFits && successLastEditedFits
|
||||||
&& successLastUpdatedFits && successPropertyFlagsFits;
|
&& successLastUpdatedFits && successPropertyFlagsFits;
|
||||||
|
|
||||||
int startOfEntityItemData = packetData->getUncompressedByteOffset();
|
int startOfEntityItemData = packetData->getUncompressedByteOffset();
|
||||||
|
|
||||||
|
@ -1307,7 +1308,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
|
||||||
|
|
||||||
APPEND_ENTITY_PROPERTY(PROP_SIMULATION_OWNER, properties._simulationOwner.toByteArray());
|
APPEND_ENTITY_PROPERTY(PROP_SIMULATION_OWNER, properties._simulationOwner.toByteArray());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_POSITION, properties.getPosition());
|
APPEND_ENTITY_PROPERTY(PROP_POSITION, properties.getPosition());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, properties.getDimensions()); // NOTE: PROP_RADIUS obsolete
|
APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, properties.getDimensions());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_ROTATION, properties.getRotation());
|
APPEND_ENTITY_PROPERTY(PROP_ROTATION, properties.getRotation());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_DENSITY, properties.getDensity());
|
APPEND_ENTITY_PROPERTY(PROP_DENSITY, properties.getDensity());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_VELOCITY, properties.getVelocity());
|
APPEND_ENTITY_PROPERTY(PROP_VELOCITY, properties.getVelocity());
|
||||||
|
@ -1463,6 +1464,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
|
||||||
properties.getType() == EntityTypes::Sphere) {
|
properties.getType() == EntityTypes::Sphere) {
|
||||||
APPEND_ENTITY_PROPERTY(PROP_SHAPE, properties.getShape());
|
APPEND_ENTITY_PROPERTY(PROP_SHAPE, properties.getShape());
|
||||||
}
|
}
|
||||||
|
|
||||||
APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName());
|
APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, properties.getCollisionSoundURL());
|
APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, properties.getCollisionSoundURL());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, properties.getActionData());
|
APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, properties.getActionData());
|
||||||
|
@ -1513,12 +1515,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
|
||||||
|
|
||||||
// If any part of the model items didn't fit, then the element is considered partial
|
// If any part of the model items didn't fit, then the element is considered partial
|
||||||
if (appendState != OctreeElement::COMPLETED) {
|
if (appendState != OctreeElement::COMPLETED) {
|
||||||
// TODO: handle mechanism for handling partial fitting data!
|
didntFitProperties = propertiesDidntFit;
|
||||||
// add this item into our list for the next appendElementData() pass
|
|
||||||
//modelTreeElementExtraEncodeData->includedItems.insert(getEntityItemID(), propertiesDidntFit);
|
|
||||||
|
|
||||||
// for now, if it's not complete, it's not successful
|
|
||||||
success = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1534,11 +1531,15 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
|
||||||
} else {
|
} else {
|
||||||
qCDebug(entities) << "ERROR - encoded edit message doesn't fit in output buffer.";
|
qCDebug(entities) << "ERROR - encoded edit message doesn't fit in output buffer.";
|
||||||
success = false;
|
success = false;
|
||||||
|
appendState = OctreeElement::NONE; // if we got here, then we didn't include the item
|
||||||
|
// maybe we should assert!!!
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
packetData->discardSubTree();
|
packetData->discardSubTree();
|
||||||
}
|
}
|
||||||
return success;
|
|
||||||
|
|
||||||
|
return appendState;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray EntityItemProperties::getPackedNormals() const {
|
QByteArray EntityItemProperties::getPackedNormals() const {
|
||||||
|
@ -1664,7 +1665,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
|
||||||
|
|
||||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SIMULATION_OWNER, QByteArray, setSimulationOwner);
|
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SIMULATION_OWNER, QByteArray, setSimulationOwner);
|
||||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_POSITION, glm::vec3, setPosition);
|
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_POSITION, glm::vec3, setPosition);
|
||||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DIMENSIONS, glm::vec3, setDimensions); // NOTE: PROP_RADIUS obsolete
|
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DIMENSIONS, glm::vec3, setDimensions);
|
||||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ROTATION, glm::quat, setRotation);
|
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ROTATION, glm::quat, setRotation);
|
||||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DENSITY, float, setDensity);
|
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DENSITY, float, setDensity);
|
||||||
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VELOCITY, glm::vec3, setVelocity);
|
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VELOCITY, glm::vec3, setVelocity);
|
||||||
|
@ -2471,3 +2472,110 @@ bool EntityItemProperties::parentRelatedPropertyChanged() const {
|
||||||
bool EntityItemProperties::queryAACubeRelatedPropertyChanged() const {
|
bool EntityItemProperties::queryAACubeRelatedPropertyChanged() const {
|
||||||
return parentRelatedPropertyChanged() || dimensionsChanged();
|
return parentRelatedPropertyChanged() || dimensionsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Checking Certifiable Properties
|
||||||
|
#define ADD_STRING_PROPERTY(n, N) if (!get##N().isEmpty()) json[#n] = get##N()
|
||||||
|
#define ADD_ENUM_PROPERTY(n, N) json[#n] = get##N##AsString()
|
||||||
|
#define ADD_INT_PROPERTY(n, N) if (get##N() != 0) json[#n] = (get##N() == (quint32) -1) ? -1.0 : ((double) get##N())
|
||||||
|
QByteArray EntityItemProperties::getStaticCertificateJSON() const {
|
||||||
|
// Produce a compact json of every non-default static certificate property, with the property names in alphabetical order.
|
||||||
|
// The static certificate properties include all an only those properties that cannot be changed without altering the identity
|
||||||
|
// of the entity as reviewed during the certification submission.
|
||||||
|
|
||||||
|
QJsonObject json;
|
||||||
|
if (!getAnimation().getURL().isEmpty()) {
|
||||||
|
json["animationURL"] = getAnimation().getURL();
|
||||||
|
}
|
||||||
|
ADD_STRING_PROPERTY(collisionSoundURL, CollisionSoundURL);
|
||||||
|
ADD_STRING_PROPERTY(compoundShapeURL, CompoundShapeURL);
|
||||||
|
ADD_INT_PROPERTY(editionNumber, EditionNumber);
|
||||||
|
ADD_INT_PROPERTY(instanceNumber, EntityInstanceNumber);
|
||||||
|
ADD_STRING_PROPERTY(itemArtist, ItemArtist);
|
||||||
|
ADD_STRING_PROPERTY(itemCategories, ItemCategories);
|
||||||
|
ADD_STRING_PROPERTY(itemDescription, ItemDescription);
|
||||||
|
ADD_STRING_PROPERTY(itemLicenseUrl, ItemLicense);
|
||||||
|
ADD_STRING_PROPERTY(itemName, ItemName);
|
||||||
|
ADD_INT_PROPERTY(limitedRun, LimitedRun);
|
||||||
|
ADD_STRING_PROPERTY(marketplaceID, MarketplaceID);
|
||||||
|
ADD_STRING_PROPERTY(modelURL, ModelURL);
|
||||||
|
ADD_STRING_PROPERTY(script, Script);
|
||||||
|
ADD_ENUM_PROPERTY(shapeType, ShapeType);
|
||||||
|
json["type"] = EntityTypes::getEntityTypeName(getType());
|
||||||
|
|
||||||
|
return QJsonDocument(json).toJson(QJsonDocument::Compact);
|
||||||
|
}
|
||||||
|
QByteArray EntityItemProperties::getStaticCertificateHash() const {
|
||||||
|
return QCryptographicHash::hash(getStaticCertificateJSON(), QCryptographicHash::Sha256);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EntityItemProperties::verifyStaticCertificateProperties() {
|
||||||
|
// True IIF a non-empty certificateID matches the static certificate json.
|
||||||
|
// I.e., if we can verify that the certificateID was produced by High Fidelity signing the static certificate hash.
|
||||||
|
|
||||||
|
if (getCertificateID().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QByteArray marketplacePublicKeyByteArray = EntityItem::_marketplacePublicKey.toUtf8();
|
||||||
|
const unsigned char* marketplacePublicKey = reinterpret_cast<const unsigned char*>(marketplacePublicKeyByteArray.constData());
|
||||||
|
int marketplacePublicKeyLength = marketplacePublicKeyByteArray.length();
|
||||||
|
|
||||||
|
BIO *bio = BIO_new_mem_buf((void*)marketplacePublicKey, marketplacePublicKeyLength);
|
||||||
|
EVP_PKEY* evp_key = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
|
||||||
|
if (evp_key) {
|
||||||
|
RSA* rsa = EVP_PKEY_get1_RSA(evp_key);
|
||||||
|
if (rsa) {
|
||||||
|
const QByteArray digestByteArray = getStaticCertificateHash();
|
||||||
|
const unsigned char* digest = reinterpret_cast<const unsigned char*>(digestByteArray.constData());
|
||||||
|
int digestLength = digestByteArray.length();
|
||||||
|
|
||||||
|
const QByteArray signatureByteArray = QByteArray::fromBase64(getCertificateID().toUtf8());
|
||||||
|
const unsigned char* signature = reinterpret_cast<const unsigned char*>(signatureByteArray.constData());
|
||||||
|
int signatureLength = signatureByteArray.length();
|
||||||
|
|
||||||
|
ERR_clear_error();
|
||||||
|
bool answer = RSA_verify(NID_sha256,
|
||||||
|
digest,
|
||||||
|
digestLength,
|
||||||
|
signature,
|
||||||
|
signatureLength,
|
||||||
|
rsa);
|
||||||
|
long error = ERR_get_error();
|
||||||
|
if (error != 0) {
|
||||||
|
const char* error_str = ERR_error_string(error, NULL);
|
||||||
|
qCWarning(entities) << "ERROR while verifying static certificate properties! RSA error:" << error_str
|
||||||
|
<< "\nStatic Cert JSON:" << getStaticCertificateJSON()
|
||||||
|
<< "\nKey:" << EntityItem::_marketplacePublicKey << "\nKey Length:" << marketplacePublicKeyLength
|
||||||
|
<< "\nDigest:" << digest << "\nDigest Length:" << digestLength
|
||||||
|
<< "\nSignature:" << signature << "\nSignature Length:" << signatureLength;
|
||||||
|
}
|
||||||
|
RSA_free(rsa);
|
||||||
|
if (bio) {
|
||||||
|
BIO_free(bio);
|
||||||
|
}
|
||||||
|
if (evp_key) {
|
||||||
|
EVP_PKEY_free(evp_key);
|
||||||
|
}
|
||||||
|
return answer;
|
||||||
|
} else {
|
||||||
|
if (bio) {
|
||||||
|
BIO_free(bio);
|
||||||
|
}
|
||||||
|
if (evp_key) {
|
||||||
|
EVP_PKEY_free(evp_key);
|
||||||
|
}
|
||||||
|
long error = ERR_get_error();
|
||||||
|
const char* error_str = ERR_error_string(error, NULL);
|
||||||
|
qCWarning(entities) << "Failed to verify static certificate properties! RSA error:" << error_str;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (bio) {
|
||||||
|
BIO_free(bio);
|
||||||
|
}
|
||||||
|
long error = ERR_get_error();
|
||||||
|
const char* error_str = ERR_error_string(error, NULL);
|
||||||
|
qCWarning(entities) << "Failed to verify static certificate properties! RSA error:" << error_str;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -262,8 +262,8 @@ public:
|
||||||
float getLocalRenderAlpha() const { return _localRenderAlpha; }
|
float getLocalRenderAlpha() const { return _localRenderAlpha; }
|
||||||
void setLocalRenderAlpha(float value) { _localRenderAlpha = value; _localRenderAlphaChanged = true; }
|
void setLocalRenderAlpha(float value) { _localRenderAlpha = value; _localRenderAlphaChanged = true; }
|
||||||
|
|
||||||
static bool encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties,
|
static OctreeElement::AppendState encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties,
|
||||||
QByteArray& buffer);
|
QByteArray& buffer, EntityPropertyFlags requestedProperties, EntityPropertyFlags& didntFitProperties);
|
||||||
|
|
||||||
static bool encodeEraseEntityMessage(const EntityItemID& entityItemID, QByteArray& buffer);
|
static bool encodeEraseEntityMessage(const EntityItemID& entityItemID, QByteArray& buffer);
|
||||||
|
|
||||||
|
@ -336,6 +336,10 @@ public:
|
||||||
QByteArray getPackedStrokeColors() const;
|
QByteArray getPackedStrokeColors() const;
|
||||||
QByteArray packStrokeColors(const QVector<glm::vec3>& strokeColors) const;
|
QByteArray packStrokeColors(const QVector<glm::vec3>& strokeColors) const;
|
||||||
|
|
||||||
|
QByteArray getStaticCertificateJSON() const;
|
||||||
|
QByteArray getStaticCertificateHash() const;
|
||||||
|
bool verifyStaticCertificateProperties();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QString getCollisionMaskAsString() const;
|
QString getCollisionMaskAsString() const;
|
||||||
void setCollisionMaskFromString(const QString& maskString);
|
void setCollisionMaskFromString(const QString& maskString);
|
||||||
|
|
|
@ -21,8 +21,7 @@ enum EntityPropertyList {
|
||||||
// these properties are supported by the EntityItem base class
|
// these properties are supported by the EntityItem base class
|
||||||
PROP_VISIBLE,
|
PROP_VISIBLE,
|
||||||
PROP_POSITION,
|
PROP_POSITION,
|
||||||
PROP_RADIUS, // NOTE: PROP_RADIUS is obsolete and only included in old format streams
|
PROP_DIMENSIONS,
|
||||||
PROP_DIMENSIONS = PROP_RADIUS,
|
|
||||||
PROP_ROTATION,
|
PROP_ROTATION,
|
||||||
PROP_DENSITY,
|
PROP_DENSITY,
|
||||||
PROP_VELOCITY,
|
PROP_VELOCITY,
|
||||||
|
@ -47,13 +46,13 @@ enum EntityPropertyList {
|
||||||
PROP_ANGULAR_VELOCITY,
|
PROP_ANGULAR_VELOCITY,
|
||||||
PROP_ANGULAR_DAMPING,
|
PROP_ANGULAR_DAMPING,
|
||||||
PROP_COLLISIONLESS,
|
PROP_COLLISIONLESS,
|
||||||
PROP_DYNAMIC,
|
PROP_DYNAMIC, // 24
|
||||||
|
|
||||||
// property used by Light entity
|
// property used by Light entity
|
||||||
PROP_IS_SPOTLIGHT,
|
PROP_IS_SPOTLIGHT,
|
||||||
PROP_DIFFUSE_COLOR,
|
PROP_DIFFUSE_COLOR,
|
||||||
PROP_AMBIENT_COLOR_UNUSED,
|
PROP_AMBIENT_COLOR_UNUSED, // FIXME - No longer used, can remove and bump protocol
|
||||||
PROP_SPECULAR_COLOR_UNUSED,
|
PROP_SPECULAR_COLOR_UNUSED, // FIXME - No longer used, can remove and bump protocol
|
||||||
PROP_INTENSITY, // Previously PROP_CONSTANT_ATTENUATION
|
PROP_INTENSITY, // Previously PROP_CONSTANT_ATTENUATION
|
||||||
PROP_LINEAR_ATTENUATION_UNUSED,
|
PROP_LINEAR_ATTENUATION_UNUSED,
|
||||||
PROP_QUADRATIC_ATTENUATION_UNUSED,
|
PROP_QUADRATIC_ATTENUATION_UNUSED,
|
||||||
|
@ -61,30 +60,30 @@ enum EntityPropertyList {
|
||||||
PROP_CUTOFF,
|
PROP_CUTOFF,
|
||||||
|
|
||||||
// available to all entities
|
// available to all entities
|
||||||
PROP_LOCKED,
|
PROP_LOCKED, // 34
|
||||||
|
|
||||||
PROP_TEXTURES, // used by Model entities
|
PROP_TEXTURES, // used by Model entities
|
||||||
PROP_ANIMATION_SETTINGS, // used by Model entities
|
PROP_ANIMATION_SETTINGS_UNUSED, // FIXME - No longer used, can remove and bump protocol
|
||||||
PROP_USER_DATA, // all entities
|
PROP_USER_DATA, // all entities -- 37
|
||||||
PROP_SHAPE_TYPE, // used by Model + zones entities
|
PROP_SHAPE_TYPE, // used by Model + zones entities
|
||||||
|
|
||||||
// used by ParticleEffect entities
|
// used by ParticleEffect entities
|
||||||
PROP_MAX_PARTICLES,
|
PROP_MAX_PARTICLES, // 39
|
||||||
PROP_LIFESPAN,
|
PROP_LIFESPAN, // 40 -- used by all entities
|
||||||
PROP_EMIT_RATE,
|
PROP_EMIT_RATE,
|
||||||
PROP_EMIT_SPEED,
|
PROP_EMIT_SPEED,
|
||||||
PROP_EMIT_STRENGTH,
|
PROP_EMIT_STRENGTH,
|
||||||
PROP_EMIT_ACCELERATION,
|
PROP_EMIT_ACCELERATION, // FIXME - doesn't seem to get set in mark all changed????
|
||||||
PROP_PARTICLE_RADIUS,
|
PROP_PARTICLE_RADIUS, // 45!!
|
||||||
|
|
||||||
PROP_COMPOUND_SHAPE_URL, // used by Model + zones entities
|
PROP_COMPOUND_SHAPE_URL, // used by Model + zones entities
|
||||||
PROP_MARKETPLACE_ID, // all entities
|
PROP_MARKETPLACE_ID, // all entities
|
||||||
PROP_ACCELERATION, // all entities
|
PROP_ACCELERATION, // all entities
|
||||||
PROP_SIMULATION_OWNER, // formerly known as PROP_SIMULATOR_ID
|
PROP_SIMULATION_OWNER, // formerly known as PROP_SIMULATOR_ID
|
||||||
PROP_NAME, // all entities
|
PROP_NAME, // all entities -- 50
|
||||||
PROP_COLLISION_SOUND_URL,
|
PROP_COLLISION_SOUND_URL,
|
||||||
PROP_RESTITUTION,
|
PROP_RESTITUTION,
|
||||||
PROP_FRICTION,
|
PROP_FRICTION, // 53
|
||||||
|
|
||||||
PROP_VOXEL_VOLUME_SIZE,
|
PROP_VOXEL_VOLUME_SIZE,
|
||||||
PROP_VOXEL_DATA,
|
PROP_VOXEL_DATA,
|
||||||
|
@ -96,7 +95,7 @@ enum EntityPropertyList {
|
||||||
|
|
||||||
// used by hyperlinks
|
// used by hyperlinks
|
||||||
PROP_HREF,
|
PROP_HREF,
|
||||||
PROP_DESCRIPTION,
|
PROP_DESCRIPTION, // 61
|
||||||
|
|
||||||
PROP_FACE_CAMERA,
|
PROP_FACE_CAMERA,
|
||||||
PROP_SCRIPT_TIMESTAMP,
|
PROP_SCRIPT_TIMESTAMP,
|
||||||
|
|
|
@ -1827,7 +1827,7 @@ bool EntityScriptingInterface::verifyStaticCertificateProperties(const QUuid& en
|
||||||
_entityTree->withReadLock([&] {
|
_entityTree->withReadLock([&] {
|
||||||
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(entityID));
|
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(entityID));
|
||||||
if (entity) {
|
if (entity) {
|
||||||
result = entity->verifyStaticCertificateProperties();
|
result = entity->getProperties().verifyStaticCertificateProperties();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1195,7 +1195,6 @@ QByteArray EntityTree::computeEncryptedNonce(const QString& certID, const QStrin
|
||||||
|
|
||||||
QWriteLocker locker(&_certNonceMapLock);
|
QWriteLocker locker(&_certNonceMapLock);
|
||||||
_certNonceMap.insert(certID, nonce);
|
_certNonceMap.insert(certID, nonce);
|
||||||
qCDebug(entities) << "Challenging ownership of Cert ID" << certID << "by encrypting and sending nonce" << nonce << "to owner.";
|
|
||||||
|
|
||||||
return encryptedText;
|
return encryptedText;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1206,9 +1205,7 @@ QByteArray EntityTree::computeEncryptedNonce(const QString& certID, const QStrin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EntityTree::verifyDecryptedNonce(const QString& certID, const QString& decryptedNonce) {
|
bool EntityTree::verifyDecryptedNonce(const QString& certID, const QString& decryptedNonce, EntityItemID& id) {
|
||||||
|
|
||||||
EntityItemID id;
|
|
||||||
{
|
{
|
||||||
QReadLocker certIdMapLocker(&_entityCertificateIDMapLock);
|
QReadLocker certIdMapLocker(&_entityCertificateIDMapLock);
|
||||||
id = _entityCertificateIDMap.value(certID);
|
id = _entityCertificateIDMap.value(certID);
|
||||||
|
@ -1221,19 +1218,116 @@ bool EntityTree::verifyDecryptedNonce(const QString& certID, const QString& decr
|
||||||
}
|
}
|
||||||
|
|
||||||
bool verificationSuccess = (actualNonce == decryptedNonce);
|
bool verificationSuccess = (actualNonce == decryptedNonce);
|
||||||
if (!verificationSuccess) {
|
|
||||||
if (!id.isNull()) {
|
if (verificationSuccess) {
|
||||||
qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "failed; deleting entity" << id
|
qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "succeeded.";
|
||||||
<< "\nActual nonce:" << actualNonce << "\nDecrypted nonce:" << decryptedNonce;
|
|
||||||
deleteEntity(id, true);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "succeeded; keeping entity" << id;
|
qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "failed."
|
||||||
|
<< "\nActual nonce:" << actualNonce << "\nDecrypted nonce:" << decryptedNonce;
|
||||||
}
|
}
|
||||||
|
|
||||||
return verificationSuccess;
|
return verificationSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EntityTree::processChallengeOwnershipRequestPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) {
|
||||||
|
int certIDByteArraySize;
|
||||||
|
int encryptedTextByteArraySize;
|
||||||
|
int nodeToChallengeByteArraySize;
|
||||||
|
|
||||||
|
message.readPrimitive(&certIDByteArraySize);
|
||||||
|
message.readPrimitive(&encryptedTextByteArraySize);
|
||||||
|
message.readPrimitive(&nodeToChallengeByteArraySize);
|
||||||
|
|
||||||
|
QByteArray certID(message.read(certIDByteArraySize));
|
||||||
|
QByteArray encryptedText(message.read(encryptedTextByteArraySize));
|
||||||
|
QByteArray nodeToChallenge(message.read(nodeToChallengeByteArraySize));
|
||||||
|
|
||||||
|
sendChallengeOwnershipRequestPacket(certID, encryptedText, nodeToChallenge, sourceNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EntityTree::processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) {
|
||||||
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
|
||||||
|
int certIDByteArraySize;
|
||||||
|
int decryptedTextByteArraySize;
|
||||||
|
int challengingNodeUUIDByteArraySize;
|
||||||
|
|
||||||
|
message.readPrimitive(&certIDByteArraySize);
|
||||||
|
message.readPrimitive(&decryptedTextByteArraySize);
|
||||||
|
message.readPrimitive(&challengingNodeUUIDByteArraySize);
|
||||||
|
|
||||||
|
QByteArray certID(message.read(certIDByteArraySize));
|
||||||
|
QByteArray decryptedText(message.read(decryptedTextByteArraySize));
|
||||||
|
QUuid challengingNode = QUuid::fromRfc4122(message.read(challengingNodeUUIDByteArraySize));
|
||||||
|
|
||||||
|
auto challengeOwnershipReplyPacket = NLPacket::create(PacketType::ChallengeOwnershipReply,
|
||||||
|
certIDByteArraySize + decryptedText.length() + 2 * sizeof(int),
|
||||||
|
true);
|
||||||
|
challengeOwnershipReplyPacket->writePrimitive(certIDByteArraySize);
|
||||||
|
challengeOwnershipReplyPacket->writePrimitive(decryptedText.length());
|
||||||
|
challengeOwnershipReplyPacket->write(certID);
|
||||||
|
challengeOwnershipReplyPacket->write(decryptedText);
|
||||||
|
|
||||||
|
nodeList->sendPacket(std::move(challengeOwnershipReplyPacket), *(nodeList->nodeWithUUID(challengingNode)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void EntityTree::sendChallengeOwnershipPacket(const QString& certID, const QString& ownerKey, const EntityItemID& entityItemID, const SharedNodePointer& senderNode) {
|
||||||
|
// 1. Encrypt a nonce with the owner's public key
|
||||||
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
|
||||||
|
QByteArray encryptedText = computeEncryptedNonce(certID, ownerKey);
|
||||||
|
|
||||||
|
if (encryptedText == "") {
|
||||||
|
qCDebug(entities) << "CRITICAL ERROR: Couldn't compute encrypted nonce. Deleting entity...";
|
||||||
|
deleteEntity(entityItemID, true);
|
||||||
|
} else {
|
||||||
|
qCDebug(entities) << "Challenging ownership of Cert ID" << certID;
|
||||||
|
// 2. Send the encrypted text to the rezzing avatar's node
|
||||||
|
QByteArray certIDByteArray = certID.toUtf8();
|
||||||
|
int certIDByteArraySize = certIDByteArray.size();
|
||||||
|
auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnership,
|
||||||
|
certIDByteArraySize + encryptedText.length() + 2 * sizeof(int),
|
||||||
|
true);
|
||||||
|
challengeOwnershipPacket->writePrimitive(certIDByteArraySize);
|
||||||
|
challengeOwnershipPacket->writePrimitive(encryptedText.length());
|
||||||
|
challengeOwnershipPacket->write(certIDByteArray);
|
||||||
|
challengeOwnershipPacket->write(encryptedText);
|
||||||
|
nodeList->sendPacket(std::move(challengeOwnershipPacket), *senderNode);
|
||||||
|
|
||||||
|
// 3. Kickoff a 10-second timeout timer that deletes the entity if we don't get an ownership response in time
|
||||||
|
if (thread() != QThread::currentThread()) {
|
||||||
|
QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer", Q_ARG(const EntityItemID&, entityItemID));
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
startChallengeOwnershipTimer(entityItemID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EntityTree::sendChallengeOwnershipRequestPacket(const QByteArray& certID, const QByteArray& encryptedText, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode) {
|
||||||
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
|
||||||
|
// In this case, Client A is challenging Client B. Client A is inspecting a certified entity that it wants
|
||||||
|
// to make sure belongs to Avatar B.
|
||||||
|
QByteArray senderNodeUUID = senderNode->getUUID().toRfc4122();
|
||||||
|
|
||||||
|
int certIDByteArraySize = certID.length();
|
||||||
|
int encryptedTextByteArraySize = encryptedText.length();
|
||||||
|
int senderNodeUUIDSize = senderNodeUUID.length();
|
||||||
|
|
||||||
|
auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest,
|
||||||
|
certIDByteArraySize + encryptedTextByteArraySize + senderNodeUUIDSize + 3 * sizeof(int),
|
||||||
|
true);
|
||||||
|
challengeOwnershipPacket->writePrimitive(certIDByteArraySize);
|
||||||
|
challengeOwnershipPacket->writePrimitive(encryptedTextByteArraySize);
|
||||||
|
challengeOwnershipPacket->writePrimitive(senderNodeUUIDSize);
|
||||||
|
challengeOwnershipPacket->write(certID);
|
||||||
|
challengeOwnershipPacket->write(encryptedText);
|
||||||
|
challengeOwnershipPacket->write(senderNodeUUID);
|
||||||
|
|
||||||
|
nodeList->sendPacket(std::move(challengeOwnershipPacket), *(nodeList->nodeWithUUID(QUuid::fromRfc4122(nodeToChallenge))));
|
||||||
|
}
|
||||||
|
|
||||||
void EntityTree::validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation) {
|
void EntityTree::validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation) {
|
||||||
// Start owner verification.
|
// Start owner verification.
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
@ -1279,33 +1373,11 @@ void EntityTree::validatePop(const QString& certID, const EntityItemID& entityIt
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Second, challenge ownership of the PoP cert
|
// Second, challenge ownership of the PoP cert
|
||||||
// 1. Encrypt a nonce with the owner's public key
|
sendChallengeOwnershipPacket(certID,
|
||||||
QByteArray encryptedText = computeEncryptedNonce(certID, jsonObject["transfer_recipient_key"].toString());
|
jsonObject["transfer_recipient_key"].toString(),
|
||||||
|
entityItemID,
|
||||||
|
senderNode);
|
||||||
|
|
||||||
if (encryptedText == "") {
|
|
||||||
qCDebug(entities) << "CRITICAL ERROR: Couldn't compute encrypted nonce. Deleting entity...";
|
|
||||||
deleteEntity(entityItemID, true);
|
|
||||||
} else {
|
|
||||||
// 2. Send the encrypted text to the rezzing avatar's node
|
|
||||||
QByteArray certIDByteArray = certID.toUtf8();
|
|
||||||
int certIDByteArraySize = certIDByteArray.size();
|
|
||||||
auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnership,
|
|
||||||
certIDByteArraySize + encryptedText.length() + 2 * sizeof(int),
|
|
||||||
true);
|
|
||||||
challengeOwnershipPacket->writePrimitive(certIDByteArraySize);
|
|
||||||
challengeOwnershipPacket->writePrimitive(encryptedText.length());
|
|
||||||
challengeOwnershipPacket->write(certIDByteArray);
|
|
||||||
challengeOwnershipPacket->write(encryptedText);
|
|
||||||
nodeList->sendPacket(std::move(challengeOwnershipPacket), *senderNode);
|
|
||||||
|
|
||||||
// 3. Kickoff a 10-second timeout timer that deletes the entity if we don't get an ownership response in time
|
|
||||||
if (thread() != QThread::currentThread()) {
|
|
||||||
QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer", Q_ARG(const EntityItemID&, entityItemID));
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
startChallengeOwnershipTimer(entityItemID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; deleting entity" << entityItemID
|
qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; deleting entity" << entityItemID
|
||||||
|
@ -1329,7 +1401,12 @@ void EntityTree::processChallengeOwnershipPacket(ReceivedMessage& message, const
|
||||||
|
|
||||||
emit killChallengeOwnershipTimeoutTimer(certID);
|
emit killChallengeOwnershipTimeoutTimer(certID);
|
||||||
|
|
||||||
verifyDecryptedNonce(certID, decryptedText);
|
EntityItemID id;
|
||||||
|
if (!verifyDecryptedNonce(certID, decryptedText, id)) {
|
||||||
|
if (!id.isNull()) {
|
||||||
|
deleteEntity(id, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength,
|
int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength,
|
||||||
|
@ -1528,7 +1605,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
||||||
_totalCreates++;
|
_totalCreates++;
|
||||||
|
|
||||||
if (newEntity && isCertified && getIsServer()) {
|
if (newEntity && isCertified && getIsServer()) {
|
||||||
if (!newEntity->verifyStaticCertificateProperties()) {
|
if (!properties.verifyStaticCertificateProperties()) {
|
||||||
qCDebug(entities) << "User" << senderNode->getUUID()
|
qCDebug(entities) << "User" << senderNode->getUUID()
|
||||||
<< "attempted to add a certified entity with ID" << entityItemID << "which failed"
|
<< "attempted to add a certified entity with ID" << entityItemID << "which failed"
|
||||||
<< "static certificate verification.";
|
<< "static certificate verification.";
|
||||||
|
|
|
@ -93,6 +93,8 @@ public:
|
||||||
void fixupTerseEditLogging(EntityItemProperties& properties, QList<QString>& changedProperties);
|
void fixupTerseEditLogging(EntityItemProperties& properties, QList<QString>& changedProperties);
|
||||||
virtual int processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength,
|
virtual int processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength,
|
||||||
const SharedNodePointer& senderNode) override;
|
const SharedNodePointer& senderNode) override;
|
||||||
|
virtual void processChallengeOwnershipRequestPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override;
|
||||||
|
virtual void processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override;
|
||||||
virtual void processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override;
|
virtual void processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override;
|
||||||
|
|
||||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||||
|
@ -273,6 +275,9 @@ public:
|
||||||
|
|
||||||
static const float DEFAULT_MAX_TMP_ENTITY_LIFETIME;
|
static const float DEFAULT_MAX_TMP_ENTITY_LIFETIME;
|
||||||
|
|
||||||
|
QByteArray computeEncryptedNonce(const QString& certID, const QString ownerKey);
|
||||||
|
bool verifyDecryptedNonce(const QString& certID, const QString& decryptedNonce, EntityItemID& id);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void deletingEntity(const EntityItemID& entityID);
|
void deletingEntity(const EntityItemID& entityID);
|
||||||
void deletingEntityPointer(EntityItem* entityID);
|
void deletingEntityPointer(EntityItem* entityID);
|
||||||
|
@ -375,8 +380,8 @@ protected:
|
||||||
Q_INVOKABLE void startPendingTransferStatusTimer(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode);
|
Q_INVOKABLE void startPendingTransferStatusTimer(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QByteArray computeEncryptedNonce(const QString& certID, const QString ownerKey);
|
void sendChallengeOwnershipPacket(const QString& certID, const QString& ownerKey, const EntityItemID& entityItemID, const SharedNodePointer& senderNode);
|
||||||
bool verifyDecryptedNonce(const QString& certID, const QString& decryptedNonce);
|
void sendChallengeOwnershipRequestPacket(const QByteArray& certID, const QByteArray& encryptedText, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode);
|
||||||
void validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation);
|
void validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -667,8 +667,11 @@ bool AddressManager::handleViewpoint(const QString& viewpointString, bool should
|
||||||
qCDebug(networking) << "Orientation parsed from lookup string is invalid. Will not use for location change.";
|
qCDebug(networking) << "Orientation parsed from lookup string is invalid. Will not use for location change.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emit locationChangeRequired(newPosition, orientationChanged, newOrientation, shouldFace);
|
emit locationChangeRequired(newPosition, orientationChanged,
|
||||||
|
LookupTrigger::VisitUserFromPAL ? cancelOutRollAndPitch(newOrientation): newOrientation,
|
||||||
|
shouldFace
|
||||||
|
);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
qCDebug(networking) << "Could not jump to position from lookup string because it has an invalid value.";
|
qCDebug(networking) << "Could not jump to position from lookup string because it has an invalid value.";
|
||||||
|
@ -732,13 +735,14 @@ bool AddressManager::setDomainInfo(const QString& hostname, quint16 port, Lookup
|
||||||
return hostChanged;
|
return hostChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddressManager::goToUser(const QString& username) {
|
void AddressManager::goToUser(const QString& username, bool shouldMatchOrientation) {
|
||||||
QString formattedUsername = QUrl::toPercentEncoding(username);
|
QString formattedUsername = QUrl::toPercentEncoding(username);
|
||||||
|
|
||||||
// for history storage handling we remember how this lookup was trigged - for a username it's always user input
|
// for history storage handling we remember how this lookup was triggered - for a username it's always user input
|
||||||
QVariantMap requestParams;
|
QVariantMap requestParams;
|
||||||
requestParams.insert(LOOKUP_TRIGGER_KEY, static_cast<int>(LookupTrigger::UserInput));
|
requestParams.insert(LOOKUP_TRIGGER_KEY, static_cast<int>(
|
||||||
|
shouldMatchOrientation ? LookupTrigger::UserInput : LookupTrigger::VisitUserFromPAL
|
||||||
|
));
|
||||||
// this is a username - pull the captured name and lookup that user's location
|
// this is a username - pull the captured name and lookup that user's location
|
||||||
DependencyManager::get<AccountManager>()->sendRequest(GET_USER_LOCATION.arg(formattedUsername),
|
DependencyManager::get<AccountManager>()->sendRequest(GET_USER_LOCATION.arg(formattedUsername),
|
||||||
AccountManagerAuth::Optional,
|
AccountManagerAuth::Optional,
|
||||||
|
@ -840,8 +844,8 @@ void AddressManager::addCurrentAddressToHistory(LookupTrigger trigger) {
|
||||||
// and do not but it into the back stack
|
// and do not but it into the back stack
|
||||||
_forwardStack.push(currentAddress());
|
_forwardStack.push(currentAddress());
|
||||||
} else {
|
} else {
|
||||||
if (trigger == LookupTrigger::UserInput) {
|
if (trigger == LookupTrigger::UserInput || trigger == LookupTrigger::VisitUserFromPAL) {
|
||||||
// anyime the user has manually looked up an address we know we should clear the forward stack
|
// anyime the user has actively triggered an address we know we should clear the forward stack
|
||||||
_forwardStack.clear();
|
_forwardStack.clear();
|
||||||
|
|
||||||
emit goForwardPossible(false);
|
emit goForwardPossible(false);
|
||||||
|
|
|
@ -54,7 +54,8 @@ public:
|
||||||
DomainPathResponse,
|
DomainPathResponse,
|
||||||
Internal,
|
Internal,
|
||||||
AttemptedRefresh,
|
AttemptedRefresh,
|
||||||
Suggestions
|
Suggestions,
|
||||||
|
VisitUserFromPAL
|
||||||
};
|
};
|
||||||
|
|
||||||
bool isConnected();
|
bool isConnected();
|
||||||
|
@ -93,7 +94,7 @@ public slots:
|
||||||
void goToLocalSandbox(QString path = "", LookupTrigger trigger = LookupTrigger::StartupFromSettings) { handleUrl(SANDBOX_HIFI_ADDRESS + path, trigger); }
|
void goToLocalSandbox(QString path = "", LookupTrigger trigger = LookupTrigger::StartupFromSettings) { handleUrl(SANDBOX_HIFI_ADDRESS + path, trigger); }
|
||||||
void goToEntry(LookupTrigger trigger = LookupTrigger::StartupFromSettings) { handleUrl(DEFAULT_HIFI_ADDRESS, trigger); }
|
void goToEntry(LookupTrigger trigger = LookupTrigger::StartupFromSettings) { handleUrl(DEFAULT_HIFI_ADDRESS, trigger); }
|
||||||
|
|
||||||
void goToUser(const QString& username);
|
void goToUser(const QString& username, bool shouldMatchOrientation = true);
|
||||||
|
|
||||||
void refreshPreviousLookup();
|
void refreshPreviousLookup();
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ public:
|
||||||
HifiSockAddr(const sockaddr* sockaddr);
|
HifiSockAddr(const sockaddr* sockaddr);
|
||||||
|
|
||||||
bool isNull() const { return _address.isNull() && _port == 0; }
|
bool isNull() const { return _address.isNull() && _port == 0; }
|
||||||
void clear() { _address = QHostAddress::Null; _port = 0;}
|
void clear() { _address.clear(); _port = 0;}
|
||||||
|
|
||||||
HifiSockAddr& operator=(const HifiSockAddr& rhsSockAddr);
|
HifiSockAddr& operator=(const HifiSockAddr& rhsSockAddr);
|
||||||
void swap(HifiSockAddr& otherSockAddr);
|
void swap(HifiSockAddr& otherSockAddr);
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
|
|
||||||
namespace NetworkingConstants {
|
namespace NetworkingConstants {
|
||||||
// If you want to use STAGING instead of STABLE,
|
// If you want to use STAGING instead of STABLE,
|
||||||
// don't forget to ALSO change the Domain Server Metaverse Server URL, which is at the top of:
|
// don't forget to ALSO change the Domain Server Metaverse Server URL inside of:
|
||||||
// <hifi repo>\domain-server\resources\web\settings\js\settings.js
|
// <hifi repo>\domain-server\resources\web\js\shared.js
|
||||||
const QUrl METAVERSE_SERVER_URL_STABLE("https://metaverse.highfidelity.com");
|
const QUrl METAVERSE_SERVER_URL_STABLE("https://metaverse.highfidelity.com");
|
||||||
const QUrl METAVERSE_SERVER_URL_STAGING("https://staging.highfidelity.com");
|
const QUrl METAVERSE_SERVER_URL_STAGING("https://staging.highfidelity.com");
|
||||||
const QUrl METAVERSE_SERVER_URL = METAVERSE_SERVER_URL_STABLE;
|
const QUrl METAVERSE_SERVER_URL = METAVERSE_SERVER_URL_STABLE;
|
||||||
|
|
|
@ -124,6 +124,8 @@ public:
|
||||||
OctreeFileReplacementFromUrl,
|
OctreeFileReplacementFromUrl,
|
||||||
ChallengeOwnership,
|
ChallengeOwnership,
|
||||||
EntityScriptCallMethod,
|
EntityScriptCallMethod,
|
||||||
|
ChallengeOwnershipRequest,
|
||||||
|
ChallengeOwnershipReply,
|
||||||
NUM_PACKET_TYPE
|
NUM_PACKET_TYPE
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -212,6 +212,8 @@ public:
|
||||||
virtual bool handlesEditPacketType(PacketType packetType) const { return false; }
|
virtual bool handlesEditPacketType(PacketType packetType) const { return false; }
|
||||||
virtual int processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength,
|
virtual int processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength,
|
||||||
const SharedNodePointer& sourceNode) { return 0; }
|
const SharedNodePointer& sourceNode) { return 0; }
|
||||||
|
virtual void processChallengeOwnershipRequestPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { return; }
|
||||||
|
virtual void processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { return; }
|
||||||
virtual void processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { return; }
|
virtual void processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { return; }
|
||||||
|
|
||||||
virtual bool recurseChildrenWithData() const { return true; }
|
virtual bool recurseChildrenWithData() const { return true; }
|
||||||
|
|
|
@ -68,8 +68,8 @@ bool OctreePacketData::append(const unsigned char* data, int length) {
|
||||||
_dirty = true;
|
_dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool wantDebug = false;
|
#ifdef WANT_DEBUG
|
||||||
if (wantDebug && !success) {
|
if (!success) {
|
||||||
qCDebug(octree) << "OctreePacketData::append(const unsigned char* data, int length) FAILING....";
|
qCDebug(octree) << "OctreePacketData::append(const unsigned char* data, int length) FAILING....";
|
||||||
qCDebug(octree) << " length=" << length;
|
qCDebug(octree) << " length=" << length;
|
||||||
qCDebug(octree) << " _bytesAvailable=" << _bytesAvailable;
|
qCDebug(octree) << " _bytesAvailable=" << _bytesAvailable;
|
||||||
|
@ -77,6 +77,7 @@ bool OctreePacketData::append(const unsigned char* data, int length) {
|
||||||
qCDebug(octree) << " _targetSize=" << _targetSize;
|
qCDebug(octree) << " _targetSize=" << _targetSize;
|
||||||
qCDebug(octree) << " _bytesReserved=" << _bytesReserved;
|
qCDebug(octree) << " _bytesReserved=" << _bytesReserved;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -647,6 +648,13 @@ void OctreePacketData::debugContent() {
|
||||||
printf("\n");
|
printf("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OctreePacketData::debugBytes() {
|
||||||
|
qCDebug(octree) << " _bytesAvailable=" << _bytesAvailable;
|
||||||
|
qCDebug(octree) << " _bytesInUse=" << _bytesInUse;
|
||||||
|
qCDebug(octree) << " _targetSize=" << _targetSize;
|
||||||
|
qCDebug(octree) << " _bytesReserved=" << _bytesReserved;
|
||||||
|
}
|
||||||
|
|
||||||
int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QString& result) {
|
int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QString& result) {
|
||||||
uint16_t length;
|
uint16_t length;
|
||||||
memcpy(&length, dataBytes, sizeof(length));
|
memcpy(&length, dataBytes, sizeof(length));
|
||||||
|
|
|
@ -240,6 +240,7 @@ public:
|
||||||
|
|
||||||
/// displays contents for debugging
|
/// displays contents for debugging
|
||||||
void debugContent();
|
void debugContent();
|
||||||
|
void debugBytes();
|
||||||
|
|
||||||
static quint64 getCompressContentTime() { return _compressContentTime; } /// total time spent compressing content
|
static quint64 getCompressContentTime() { return _compressContentTime; } /// total time spent compressing content
|
||||||
static quint64 getCompressContentCalls() { return _compressContentCalls; } /// total calls to compress content
|
static quint64 getCompressContentCalls() { return _compressContentCalls; } /// total calls to compress content
|
||||||
|
|
|
@ -262,6 +262,8 @@ public:
|
||||||
|
|
||||||
Q_INVOKABLE MeshProxyList getMeshes() const;
|
Q_INVOKABLE MeshProxyList getMeshes() const;
|
||||||
|
|
||||||
|
void scaleToFit();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void loadURLFinished(bool success);
|
void loadURLFinished(bool success);
|
||||||
|
|
||||||
|
@ -320,7 +322,6 @@ protected:
|
||||||
virtual void initJointStates();
|
virtual void initJointStates();
|
||||||
|
|
||||||
void setScaleInternal(const glm::vec3& scale);
|
void setScaleInternal(const glm::vec3& scale);
|
||||||
void scaleToFit();
|
|
||||||
void snapToRegistrationPoint();
|
void snapToRegistrationPoint();
|
||||||
|
|
||||||
void computeMeshPartLocalBounds();
|
void computeMeshPartLocalBounds();
|
||||||
|
|
|
@ -91,32 +91,6 @@ QVariantMap HifiConfigVariantMap::mergeCLParametersWithJSONConfig(const QStringL
|
||||||
return mergedMap;
|
return mergedMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
HifiConfigVariantMap::HifiConfigVariantMap() :
|
|
||||||
_userConfigFilename(),
|
|
||||||
_masterConfig(),
|
|
||||||
_userConfig(),
|
|
||||||
_mergedConfig()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void HifiConfigVariantMap::loadMasterAndUserConfig(const QStringList& argumentList) {
|
|
||||||
// check if there is a master config file
|
|
||||||
const QString MASTER_CONFIG_FILE_OPTION = "--master-config";
|
|
||||||
|
|
||||||
int masterConfigIndex = argumentList.indexOf(MASTER_CONFIG_FILE_OPTION);
|
|
||||||
if (masterConfigIndex != -1) {
|
|
||||||
QString masterConfigFilepath = argumentList[masterConfigIndex + 1];
|
|
||||||
|
|
||||||
loadMapFromJSONFile(_masterConfig, masterConfigFilepath);
|
|
||||||
}
|
|
||||||
|
|
||||||
// load the user config - that method replace loadMasterAndUserConfig after the 1.7 migration
|
|
||||||
loadConfig(argumentList);
|
|
||||||
|
|
||||||
mergeMasterAndUserConfigs();
|
|
||||||
}
|
|
||||||
|
|
||||||
void HifiConfigVariantMap::loadConfig(const QStringList& argumentList) {
|
void HifiConfigVariantMap::loadConfig(const QStringList& argumentList) {
|
||||||
// load the user config
|
// load the user config
|
||||||
const QString USER_CONFIG_FILE_OPTION = "--user-config";
|
const QString USER_CONFIG_FILE_OPTION = "--user-config";
|
||||||
|
@ -172,14 +146,6 @@ void HifiConfigVariantMap::loadConfig(const QStringList& argumentList) {
|
||||||
loadMapFromJSONFile(_userConfig, _userConfigFilename);
|
loadMapFromJSONFile(_userConfig, _userConfigFilename);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HifiConfigVariantMap::mergeMasterAndUserConfigs() {
|
|
||||||
// the merged config is initially matched to the master config
|
|
||||||
_mergedConfig = _masterConfig;
|
|
||||||
|
|
||||||
// then we merge in anything missing from the user config
|
|
||||||
addMissingValuesToExistingMap(_mergedConfig, _userConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
void HifiConfigVariantMap::loadMapFromJSONFile(QVariantMap& existingMap, const QString& filename) {
|
void HifiConfigVariantMap::loadMapFromJSONFile(QVariantMap& existingMap, const QString& filename) {
|
||||||
QFile configFile(filename);
|
QFile configFile(filename);
|
||||||
|
|
||||||
|
|
|
@ -21,26 +21,19 @@ class HifiConfigVariantMap {
|
||||||
public:
|
public:
|
||||||
static QVariantMap mergeCLParametersWithJSONConfig(const QStringList& argumentList);
|
static QVariantMap mergeCLParametersWithJSONConfig(const QStringList& argumentList);
|
||||||
|
|
||||||
HifiConfigVariantMap();
|
|
||||||
void loadMasterAndUserConfig(const QStringList& argumentList);
|
|
||||||
void loadConfig(const QStringList& argumentList);
|
void loadConfig(const QStringList& argumentList);
|
||||||
|
|
||||||
const QVariant value(const QString& key) const { return _userConfig.value(key); }
|
const QVariant value(const QString& key) const { return _userConfig.value(key); }
|
||||||
QVariant* valueForKeyPath(const QString& keyPath, bool shouldCreateIfMissing = false)
|
QVariant* valueForKeyPath(const QString& keyPath, bool shouldCreateIfMissing = false)
|
||||||
{ return ::valueForKeyPath(_userConfig, keyPath, shouldCreateIfMissing); }
|
{ return ::valueForKeyPath(_userConfig, keyPath, shouldCreateIfMissing); }
|
||||||
|
|
||||||
QVariantMap& getMergedConfig() { return _mergedConfig; }
|
|
||||||
QVariantMap& getConfig() { return _userConfig; }
|
QVariantMap& getConfig() { return _userConfig; }
|
||||||
|
|
||||||
void mergeMasterAndUserConfigs();
|
|
||||||
|
|
||||||
const QString& getUserConfigFilename() const { return _userConfigFilename; }
|
const QString& getUserConfigFilename() const { return _userConfigFilename; }
|
||||||
private:
|
private:
|
||||||
QString _userConfigFilename;
|
QString _userConfigFilename;
|
||||||
|
|
||||||
QVariantMap _masterConfig;
|
|
||||||
QVariantMap _userConfig;
|
QVariantMap _userConfig;
|
||||||
QVariantMap _mergedConfig;
|
|
||||||
|
|
||||||
void loadMapFromJSONFile(QVariantMap& existingMap, const QString& filename);
|
void loadMapFromJSONFile(QVariantMap& existingMap, const QString& filename);
|
||||||
void addMissingValuesToExistingMap(QVariantMap& existingMap, const QVariantMap& newMap);
|
void addMissingValuesToExistingMap(QVariantMap& existingMap, const QVariantMap& newMap);
|
||||||
|
|
|
@ -23,9 +23,15 @@
|
||||||
|
|
||||||
#include "SharedLogging.h"
|
#include "SharedLogging.h"
|
||||||
|
|
||||||
|
const QSettings::Format JSON_FORMAT = QSettings::registerFormat("json", readJSONFile, writeJSONFile);
|
||||||
|
|
||||||
QSettings::SettingsMap jsonDocumentToVariantMap(const QJsonDocument& document);
|
QSettings::SettingsMap jsonDocumentToVariantMap(const QJsonDocument& document);
|
||||||
QJsonDocument variantMapToJsonDocument(const QSettings::SettingsMap& map);
|
QJsonDocument variantMapToJsonDocument(const QSettings::SettingsMap& map);
|
||||||
|
|
||||||
|
QString settingsFilename() {
|
||||||
|
return QSettings().fileName();
|
||||||
|
}
|
||||||
|
|
||||||
bool readJSONFile(QIODevice& device, QSettings::SettingsMap& map) {
|
bool readJSONFile(QIODevice& device, QSettings::SettingsMap& map) {
|
||||||
QJsonParseError jsonParseError;
|
QJsonParseError jsonParseError;
|
||||||
|
|
||||||
|
|
|
@ -14,12 +14,13 @@
|
||||||
|
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
|
|
||||||
|
extern const QSettings::Format JSON_FORMAT;
|
||||||
|
|
||||||
|
QString settingsFilename();
|
||||||
|
|
||||||
bool readJSONFile(QIODevice& device, QSettings::SettingsMap& map);
|
bool readJSONFile(QIODevice& device, QSettings::SettingsMap& map);
|
||||||
bool writeJSONFile(QIODevice& device, const QSettings::SettingsMap& map);
|
bool writeJSONFile(QIODevice& device, const QSettings::SettingsMap& map);
|
||||||
|
|
||||||
static const auto JSON_FORMAT = QSettings::registerFormat("json", readJSONFile, writeJSONFile);
|
|
||||||
|
|
||||||
void loadOldINIFile(QSettings& settings);
|
void loadOldINIFile(QSettings& settings);
|
||||||
|
|
||||||
|
|
||||||
#endif // hifi_SettingHelpers_h
|
#endif // hifi_SettingHelpers_h
|
||||||
|
|
|
@ -290,11 +290,17 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// change pricing to GET on button hover
|
// change pricing to GET/BUY on button hover
|
||||||
$('body').on('mouseenter', '#price-or-edit .price', function () {
|
$('body').on('mouseenter', '#price-or-edit .price', function () {
|
||||||
var $this = $(this);
|
var $this = $(this);
|
||||||
$this.data('initialHtml', $this.html());
|
$this.data('initialHtml', $this.html());
|
||||||
$this.text('GET');
|
|
||||||
|
var cost = $(this).parent().siblings().text();
|
||||||
|
if (parseInt(cost) > 0) {
|
||||||
|
$this.text('BUY');
|
||||||
|
} else {
|
||||||
|
$this.text('GET');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$('body').on('mouseleave', '#price-or-edit .price', function () {
|
$('body').on('mouseleave', '#price-or-edit .price', function () {
|
||||||
|
|
|
@ -74,7 +74,7 @@ void OctreeTests::propertyFlagsTests() {
|
||||||
EntityPropertyFlags props;
|
EntityPropertyFlags props;
|
||||||
props.setHasProperty(PROP_VISIBLE);
|
props.setHasProperty(PROP_VISIBLE);
|
||||||
props.setHasProperty(PROP_POSITION);
|
props.setHasProperty(PROP_POSITION);
|
||||||
props.setHasProperty(PROP_RADIUS);
|
props.setHasProperty(PROP_DIMENSIONS);
|
||||||
props.setHasProperty(PROP_MODEL_URL);
|
props.setHasProperty(PROP_MODEL_URL);
|
||||||
props.setHasProperty(PROP_COMPOUND_SHAPE_URL);
|
props.setHasProperty(PROP_COMPOUND_SHAPE_URL);
|
||||||
props.setHasProperty(PROP_ROTATION);
|
props.setHasProperty(PROP_ROTATION);
|
||||||
|
|
Loading…
Reference in a new issue