SL-20229 Add GenericStreamingMessage and use it to receive GLTF material overrides

master
RunitaiLinden 2023-08-29 16:42:55 -05:00
parent a3a8116060
commit 455bbcf742
18 changed files with 430 additions and 50 deletions

View File

@ -30,6 +30,7 @@ set(llmessage_SOURCE_FILES
lldispatcher.cpp
llexperiencecache.cpp
llfiltersd2xmlrpc.cpp
llgenericstreamingmessage.cpp
llhost.cpp
llhttpnode.cpp
llhttpsdhandler.cpp
@ -114,6 +115,7 @@ set(llmessage_HEADER_FILES
llextendedstatus.h
llfiltersd2xmlrpc.h
llfollowcamparams.h
llgenericstreamingmessage.h
llhost.h
llhttpnode.h
llhttpnodeadapter.h

View File

@ -0,0 +1,72 @@
/**
* @file llgenericstreamingmessage.cpp
* @brief Generic Streaming Message helpers. Shared between viewer and simulator.
*
* $LicenseInfo:firstyear=2023&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2023, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "linden_common.h"
#include "llgenericstreamingmessage.h"
#include "message.h"
void LLGenericStreamingMessage::send(LLMessageSystem* msg)
{
#if 0 // viewer cannot send GenericStreamingMessage
msg->newMessageFast(_PREHASH_GenericStreamingMessage);
if (mData.size() < 1024 * 7)
{ // disable warning about big messages unless we're sending a REALLY big message
msg->tempDisableWarnAboutBigMessage();
}
else
{
LL_WARNS("Messaging") << "Attempted to send too large GenericStreamingMessage, dropping." << LL_ENDL;
return;
}
msg->nextBlockFast(_PREHASH_MethodData);
msg->addU16Fast(_PREHASH_Method, mMethod);
msg->nextBlockFast(_PREHASH_DataBlock);
msg->addStringFast(_PREHASH_Data, mData.c_str());
#endif
}
void LLGenericStreamingMessage::unpack(LLMessageSystem* msg)
{
U16* m = (U16*)&mMethod; // squirrely pass enum as U16 by reference
msg->getU16Fast(_PREHASH_MethodData, _PREHASH_Method, *m);
constexpr int MAX_SIZE = 7 * 1024;
char buffer[MAX_SIZE];
// NOTE: don't use getStringFast to avoid 1200 byte truncation
U32 size = msg->getSizeFast(_PREHASH_DataBlock, _PREHASH_Data);
msg->getBinaryDataFast(_PREHASH_DataBlock, _PREHASH_Data, buffer, size, 0, MAX_SIZE);
mData.assign(buffer, size);
}

View File

@ -0,0 +1,50 @@
/**
* @file llgenericstreamingmessage.h
* @brief Generic Streaming Message helpers. Shared between viewer and simulator.
*
* $LicenseInfo:firstyear=2023&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2023, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#pragma once
#include <string>
#include "stdtypes.h"
class LLMessageSystem;
class LLGenericStreamingMessage
{
public:
enum Method : U16
{
METHOD_GLTF_MATERIAL_OVERRIDE = 0x4175,
METHOD_UNKNOWN = 0xFFFF,
};
void send(LLMessageSystem* msg);
void unpack(LLMessageSystem* msg);
Method mMethod = METHOD_UNKNOWN;
std::string mData;
};

View File

@ -1367,6 +1367,7 @@ char const* const _PREHASH_MuteType = LLMessageStringTable::getInstance()->getSt
char const* const _PREHASH_IMViaEMail = LLMessageStringTable::getInstance()->getString("IMViaEMail");
char const* const _PREHASH_RentPrice = LLMessageStringTable::getInstance()->getString("RentPrice");
char const* const _PREHASH_GenericMessage = LLMessageStringTable::getInstance()->getString("GenericMessage");
char const* const _PREHASH_GenericStreamingMessage = LLMessageStringTable::getInstance()->getString("GenericStreamingMessage");
char const* const _PREHASH_ChildAgentAlive = LLMessageStringTable::getInstance()->getString("ChildAgentAlive");
char const* const _PREHASH_AssetType = LLMessageStringTable::getInstance()->getString("AssetType");
char const* const _PREHASH_SpawnPointBlock = LLMessageStringTable::getInstance()->getString("SpawnPointBlock");

View File

@ -1368,6 +1368,7 @@ extern char const* const _PREHASH_MuteType;
extern char const* const _PREHASH_IMViaEMail;
extern char const* const _PREHASH_RentPrice;
extern char const* const _PREHASH_GenericMessage;
extern char const* const _PREHASH_GenericStreamingMessage;
extern char const* const _PREHASH_ChildAgentAlive;
extern char const* const _PREHASH_AssetType;
extern char const* const _PREHASH_SpawnPointBlock;

View File

@ -27,6 +27,7 @@
#include "linden_common.h"
#include "llgltfmaterial.h"
#include "llsdserialize.h"
// NOTE -- this should be the one and only place tiny_gltf.h is included
#include "tinygltf/tiny_gltf.h"
@ -693,6 +694,177 @@ void LLGLTFMaterial::applyOverride(const LLGLTFMaterial& override_mat)
}
}
void LLGLTFMaterial::getOverrideLLSD(const LLGLTFMaterial& override_mat, LLSD& data)
{
LL_PROFILE_ZONE_SCOPED;
llassert(data.isUndefined());
// make every effort to shave bytes here
for (int i = 0; i < GLTF_TEXTURE_INFO_COUNT; ++i)
{
LLUUID& texture_id = mTextureId[i];
const LLUUID& override_texture_id = override_mat.mTextureId[i];
if (override_texture_id.notNull() && override_texture_id != texture_id)
{
data["tex"][i] = LLSD::UUID(override_texture_id);
}
}
if (override_mat.mBaseColor != getDefaultBaseColor())
{
data["bc"] = override_mat.mBaseColor.getValue();
}
if (override_mat.mEmissiveColor != getDefaultEmissiveColor())
{
data["ec"] = override_mat.mEmissiveColor.getValue();
}
if (override_mat.mMetallicFactor != getDefaultMetallicFactor())
{
data["mf"] = override_mat.mMetallicFactor;
}
if (override_mat.mRoughnessFactor != getDefaultRoughnessFactor())
{
data["rf"] = override_mat.mRoughnessFactor;
}
if (override_mat.mAlphaMode != getDefaultAlphaMode() || override_mat.mOverrideAlphaMode)
{
data["am"] = override_mat.mAlphaMode;
}
if (override_mat.mAlphaCutoff != getDefaultAlphaCutoff())
{
data["ac"] = override_mat.mAlphaCutoff;
}
if (override_mat.mDoubleSided != getDefaultDoubleSided() || override_mat.mOverrideDoubleSided)
{
data["ds"] = override_mat.mDoubleSided;
}
for (int i = 0; i < GLTF_TEXTURE_INFO_COUNT; ++i)
{
if (override_mat.mTextureTransform[i].mOffset != getDefaultTextureOffset())
{
data["ti"][i]["o"] = override_mat.mTextureTransform[i].mOffset.getValue();
}
if (override_mat.mTextureTransform[i].mScale != getDefaultTextureScale())
{
data["ti"][i]["s"] = override_mat.mTextureTransform[i].mScale.getValue();
}
if (override_mat.mTextureTransform[i].mRotation != getDefaultTextureRotation())
{
data["ti"][i]["r"] = override_mat.mTextureTransform[i].mRotation;
}
}
#if 0
{
std::ostringstream ostr;
LLSDSerialize::serialize(data, ostr, LLSDSerialize::LLSD_NOTATION);
std::string param_str(ostr.str());
LL_INFOS() << param_str << LL_ENDL;
LL_INFOS() << "Notation size: " << param_str.size() << LL_ENDL;
}
{
std::ostringstream ostr;
LLSDSerialize::serialize(data, ostr, LLSDSerialize::LLSD_BINARY);
std::string param_str(ostr.str());
LL_INFOS() << "Binary size: " << param_str.size() << LL_ENDL;
}
#endif
}
void LLGLTFMaterial::applyOverrideLLSD(const LLSD& data)
{
const LLSD& tex = data["tex"];
if (tex.isArray())
{
for (int i = 0; i < tex.size(); ++i)
{
mTextureId[i] = tex[i].asUUID();
}
}
const LLSD& bc = data["bc"];
if (bc.isDefined())
{
mBaseColor.setValue(bc);
}
const LLSD& ec = data["ec"];
if (ec.isDefined())
{
mEmissiveColor.setValue(ec);
}
const LLSD& mf = data["mf"];
if (mf.isReal())
{
mMetallicFactor = mf.asReal();
}
const LLSD& rf = data["rf"];
if (rf.isReal())
{
mRoughnessFactor = rf.asReal();
}
const LLSD& am = data["am"];
if (am.isInteger())
{
mAlphaMode = (AlphaMode) am.asInteger();
}
const LLSD& ac = data["ac"];
if (ac.isReal())
{
mAlphaCutoff = ac.asReal();
}
const LLSD& ds = data["ds"];
if (data.isBoolean())
{
mDoubleSided = ds.asBoolean();
mOverrideDoubleSided = true;
}
const LLSD& ti = data["ti"];
if (ti.isArray())
{
for (int i = 0; i < GLTF_TEXTURE_INFO_COUNT; ++i)
{
const LLSD& o = ti[i]["o"];
if (o.isDefined())
{
mTextureTransform[i].mOffset.setValue(o);
}
const LLSD& s = ti[i]["s"];
if (s.isDefined())
{
mTextureTransform[i].mScale.setValue(s);
}
const LLSD& r = ti[i]["r"];
if (r.isReal())
{
mTextureTransform[i].mRotation = r.asReal();
}
}
}
}
LLUUID LLGLTFMaterial::getHash() const
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;

View File

@ -176,6 +176,7 @@ public:
// get the contents of this LLGLTFMaterial as a json string
std::string asJSON(bool prettyprint = false) const;
// initialize from given tinygltf::Model
// model - the model to reference
// mat_index - index of material in model's material array
@ -185,6 +186,14 @@ public:
void writeToModel(tinygltf::Model& model, S32 mat_index) const;
void applyOverride(const LLGLTFMaterial& override_mat);
// apply the given LLSD override data
void applyOverrideLLSD(const LLSD& data);
// Get the given override on this LLGLTFMaterial as LLSD
// override_mat -- the override source data
// data -- output LLSD object (should be passed in empty)
void getOverrideLLSD(const LLGLTFMaterial& override_mat, LLSD& data);
// For base materials only (i.e. assets). Clears transforms to
// default since they're not supported in assets yet.

View File

@ -4138,7 +4138,7 @@ U32 LLAppViewer::getObjectCacheVersion()
{
// Viewer object cache version, change if object update
// format changes. JC
const U32 INDRA_OBJECT_CACHE_VERSION = 16;
const U32 INDRA_OBJECT_CACHE_VERSION = 17;
return INDRA_OBJECT_CACHE_VERSION;
}

View File

@ -160,9 +160,9 @@ public:
// sides - array of S32 indices of texture entries
// gltf_json - array of corresponding Strings of GLTF json for override data
LLSD message;
bool success = true;
#if 0 //deprecated
for(const std::string& llsdRaw : strings)
{
std::istringstream llsdData(llsdRaw);
@ -198,6 +198,7 @@ public:
applyData(object_override);
}
#endif
return success;
}
@ -213,6 +214,7 @@ public:
{
// Parse the data
#if 0 // DEPRECATED
LL::WorkQueue::ptr_t main_queue = LL::WorkQueue::getInstance("mainloop");
LL::WorkQueue::ptr_t general_queue = LL::WorkQueue::getInstance("General");
@ -235,24 +237,17 @@ public:
results.reserve(sides.size());
// parse json
std::unordered_map<S32, std::string>::const_iterator iter = sides.begin();
std::unordered_map<S32, std::string>::const_iterator end = sides.end();
std::unordered_map<S32, LLSD>::const_iterator iter = sides.begin();
std::unordered_map<S32, LLSD>::const_iterator end = sides.end();
while (iter != end)
{
std::string warn_msg, error_msg;
ReturnData result;
bool success = result.mMaterial.fromJSON(iter->second, warn_msg, error_msg);
result.mSuccess = success;
result.mMaterial.applyOverrideLLSD(iter->second);
result.mSuccess = true;
result.mSide = iter->first;
if (!success)
{
LL_WARNS("GLTF") << "failed to parse GLTF override data. errors: " << error_msg << " | warnings: " << warn_msg << LL_ENDL;
}
results.push_back(result);
iter++;
}
@ -318,6 +313,7 @@ public:
}
});
}
#endif
}
private:
@ -330,6 +326,59 @@ namespace
LLGLTFMaterialOverrideDispatchHandler handle_gltf_override_message;
}
void LLGLTFMaterialList::applyOverrideMessage(LLMessageSystem* msg, const std::string& data_in)
{
std::istringstream str(data_in);
LLSD data;
LLSDSerialize::fromNotation(data, str, data_in.length());
const LLHost& host = msg->getSender();
LLViewerRegion* region = LLWorld::instance().getRegion(host);
if (region)
{
U32 local_id = data.get("id").asInteger();
LLUUID id;
gObjectList.getUUIDFromLocal(id, local_id, host.getAddress(), host.getPort());
LLViewerObject* obj = gObjectList.findObject(id);
if (obj)
{
const LLSD& tes = data["te"];
const LLSD& od = data["od"];
if (tes.isArray())
{
LLGLTFOverrideCacheEntry cache;
cache.mLocalId = local_id;
cache.mObjectId = id;
cache.mRegionHandle = region->getHandle();
for (int i = 0; i < tes.size(); ++i)
{
S32 te = tes[i].asInteger();
LLGLTFMaterial* mat = new LLGLTFMaterial(); // setTEGLTFMaterialOverride will take ownership
mat->applyOverrideLLSD(od[i]);
obj->setTEGLTFMaterialOverride(te, mat);
cache.mSides[te] = od[i];
cache.mGLTFMaterial[te] = mat;
if (obj->getTE(te) && obj->getTE(te)->isSelected())
{
handle_gltf_override_message.doSelectionCallbacks(id, te);
}
}
region->cacheFullUpdateGLTFOverride(cache);
}
}
}
}
void LLGLTFMaterialList::queueOverrideUpdate(const LLUUID& id, S32 side, LLGLTFMaterial* override_data)
{
#if 0

View File

@ -101,6 +101,9 @@ public:
static void loadCacheOverrides(const LLGLTFOverrideCacheEntry& override);
// Apply an override update with the given data
void applyOverrideMessage(LLMessageSystem* msg, const std::string& data);
private:
friend class LLGLTFMaterialOverrideDispatchHandler;
// save an override update that we got from the simulator for later (for example, if an override arrived for an unknown object)

View File

@ -2709,6 +2709,7 @@ void register_viewer_callbacks(LLMessageSystem* msg)
msg->setHandlerFunc("InitiateDownload", process_initiate_download);
msg->setHandlerFunc("LandStatReply", LLFloaterTopObjects::handle_land_reply);
msg->setHandlerFunc("GenericMessage", process_generic_message);
msg->setHandlerFunc("GenericStreamingMessage", process_generic_streaming_message);
msg->setHandlerFunc("LargeGenericMessage", process_large_generic_message);
msg->setHandlerFuncFast(_PREHASH_FeatureDisabled, process_feature_disabled_message);

View File

@ -32,9 +32,10 @@
#include "lldispatcher.h"
#include "lluuid.h"
#include "message.h"
#include "llgenericstreamingmessage.h"
#include "llagent.h"
#include "llgltfmateriallist.h"
LLDispatcher gGenericDispatcher;
@ -92,6 +93,21 @@ void process_generic_message(LLMessageSystem* msg, void**)
}
}
void process_generic_streaming_message(LLMessageSystem* msg, void**)
{
LLGenericStreamingMessage data;
data.unpack(msg);
switch (data.mMethod)
{
case LLGenericStreamingMessage::METHOD_GLTF_MATERIAL_OVERRIDE:
gGLTFMaterialList.applyOverrideMessage(msg, data.mData);
break;
default:
LL_WARNS() << "GenericStreamingMessage received unknown method: " << data.mMethod << LL_ENDL;
break;
}
}
void process_large_generic_message(LLMessageSystem* msg, void**)
{
LLUUID agent_id;

View File

@ -38,6 +38,7 @@ void send_generic_message(const std::string& method,
const LLUUID& invoice = LLUUID::null);
void process_generic_message(LLMessageSystem* msg, void**);
void process_generic_streaming_message(LLMessageSystem* msg, void**);
void process_large_generic_message(LLMessageSystem* msg, void**);

View File

@ -215,7 +215,7 @@ public:
LLVOCacheEntry::vocache_entry_set_t mVisibleEntries; //must-be-created visible entries wait for objects creation.
LLVOCacheEntry::vocache_entry_priority_list_t mWaitingList; //transient list storing sorted visible entries waiting for object creation.
std::set<U32> mNonCacheableCreatedList; //list of local ids of all non-cacheable objects
LLVOCacheEntry::vocache_gltf_overrides_map_t mGLTFOverridesJson; // for materials
LLVOCacheEntry::vocache_gltf_overrides_map_t mGLTFOverridesLLSD; // for materials
// time?
// LRU info?
@ -787,7 +787,7 @@ void LLViewerRegion::loadObjectCache()
{
LLVOCache & vocache = LLVOCache::instance();
vocache.readFromCache(mHandle, mImpl->mCacheID, mImpl->mCacheMap);
vocache.readGenericExtrasFromCache(mHandle, mImpl->mCacheID, mImpl->mGLTFOverridesJson);
vocache.readGenericExtrasFromCache(mHandle, mImpl->mCacheID, mImpl->mGLTFOverridesLLSD);
if (mImpl->mCacheMap.empty())
{
@ -817,7 +817,7 @@ void LLViewerRegion::saveObjectCache()
LLVOCache & instance = LLVOCache::instance();
instance.writeToCache(mHandle, mImpl->mCacheID, mImpl->mCacheMap, mCacheDirty, removal_enabled);
instance.writeGenericExtrasToCache(mHandle, mImpl->mCacheID, mImpl->mGLTFOverridesJson, mCacheDirty, removal_enabled);
instance.writeGenericExtrasToCache(mHandle, mImpl->mCacheID, mImpl->mGLTFOverridesLLSD, mCacheDirty, removal_enabled);
mCacheDirty = FALSE;
}
@ -2656,7 +2656,7 @@ LLViewerRegion::eCacheUpdateResult LLViewerRegion::cacheFullUpdate(LLViewerObjec
void LLViewerRegion::cacheFullUpdateGLTFOverride(const LLGLTFOverrideCacheEntry &override_data)
{
U32 local_id = override_data.mLocalId;
mImpl->mGLTFOverridesJson[local_id] = override_data;
mImpl->mGLTFOverridesLLSD[local_id] = override_data;
}
LLVOCacheEntry* LLViewerRegion::getCacheEntryForOctree(U32 local_id)
@ -3546,8 +3546,8 @@ std::string LLViewerRegion::getSimHostName()
void LLViewerRegion::loadCacheMiscExtras(U32 local_id)
{
auto iter = mImpl->mGLTFOverridesJson.find(local_id);
if (iter != mImpl->mGLTFOverridesJson.end())
auto iter = mImpl->mGLTFOverridesLLSD.find(local_id);
if (iter != mImpl->mGLTFOverridesLLSD.end())
{
LLGLTFMaterialList::loadCacheOverrides(iter->second);
}
@ -3559,8 +3559,8 @@ void LLViewerRegion::applyCacheMiscExtras(LLViewerObject* obj)
llassert(obj);
U32 local_id = obj->getLocalID();
auto iter = mImpl->mGLTFOverridesJson.find(local_id);
if (iter != mImpl->mGLTFOverridesJson.end())
auto iter = mImpl->mGLTFOverridesLLSD.find(local_id);
if (iter != mImpl->mGLTFOverridesLLSD.end())
{
llassert(iter->second.mGLTFMaterial.size() == iter->second.mSides.size());

View File

@ -85,40 +85,24 @@ bool LLGLTFOverrideCacheEntry::fromLLSD(const LLSD& data)
// message should be interpreted thusly:
/// sides is a list of face indices
// gltf_json is a list of corresponding json
// gltf_llsd is a list of corresponding GLTF override LLSD
// any side not represented in "sides" has no override
if (data.has("sides") && data.has("gltf_json"))
if (data.has("sides") && data.has("gltf_llsd"))
{
LLSD const& sides = data.get("sides");
LLSD const& gltf_json = data.get("gltf_json");
LLSD const& gltf_llsd = data.get("gltf_llsd");
if (sides.isArray() && gltf_json.isArray() &&
if (sides.isArray() && gltf_llsd.isArray() &&
sides.size() != 0 &&
sides.size() == gltf_json.size())
sides.size() == gltf_llsd.size())
{
for (int i = 0; i < sides.size(); ++i)
{
S32 side_idx = sides[i].asInteger();
std::string gltf_json_str = gltf_json[i].asString();
mSides[side_idx] = gltf_json_str;
mSides[side_idx] = gltf_llsd[i];
LLGLTFMaterial* override_mat = new LLGLTFMaterial();
std::string error, warn;
if (override_mat->fromJSON(gltf_json_str, warn, error))
{
mGLTFMaterial[side_idx] = override_mat;
}
else
{
LL_WARNS() << "Invalid GLTF string: \n" << gltf_json_str << LL_ENDL;
if (!error.empty())
{
LL_WARNS() << "Error: " << error << LL_ENDL;
}
if (!warn.empty())
{
LL_WARNS() << "Warning: " << warn << LL_ENDL;
}
}
override_mat->applyOverrideLLSD(gltf_llsd[i]);
mGLTFMaterial[side_idx] = override_mat;
}
}
else
@ -157,7 +141,7 @@ LLSD LLGLTFOverrideCacheEntry::toLLSD() const
// check that mSides and mGLTFMaterial have exactly the same keys present
llassert(mGLTFMaterial.count(side.first) == 1);
data["sides"].append(LLSD::Integer(side.first));
data["gltf_json"].append(side.second);
data["gltf_llsd"].append(side.second);
}
return data;

View File

@ -48,7 +48,7 @@ public:
LLUUID mObjectId;
U32 mLocalId = 0;
std::unordered_map<S32, std::string> mSides; //json per side
std::unordered_map<S32, LLSD> mSides; //override LLSD per side
std::unordered_map<S32, LLPointer<LLGLTFMaterial> > mGLTFMaterial; //GLTF material per side
U64 mRegionHandle = 0;
};

View File

@ -5790,6 +5790,25 @@ version 2.0
}
}
// GenericStreamingMessage
// Optimized generic message for streaming arbitrary data to viewer
// Avoid payloads over 7KB (8KB ceiling)
// Method -- magic number indicating method to use to decode payload:
// 0x4175 - GLTF material override data
// Payload -- data to be decoded
{
GenericStreamingMessage High 31 Trusted Unencoded
{
MethodData Single
{ Method U16 }
}
{
DataBlock Single
{ Data Variable 2 }
}
}
// LargeGenericMessage
// Similar to the above messages, but can handle larger payloads and serialized
// LLSD. Uses HTTP transport

View File

@ -1 +1 @@
dddb11f7e45f1779ff536819f36a20e63d572ba8
992450072b9c04c2157247be5cf5341b96d6f167