| //* Copyright 2017 The NXT Authors |
| //* |
| //* Licensed under the Apache License, Version 2.0 (the "License"); |
| //* you may not use this file except in compliance with the License. |
| //* You may obtain a copy of the License at |
| //* |
| //* http://www.apache.org/licenses/LICENSE-2.0 |
| //* |
| //* Unless required by applicable law or agreed to in writing, software |
| //* distributed under the License is distributed on an "AS IS" BASIS, |
| //* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| //* See the License for the specific language governing permissions and |
| //* limitations under the License. |
| |
| #include "wire/Wire.h" |
| #include "wire/WireCmd.h" |
| |
| #include "common/Assert.h" |
| |
| #include <algorithm> |
| #include <cstdlib> |
| #include <cstring> |
| #include <vector> |
| |
| namespace nxt { namespace wire { |
| |
| namespace server { |
| class Server; |
| |
| struct MapUserdata { |
| Server* server; |
| uint32_t bufferId; |
| uint32_t bufferSerial; |
| uint32_t requestSerial; |
| uint32_t size; |
| bool isWrite; |
| }; |
| |
| //* Stores what the backend knows about the type. |
| template<typename T> |
| struct ObjectDataBase { |
| //* The backend-provided handle and serial to this object. |
| T handle; |
| uint32_t serial = 0; |
| |
| //* Built object ID and serial, needed to send to the client along with builder error callbacks |
| //* TODO(cwallez@chromium.org) only have this for builder T |
| uint32_t builtObjectId = 0; |
| uint32_t builtObjectSerial = 0; |
| |
| //* Used by the error-propagation mechanism to know if this object is an error. |
| //* TODO(cwallez@chromium.org): this is doubling the memory usage of |
| //* std::vector<ObjectDataBase> consider making it a special marker value in handle instead. |
| bool valid; |
| //* Whether this object has been allocated, used by the KnownObjects queries |
| //* TODO(cwallez@chromium.org): make this an internal bit vector in KnownObjects. |
| bool allocated; |
| |
| //* TODO(cwallez@chromium.org): this is only useful for buffers |
| void* mappedData = nullptr; |
| size_t mappedDataSize = 0; |
| }; |
| |
| //* Keeps track of the mapping between client IDs and backend objects. |
| template<typename T> |
| class KnownObjects { |
| public: |
| using Data = ObjectDataBase<T>; |
| |
| KnownObjects() { |
| //* Pre-allocate ID 0 to refer to the null handle. |
| Data nullObject; |
| nullObject.handle = nullptr; |
| nullObject.valid = true; |
| nullObject.allocated = true; |
| mKnown.push_back(nullObject); |
| } |
| |
| //* Get a backend objects for a given client ID. |
| //* Returns nullptr if the ID hasn't previously been allocated. |
| const Data* Get(uint32_t id) const { |
| if (id >= mKnown.size()) { |
| return nullptr; |
| } |
| |
| const Data* data = &mKnown[id]; |
| |
| if (!data->allocated) { |
| return nullptr; |
| } |
| |
| return data; |
| } |
| Data* Get(uint32_t id) { |
| if (id >= mKnown.size()) { |
| return nullptr; |
| } |
| |
| Data* data = &mKnown[id]; |
| |
| if (!data->allocated) { |
| return nullptr; |
| } |
| |
| return data; |
| } |
| |
| //* Allocates the data for a given ID and returns it. |
| //* Returns nullptr if the ID is already allocated, or too far ahead. |
| //* Invalidates all the Data* |
| Data* Allocate(uint32_t id) { |
| if (id > mKnown.size()) { |
| return nullptr; |
| } |
| |
| Data data; |
| data.allocated = true; |
| data.valid = false; |
| data.handle = nullptr; |
| |
| if (id >= mKnown.size()) { |
| mKnown.push_back(data); |
| return &mKnown.back(); |
| } |
| |
| if (mKnown[id].allocated) { |
| return nullptr; |
| } |
| |
| mKnown[id] = data; |
| return &mKnown[id]; |
| } |
| |
| //* Marks an ID as deallocated |
| void Free(uint32_t id) { |
| ASSERT(id < mKnown.size()); |
| mKnown[id].allocated = false; |
| } |
| |
| private: |
| std::vector<Data> mKnown; |
| }; |
| |
| void ForwardDeviceErrorToServer(const char* message, nxtCallbackUserdata userdata); |
| |
| {% for type in by_category["object"] if type.is_builder%} |
| void Forward{{type.name.CamelCase()}}ToClient(nxtBuilderErrorStatus status, const char* message, nxtCallbackUserdata userdata1, nxtCallbackUserdata userdata2); |
| {% endfor %} |
| |
| void ForwardBufferMapReadAsync(nxtBufferMapAsyncStatus status, const void* ptr, nxtCallbackUserdata userdata); |
| void ForwardBufferMapWriteAsync(nxtBufferMapAsyncStatus status, void* ptr, nxtCallbackUserdata userdata); |
| |
| // A really really simple implementation of the DeserializeAllocator. It's main feature |
| // is that it has some inline storage so as to avoid allocations for the majority of |
| // commands. |
| class ServerAllocator : public DeserializeAllocator { |
| public: |
| ServerAllocator() { |
| Reset(); |
| } |
| |
| ~ServerAllocator() { |
| Reset(); |
| } |
| |
| void* GetSpace(size_t size) override { |
| // Return space in the current buffer if possible first. |
| if (mRemainingSize >= size) { |
| char* buffer = mCurrentBuffer; |
| mCurrentBuffer += size; |
| mRemainingSize -= size; |
| return buffer; |
| } |
| |
| // Otherwise allocate a new buffer and try again. |
| size_t allocationSize = std::max(size, size_t(2048)); |
| char* allocation = static_cast<char*>(malloc(allocationSize)); |
| if (allocation == nullptr) { |
| return nullptr; |
| } |
| |
| mAllocations.push_back(allocation); |
| mCurrentBuffer = allocation; |
| mRemainingSize = allocationSize; |
| return GetSpace(size); |
| } |
| |
| void Reset() { |
| for (auto allocation : mAllocations) { |
| free(allocation); |
| } |
| mAllocations.clear(); |
| |
| // The initial buffer is the inline buffer so that some allocations can be skipped |
| mCurrentBuffer = mStaticBuffer; |
| mRemainingSize = sizeof(mStaticBuffer); |
| } |
| |
| private: |
| size_t mRemainingSize = 0; |
| char* mCurrentBuffer = nullptr; |
| char mStaticBuffer[2048]; |
| std::vector<char*> mAllocations; |
| }; |
| |
| class Server : public CommandHandler, public ObjectIdResolver { |
| public: |
| Server(nxtDevice device, const nxtProcTable& procs, CommandSerializer* serializer) |
| : mProcs(procs), mSerializer(serializer) { |
| //* The client-server knowledge is bootstrapped with device 1. |
| auto* deviceData = mKnownDevice.Allocate(1); |
| deviceData->handle = device; |
| deviceData->valid = true; |
| |
| auto userdata = static_cast<nxtCallbackUserdata>(reinterpret_cast<intptr_t>(this)); |
| procs.deviceSetErrorCallback(device, ForwardDeviceErrorToServer, userdata); |
| } |
| |
| void OnDeviceError(const char* message) { |
| ReturnDeviceErrorCallbackCmd cmd; |
| cmd.messageStrlen = std::strlen(message); |
| |
| auto allocCmd = static_cast<ReturnDeviceErrorCallbackCmd*>(GetCmdSpace(sizeof(cmd))); |
| *allocCmd = cmd; |
| |
| char* messageAlloc = static_cast<char*>(GetCmdSpace(cmd.messageStrlen + 1)); |
| strcpy(messageAlloc, message); |
| } |
| |
| {% for type in by_category["object"] if type.is_builder%} |
| {% set Type = type.name.CamelCase() %} |
| void On{{Type}}Error(nxtBuilderErrorStatus status, const char* message, uint32_t id, uint32_t serial) { |
| auto* builder = mKnown{{Type}}.Get(id); |
| |
| if (builder == nullptr || builder->serial != serial) { |
| return; |
| } |
| |
| if (status != NXT_BUILDER_ERROR_STATUS_SUCCESS) { |
| builder->valid = false; |
| } |
| |
| if (status != NXT_BUILDER_ERROR_STATUS_UNKNOWN) { |
| //* Unknown is the only status that can be returned without a call to GetResult |
| //* so we are guaranteed to have created an object. |
| ASSERT(builder->builtObjectId != 0); |
| |
| Return{{Type}}ErrorCallbackCmd cmd; |
| cmd.builtObjectId = builder->builtObjectId; |
| cmd.builtObjectSerial = builder->builtObjectSerial; |
| cmd.status = status; |
| cmd.messageStrlen = std::strlen(message); |
| |
| auto allocCmd = static_cast<Return{{Type}}ErrorCallbackCmd*>(GetCmdSpace(sizeof(cmd))); |
| *allocCmd = cmd; |
| char* messageAlloc = static_cast<char*>(GetCmdSpace(strlen(message) + 1)); |
| strcpy(messageAlloc, message); |
| } |
| } |
| {% endfor %} |
| |
| void OnMapReadAsyncCallback(nxtBufferMapAsyncStatus status, const void* ptr, MapUserdata* data) { |
| ReturnBufferMapReadAsyncCallbackCmd cmd; |
| cmd.bufferId = data->bufferId; |
| cmd.bufferSerial = data->bufferSerial; |
| cmd.requestSerial = data->requestSerial; |
| cmd.status = status; |
| cmd.dataLength = 0; |
| |
| auto allocCmd = static_cast<ReturnBufferMapReadAsyncCallbackCmd*>(GetCmdSpace(sizeof(cmd))); |
| *allocCmd = cmd; |
| |
| if (status == NXT_BUFFER_MAP_ASYNC_STATUS_SUCCESS) { |
| allocCmd->dataLength = data->size; |
| |
| void* dataAlloc = GetCmdSpace(data->size); |
| memcpy(dataAlloc, ptr, data->size); |
| } |
| |
| delete data; |
| } |
| |
| void OnMapWriteAsyncCallback(nxtBufferMapAsyncStatus status, void* ptr, MapUserdata* data) { |
| ReturnBufferMapWriteAsyncCallbackCmd cmd; |
| cmd.bufferId = data->bufferId; |
| cmd.bufferSerial = data->bufferSerial; |
| cmd.requestSerial = data->requestSerial; |
| cmd.status = status; |
| |
| auto allocCmd = static_cast<ReturnBufferMapWriteAsyncCallbackCmd*>(GetCmdSpace(sizeof(cmd))); |
| *allocCmd = cmd; |
| |
| if (status == NXT_BUFFER_MAP_ASYNC_STATUS_SUCCESS) { |
| auto* selfData = mKnownBuffer.Get(data->bufferId); |
| ASSERT(selfData != nullptr); |
| |
| selfData->mappedData = ptr; |
| selfData->mappedDataSize = data->size; |
| } |
| |
| delete data; |
| } |
| |
| const char* HandleCommands(const char* commands, size_t size) override { |
| mProcs.deviceTick(mKnownDevice.Get(1)->handle); |
| |
| while (size >= sizeof(WireCmd)) { |
| WireCmd cmdId = *reinterpret_cast<const WireCmd*>(commands); |
| |
| bool success = false; |
| switch (cmdId) { |
| {% for type in by_category["object"] %} |
| {% for method in type.methods %} |
| {% set Suffix = as_MethodSuffix(type.name, method.name) %} |
| case WireCmd::{{Suffix}}: |
| success = Handle{{Suffix}}(&commands, &size); |
| break; |
| {% endfor %} |
| {% set Suffix = as_MethodSuffix(type.name, Name("destroy")) %} |
| case WireCmd::{{Suffix}}: |
| success = Handle{{Suffix}}(&commands, &size); |
| break; |
| {% endfor %} |
| case WireCmd::BufferMapAsync: |
| success = HandleBufferMapAsync(&commands, &size); |
| break; |
| case WireCmd::BufferUpdateMappedDataCmd: |
| success = HandleBufferUpdateMappedData(&commands, &size); |
| break; |
| |
| default: |
| success = false; |
| } |
| |
| if (!success) { |
| return nullptr; |
| } |
| mAllocator.Reset(); |
| } |
| |
| if (size != 0) { |
| return nullptr; |
| } |
| |
| return commands; |
| } |
| |
| private: |
| nxtProcTable mProcs; |
| CommandSerializer* mSerializer = nullptr; |
| |
| ServerAllocator mAllocator; |
| |
| void* GetCmdSpace(size_t size) { |
| return mSerializer->GetCmdSpace(size); |
| } |
| |
| // Implementation of the ObjectIdResolver interface |
| {% for type in by_category["object"] %} |
| DeserializeResult GetFromId(ObjectId id, {{as_cType(type.name)}}* out) const override { |
| auto data = mKnown{{type.name.CamelCase()}}.Get(id); |
| if (data == nullptr) { |
| return DeserializeResult::FatalError; |
| } |
| |
| *out = data->handle; |
| if (data->valid) { |
| return DeserializeResult::Success; |
| } else { |
| return DeserializeResult::ErrorObject; |
| } |
| } |
| {% endfor %} |
| |
| //* The list of known IDs for each object type. |
| {% for type in by_category["object"] %} |
| KnownObjects<{{as_cType(type.name)}}> mKnown{{type.name.CamelCase()}}; |
| {% endfor %} |
| |
| //* Helper function for the getting of the command data in command handlers. |
| //* Checks there is enough data left, updates the buffer / size and returns |
| //* the command (or nullptr for an error). |
| template <typename T> |
| static const T* GetData(const char** buffer, size_t* size, size_t count) { |
| // TODO(cwallez@chromium.org): Check for overflow |
| size_t totalSize = count * sizeof(T); |
| if (*size < totalSize) { |
| return nullptr; |
| } |
| |
| const T* data = reinterpret_cast<const T*>(*buffer); |
| |
| *buffer += totalSize; |
| *size -= totalSize; |
| |
| return data; |
| } |
| template <typename T> |
| static const T* GetCommand(const char** commands, size_t* size) { |
| return GetData<T>(commands, size, 1); |
| } |
| |
| {% set custom_pre_handler_commands = ["BufferUnmap"] %} |
| |
| bool PreHandleBufferUnmap(const BufferUnmapCmd& cmd) { |
| auto* selfData = mKnownBuffer.Get(cmd.selfId); |
| ASSERT(selfData != nullptr); |
| |
| selfData->mappedData = nullptr; |
| |
| return true; |
| } |
| |
| //* Implementation of the command handlers |
| {% for type in by_category["object"] %} |
| {% for method in type.methods %} |
| {% set Suffix = as_MethodSuffix(type.name, method.name) %} |
| |
| //* The generic command handlers |
| |
| bool Handle{{Suffix}}(const char** commands, size_t* size) { |
| {{Suffix}}Cmd cmd; |
| DeserializeResult deserializeResult = cmd.Deserialize(commands, size, &mAllocator, *this); |
| |
| if (deserializeResult == DeserializeResult::FatalError) { |
| return false; |
| } |
| |
| {% if Suffix in custom_pre_handler_commands %} |
| if (!PreHandle{{Suffix}}(cmd)) { |
| return false; |
| } |
| {% endif %} |
| |
| //* Unpack 'self' |
| auto* selfData = mKnown{{type.name.CamelCase()}}.Get(cmd.selfId); |
| ASSERT(selfData != nullptr); |
| |
| //* In all cases allocate the object data as it will be refered-to by the client. |
| {% set return_type = method.return_type %} |
| {% set returns = return_type.name.canonical_case() != "void" %} |
| {% if returns %} |
| {% set Type = method.return_type.name.CamelCase() %} |
| auto* resultData = mKnown{{Type}}.Allocate(cmd.resultId); |
| if (resultData == nullptr) { |
| return false; |
| } |
| resultData->serial = cmd.resultSerial; |
| |
| {% if type.is_builder %} |
| selfData->builtObjectId = cmd.resultId; |
| selfData->builtObjectSerial = cmd.resultSerial; |
| {% endif %} |
| {% endif %} |
| |
| //* After the data is allocated, apply the argument error propagation mechanism |
| if (deserializeResult == DeserializeResult::ErrorObject) { |
| {% if type.is_builder %} |
| selfData->valid = false; |
| //* If we are in GetResult, fake an error callback |
| {% if returns %} |
| On{{type.name.CamelCase()}}Error(NXT_BUILDER_ERROR_STATUS_ERROR, "Maybe monad", cmd.selfId, selfData->serial); |
| {% endif %} |
| {% endif %} |
| return true; |
| } |
| |
| {% if returns %} |
| auto result ={{" "}} |
| {%- endif %} |
| mProcs.{{as_varName(type.name, method.name)}}(cmd.self |
| {%- for arg in method.arguments -%} |
| , cmd.{{as_varName(arg.name)}} |
| {%- endfor -%} |
| ); |
| |
| {% if returns %} |
| resultData->handle = result; |
| resultData->valid = result != nullptr; |
| |
| //* builders remember the ID of the object they built so that they can send it |
| //* in the callback to the client. |
| {% if return_type.is_builder %} |
| if (result != nullptr) { |
| uint64_t userdata1 = static_cast<uint64_t>(reinterpret_cast<uintptr_t>(this)); |
| uint64_t userdata2 = (uint64_t(resultData->serial) << uint64_t(32)) + cmd.resultId; |
| mProcs.{{as_varName(return_type.name, Name("set error callback"))}}(result, Forward{{return_type.name.CamelCase()}}ToClient, userdata1, userdata2); |
| } |
| {% endif %} |
| {% endif %} |
| |
| return true; |
| } |
| {% endfor %} |
| |
| //* Handlers for the destruction of objects: clients do the tracking of the |
| //* reference / release and only send destroy on refcount = 0. |
| {% set Suffix = as_MethodSuffix(type.name, Name("destroy")) %} |
| bool Handle{{Suffix}}(const char** commands, size_t* size) { |
| const auto* cmd = GetCommand<{{Suffix}}Cmd>(commands, size); |
| if (cmd == nullptr) { |
| return false; |
| } |
| |
| ObjectId objectId = cmd->objectId; |
| |
| //* ID 0 are reserved for nullptr and cannot be destroyed. |
| if (objectId == 0) { |
| return false; |
| } |
| |
| auto* data = mKnown{{type.name.CamelCase()}}.Get(objectId); |
| if (data == nullptr) { |
| return false; |
| } |
| |
| if (data->valid) { |
| mProcs.{{as_varName(type.name, Name("release"))}}(data->handle); |
| } |
| |
| mKnown{{type.name.CamelCase()}}.Free(objectId); |
| return true; |
| } |
| {% endfor %} |
| |
| bool HandleBufferMapAsync(const char** commands, size_t* size) { |
| //* These requests are just forwarded to the buffer, with userdata containing what the client |
| //* will require in the return command. |
| const auto* cmd = GetCommand<BufferMapAsyncCmd>(commands, size); |
| if (cmd == nullptr) { |
| return false; |
| } |
| |
| ObjectId bufferId = cmd->bufferId; |
| uint32_t requestSerial = cmd->requestSerial; |
| uint32_t requestSize = cmd->size; |
| uint32_t requestStart = cmd->start; |
| bool isWrite = cmd->isWrite; |
| |
| auto* buffer = mKnownBuffer.Get(bufferId); |
| if (buffer == nullptr) { |
| return false; |
| } |
| |
| auto* data = new MapUserdata; |
| data->server = this; |
| data->bufferId = bufferId; |
| data->bufferSerial = buffer->serial; |
| data->requestSerial = requestSerial; |
| data->size = requestSize; |
| data->isWrite = isWrite; |
| |
| auto userdata = static_cast<uint64_t>(reinterpret_cast<uintptr_t>(data)); |
| |
| if (!buffer->valid) { |
| //* Fake the buffer returning a failure, data will be freed in this call. |
| if (isWrite) { |
| ForwardBufferMapWriteAsync(NXT_BUFFER_MAP_ASYNC_STATUS_ERROR, nullptr, userdata); |
| } else { |
| ForwardBufferMapReadAsync(NXT_BUFFER_MAP_ASYNC_STATUS_ERROR, nullptr, userdata); |
| } |
| return true; |
| } |
| |
| if (isWrite) { |
| mProcs.bufferMapWriteAsync(buffer->handle, requestStart, requestSize, ForwardBufferMapWriteAsync, userdata); |
| } else { |
| mProcs.bufferMapReadAsync(buffer->handle, requestStart, requestSize, ForwardBufferMapReadAsync, userdata); |
| } |
| |
| return true; |
| } |
| |
| bool HandleBufferUpdateMappedData(const char** commands, size_t* size) { |
| const auto* cmd = GetCommand<BufferUpdateMappedDataCmd>(commands, size); |
| if (cmd == nullptr) { |
| return false; |
| } |
| |
| ObjectId bufferId = cmd->bufferId; |
| size_t dataLength = cmd->dataLength; |
| |
| auto* buffer = mKnownBuffer.Get(bufferId); |
| if (buffer == nullptr || !buffer->valid || buffer->mappedData == nullptr || |
| buffer->mappedDataSize != dataLength) { |
| return false; |
| } |
| |
| const char* data = GetData<char>(commands, size, dataLength); |
| if (data == nullptr) { |
| return false; |
| } |
| |
| memcpy(buffer->mappedData, data, dataLength); |
| |
| return true; |
| } |
| }; |
| |
| void ForwardDeviceErrorToServer(const char* message, nxtCallbackUserdata userdata) { |
| auto server = reinterpret_cast<Server*>(static_cast<intptr_t>(userdata)); |
| server->OnDeviceError(message); |
| } |
| |
| {% for type in by_category["object"] if type.is_builder%} |
| void Forward{{type.name.CamelCase()}}ToClient(nxtBuilderErrorStatus status, const char* message, nxtCallbackUserdata userdata1, nxtCallbackUserdata userdata2) { |
| auto server = reinterpret_cast<Server*>(static_cast<uintptr_t>(userdata1)); |
| uint32_t id = userdata2 & 0xFFFFFFFFu; |
| uint32_t serial = userdata2 >> uint64_t(32); |
| server->On{{type.name.CamelCase()}}Error(status, message, id, serial); |
| } |
| {% endfor %} |
| |
| void ForwardBufferMapReadAsync(nxtBufferMapAsyncStatus status, const void* ptr, nxtCallbackUserdata userdata) { |
| auto data = reinterpret_cast<MapUserdata*>(static_cast<uintptr_t>(userdata)); |
| data->server->OnMapReadAsyncCallback(status, ptr, data); |
| } |
| |
| void ForwardBufferMapWriteAsync(nxtBufferMapAsyncStatus status, void* ptr, nxtCallbackUserdata userdata) { |
| auto data = reinterpret_cast<MapUserdata*>(static_cast<uintptr_t>(userdata)); |
| data->server->OnMapWriteAsyncCallback(status, ptr, data); |
| } |
| } |
| |
| CommandHandler* NewServerCommandHandler(nxtDevice device, const nxtProcTable& procs, CommandSerializer* serializer) { |
| return new server::Server(device, procs, serializer); |
| } |
| |
| }} // namespace nxt::wire |