summaryrefslogtreecommitdiff
path: root/xmake/core/base
diff options
context:
space:
mode:
authorruki <[email protected]>2019-11-04 22:20:02 +0800
committerGitHub <[email protected]>2019-11-04 22:20:02 +0800
commitb1eee1ec65dd0f9d3a26af52596d86c331160587 (patch)
tree6811a354f07c146ba4417e649d3a31d0c9aabd45 /xmake/core/base
parent20b40c57bbe9d28f822d5feac304a5b892b02790 (diff)
parentf00316a42943a9a75303658f8d646d35f9e8d1b9 (diff)
Merge pull request #593 from xmake-io/socket
Add io.socket support
Diffstat (limited to 'xmake/core/base')
-rw-r--r--xmake/core/base/bytes.lua441
-rw-r--r--xmake/core/base/io.lua180
-rw-r--r--xmake/core/base/socket.lua581
3 files changed, 1153 insertions, 49 deletions
diff --git a/xmake/core/base/bytes.lua b/xmake/core/base/bytes.lua
new file mode 100644
index 000000000..6c62697aa
--- /dev/null
+++ b/xmake/core/base/bytes.lua
@@ -0,0 +1,441 @@
+--!A cross-platform build utility based on Lua
+--
+-- 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.
+--
+-- Copyright (C) 2015 - 2019, TBOOX Open Source Group.
+--
+-- @author ruki
+-- @file bytes.lua
+--
+
+-- define module: bytes
+local bytes = bytes or {}
+local _instance = _instance or {}
+
+-- load modules
+local bit = require('bit')
+local ffi = require('ffi')
+local os = require("base/os")
+local utils = require("base/utils")
+
+-- define ffi interfaces
+ffi.cdef[[
+ void* malloc(size_t size);
+ void free(void* data);
+]]
+
+-- new a bytes instance
+--
+-- bytes(size): allocates a buffer of given size
+-- bytes(size, ptr [, manage]): mounts buffer on existing storage (manage memory or not)
+-- bytes(str): mounts a buffer from the given string
+-- bytes(bytes, start, last): mounts a buffer from another one, with start/last limits
+-- bytes(bytes1, bytes2, bytes3, ...): allocates and concat buffer from list of byte buffers
+-- bytes(bytes): allocates a buffer from another one (strict replica, sharing memory)
+-- bytes({bytes1, bytes2, ...}): allocates and concat buffer from a list of byte buffers (table)
+--
+function _instance.new(...)
+ local args = {...}
+ local arg1, arg2, arg3 = unpack(args)
+ local instance = table.inherit(_instance)
+ if type(arg1) == "number" then
+ local size = arg1
+ local ptr = arg2
+ if ptr then
+ -- bytes(size, ptr [, manage]): mounts buffer on existing storage (manage memory or not)
+ local manage = arg3
+ if manage then
+ instance._CDATA = ffi.gc(ffi.cast("unsigned char*", ptr), ffi.C.free)
+ instance._MANAGED = true
+ else
+ instance._CDATA = ffi.cast("unsigned char*", ptr)
+ instance._MANAGED = false
+ end
+ else
+ -- bytes(size): allocates a buffer of given size
+ ptr = ffi.C.malloc(size)
+ instance._CDATA = ffi.gc(ffi.cast("unsigned char*", ptr), ffi.C.free)
+ instance._MANAGED = true
+ end
+ instance._SIZE = size
+ instance._READONLY = false
+ elseif type(arg1) == "string" then
+ -- bytes(str): mounts a buffer from the given string
+ local str = arg1
+ instance._SIZE = #str
+ instance._CDATA = ffi.cast("unsigned char*", str)
+ instance._REF = str -- keep ref for GC
+ instance._MANAGED = false
+ instance._READONLY = true
+ elseif type(arg1) == "table" then
+ if type(arg2) == 'number' then
+ -- bytes(bytes, start, last): mounts a buffer from another one, with start/last limits:
+ local b = arg1
+ local start = arg2 or 1
+ local last = arg3 or b:size()
+ if start < 1 or last > b:size() then
+ os.raise("incorrect bounds(%d-%d) for bytes(...)!", start, last)
+ end
+ instance._SIZE = last - start + 1
+ instance._CDATA = b:cdata() - 1 + start
+ instance._REF = b -- keep lua ref for GC
+ instance._MANAGED = false
+ instance._READONLY = b:readonly()
+ elseif type(arg2) == "table" then
+ -- bytes(bytes1, bytes2, bytes3, ...): allocates and concat buffer from list of byte buffers
+ instance._SIZE = 0
+ for _, b in ipairs(args) do
+ instance._SIZE = instance._SIZE + b:size()
+ end
+ instance._CDATA = ffi.gc(ffi.cast("unsigned char*", ffi.C.malloc(instance._SIZE)), ffi.C.free)
+ local offset = 0
+ for _, b in ipairs(args) do
+ ffi.copy(instance._CDATA + offset, b:cdata(), b:size())
+ offset = offset + b:size()
+ end
+ instance._MANAGED = true
+ instance._READONLY = false
+ elseif not arg2 and arg1[1] and type(arg1[1]) == 'table' then
+ -- bytes({bytes1, bytes2, ...}): allocates and concat buffer from a list of byte buffers (table)
+ args = arg1
+ instance._SIZE = 0
+ for _, b in ipairs(args) do
+ instance._SIZE = instance._SIZE + b:size()
+ end
+ instance._CDATA = ffi.gc(ffi.cast("unsigned char*", ffi.C.malloc(instance._SIZE)), ffi.C.free)
+ local offset = 0
+ for _, b in ipairs(args) do
+ ffi.copy(instance._CDATA + offset, b._CDATA, b:size())
+ offset = offset + b:size()
+ end
+ instance._MANAGED = true
+ instance._READONLY = false
+ elseif not arg2 and arg1:size() then
+ -- bytes(bytes): allocates a buffer from another one (strict replica, sharing memory)
+ local b = arg1
+ local start = 1
+ local last = arg3 or b:size()
+ if start < 1 or last > b:size() then
+ os.raise("incorrect bounds(%d-%d)!", start, last)
+ end
+ instance._SIZE = last - start + 1
+ instance._CDATA = b:cdata() - 1 + start
+ instance._REF = b -- keep lua ref for GC
+ instance._MANAGED = false
+ instance._READONLY = b:readonly()
+ end
+ end
+ if instance:cdata() == nil then
+ os.raise("invalid arguments for bytes(...)!")
+ end
+ setmetatable(instance, _instance)
+ return instance
+end
+
+-- get bytes size
+function _instance:size()
+ return self._SIZE
+end
+
+-- get bytes data
+function _instance:cdata()
+ return self._CDATA
+end
+
+-- get data address
+function _instance:caddr()
+ return tonumber(ffi.cast('long', self:cdata()))
+end
+
+-- readonly?
+function _instance:readonly()
+ return self._READONLY
+end
+
+-- bytes:ipairs()
+function _instance:ipairs()
+ local index = 0
+ return function (...)
+ if index < self:size() then
+ index = index + 1
+ return index, self[index]
+ end
+ end
+end
+
+-- get a slice of bytes
+function _instance:slice(start, last)
+ return bytes(self, start, last)
+end
+
+-- copy bytes
+function _instance:copy(src)
+ if self:readonly() then
+ os.raise("%s: cannot be modified!", self)
+ end
+ if type(src) == 'string' then
+ src = bytes(src)
+ end
+ if src:size() ~= self:size() then
+ os.raise("%s: cannot copy bytes, src and dst must have same size(%d->%d)!", self, src:size(), self:size())
+ end
+ ffi.copy(self:cdata(), src:cdata(), self:size())
+ return self
+end
+
+-- clone a new bytes buffer
+function _instance:clone()
+ local new = bytes(self:size())
+ new:copy(self)
+ return new
+end
+
+-- dump whole bytes data
+function _instance:dump()
+
+ local i = 0
+ local n = 147
+ local p = 0
+ local e = self:size()
+ local line = nil
+ while p < e do
+ line = ""
+ if p + 0x20 <= e then
+
+ -- dump offset
+ line = line .. string.format("${yellow}%08X ${green}", p)
+
+ -- dump data
+ for i = 0, 0x20 - 1 do
+ if (i % 4) == 0 then
+ line = line .. " "
+ end
+ line = line .. string.format(" %02X", self[p + i + 1])
+ end
+
+ -- dump spaces
+ line = line .. " "
+
+ -- dump characters
+ line = line .. "${magenta}"
+ for i = 0, 0x20 - 1 do
+ local v = self[p + i + 1]
+ if v > 0x1f and v < 0x7f then
+ line = line .. string.format("%c", v)
+ else
+ line = line .. '.'
+ end
+ end
+ line = line .. "${clear}"
+
+ -- dump line
+ utils.cprint(line)
+
+ -- next line
+ p = p + 0x20
+
+ elseif p < e then
+
+ -- init padding
+ local padding = n - 0x20
+
+ -- dump offset
+ line = line .. string.format("${yellow}%08X ${green}", p)
+ if padding >= 9 then
+ padding = padding - 9
+ end
+
+ -- dump data
+ local left = e - p
+ for i = 0, left - 1 do
+ if (i % 4) == 0 then
+ line = line .. " "
+ if padding then
+ padding = padding - 1
+ end
+ end
+ line = line .. string.format(" %02X", self[p + i + 1])
+ if padding >= 3 then
+ padding = padding - 3
+ end
+ end
+
+ -- dump spaces
+ while padding > 0 do
+ line = line .. " "
+ padding = padding - 1
+ end
+
+ -- dump characters
+ line = line .. "${magenta}"
+ for i = 0, left - 1 do
+ local v = self[p + i + 1]
+ if v > 0x1f and v < 0x7f then
+ line = line .. string.format("%c", v)
+ else
+ line = line .. '.'
+ end
+ end
+ line = line .. "${clear}"
+
+
+ -- dump line
+ utils.cprint(line)
+
+ -- next line
+ p = p + left
+
+ else
+ break
+ end
+ end
+end
+
+-- convert bytes to string
+function _instance:str(i, j)
+ local offset = i and i - 1 or 0
+ return ffi.string(self:cdata() + offset, (j or self:size()) - offset)
+end
+
+-- get uint8 value
+function _instance:u8(offset)
+ return self[offset]
+end
+
+-- get sint8 value
+function _instance:s8(offset)
+ local value = self[offset]
+ return value < 0x80 and value or -0x100 + value
+end
+
+-- get uint16 little-endian value
+function _instance:u16le(offset)
+ return bit.lshift(self[offset + 1], 8) + self[offset]
+end
+
+-- get uint16 big-endian value
+function _instance:u16be(offset)
+ return bit.lshift(self[offset], 8) + self[offset + 1]
+end
+
+-- get sint16 little-endian value
+function _instance:s16le(offset)
+ local value = self:u16le(offset)
+ return value < 0x8000 and value or -0x10000 + value
+end
+
+-- get sint16 big-endian value
+function _instance:s16be(offset)
+ local value = self:u16be(offset)
+ return value < 0x8000 and value or -0x10000 + value
+end
+
+-- get uint32 little-endian value
+function _instance:u32le(offset)
+ return self[offset + 3] * 0x1000000 + bit.lshift(self[offset + 2], 16) + bit.lshift(self[offset + 1], 8) + self[offset]
+end
+
+-- get uint32 big-endian value
+function _instance:u32be(offset)
+ return self[offset] * 0x1000000 + bit.lshift(self[offset + 1], 16) + bit.lshift(self[offset + 2], 8) + self[offset + 3]
+end
+
+-- get sint32 little-endian value
+function _instance:s32le(offset)
+ return bit.tobit(self:u32le(offset))
+end
+
+-- get sint32 big-endian value
+function _instance:s32be(offset)
+ return bit.tobit(self:u32be(offset))
+end
+
+-- get byte or bytes slice at the given index position
+--
+-- bytes[1]
+-- bytes[{1, 2}]
+--
+function _instance:__index(key)
+ if type(key) == "number" then
+ if key < 1 or key > self:size() then
+ os.raise("%s: index(%d/%d) out of bounds!", self, key, self:size())
+ end
+ return self._CDATA[key - 1]
+ elseif type(key) == "table" then
+ local start, last = key[1], key[2]
+ return self:slice(start, last)
+ end
+ return rawget(self, key)
+end
+
+-- set byte or bytes slice at the given index position
+--
+-- bytes[1] = 0x1
+-- bytes[{1, 2}] = bytes(2)
+--
+function _instance:__newindex(key, value)
+ if self:readonly() then
+ os.raise("%s: cannot modify value at index[%s]!", self, key)
+ end
+ if type(key) == "number" then
+ if key < 1 or key > self:size() then
+ os.raise("%s: index(%d/%d) out of bounds!", self, key, self:size())
+ end
+ self._CDATA[key - 1] = value
+ return
+ elseif type(key) == "table" then
+ local start, last = key[1], key[2]
+ self:slice(start,last):copy(value)
+ return
+ end
+ rawset(self, key, value)
+end
+
+-- concat two bytes buffer
+function _instance:__concat(other)
+ local new = bytes(self:size() + other:size())
+ new:slice(1, self:size()):copy(self)
+ new:slice(self:size() + 1, new:size()):copy(other)
+ return new
+end
+
+-- tostring(bytes)
+function _instance:__tostring()
+ local parts = {}
+ local size = self:size()
+ if size > 8 then
+ size = 8
+ end
+ for i = 1, size do
+ parts[i] = "0x" .. bit.tohex(self[i], 2)
+ end
+ return "<bytes(" .. self:size() .. "): " .. table.concat(parts, " ") .. (self:size() > 8 and "..>" or ">")
+end
+
+-- new an bytes instance
+function bytes.new(...)
+ return _instance.new(...)
+end
+
+-- register call function
+setmetatable(bytes, {
+ __call = function (_, ...)
+ return bytes.new(...)
+ end,
+ __tostring = function()
+ return "<bytes>"
+ end
+})
+
+-- return module: bytes
+return bytes
diff --git a/xmake/core/base/io.lua b/xmake/core/base/io.lua
index f6c931970..6dc13ca30 100644
--- a/xmake/core/base/io.lua
+++ b/xmake/core/base/io.lua
@@ -55,10 +55,15 @@ end
-- close file
function _file:close()
- if not self._FILE then
- return false, string.format("file(%s) has been closed!", self:name())
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return false, errors
end
- local ok, errors = io.file_close(self._FILE)
+
+ -- close file
+ ok, errors = io.file_close(self._FILE)
if ok then
self._FILE = nil
end
@@ -67,7 +72,11 @@ end
-- tostring(file)
function _file:__tostring()
- return "file: " .. self:name()
+ local str = self:path()
+ if #str > 16 then
+ str = ".." .. str:sub(#str - 16, #str)
+ end
+ return "<file: " .. str .. ">"
end
-- gc(file)
@@ -84,101 +93,142 @@ end
-- get file rawfd
function _file:rawfd()
- if not self._FILE then
- return false, string.format("file(%s) has been closed!", self:name())
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return nil, errors
end
+
+ -- get file rawfd
local result, errors = io.file_rawfd(self._FILE)
if not result and errors then
- errors = string.format("file(%s): %s", self:name(), errors)
+ errors = string.format("%s: %s", self, errors)
end
return result, errors
end
-- get file size
function _file:size()
- if not self._FILE then
- return false, string.format("file(%s) has been closed!", self:name())
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return nil, errors
end
+
+ -- get file size
local result, errors = io.file_size(self._FILE)
if not result and errors then
- errors = string.format("file(%s): %s", self:name(), errors)
+ errors = string.format("%s: %s", self, errors)
end
return result, errors
end
-- read data from file
function _file:read(fmt, opt)
- if not self._FILE then
- return false, string.format("file(%s) has been closed!", self:name())
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return nil, errors
end
+
+ -- read file
opt = opt or {}
local result, errors = io.file_read(self._FILE, fmt, opt.continuation)
if errors then
- errors = string.format("file(%s): %s", self:name(), errors)
+ errors = string.format("%s: %s", self, errors)
end
return result, errors
end
-- write data to file
function _file:write(...)
- if not self._FILE then
- return false, string.format("file(%s) has been closed!", self:name())
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return false, errors
end
- local ok, errors = io.file_write(self._FILE, ...)
+
+ -- write file
+ ok, errors = io.file_write(self._FILE, ...)
if not ok and errors then
- errors = string.format("file(%s): %s", self:name(), errors)
+ errors = string.format("%s: %s", self, errors)
end
return ok, errors
end
-- seek offset at file
function _file:seek(whence, offset)
- if not self._FILE then
- return false, string.format("file(%s) has been closed!", self:name())
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return false, errors
end
+
+ -- seek file
local result, errors = io.file_seek(self._FILE, whence, offset)
if not result and errors then
- errors = string.format("file(%s): %s", self:name(), errors)
+ errors = string.format("%s: %s", self, errors)
end
return result, errors
end
-- flush data to file
function _file:flush()
- if not self._FILE then
- return false, string.format("file(%s) has been closed!", self:name())
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return false, errors
end
- local ok, errors = io.file_flush(self._FILE)
+
+ -- flush file
+ ok, errors = io.file_flush(self._FILE)
if not ok and errors then
- errors = string.format("file(%s): %s", self:name(), errors)
+ errors = string.format("%s: %s", self, errors)
end
return ok, errors
end
-- this file is a tty?
function _file:isatty()
- if not self._FILE then
- return false, string.format("file(%s) has been closed!", self:name())
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return nil, errors
end
- local ok, errors = io.file_isatty(self._FILE)
+
+ -- is a tty?
+ ok, errors = io.file_isatty(self._FILE)
if ok == nil and errors then
- errors = string.format("file(%s): %s", self:name(), errors)
+ errors = string.format("%s: %s", self, errors)
end
return ok, errors
end
--- iterator of lines
-function _file._lines_iter(data)
- local l = data.file:read("l", data.opt)
- if not l and data.opt.close_on_finished then
- data.file:close()
+-- ensure the file is opened
+function _file:_ensure_opened()
+ if not self._FILE then
+ return false, string.format("%s: has been closed!", self)
end
- return l
+ return true
end
--- read all lines from a file
+-- read all lines from file
function _file:lines(opt)
- return _file._lines_iter, { file = assert(self), opt = opt or {} }
+ opt = opt or {}
+ return function()
+ local l = self:read("l", opt)
+ if not l and opt.close_on_finished then
+ self:close()
+ end
+ return l
+ end
end
-- print file
@@ -245,14 +295,19 @@ end
-- @return ok, errors
--
function _filelock:lock(opt)
- if not self._LOCK then
- return false, string.format("filelock(%s) has been closed!", self:name())
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return false, errors
end
+
+ -- lock it
if self._LOCKED_NUM > 0 or io.filelock_lock(self._LOCK, opt) then
self._LOCKED_NUM = self._LOCKED_NUM + 1
return true
else
- return false, string.format("filelock(%s): lock %s failed!", self:name(), self:path())
+ return false, string.format("%s: lock failed!", self)
end
end
@@ -263,22 +318,32 @@ end
-- @return ok, errors
--
function _filelock:trylock(opt)
- if not self._LOCK then
- return false, string.format("filelock(%s) has been closed!", self:name())
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return false, errors
end
+
+ -- try lock it
if self._LOCKED_NUM > 0 or io.filelock_trylock(self._LOCK, opt) then
self._LOCKED_NUM = self._LOCKED_NUM + 1
return true
else
- return false, string.format("filelock(%s): trylock %s failed!", self:name(), self:path())
+ return false, string.format("%s: trylock failed!", self)
end
end
-- unlock file
function _filelock:unlock(opt)
- if not self._LOCK then
- return false, string.format("filelock(%s) has been closed!", self:name())
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return false, errors
end
+
+ -- unlock it
if self._LOCKED_NUM > 1 or (self._LOCKED_NUM > 0 and io.filelock_unlock(self._LOCK)) then
if self._LOCKED_NUM > 0 then
self._LOCKED_NUM = self._LOCKED_NUM - 1
@@ -287,16 +352,21 @@ function _filelock:unlock(opt)
end
return true
else
- return false, string.format("filelock(%s): unlock %s failed!", self:name(), self:path())
+ return false, string.format("%s: unlock failed!", self)
end
end
-- close filelock
function _filelock:close()
- if not self._LOCK then
- return false, string.format("filelock(%s) has been closed!", self:name())
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return false, errors
end
- local ok = io.filelock_close(self._LOCK)
+
+ -- close it
+ ok = io.filelock_close(self._LOCK)
if ok then
self._LOCK = nil
self._LOCKED_NUM = 0
@@ -304,9 +374,21 @@ function _filelock:close()
return ok
end
+-- ensure the file is opened
+function _filelock:_ensure_opened()
+ if not self._LOCK then
+ return false, string.format("%s: has been closed!", self)
+ end
+ return true
+end
+
-- tostring(filelock)
function _filelock:__tostring()
- return "filelock: " .. self:name()
+ local str = self:path()
+ if #str > 16 then
+ str = ".." .. str:sub(#str - 16, #str)
+ end
+ return "<filelock: " .. str .. ">"
end
-- gc(filelock)
diff --git a/xmake/core/base/socket.lua b/xmake/core/base/socket.lua
new file mode 100644
index 000000000..4a2dfdcdc
--- /dev/null
+++ b/xmake/core/base/socket.lua
@@ -0,0 +1,581 @@
+--!A cross-platform build utility based on Lua
+--
+-- 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.
+--
+-- Copyright (C) 2015 - 2019, TBOOX Open Source Group.
+--
+-- @author ruki
+-- @file socket.lua
+--
+
+-- define module
+local socket = socket or {}
+local _instance = _instance or {}
+
+-- load modules
+local io = require("base/io")
+local bytes = require("base/bytes")
+local table = require("base/table")
+local string = require("base/string")
+
+-- the socket types
+socket.TCP = 1
+socket.UDP = 2
+socket.ICMP = 3
+
+-- the socket families
+socket.IPV4 = 1
+socket.IPV6 = 2
+
+-- the socket events
+socket.EV_RECV = 1
+socket.EV_SEND = 2
+socket.EV_CONN = socket.EV_SEND
+socket.EV_ACPT = socket.EV_RECV
+
+-- new a socket
+function _instance.new(socktype, family, sock)
+ local instance = table.inherit(_instance)
+ instance._SOCK = sock
+ instance._TYPE = socktype
+ instance._FAMILY = family
+ setmetatable(instance, _instance)
+ return instance
+end
+
+-- get socket type
+function _instance:type()
+ return self._TYPE
+end
+
+-- get socket family
+function _instance:family()
+ return self._FAMILY
+end
+
+-- get socket rawfd
+function _instance:rawfd()
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return nil, errors
+ end
+
+ -- get rawfd
+ local result, errors = io.socket_rawfd(self._SOCK)
+ if not result and errors then
+ errors = string.format("%s: %s", self, errors)
+ end
+ return result, errors
+end
+
+-- bind socket
+function _instance:bind(addr, port)
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return -1, errors
+ end
+
+ -- bind it
+ local ok, errors = io.socket_bind(self._SOCK, addr, port, self:family())
+ if not ok and errors then
+ errors = string.format("%s: %s", self, errors)
+ end
+ return ok, errors
+end
+
+-- listen socket
+function _instance:listen(backlog)
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return -1, errors
+ end
+
+ -- listen it
+ local ok, errors = io.socket_listen(self._SOCK, backlog or 10)
+ if not ok and errors then
+ errors = string.format("%s: %s", self, errors)
+ end
+ return ok, errors
+end
+
+-- accept socket
+function _instance:accept(opt)
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return -1, errors
+ end
+
+ -- accept it
+ local sock, errors = io.socket_accept(self._SOCK)
+ if not sock and not errors then
+ opt = opt or {}
+ local events, waiterrs = self:wait(socket.EV_ACPT, opt.timeout or -1)
+ if events == socket.EV_ACPT then
+ sock, errors = io.socket_accept(self._SOCK)
+ else
+ errors = waiterrs
+ end
+ end
+ if not sock and errors then
+ errors = string.format("%s: %s", self, errors)
+ end
+ if sock then
+ sock = _instance.new(self:type(), self:family(), sock)
+ end
+ return sock, errors
+end
+
+-- connect socket
+function _instance:connect(addr, port, opt)
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return -1, errors
+ end
+
+ -- connect it
+ local ok, errors = io.socket_connect(self._SOCK, addr, port, self:family())
+ if ok == 0 then
+ opt = opt or {}
+ local events, waiterrs = self:wait(socket.EV_CONN, opt.timeout or -1)
+ if events == socket.EV_CONN then
+ ok, errors = io.socket_connect(self._SOCK, addr, port, self:family())
+ else
+ errors = waiterrs
+ end
+ end
+ if ok < 0 and errors then
+ errors = string.format("%s: %s", self, errors)
+ end
+ return ok, errors
+end
+
+-- send data to socket
+function _instance:send(data, opt)
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return -1, errors
+ end
+
+ -- data is bytes? unpack the raw address
+ local datasize = #data
+ if type(data) == "table" and data.caddr then
+ datasize = data:size()
+ data = {data = data:caddr(), size = data:size()}
+ end
+
+ -- init start and last
+ opt = opt or {}
+ local start = opt.start or 1
+ local last = opt.last or datasize
+
+ -- check start and last
+ if start > last or start < 1 then
+ return -1, string.format("%s: invalid start(%d) and last(%d)!", self, start, last)
+ end
+
+ -- send it
+ local send = 0
+ local real = 0
+ local wait = false
+ local errors = nil
+ if opt.block then
+ local size = last + 1 - start
+ while start <= last do
+ real, errors = io.socket_send(self._SOCK, data, start, last)
+ if real > 0 then
+ send = send + real
+ start = start + real
+ wait = false
+ elseif real == 0 and not wait then
+ local events, waiterrs = self:wait(socket.EV_SEND, opt.timeout or -1)
+ if events == socket.EV_SEND then
+ wait = true
+ else
+ errors = waiterrs
+ break
+ end
+ else
+ break
+ end
+ end
+ if send ~= size then
+ send = -1
+ end
+ else
+ send, errors = io.socket_send(self._SOCK, data, start, last)
+ if send < 0 and errors then
+ errors = string.format("%s: %s", self, errors)
+ end
+ end
+ return send, errors
+end
+
+-- send file to socket
+function _instance:sendfile(file, opt)
+
+ -- ensure the socket opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return -1, errors
+ end
+
+ -- ensure the file opened
+ local ok, errors = file:_ensure_opened()
+ if not ok then
+ return -1, errors
+ end
+
+ -- init start and last
+ opt = opt or {}
+ local start = opt.start or 1
+ local last = opt.last or file:size()
+
+ -- check start and last
+ if start > last or start < 1 then
+ return -1, string.format("%s: invalid start(%d) and last(%d)!", self, start, last)
+ end
+
+ -- send it
+ local send = 0
+ local real = 0
+ local wait = false
+ local errors = nil
+ if opt.block then
+ local size = last + 1 - start
+ while start <= last do
+ real, errors = io.socket_sendfile(self._SOCK, file._FILE, start, last)
+ if real > 0 then
+ send = send + real
+ start = start + real
+ wait = false
+ elseif real == 0 and not wait then
+ local events, waiterrs = self:wait(socket.EV_SEND, opt.timeout or -1)
+ if events == socket.EV_SEND then
+ wait = true
+ else
+ errors = waiterrs
+ break
+ end
+ else
+ break
+ end
+ end
+ if send ~= size then
+ send = -1
+ end
+ else
+ send, errors = io.socket_sendfile(self._SOCK, file._FILE, start, last)
+ if send < 0 and errors then
+ errors = string.format("%s: %s", self, errors)
+ end
+ end
+ return send, errors
+end
+
+-- recv data from socket
+function _instance:recv(size, opt)
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return -1, errors
+ end
+
+ -- check size
+ if size == 0 then
+ return 0
+ elseif size == nil or size < 0 then
+ return -1, string.format("%s: invalid size(%d)!", self, size)
+ end
+
+ -- recv it
+ opt = opt or {}
+ local recv = 0
+ local real = 0
+ local wait = false
+ local data_or_errors = nil
+ if opt.block then
+ local results = {}
+ while recv < size do
+ real, data_or_errors = io.socket_recv(self._SOCK, size - recv)
+ if real > 0 then
+ recv = recv + real
+ wait = false
+ table.insert(results, bytes(data_or_errors))
+ elseif real == 0 and not wait then
+ local events, waiterrs = self:wait(socket.EV_RECV, opt.timeout or -1)
+ if events == socket.EV_RECV then
+ wait = true
+ else
+ data_or_errors = waiterrs
+ break
+ end
+ else
+ break
+ end
+ end
+ if recv == size then
+ data_or_errors = bytes(results)
+ else
+ recv = -1
+ end
+ else
+ recv, data_or_errors = io.socket_recv(self._SOCK, size)
+ if recv > 0 then
+ data_or_errors = bytes(data_or_errors)
+ end
+ end
+ if recv < 0 and data_or_errors then
+ data_or_errors = string.format("%s: %s", self, data_or_errors)
+ end
+ return recv, data_or_errors
+end
+
+-- send udp data to peer
+function _instance:sendto(data, addr, port, opt)
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return -1, errors
+ end
+
+ -- only for udp
+ if self:type() ~= socket.UDP then
+ return -1, string.format("%s: sendto() only for udp socket!", self)
+ end
+
+ -- check address
+ if not addr or not port then
+ return -1, string.format("%s: sendto empty address!", self)
+ end
+
+ -- data is bytes? unpack the raw address
+ if type(data) == "table" and data.caddr then
+ data = {data = data:caddr(), size = data:size()}
+ end
+
+ -- send it
+ opt = opt or {}
+ local send = 0
+ local wait = false
+ local errors = nil
+ if opt.block then
+ while true do
+ send, errors = io.socket_sendto(self._SOCK, data, addr, port, self:family())
+ if send == 0 and not wait then
+ local events, waiterrs = self:wait(socket.EV_SEND, opt.timeout or -1)
+ if events == socket.EV_SEND then
+ wait = true
+ else
+ errors = waiterrs
+ break
+ end
+ else
+ break
+ end
+ end
+ else
+ send, errors = io.socket_sendto(self._SOCK, data, addr, port, self:family())
+ if send < 0 and errors then
+ errors = string.format("%s: %s", self, errors)
+ end
+ end
+ return send, errors
+end
+
+-- recv udp data from peer
+function _instance:recvfrom(size, opt)
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return -1, errors
+ end
+
+ -- only for udp
+ if self:type() ~= socket.UDP then
+ return -1, string.format("%s: sendto() only for udp socket!", self)
+ end
+
+ -- check size
+ if size == 0 then
+ return 0
+ elseif size == nil or size < 0 then
+ return -1, string.format("%s: invalid size(%d)!", self, size)
+ end
+
+ -- recv it
+ opt = opt or {}
+ local recv = 0
+ local wait = false
+ local data_or_errors = nil
+ if opt.block then
+ while true do
+ recv, data_or_errors, addr, port = io.socket_recvfrom(self._SOCK, size)
+ if recv > 0 then
+ data_or_errors = bytes(data_or_errors)
+ break
+ elseif recv == 0 and not wait then
+ local events, waiterrs = self:wait(socket.EV_RECV, opt.timeout or -1)
+ if events == socket.EV_RECV then
+ wait = true
+ else
+ recv = -1
+ data_or_errors = waiterrs
+ break
+ end
+ else
+ break
+ end
+ end
+ else
+ recv, data_or_errors, addr, port = io.socket_recvfrom(self._SOCK, size)
+ if recv > 0 then
+ data_or_errors = bytes(data_or_errors)
+ end
+ end
+ if recv < 0 and data_or_errors then
+ data_or_errors = string.format("%s: %s", self, data_or_errors)
+ end
+ return recv, data_or_errors, addr, port
+end
+
+-- wait socket events
+function _instance:wait(events, timeout)
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return -1, errors
+ end
+
+ -- wait it
+ local events, errors = io.socket_wait(self._SOCK, events, timeout or -1)
+ if events < 0 and errors then
+ errors = string.format("%s: %s", self, errors)
+ end
+ return events, errors
+end
+
+-- close socket
+function _instance:close()
+
+ -- ensure opened
+ local ok, errors = self:_ensure_opened()
+ if not ok then
+ return false, errors
+ end
+
+ -- close it
+ ok = io.socket_close(self._SOCK)
+ if ok then
+ self._SOCK = nil
+ end
+ return ok
+end
+
+-- ensure the socket is opened
+function _instance:_ensure_opened()
+ if not self._SOCK then
+ return false, string.format("%s: has been closed!", self)
+ end
+ return true
+end
+
+-- tostring(socket)
+function _instance:__tostring()
+ local rawfd = self:rawfd() or "closed"
+ local types = {"tcp", "udp", "icmp"}
+ return string.format("<socket: %s%s/%s>", types[self:type()], self:family() == socket.IPV6 and "6" or "4", rawfd)
+end
+
+-- gc(socket)
+function _instance:__gc()
+ if self._SOCK and io.socket_close(self._SOCK) then
+ self._SOCK = nil
+ end
+end
+
+-- open a socket
+--
+-- @param socktype the socket type, e.g. tcp, udp, icmp
+-- @param family the address family, e.g. ipv4, ipv6
+--
+-- @return the socket instance
+--
+function socket.open(socktype, family)
+ socktype = socktype or socket.TCP
+ family = family or socket.IPV4
+ local sock, errors = io.socket_open(socktype, family)
+ if sock then
+ return _instance.new(socktype, family, sock)
+ else
+ return nil, errors or string.format("failed to open socket(%s/%s)!", socktype, family)
+ end
+end
+
+-- open tcp socket
+function socket.tcp(opt)
+ opt = opt or {}
+ return socket.open(socket.TCP, opt.family or socket.IPV4)
+end
+
+-- open udp socket
+function socket.udp(opt)
+ opt = opt or {}
+ return socket.open(socket.UDP, opt.family or socket.IPV4)
+end
+
+-- open and bind tcp socket
+function socket.bind(addr, port, opt)
+ local sock, errors = socket.tcp(opt)
+ if not sock then
+ return nil, errors
+ end
+ local ok, errors = sock:bind(addr, port)
+ if not ok then
+ sock:close()
+ return nil, string.format("bind %s:%s failed, errors: %s!", addr, port, errors or "")
+ end
+ return sock
+end
+
+-- open and connect tcp socket
+function socket.connect(addr, port, opt)
+ local sock, errors = socket.tcp(opt)
+ if not sock then
+ return nil, errors
+ end
+ local ok, errors = sock:connect(addr, port, opt)
+ if ok <= 0 then
+ sock:close()
+ return nil, string.format("connect %s:%s failed, errors: %s!", addr, port, errors or "")
+ end
+ return sock
+end
+
+-- return module
+return socket