Skip to content

Instantly share code, notes, and snippets.

@ggcrunchy
Last active July 18, 2016 03:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ggcrunchy/d01d0487cdd77fc9006ac480266dfa76 to your computer and use it in GitHub Desktop.
Save ggcrunchy/d01d0487cdd77fc9006ac480266dfa76 to your computer and use it in GitHub Desktop.
An attempt at a nice byte-reading interface, for Lua-C/C++ interop in various projects
/*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* [ MIT license: http://www.opensource.org/licenses/mit-license.php ]
*/
#include "ByteReader.h"
// Constructor
ByteReader::ByteReader (lua_State * L, int arg, bool bReplace) : mBytes(NULL), mForbidden(false)
{
// Take the object's length as a best guess of the byte count. If the object is a string,
// these are exactly the bytes we want. Otherwise, look up the __bytes metafield.
mCount = lua_objlen(L, arg);
if (lua_isstring(L, arg)) mBytes = lua_tostring(L, arg);
else
{
if (arg < 0 && -arg <= lua_gettop(L)) arg = lua_gettop(L) + arg + 1; // account for negative indices in stack
if (luaL_getmetafield(L, arg, "__bytes")) // ...[, __bytes]
{
LookupBytes(L, arg);
if (bReplace && mBytes) lua_replace(L, arg); // ..., bytes, ...
}
else PushError(L, "Unable to read bytes from %s at index %i", arg); // ..., err
}
}
// Try to get bytes from an object's __bytes metafield
void ByteReader::LookupBytes (lua_State * L, int arg)
{
const char * names[] = { "forbidden", "vector", "uchar", "none", NULL };
int options[] = { eForbidden, eVector, eUchar, eNone };
int res = options[luaL_checkoption(L, arg, "none", names)];
if (res == eForbidden)
{
mForbidden = true;
PushError(L, "Byte-reading explicitly forbidden from %s at index %i", arg); // ..., __bytes, err
}
else if (!lua_isfunction(L, -1)) PointToBytes(L, arg, res);
else
{
lua_pushvalue(L, arg); // ..., __bytes, object
if (lua_pcall(L, 1, 1, 0) == 0) // ..., bytes / false[, err]
{
ByteReader result(L, -1, false);
mBytes = result.mBytes;
mCount = result.mCount;
}
}
}
// Point to the userdata's bytes, possibly at an offset
void ByteReader::PointToBytes (lua_State * L, int arg, int option)
{
if (lua_type(L, arg) == LUA_TUSERDATA)
{
if (option == eVector)
{
#ifndef BYTE_READER_NO_VECTOR
auto vec = (std::vector<unsigned char> *)lua_touserdata(L, arg);
mBytes = &vec[0];
mCount = vec->size();
#else
PushError(L, "Vector support is disabled");
#endif
}
else if (option == eUchar)
{
auto uchar = (ByteReaderUchar *)lua_touserdata(L, arg);
mBytes = uchar->mBytes;
mCount = uchar->mCount;
}
else
{
lua_Integer offset = luaL_optinteger(L, -1, 0);
if (offset < 0 || size_t(offset) > mCount) PushError(L, "Offset of %s at index %i fails bounds check", arg);
else
{
mBytes = ((unsigned char *)lua_touserdata(L, arg)) + offset;
mCount -= offset;
}
}
}
else PushError(L, "Cannot point to %s at index %i", arg);
}
// Wrapper for common error-pushing pattern
void ByteReader::PushError (lua_State * L, const char * format, int arg)
{
lua_pushfstring(L, format, luaL_typename(L, arg), arg); // ..., err
}
/*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* [ MIT license: http://www.opensource.org/licenses/mit-license.php ]
*/
#ifndef BYTE_READER_H
#define BYTE_READER_H
extern "C" {
#include "lua.h"
#include "lauxlib.h"
}
#ifndef BYTE_READER_NO_VECTOR
#include <vector>
#endif
struct ByteReaderUchar {
unsigned char * mBytes; // Stream of bytes
size_t mCount; // Byte count
};
class ByteReader {
public:
const void * mBytes; // Resolved byte stream
size_t mCount; // Number of bytes available in stream
bool mForbidden; // Byte-reading explicitly disallowed?
ByteReader (lua_State * L, int arg, bool bReplace = true);
enum { eForbidden, eVector, eUchar, eNone };
private:
void LookupBytes (lua_State * L, int arg);
void PointToBytes (lua_State * L, int arg, int option);
void PushError (lua_State * L, const char * format, int arg);
};
// A `ByteReader` transforms Lua inputs adhering to a simple protocol into
// a bytes-count pair that other C and C++ APIs are able to safely consume.
// The protocol goes as follows:
//
// The object at index `arg` is examined.
//
// If it happens to be a string, we can use its bytes and length directly.
//
// Failing that, the object's metatable (if it has one) is queried for field
// **__bytes**. Should no value at all be found, the object clearly does not
// honor the protocol, so we quit.
//
// When **__bytes** has a value of **"forbidden"**, the object's type is
// understood to say "byte-reading is specifically disallowed". In this
// situation, there is nothing more we can do, so we quit; the reader's
// **mForbidden** member will be **true** to indicate the special
// circumstances.
//
// If access is allowed, we next check whether the object is a full userdata.
// If so, there are a few possibilities:
//
// - **__bytes** has a value of **"uchar"**: the userdata's first bytes contain
// a `ByteReaderUchar` struct, with
// **mBytes** and **mCount** members.
//
// - **__bytes** has a value of **"vector"**: the userdata's first bytes hold a
// `std::vector<unsigned char>`. Its contents supply the bytes, with `size()`
// as the count. **BYTE\_READER\_NO\_VECTOR** may be defined to disable the
// vector code path.
//
// - Last but not least, we use the userdata's bytes and length directly, as
// done with strings. When **__bytes** contains an integer, it is assumed to be
// an offset (between 0 and the length minus 1) where valid bytes begin, with
// the final length shortened accordingly.
//
// If the object was not a full userdata, **__bytes** must be a function, to be
// called as
// object = func(object)
// The process (i.e. is the object a string? if not, does it have a **__bytes**
// metafield? et al.) then recurses on this new object and uses its result
// instead.
//
// When bytes are successfully found, the reader's **mBytes** member will point
// to them, with the byte count stored in **mCount**. If `bReplace` is **true**
// (the default), the bytes (either a string or full userdata) are moved into
// slot `arg`, overwriting the original object.
//
// Should an error happen along the way, **mBytes** will be NULL and an error
// message will be pushed on top of the stack.
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment