Networking Utilities
--------------------
-* ``util/fifosock.lua`` -- wraps around the nodemcu/ESP8266 socket sending
- side to provide FIFO execution of ``:send`` calls. Absent such a
- facility, each ``:send`` is run asynchronously as its own task.
+* The ``fifosock`` module that used to be here has been merged into NodeMCU!
+ See ``lua_modules/fifo/fifosock.lua`` in its repository or perhaps skip to
+ https://nodemcu.readthedocs.io/en/dev/lua-modules/fifosock/ .
Networking Framework
--------------------
+++ /dev/null
--- Remove an element and pass it to k; if that returns a value, leave that
--- pending at the top of the fifo. Thus, we can get events that do multiple
--- things.
---
--- If k returns nil, the fifo will be advanced. Moreover, k may return a
--- second result, a boolean, which indicates whether or not this dequeue
--- "counts" as one; this is useful for "phantom" elements in the fifo, such as
--- (placeholders for) callbacks to observers, that cannot otherwise act as
--- ordinary fifo elements do.
---
--- k is also given a second argument, a boolean indicating that this is the
--- last element in the queue.
---
--- If the queue is empty, do not invoke k but flag it to enable immediate
--- execution at the next call to queue.
---
--- Returns 'true' if the queue contained at least one non-phantom entry,
--- 'false' otherwise.
-local function dequeue(q,k)
- if #q > 0
- then
- local new, again = k(q[1], #q == 1)
- if new == nil
- then table.remove(q,1)
- if again then return dequeue(q, k) end -- note tail call
- else q[1] = new
- end
- return true
- else q._go = true ; return false
- end
-end
--- Queue a on queue q.
---
--- If k is provided and the queue has previously drained, dequeue immediately
--- as if k had passed to dequeue. This is useful when k will arrange for
--- subsequent dequeues.
-local function queue(q,a,k)
- table.insert(q,a)
- if k ~= nil and q._go then q._go = false; dequeue(q, k) end
-end
--- return a FIFO constructor
-return function()
- return { ['_go'] = true ; ['queue'] = queue ; ['dequeue'] = dequeue }
-end
. ./host/pushcommon.sh
+[ -d firm ] || {
+ echo "./firm should be a symbolic link to the nodemcu firmware"
+ exit 1
+}
+
+dopushcompile firm/lua_modules/fifo/fifo.lua
+dopushcompile firm/lua_modules/fifo/fifosock.lua
+
dopushcompile util/diag.lua
-dopushcompile fifo/fifo.lua
-dopushcompile fifo/fifo-diag.lua
dopushcompile tq/tq.lua
dopushcompile tq/tq-diag.lua
dopushcompile net/nwfnet.lua
#dopushtext net/conf/nwfnet.conf
#dopushtext net/conf/nwfnet.cert
#dopushtext net/conf/nwfnet.conf2
-dopushcompile net/fifosock.lua
dopushcompile telnetd/telnetd.lua
dopushcompile telnetd/telnetd-file.lua
dopushcompile telnetd/telnetd-diag.lua
+++ /dev/null
--- Wrap a two-staged fifo around a socket's send, borrowing TerryE's
--- scheme from NodeMCU's lua_examples/telnet/telnet.lua .
---
--- Our fifos can take functions; these can be useful for either lazy
--- generators or callbacks for parts of the stream having been sent.
-
-local BIGTHRESH = 256 -- how big is a "big" string?
-local SPLITSLOP = 16 -- any slop in the big question?
-local FSMALLLIM = 32 -- maximum number of small strings held
-local COALIMIT = 3
-
-local concat = table.concat
-local insert = table.insert
-local gc = collectgarbage
-
-local fifo = OVL.fifo()
-
-return function(sock)
- local fsmall, lsmall, fbig = {}, 0, fifo()
-
- local ssla, sslan = nil, 0
- local ssend = function(s,islast)
- ns = nil
-
- -- Optimistically, try coalescing FIFO dequeues. But, don't try to
- -- coalesce function outputs, since functions might be staging their
- -- execution on the send event implied by being called.
-
- if type(s) == "function" then
- if sslan ~= 0 then
- sock:send(ssla)
- ssla, sslan = nil, 0; gc()
- return s, false -- stay as is and wait for :on("sent")
- end
- s, ns = s()
- elseif type(s) == "string" and sslan < COALIMIT then
- if sslan == 0
- then ssla, sslan = s, 1
- else ssla, sslan = ssla .. s, sslan + 1
- end
- if islast then
- -- this is shipping; if there's room, steal the small fifo, too
- if sslan < COALIMIT then
- sock:send(ssla .. concat(fsmall))
- fsmall, lsmall = {}, 0
- else
- sock:send(ssla)
- end
- ssla, sslan = "", 0; gc()
- return nil, false
- else
- return nil, true
- end
- end
-
- if s ~= nil then
- if sslan == 0 then sock:send(s) else sock:send(ssla .. s) end
- ssla, sslan = nil, 0; gc()
- return ns or nil, false
- elseif sslan ~= 0 then
- assert (ns == nil)
- sock:send(ssla)
- ssla, sslan = nil, 0; gc()
- return nil, false
- else
- assert (ns == nil)
- return nil, true
- end
- end
-
- -- Move fsmall to fbig; might send if fbig empty
- local function promote()
- if #fsmall == 0 then return end
- local str = concat(fsmall)
- fsmall, lsmall = {}, 0
- fbig:queue(str, ssend)
- end
-
- local function sendnext()
- if not fbig:dequeue(ssend) then promote() end
- end
-
- sock:on("sent", sendnext)
-
- return function(s)
- -- don't sweat the petty things
- if s == nil or s == "" then return end
-
- -- Function? Go ahead and queue this thing in the right place.
- if type(s) == "function" then promote(); fbig:queue(s, ssend); return; end
-
- s = tostring(s)
-
- -- small fifo would overfill? promote it
- if lsmall + #s > BIGTHRESH or #fsmall >= FSMALLLIM then promote() end
-
- -- big string? chunk and queue big components immediately
- -- behind any promotion that just took place, but cork sending in
- -- case we're the head of line
- local corked = false
- while #s > BIGTHRESH + SPLITSLOP do
- local pfx
- pfx, s = s:sub(1,BIGTHRESH), s:sub(BIGTHRESH+1)
- fbig:queue(pfx, function(t) corked = true; return t end)
- end
-
- -- Big string? queue and maybe tx now
- if #s > BIGTHRESH then fbig:queue(s, ssend)
- -- small and empty line; start txing now. (maybe no corking)
- elseif fbig._go and lsmall == 0 then fbig:queue(s, ssend)
- -- small and queue already moving
- else insert(fsmall, s) ; lsmall = lsmall + #s
- end
-
- -- if it happened that we corked the transmission above, uncork now
- if corked then sendnext() end
- end
-end
["info"] = function(_,s) s(string.format("major=%d minor=%d dev=%d chip=%d flash=%d fs=%d fm=%d fs=%d",node.info())) end,
["heap"] = function(_,s) s(string.format("free=%d",node.heap())) end,
["fifo"] = function(_,s) if rtcfifo and rtcfifo.ready() ~= 0 then s(string.format("fifo=%d",rtcfifo.count())) else s("no rtcfifo") end end,
- -- restart in one tick, so that network callbacks have a chance to fire
+ -- restart in some ticks, so that network callbacks have a chance to fire
-- first, or else we might crash. Ick!
- ["restart"] = function(_,s) tmr.create():alarm(1, tmr.ALARM_SINGLE, node.restart) end,
+ --
+ -- Apparently we need "some" because the fifosock sometimes takes one to do its uncorking
+ -- and the network stack is its own monstrosity. Either way, probably fine.
+ ["restart"] = function(_,s) tmr.create():alarm(10, tmr.ALARM_SINGLE, node.restart) end,
["exec"] = function(l,s)
local f, err = loadstring(l)
if f
function(_) tx("?") k(true) end,tx)
end
function self.server(sock_)
- local fsend = OVL.fifosock()(sock_)
+ local fsend = (require "fifosock").wrap(sock_)
local function teardown(rawsock)
rawsock:on("sent", nil)
rawsock:on("receive", nil)
rawsock:on("disconnection", nil)
tryon("disconn",fsend)
+ fsend=nil
end
sock_:on("receive",function(s_,input) self.rx(fsend,input,function(c) if c then fsend("\n$ ") else s_:close() teardown(s_) end end) end)
sock_:on("disconnection",function(s_, x) teardown(s_) end)
+++ /dev/null
-OVL={}
-OVL.fifo = function() return dofile("./fifo/fifo.lua") end
-package.loaded["fifosock"] = dofile("./net/fifosock.lua")
-
-verbose = 0
-
-vprint = (verbose > 0) and print or function() end
-outs = {}
-
-fakesock = {
- cb = nil,
- on = function(this, _, cb) vprint("CBSET") this.cb = cb end,
- send = function(this, s) vprint("SEND", (verbose > 1) and s) table.insert(outs, s) end,
-}
-function sent() vprint("CB") fakesock.cb() end
-
-fsend = require "fifosock" (fakesock)
-function nocoal() fsend(function() return nil end) end
-function fcheck(x)
- vprint ("CHECK", (verbose > 1) and x)
- assert (#outs > 0)
- assert (x == outs[1])
- table.remove(outs, 1)
-end
-function fsendc(x) fsend(x) fcheck(x) end
-function fchecke() vprint("CHECKE") assert (#outs == 0) end
-
-fsendc("abracadabra none")
-sent() ; fchecke()
-
-fsendc("abracadabra three")
-fsend("short")
-fsend("string")
-fsend("build")
-sent() ; fcheck("shortstringbuild")
-sent() ; fchecke()
-
--- Hit default FSMALLLIM while building up
-fsendc("abracadabra lots small")
-for i = 1, 32 do fsend("a") end
-nocoal()
-for i = 1, 4 do fsend("a") end
-sent() ; fcheck(string.rep("a", 32))
-sent() ; fcheck(string.rep("a", 4))
-sent() ; fchecke()
-
--- Hit string length while building up
-fsendc("abracadabra overlong")
-for i = 1, 10 do fsend(string.rep("a",32)) end
-sent() ; fcheck(string.rep("a", 320))
-sent() ; fchecke()
-
--- Hit neither before sending a big string
-fsendc("abracadabra mid long")
-for i = 1, 6 do fsend(string.rep("a",32)) end
-fsend(string.rep("b", 256))
-nocoal()
-for i = 1, 6 do fsend(string.rep("c",32)) end
-sent() ; fcheck(string.rep("a", 192) .. string.rep("b", 256))
-sent() ; fcheck(string.rep("c", 192))
-sent() ; fchecke()
-
--- send a huge string, verify that it coalesces
-fsendc(string.rep("a",256) .. string.rep("b", 256) .. string.rep("c", 260))
-sent() ; fchecke()
-
--- send a huge string, verify that it coalesces save for the short bit at the end
-fsend(string.rep("a",256) .. string.rep("b", 256) .. string.rep("c", 256) .. string.rep("d",256))
-fsend("e")
-fcheck(string.rep("a",256) .. string.rep("b", 256) .. string.rep("c", 256))
-sent() ; fcheck(string.rep("d",256) .. "e")
-sent() ; fchecke()
-
--- send enough that our 4x lookahead still leaves something in the queue
-fsend(string.rep("a",512) .. string.rep("b", 512) .. string.rep("c", 512))
-fcheck(string.rep("a",512) .. string.rep("b", 512))
-sent() ; fcheck(string.rep("c",512))
-sent() ; fchecke()
-
--- test a lazy generator
-local ix = 0
-local function gen() vprint("GEN", ix); ix = ix + 1; return ("a" .. ix), ix < 3 and gen end
-fsend(gen)
-fsend("b")
-fcheck("a1")
-sent() ; fcheck("a2")
-sent() ; fcheck("a3")
-sent() ; fcheck("b")
-sent() ; fchecke()
-
--- test a completeion-like callback that does send text
-local ix = 0
-local function gen() vprint("GEN"); ix = 1; return "efgh", nil end
-fsend("abcd"); fsend(gen); fsend("ijkl")
-assert (ix == 0)
- fcheck("abcd"); assert (ix == 0)
-sent() ; fcheck("efgh"); assert (ix == 1); ix = 0
-sent() ; fcheck("ijkl"); assert (ix == 0)
-sent() ; fchecke()
-
--- and one that doesn't
-local ix = 0
-local function gen() vprint("GEN"); ix = 1; return nil, nil end
-fsend("abcd"); fsend(gen); fsend("ijkl")
-assert (ix == 0)
- fcheck("abcd"); assert (ix == 0)
-sent() ; fcheck("ijkl"); assert (ix == 1); ix = 0
-sent() ; fchecke() ; assert (ix == 0)
-
-print("All tests OK")