From 640b240570709b2a42df00a7b2ce455acb9becbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergiusz=20=27q3k=27=20Baza=C5=84ski?= Date: Thu, 5 Sep 2013 22:48:54 +0200 Subject: [PATCH] Initial commit of test/ugly/prototype code. --- .gitignore | 2 + README | 19 + code.lua | 234 +++++++++ lua-libs/JSON.lua | 861 +++++++++++++++++++++++++++++++++ lua-libs/bit.lua | 260 ++++++++++ lua-libs/luai2c.tar.bz2 | Bin 0 -> 1899 bytes lua-libs/luasha2-0.1.tar.gz | Bin 0 -> 11892 bytes lua-libs/mips-bin/libluai2c.so | Bin 0 -> 7955 bytes lua-libs/mips-bin/libsha2.so | Bin 0 -> 17054 bytes 9 files changed, 1376 insertions(+) create mode 100644 .gitignore create mode 100644 README create mode 100644 code.lua create mode 100644 lua-libs/JSON.lua create mode 100644 lua-libs/bit.lua create mode 100644 lua-libs/luai2c.tar.bz2 create mode 100644 lua-libs/luasha2-0.1.tar.gz create mode 100755 lua-libs/mips-bin/libluai2c.so create mode 100755 lua-libs/mips-bin/libsha2.so diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..674aaad --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +openwrt +sdk diff --git a/README b/README new file mode 100644 index 0000000..6ababf4 --- /dev/null +++ b/README @@ -0,0 +1,19 @@ +This is the Hackerspace Warsaw electronic lock mk2 project. + +Work in progress! This is a prototype for a TP-Link WR703n based hardware +solution - with a I2C bus on which there is a MAX7300 IO expander and a PN532 +NFC tag reader. The I2C is software bit-banged by the kernel, drivers for both +the IO expander and the PN532 are written in Lua. + +Files: + hslockmk2/ - this directory + /code.lua - main Lua source code + /lua-libs/ - necessary Lua libraries + /mips-bin/ - C libraries compiled for MIPS/OpenWRT + luai2c.tar.gz - C i2c library + luasha2.tar.gz - C sha2 library + JSON.lua - JSON library + bit.lua - bitwise operations for Lua 5.1 + +You will additonally need luasec, luaposix and luasocket. But you can easily +find these in your favourite distribution, or even the OpenWRT source tree. diff --git a/code.lua b/code.lua new file mode 100644 index 0000000..818e3c1 --- /dev/null +++ b/code.lua @@ -0,0 +1,234 @@ +-- The MIT License (MIT) +-- +-- Copyright (c) 2013 Sergiusz 'q3k' BazaƄski +-- +-- 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. + +-- This is the HSWAW lock mk2. source code. WIP. refucktor badly needed. + +local i2c = require("libluai2c") +require('bit') +require('socket') +require('posix') +local sha2 = require('libsha2') +local https = require('ssl.https') + +q3k = {} +q3k.Config = {} + +q3k.Config.I2CBus = 0 + +q3k.Config.I2CGPIO = 0x40 +q3k.Config.I2CNFC = 0x24 + +q3k.I2CWrite = function(Address, ...) + local Data = {...} + local Bytes = string.format("%02x ", Address) + for _, Byte in pairs(Data) do + Bytes = Bytes .. string.format("%02x ", Byte) + end + local DataString = string.char(unpack(Data)) + return i2c.write(q3k.Config.I2CBus, Address, DataString) +end + +q3k.I2CRead = function(Address, BytesOut, ...) + local Data = {...} + local DataString = string.char(unpack(Data)) + local Status, Data = i2c.read(q3k.Config.I2CBus, Address, BytesOut, DataString) + local Return = {} + + if Status == 0 then + Data:gsub(".", function(c) + Return[#Return+1] = string.byte(c) + end) + return Return + end +end + +-- sigh. luaposix on openwrt doesn't have nanosleep() +q3k.Sleep = function(Seconds) + socket.select(nil, nil, Seconds) +end + +-- GPIO Functions +q3k.SetupGPIO = function() + -- Set up pins 28, 29, 30, 31 as output + q3k.I2CWrite(q3k.Config.I2CGPIO, 0x0F, 0x55) + q3k.DoorClose() + -- Set up pins 12, 13, 14, 15 as input without pullups + q3k.I2CWrite(q3k.Config.I2CGPIO, 0x0B, 0xAA) + + -- Turn on GPIO Multiplexer + q3k.I2CWrite(q3k.Config.I2CGPIO, 0x04, 0x01) +end + +q3k.DoorOpen = function() + -- Turn on pin 31 (door) + q3k.I2CWrite(q3k.Config.I2CGPIO, 0x3F, 0x01) +end + +q3k.DoorClose = function() + -- Turn off pin 31 (door) + q3k.I2CWrite(q3k.Config.I2CGPIO, 0x3F, 0x00) +end + +-- NFC (PN532) functions +local PN532_PREAMBLE = 0x00 +local PN532_STARTCODE1 = 0x00 +local PN532_STARTCODE2 = 0xFF +local PN532_POSTAMBLE = 0x00 + +local PN532_HOSTTOPN532 = 0xD4 +local PN532_PN532TOHOST = 0xD5 + +q3k.NFCIRQRead = function() + local Data = q3k.I2CRead(q3k.Config.I2CGPIO, 1, 0x2C) + if Data then + return Data[1] + else + return 1 + end +end + +q3k.NFCWaitForIRQ = function(Timeout) + local Timeout = Timeout or 5 + local WaitStart = os.time() + while os.time() < WaitStart + Timeout do + local IRQStatus = q3k.NFCIRQRead() + if IRQStatus == 0 then + break + end + q3k.Sleep(0.2) + end + local IRQStatus = q3k.NFCIRQRead() + if IRQStatus ~= 0 then + return false + end + return true +end + +q3k.NFCWriteCommand = function(...) + local Command = {...} + local Checksum = PN532_PREAMBLE + PN532_STARTCODE1 + PN532_STARTCODE2 + local WireCommand = { PN532_PREAMBLE, PN532_STARTCODE1, PN532_STARTCODE2 } + + WireCommand[#WireCommand+1] = (#Command + 1) + WireCommand[#WireCommand+1] = bit.band(bit.bnot(#Command +1) + 1, 0xFF) + + Checksum = Checksum + PN532_HOSTTOPN532 + WireCommand[#WireCommand+1] = PN532_HOSTTOPN532 + + for _, Byte in pairs(Command) do + Checksum = Checksum + Byte + WireCommand[#WireCommand+1] = Byte + end + + WireCommand[#WireCommand+1] = bit.band(bit.bnot(Checksum), 0xFF) + WireCommand[#WireCommand+1] = PN532_POSTAMBLE + + q3k.I2CWrite(q3k.Config.I2CNFC, unpack(WireCommand)) +end + +q3k.NFCSendAndAck = function(...) + q3k.NFCWriteCommand(...) + local IRQArrived = q3k.NFCWaitForIRQ() + if not IRQArrived then + return false + end + local ACK = q3k.I2CRead(q3k.Config.I2CNFC, 8) + if ACK[1] == 1 and ACK[2] == 0 and ACK[3] == 0 and ACK[4] == 255 + and ACK[5] == 0 and ACK[6] == 255 and ACK[7] == 0 then + return true + else + return false + end +end + +q3k.NFCReadFrame = function(Count) + local Bytes = q3k.I2CRead(q3k.Config.I2CNFC, Count+2) + table.remove(Bytes, 1) + table.remove(Bytes, #Bytes) + return Bytes +end + +-- Mock until we have a keypad +q3k.ReadPIN = function() + print "[mock] Entering PIN..." + q3k.Sleep(2) + print "[mock] PIN entered." + return { 0, 0, 0, 0 } +end + +-- API stuff + +-- the PIN is a table like { 1, 2, 3, 4 } +-- the NFC ID is a table like { 0xde, 0xad, 0xbe, 0xef } +q3k.CalculateHash = function(PIN, NFCID) + local PINString = string.format("%i%i%i%i", PIN[1], PIN[2], PIN[3], PIN[4]) + local PINNumber = tonumber(PINString) + local IDString = string.format("%02x%02x%02x%02x", NFCID[4], NFCID[3], NFCID[2], NFCID[1]) + local Source = string.format("%08x:%s", PINNumber, IDString) + return sha2.sha256hex(Source) +end + +q3k.GetUserFromHash = function(Hash) + local Body, Code, Headers, Status = https.request('https://auth.hackerspace.pl/mifare', 'hash=' .. Hash) + if Code ~= 200 then + return nil + end + if #Body > 100 then + -- probably an error code + return nil + end + return Body +end + +-- debug bytes +local db = function(Data) + local s = "" + for _, Byte in pairs(Data) do + s = s .. string.format("%02x ", Byte) + end + print(s) +end + +local main = function() + q3k.SetupGPIO() + q3k.NFCSendAndAck(0x14, 0x01, 0x14, 0x01) + + while true do + q3k.NFCSendAndAck(0x4A, 1, 0) + if q3k.NFCWaitForIRQ(120) then + print "Card arrived in field" + local Bytes = q3k.NFCReadFrame(20) + if Bytes ~= nil and #Bytes == 20 then + local NFCID = { Bytes[14], Bytes[15], Bytes[16], Bytes[17] } + local Hash = q3k.CalculateHash(q3k.ReadPIN(), NFCID) + local User = q3k.GetUserFromHash(Hash) + if User == nil then + print "FAILED! no such user!" + else + print(string.format("User: %s", User)) + end + end + end + end +end + +main() diff --git a/lua-libs/JSON.lua b/lua-libs/JSON.lua new file mode 100644 index 0000000..f38ea9d --- /dev/null +++ b/lua-libs/JSON.lua @@ -0,0 +1,861 @@ +-- -*- coding: utf-8 -*- +-- +-- Copyright 2010-2013 Jeffrey Friedl +-- http://regex.info/blog/ +-- +-- Latest copy: http://regex.info/blog/lua/json +-- +local VERSION = 20130120.6 -- version history at end of file +local OBJDEF = { VERSION = VERSION } + +-- +-- Simple JSON encoding and decoding in pure Lua. +-- http://www.json.org/ +-- +-- +-- JSON = (loadfile "JSON.lua")() -- one-time load of the routines +-- +-- local lua_value = JSON:decode(raw_json_text) +-- +-- local raw_json_text = JSON:encode(lua_table_or_value) +-- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability +-- +-- +-- DECODING +-- +-- JSON = (loadfile "JSON.lua")() -- one-time load of the routines +-- +-- local lua_value = JSON:decode(raw_json_text) +-- +-- If the JSON text is for an object or an array, e.g. +-- { "what": "books", "count": 3 } +-- or +-- [ "Larry", "Curly", "Moe" ] +-- +-- the result is a Lua table, e.g. +-- { what = "books", count = 3 } +-- or +-- { "Larry", "Curly", "Moe" } +-- +-- +-- The encode and decode routines accept an optional second argument, "etc", which is not used +-- during encoding or decoding, but upon error is passed along to error handlers. It can be of any +-- type (including nil). +-- +-- With most errors during decoding, this code calls +-- +-- JSON:onDecodeError(message, text, location, etc) +-- +-- with a message about the error, and if known, the JSON text being parsed and the byte count +-- where the problem was discovered. You can replace the default JSON:onDecodeError() with your +-- own function. +-- +-- The default onDecodeError() merely augments the message with data about the text and the +-- location if known (and if a second 'etc' argument had been provided to decode(), its value is +-- tacked onto the message as well), and then calls JSON.assert(), which itself defaults to Lua's +-- built-in assert(), and can also be overridden. +-- +-- For example, in an Adobe Lightroom plugin, you might use something like +-- +-- function JSON:onDecodeError(message, text, location, etc) +-- LrErrors.throwUserError("Internal Error: invalid JSON data") +-- end +-- +-- or even just +-- +-- function JSON.assert(message) +-- LrErrors.throwUserError("Internal Error: " .. message) +-- end +-- +-- If JSON:decode() is passed a nil, this is called instead: +-- +-- JSON:onDecodeOfNilError(message, nil, nil, etc) +-- +-- and if JSON:decode() is passed HTML instead of JSON, this is called: +-- +-- JSON:onDecodeOfHTMLError(message, text, nil, etc) +-- +-- The use of the fourth 'etc' argument allows stronger coordination between decoding and error +-- reporting, especially when you provide your own error-handling routines. Continuing with the +-- the Adobe Lightroom plugin example: +-- +-- function JSON:onDecodeError(message, text, location, etc) +-- local note = "Internal Error: invalid JSON data" +-- if type(etc) = 'table' and etc.photo then +-- note = note .. " while processing for " .. etc.photo:getFormattedMetadata('fileName') +-- end +-- LrErrors.throwUserError(note) +-- end +-- +-- : +-- : +-- +-- for i, photo in ipairs(photosToProcess) do +-- : +-- : +-- local data = JSON:decode(someJsonText, { photo = photo }) +-- : +-- : +-- end +-- +-- +-- +-- + +-- DECODING AND STRICT TYPES +-- +-- Because both JSON objects and JSON arrays are converted to Lua tables, it's not normally +-- possible to tell which a Lua table came from, or guarantee decode-encode round-trip +-- equivalency. +-- +-- However, if you enable strictTypes, e.g. +-- +-- JSON = (loadfile "JSON.lua")() --load the routines +-- JSON.strictTypes = true +-- +-- then the Lua table resulting from the decoding of a JSON object or JSON array is marked via Lua +-- metatable, so that when re-encoded with JSON:encode() it ends up as the appropriate JSON type. +-- +-- (This is not the default because other routines may not work well with tables that have a +-- metatable set, for example, Lightroom API calls.) +-- +-- +-- ENCODING +-- +-- JSON = (loadfile "JSON.lua")() -- one-time load of the routines +-- +-- local raw_json_text = JSON:encode(lua_table_or_value) +-- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability + +-- On error during encoding, this code calls: +-- +-- JSON:onEncodeError(message, etc) +-- +-- which you can override in your local JSON object. +-- +-- +-- SUMMARY OF METHODS YOU CAN OVERRIDE IN YOUR LOCAL LUA JSON OBJECT +-- +-- assert +-- onDecodeError +-- onDecodeOfNilError +-- onDecodeOfHTMLError +-- onEncodeError +-- +-- If you want to create a separate Lua JSON object with its own error handlers, +-- you can reload JSON.lua or use the :new() method. +-- +--------------------------------------------------------------------------- + + +local author = "-[ JSON.lua package by Jeffrey Friedl (http://regex.info/blog/lua/json), version " .. tostring(VERSION) .. " ]-" +local isArray = { __tostring = function() return "JSON array" end } isArray.__index = isArray +local isObject = { __tostring = function() return "JSON object" end } isObject.__index = isObject + + +function OBJDEF:newArray(tbl) + return setmetatable(tbl or {}, isArray) +end + +function OBJDEF:newObject(tbl) + return setmetatable(tbl or {}, isObject) +end + +local function unicode_codepoint_as_utf8(codepoint) + -- + -- codepoint is a number + -- + if codepoint <= 127 then + return string.char(codepoint) + + elseif codepoint <= 2047 then + -- + -- 110yyyxx 10xxxxxx <-- useful notation from http://en.wikipedia.org/wiki/Utf8 + -- + local highpart = math.floor(codepoint / 0x40) + local lowpart = codepoint - (0x40 * highpart) + return string.char(0xC0 + highpart, + 0x80 + lowpart) + + elseif codepoint <= 65535 then + -- + -- 1110yyyy 10yyyyxx 10xxxxxx + -- + local highpart = math.floor(codepoint / 0x1000) + local remainder = codepoint - 0x1000 * highpart + local midpart = math.floor(remainder / 0x40) + local lowpart = remainder - 0x40 * midpart + + highpart = 0xE0 + highpart + midpart = 0x80 + midpart + lowpart = 0x80 + lowpart + + -- + -- Check for an invalid character (thanks Andy R. at Adobe). + -- See table 3.7, page 93, in http://www.unicode.org/versions/Unicode5.2.0/ch03.pdf#G28070 + -- + if ( highpart == 0xE0 and midpart < 0xA0 ) or + ( highpart == 0xED and midpart > 0x9F ) or + ( highpart == 0xF0 and midpart < 0x90 ) or + ( highpart == 0xF4 and midpart > 0x8F ) + then + return "?" + else + return string.char(highpart, + midpart, + lowpart) + end + + else + -- + -- 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx + -- + local highpart = math.floor(codepoint / 0x40000) + local remainder = codepoint - 0x40000 * highpart + local midA = math.floor(remainder / 0x1000) + remainder = remainder - 0x1000 * midA + local midB = math.floor(remainder / 0x40) + local lowpart = remainder - 0x40 * midB + + return string.char(0xF0 + highpart, + 0x80 + midA, + 0x80 + midB, + 0x80 + lowpart) + end +end + +function OBJDEF:onDecodeError(message, text, location, etc) + if text then + if location then + message = string.format("%s at char %d of: %s", message, location, text) + else + message = string.format("%s: %s", message, text) + end + end + if etc ~= nil then + message = message .. " (" .. OBJDEF:encode(etc) .. ")" + end + + if self.assert then + self.assert(false, message) + else + assert(false, message) + end +end + +OBJDEF.onDecodeOfNilError = OBJDEF.onDecodeError +OBJDEF.onDecodeOfHTMLError = OBJDEF.onDecodeError + +function OBJDEF:onEncodeError(message, etc) + if etc ~= nil then + message = message .. " (" .. OBJDEF:encode(etc) .. ")" + end + + if self.assert then + self.assert(false, message) + else + assert(false, message) + end +end + +local function grok_number(self, text, start, etc) + -- + -- Grab the integer part + -- + local integer_part = text:match('^-?[1-9]%d*', start) + or text:match("^-?0", start) + + if not integer_part then + self:onDecodeError("expected number", text, start, etc) + end + + local i = start + integer_part:len() + + -- + -- Grab an optional decimal part + -- + local decimal_part = text:match('^%.%d+', i) or "" + + i = i + decimal_part:len() + + -- + -- Grab an optional exponential part + -- + local exponent_part = text:match('^[eE][-+]?%d+', i) or "" + + i = i + exponent_part:len() + + local full_number_text = integer_part .. decimal_part .. exponent_part + local as_number = tonumber(full_number_text) + + if not as_number then + self:onDecodeError("bad number", text, start, etc) + end + + return as_number, i +end + + +local function grok_string(self, text, start, etc) + + if text:sub(start,start) ~= '"' then + self:onDecodeError("expected string's opening quote", text, start, etc) + end + + local i = start + 1 -- +1 to bypass the initial quote + local text_len = text:len() + local VALUE = "" + while i <= text_len do + local c = text:sub(i,i) + if c == '"' then + return VALUE, i + 1 + end + if c ~= '\\' then + VALUE = VALUE .. c + i = i + 1 + elseif text:match('^\\b', i) then + VALUE = VALUE .. "\b" + i = i + 2 + elseif text:match('^\\f', i) then + VALUE = VALUE .. "\f" + i = i + 2 + elseif text:match('^\\n', i) then + VALUE = VALUE .. "\n" + i = i + 2 + elseif text:match('^\\r', i) then + VALUE = VALUE .. "\r" + i = i + 2 + elseif text:match('^\\t', i) then + VALUE = VALUE .. "\t" + i = i + 2 + else + local hex = text:match('^\\u([0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i) + if hex then + i = i + 6 -- bypass what we just read + + -- We have a Unicode codepoint. It could be standalone, or if in the proper range and + -- followed by another in a specific range, it'll be a two-code surrogate pair. + local codepoint = tonumber(hex, 16) + if codepoint >= 0xD800 and codepoint <= 0xDBFF then + -- it's a hi surrogate... see whether we have a following low + local lo_surrogate = text:match('^\\u([dD][cdefCDEF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i) + if lo_surrogate then + i = i + 6 -- bypass the low surrogate we just read + codepoint = 0x2400 + (codepoint - 0xD800) * 0x400 + tonumber(lo_surrogate, 16) + else + -- not a proper low, so we'll just leave the first codepoint as is and spit it out. + end + end + VALUE = VALUE .. unicode_codepoint_as_utf8(codepoint) + + else + + -- just pass through what's escaped + VALUE = VALUE .. text:match('^\\(.)', i) + i = i + 2 + end + end + end + + self:onDecodeError("unclosed string", text, start, etc) +end + +local function skip_whitespace(text, start) + + local match_start, match_end = text:find("^[ \n\r\t]+", start) -- [http://www.ietf.org/rfc/rfc4627.txt] Section 2 + if match_end then + return match_end + 1 + else + return start + end +end + +local grok_one -- assigned later + +local function grok_object(self, text, start, etc) + if not text:sub(start,start) == '{' then + self:onDecodeError("expected '{'", text, start, etc) + end + + local i = skip_whitespace(text, start + 1) -- +1 to skip the '{' + + local VALUE = self.strictTypes and self:newObject { } or { } + + if text:sub(i,i) == '}' then + return VALUE, i + 1 + end + local text_len = text:len() + while i <= text_len do + local key, new_i = grok_string(self, text, i, etc) + + i = skip_whitespace(text, new_i) + + if text:sub(i, i) ~= ':' then + self:onDecodeError("expected colon", text, i, etc) + end + + i = skip_whitespace(text, i + 1) + + local val, new_i = grok_one(self, text, i) + + VALUE[key] = val + + -- + -- Expect now either '}' to end things, or a ',' to allow us to continue. + -- + i = skip_whitespace(text, new_i) + + local c = text:sub(i,i) + + if c == '}' then + return VALUE, i + 1 + end + + if text:sub(i, i) ~= ',' then + self:onDecodeError("expected comma or '}'", text, i, etc) + end + + i = skip_whitespace(text, i + 1) + end + + self:onDecodeError("unclosed '{'", text, start, etc) +end + +local function grok_array(self, text, start, etc) + if not text:sub(start,start) == '[' then + self:onDecodeError("expected '['", text, start, etc) + end + + local i = skip_whitespace(text, start + 1) -- +1 to skip the '[' + local VALUE = self.strictTypes and self:newArray { } or { } + if text:sub(i,i) == ']' then + return VALUE, i + 1 + end + + local text_len = text:len() + while i <= text_len do + local val, new_i = grok_one(self, text, i) + + table.insert(VALUE, val) + + i = skip_whitespace(text, new_i) + + -- + -- Expect now either ']' to end things, or a ',' to allow us to continue. + -- + local c = text:sub(i,i) + if c == ']' then + return VALUE, i + 1 + end + if text:sub(i, i) ~= ',' then + self:onDecodeError("expected comma or '['", text, i, etc) + end + i = skip_whitespace(text, i + 1) + end + self:onDecodeError("unclosed '['", text, start, etc) +end + + +grok_one = function(self, text, start, etc) + -- Skip any whitespace + start = skip_whitespace(text, start) + + if start > text:len() then + self:onDecodeError("unexpected end of string", text, nil, etc) + end + + if text:find('^"', start) then + return grok_string(self, text, start, etc) + + elseif text:find('^[-0123456789 ]', start) then + return grok_number(self, text, start, etc) + + elseif text:find('^%{', start) then + return grok_object(self, text, start, etc) + + elseif text:find('^%[', start) then + return grok_array(self, text, start, etc) + + elseif text:find('^true', start) then + return true, start + 4 + + elseif text:find('^false', start) then + return false, start + 5 + + elseif text:find('^null', start) then + return nil, start + 4 + + else + self:onDecodeError("can't parse JSON", text, start, etc) + end +end + +function OBJDEF:decode(text, etc) + if type(self) ~= 'table' or self.__index ~= OBJDEF then + OBJDEF:onDecodeError("JSON:decode must be called in method format", nil, nil, etc) + end + + if text == nil then + self:onDecodeOfNilError(string.format("nil passed to JSON:decode()"), nil, nil, etc) + elseif type(text) ~= 'string' then + self:onDecodeError(string.format("expected string argument to JSON:decode(), got %s", type(text)), nil, nil, etc) + end + + if text:match('^%s*$') then + return nil + end + + if text:match('^%s*<') then + -- Can't be JSON... we'll assume it's HTML + self:onDecodeOfHTMLError(string.format("html passed to JSON:decode()"), text, nil, etc) + end + + -- + -- Ensure that it's not UTF-32 or UTF-16. + -- Those are perfectly valid encodings for JSON (as per RFC 4627 section 3), + -- but this package can't handle them. + -- + if text:sub(1,1):byte() == 0 or (text:len() >= 2 and text:sub(2,2):byte() == 0) then + self:onDecodeError("JSON package groks only UTF-8, sorry", text, nil, etc) + end + + local success, value = pcall(grok_one, self, text, 1, etc) + if success then + return value + else + -- should never get here... JSON parse errors should have been caught earlier + assert(false, value) + return nil + end +end + +local function backslash_replacement_function(c) + if c == "\n" then + return "\\n" + elseif c == "\r" then + return "\\r" + elseif c == "\t" then + return "\\t" + elseif c == "\b" then + return "\\b" + elseif c == "\f" then + return "\\f" + elseif c == '"' then + return '\\"' + elseif c == '\\' then + return '\\\\' + else + return string.format("\\u%04x", c:byte()) + end +end + +local chars_to_be_escaped_in_JSON_string + = '[' + .. '"' -- class sub-pattern to match a double quote + .. '%\\' -- class sub-pattern to match a backslash + .. '%z' -- class sub-pattern to match a null + .. '\001' .. '-' .. '\031' -- class sub-pattern to match control characters + .. ']' + +local function json_string_literal(value) + local newval = value:gsub(chars_to_be_escaped_in_JSON_string, backslash_replacement_function) + return '"' .. newval .. '"' +end + +local function object_or_array(self, T, etc) + -- + -- We need to inspect all the keys... if there are any strings, we'll convert to a JSON + -- object. If there are only numbers, it's a JSON array. + -- + -- If we'll be converting to a JSON object, we'll want to sort the keys so that the + -- end result is deterministic. + -- + local string_keys = { } + local seen_number_key = false + local maximum_number_key + + for key in pairs(T) do + if type(key) == 'number' then + seen_number_key = true + if not maximum_number_key or maximum_number_key < key then + maximum_number_key = key + end + elseif type(key) == 'string' then + table.insert(string_keys, key) + else + self:onEncodeError("can't encode table with a key of type " .. type(key), etc) + end + end + + if seen_number_key and #string_keys > 0 then + -- + -- Mixed key types... don't know what to do, so bail + -- + self:onEncodeError("a table with both numeric and string keys could be an object or array; aborting", etc) + + elseif #string_keys == 0 then + -- + -- An array + -- + if seen_number_key then + return nil, maximum_number_key -- an array + else + -- + -- An empty table... + -- + if tostring(T) == "JSON array" then + return nil + elseif tostring(T) == "JSON object" then + return { } + else + -- have to guess, so we'll pick array, since empty arrays are likely more common than empty objects + return nil + end + end + else + -- + -- An object, so return a list of keys + -- + table.sort(string_keys) + return string_keys + end +end + +-- +-- Encode +-- +local encode_value -- must predeclare because it calls itself +function encode_value(self, value, parents, etc) + + + if value == nil then + return 'null' + end + + if type(value) == 'string' then + return json_string_literal(value) + elseif type(value) == 'number' then + if value ~= value then + -- + -- NaN (Not a Number). + -- JSON has no NaN, so we have to fudge the best we can. This should really be a package option. + -- + return "null" + elseif value >= math.huge then + -- + -- Positive infinity. JSON has no INF, so we have to fudge the best we can. This should + -- really be a package option. Note: at least with some implementations, positive infinity + -- is both ">= math.huge" and "<= -math.huge", which makes no sense but that's how it is. + -- Negative infinity is properly "<= -math.huge". So, we must be sure to check the ">=" + -- case first. + -- + return "1e+9999" + elseif value <= -math.huge then + -- + -- Negative infinity. + -- JSON has no INF, so we have to fudge the best we can. This should really be a package option. + -- + return "-1e+9999" + else + return tostring(value) + end + elseif type(value) == 'boolean' then + return tostring(value) + + elseif type(value) ~= 'table' then + self:onEncodeError("can't convert " .. type(value) .. " to JSON", etc) + + else + -- + -- A table to be converted to either a JSON object or array. + -- + local T = value + + if parents[T] then + self:onEncodeError("table " .. tostring(T) .. " is a child of itself", etc) + else + parents[T] = true + end + + local result_value + + local object_keys, maximum_number_key = object_or_array(self, T, etc) + if maximum_number_key then + -- + -- An array... + -- + local ITEMS = { } + for i = 1, maximum_number_key do + table.insert(ITEMS, encode_value(self, T[i], parents, etc)) + end + + result_value = "[" .. table.concat(ITEMS, ",") .. "]" + elseif object_keys then + -- + -- An object + -- + + -- + -- We'll always sort the keys, so that comparisons can be made on + -- the results, etc. The actual order is not particularly + -- important (e.g. it doesn't matter what character set we sort + -- as); it's only important that it be deterministic... the same + -- every time. + -- + local PARTS = { } + for _, key in ipairs(object_keys) do + local encoded_key = encode_value(self, tostring(key), parents, etc) + local encoded_val = encode_value(self, T[key], parents, etc) + table.insert(PARTS, string.format("%s:%s", encoded_key, encoded_val)) + end + result_value = "{" .. table.concat(PARTS, ",") .. "}" + else + -- + -- An empty array/object... we'll treat it as an array, though it should really be an option + -- + result_value = "[]" + end + + parents[T] = false + return result_value + end +end + +local encode_pretty_value -- must predeclare because it calls itself +function encode_pretty_value(self, value, parents, indent, etc) + + if type(value) == 'string' then + return json_string_literal(value) + + elseif type(value) == 'number' then + return tostring(value) + + elseif type(value) == 'boolean' then + return tostring(value) + + elseif type(value) == 'nil' then + return 'null' + + elseif type(value) ~= 'table' then + self:onEncodeError("can't convert " .. type(value) .. " to JSON", etc) + + else + -- + -- A table to be converted to either a JSON object or array. + -- + local T = value + + if parents[T] then + self:onEncodeError("table " .. tostring(T) .. " is a child of itself", etc) + end + parents[T] = true + + local result_value + + local object_keys = object_or_array(self, T, etc) + if not object_keys then + -- + -- An array... + -- + local ITEMS = { } + for i = 1, #T do + table.insert(ITEMS, encode_pretty_value(self, T[i], parents, indent, etc)) + end + + result_value = "[ " .. table.concat(ITEMS, ", ") .. " ]" + + else + + -- + -- An object -- can keys be numbers? + -- + + local KEYS = { } + local max_key_length = 0 + for _, key in ipairs(object_keys) do + local encoded = encode_pretty_value(self, tostring(key), parents, "", etc) + max_key_length = math.max(max_key_length, #encoded) + table.insert(KEYS, encoded) + end + local key_indent = indent .. " " + local subtable_indent = indent .. string.rep(" ", max_key_length + 2 + 4) + local FORMAT = "%s%" .. tostring(max_key_length) .. "s: %s" + + local COMBINED_PARTS = { } + for i, key in ipairs(object_keys) do + local encoded_val = encode_pretty_value(self, T[key], parents, subtable_indent, etc) + table.insert(COMBINED_PARTS, string.format(FORMAT, key_indent, KEYS[i], encoded_val)) + end + result_value = "{\n" .. table.concat(COMBINED_PARTS, ",\n") .. "\n" .. indent .. "}" + end + + parents[T] = false + return result_value + end +end + +function OBJDEF:encode(value, etc) + if type(self) ~= 'table' or self.__index ~= OBJDEF then + OBJDEF:onEncodeError("JSON:encode must be called in method format", etc) + end + + local parents = {} + return encode_value(self, value, parents, etc) +end + +function OBJDEF:encode_pretty(value, etc) + local parents = {} + local subtable_indent = "" + return encode_pretty_value(self, value, parents, subtable_indent, etc) +end + +function OBJDEF.__tostring() + return "JSON encode/decode package" +end + +OBJDEF.__index = OBJDEF + +function OBJDEF:new(args) + local new = { } + + if args then + for key, val in pairs(args) do + new[key] = val + end + end + + return setmetatable(new, OBJDEF) +end + +return OBJDEF:new() + +-- +-- Version history: +-- +-- 20130120.6 Comment update: added a link to the specific page on my blog where this code can +-- be found, so that folks who come across the code outside of my blog can find updates +-- more easily. +-- +-- 20111207.5 Added support for the 'etc' arguments, for better error reporting. +-- +-- 20110731.4 More feedback from David Kolf on how to make the tests for Nan/Infinity system independent. +-- +-- 20110730.3 Incorporated feedback from David Kolf at http://lua-users.org/wiki/JsonModules: +-- +-- * When encoding lua for JSON, Sparse numeric arrays are now handled by +-- spitting out full arrays, such that +-- JSON:encode({"one", "two", [10] = "ten"}) +-- returns +-- ["one","two",null,null,null,null,null,null,null,"ten"] +-- +-- In 20100810.2 and earlier, only up to the first non-null value would have been retained. +-- +-- * When encoding lua for JSON, numeric value NaN gets spit out as null, and infinity as "1+e9999". +-- Version 20100810.2 and earlier created invalid JSON in both cases. +-- +-- * Unicode surrogate pairs are now detected when decoding JSON. +-- +-- 20100810.2 added some checking to ensure that an invalid Unicode character couldn't leak in to the UTF-8 encoding +-- +-- 20100731.1 initial public release +-- diff --git a/lua-libs/bit.lua b/lua-libs/bit.lua new file mode 100644 index 0000000..94e60d7 --- /dev/null +++ b/lua-libs/bit.lua @@ -0,0 +1,260 @@ +--[[--------------- +LuaBit v0.4 +------------------- +a bitwise operation lib for lua. + +http://luaforge.net/projects/bit/ + +How to use: +------------------- + bit.bnot(n) -- bitwise not (~n) + bit.band(m, n) -- bitwise and (m & n) + bit.bor(m, n) -- bitwise or (m | n) + bit.bxor(m, n) -- bitwise xor (m ^ n) + bit.brshift(n, bits) -- right shift (n >> bits) + bit.blshift(n, bits) -- left shift (n << bits) + bit.blogic_rshift(n, bits) -- logic right shift(zero fill >>>) + +Please note that bit.brshift and bit.blshift only support number within +32 bits. + +2 utility functions are provided too: + bit.tobits(n) -- convert n into a bit table(which is a 1/0 sequence) + -- high bits first + bit.tonumb(bit_tbl) -- convert a bit table into a number +------------------- + +Under the MIT license. + +copyright(c) 2006~2007 hanzhao (abrash_han@hotmail.com) +--]]--------------- + +do + +------------------------ +-- bit lib implementions + +local function check_int(n) + -- checking not float + if(n - math.floor(n) > 0) then + error("trying to use bitwise operation on non-integer!") + end +end + +local function to_bits(n) + check_int(n) + if(n < 0) then + -- negative + return to_bits(bit.bnot(math.abs(n)) + 1) + end + -- to bits table + local tbl = {} + local cnt = 1 + while (n > 0) do + local last = math.mod(n,2) + if(last == 1) then + tbl[cnt] = 1 + else + tbl[cnt] = 0 + end + n = (n-last)/2 + cnt = cnt + 1 + end + + return tbl +end + +local function tbl_to_number(tbl) + local n = table.getn(tbl) + + local rslt = 0 + local power = 1 + for i = 1, n do + rslt = rslt + tbl[i]*power + power = power*2 + end + + return rslt +end + +local function expand(tbl_m, tbl_n) + local big = {} + local small = {} + if(table.getn(tbl_m) > table.getn(tbl_n)) then + big = tbl_m + small = tbl_n + else + big = tbl_n + small = tbl_m + end + -- expand small + for i = table.getn(small) + 1, table.getn(big) do + small[i] = 0 + end + +end + +local function bit_or(m, n) + local tbl_m = to_bits(m) + local tbl_n = to_bits(n) + expand(tbl_m, tbl_n) + + local tbl = {} + local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n)) + for i = 1, rslt do + if(tbl_m[i]== 0 and tbl_n[i] == 0) then + tbl[i] = 0 + else + tbl[i] = 1 + end + end + + return tbl_to_number(tbl) +end + +local function bit_and(m, n) + local tbl_m = to_bits(m) + local tbl_n = to_bits(n) + expand(tbl_m, tbl_n) + + local tbl = {} + local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n)) + for i = 1, rslt do + if(tbl_m[i]== 0 or tbl_n[i] == 0) then + tbl[i] = 0 + else + tbl[i] = 1 + end + end + + return tbl_to_number(tbl) +end + +local function bit_not(n) + + local tbl = to_bits(n) + local size = math.max(table.getn(tbl), 32) + for i = 1, size do + if(tbl[i] == 1) then + tbl[i] = 0 + else + tbl[i] = 1 + end + end + return tbl_to_number(tbl) +end + +local function bit_xor(m, n) + local tbl_m = to_bits(m) + local tbl_n = to_bits(n) + expand(tbl_m, tbl_n) + + local tbl = {} + local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n)) + for i = 1, rslt do + if(tbl_m[i] ~= tbl_n[i]) then + tbl[i] = 1 + else + tbl[i] = 0 + end + end + + --table.foreach(tbl, print) + + return tbl_to_number(tbl) +end + +local function bit_rshift(n, bits) + check_int(n) + + local high_bit = 0 + if(n < 0) then + -- negative + n = bit_not(math.abs(n)) + 1 + high_bit = 2147483648 -- 0x80000000 + end + + for i=1, bits do + n = n/2 + n = bit_or(math.floor(n), high_bit) + end + return math.floor(n) +end + +-- logic rightshift assures zero filling shift +local function bit_logic_rshift(n, bits) + check_int(n) + if(n < 0) then + -- negative + n = bit_not(math.abs(n)) + 1 + end + for i=1, bits do + n = n/2 + end + return math.floor(n) +end + +local function bit_lshift(n, bits) + check_int(n) + + if(n < 0) then + -- negative + n = bit_not(math.abs(n)) + 1 + end + + for i=1, bits do + n = n*2 + end + return bit_and(n, 4294967295) -- 0xFFFFFFFF +end + +local function bit_xor2(m, n) + local rhs = bit_or(bit_not(m), bit_not(n)) + local lhs = bit_or(m, n) + local rslt = bit_and(lhs, rhs) + return rslt +end + +-------------------- +-- bit lib interface + +bit = { + -- bit operations + bnot = bit_not, + band = bit_and, + bor = bit_or, + bxor = bit_xor, + brshift = bit_rshift, + blshift = bit_lshift, + bxor2 = bit_xor2, + blogic_rshift = bit_logic_rshift, + + -- utility func + tobits = to_bits, + tonumb = tbl_to_number, +} + +end + +--[[ +for i = 1, 100 do + for j = 1, 100 do + if(bit.bxor(i, j) ~= bit.bxor2(i, j)) then + error("bit.xor failed.") + end + end +end +--]] + + + + + + + + + + + + + diff --git a/lua-libs/luai2c.tar.bz2 b/lua-libs/luai2c.tar.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..712c8e096350bcc03220cebd8a92e7e4fa2bd87d GIT binary patch literal 1899 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 literal 0 HcmV?d00001