mirror of
https://github.com/XeonSquared/OC-Copper.git
synced 2024-11-23 01:48:06 +11:00
Move workspace forward to include some applications & application protocol documents
This commit is contained in:
parent
bf3ec0ec26
commit
2c13ad9469
43
oc/app/chat-cli.lua
Normal file
43
oc/app/chat-cli.lua
Normal file
@ -0,0 +1,43 @@
|
||||
-- Chat client
|
||||
local serv = ({...})[1]
|
||||
local event = require("event")
|
||||
local term = require("term")
|
||||
local gpu = term.gpu()
|
||||
local occure = require("occure")
|
||||
-- show a message onscreen
|
||||
local sw, sh = gpu.getResolution()
|
||||
-- The idea is to use the term API and the normal stuff at the same time.
|
||||
-- Ehehe.
|
||||
local function postMessage(s)
|
||||
gpu.copy(1, 2, sw, sh - 3, 0, -1)
|
||||
gpu.fill(1, sh - 2, sw, 2, " ")
|
||||
gpu.set(1, sh - 2, s)
|
||||
end
|
||||
local function sysCallback(tp, nfrom, nto, port, data, un)
|
||||
if tp == "copper_packet" then
|
||||
if nfrom == serv then
|
||||
if nto == occure.getHostname() then
|
||||
if port == 3 then
|
||||
postMessage(data)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
event.listen("copper_packet", sysCallback)
|
||||
local cancelMeLater = event.timer(1, function()
|
||||
occure.output(serv, 2, "")
|
||||
end, math.huge)
|
||||
pcall(function()
|
||||
while true do
|
||||
term.setCursor(1, sh)
|
||||
local text = term.read({nowrap = true})
|
||||
-- Because Term Sucks (tm)
|
||||
gpu.fill(1, sh - 1, sw, 1, " ")
|
||||
gpu.copy(1, 1, sw, sh - 1, 0, 1)
|
||||
gpu.fill(1, 1, sw, 1, " ")
|
||||
occure.output(serv, 2, text:sub(1, text:len() - 1))
|
||||
end
|
||||
end)
|
||||
event.ignore("copper_packet", sysCallback)
|
||||
event.cancel(cancelMeLater)
|
47
oc/app/chat-srv.lua
Normal file
47
oc/app/chat-srv.lua
Normal file
@ -0,0 +1,47 @@
|
||||
-- Chat server.
|
||||
local occure = require("occure")
|
||||
local event = require("event")
|
||||
|
||||
local subscriberPool = {}
|
||||
local maxSubscribers = 100
|
||||
local function removeSubscriber(a)
|
||||
for i = 1, #subscriberPool do
|
||||
if subscriberPool[i] == a then
|
||||
table.remove(subscriberPool, i)
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
local function addSubscriber(a)
|
||||
if removeSubscriber(a) then
|
||||
table.insert(subscriberPool, a)
|
||||
return
|
||||
end
|
||||
table.insert(subscriberPool, a)
|
||||
if #subscriberPool > maxSubscribers then
|
||||
table.remove(subscriberPool, 1)
|
||||
end
|
||||
end
|
||||
local repeatMessage = nil
|
||||
function repeatMessage(msg)
|
||||
print(msg)
|
||||
for _, v in ipairs(subscriberPool) do
|
||||
occure.output(v, 3, msg)
|
||||
end
|
||||
end
|
||||
while true do
|
||||
-- Null packets: "subscribe me"
|
||||
local tp, nfrom, nto, nport, data = event.pull("copper_packet")
|
||||
if tp == "copper_packet" then
|
||||
if nto == occure.getHostname() then
|
||||
if nport == 2 then
|
||||
if data == "" then
|
||||
addSubscriber(nfrom)
|
||||
else
|
||||
repeatMessage("<" .. nfrom .. "> " .. data)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
20
oc/app/ping.lua
Normal file
20
oc/app/ping.lua
Normal file
@ -0,0 +1,20 @@
|
||||
local occure = require("occure")
|
||||
local computer = require("computer")
|
||||
local event = require("event")
|
||||
|
||||
local args = {...}
|
||||
local startTime = computer.uptime()
|
||||
local completed = 0
|
||||
for _, v in ipairs(args) do
|
||||
occure.output(v, 0, "", false, function ()
|
||||
print("Ping response from " .. v .. " @ " .. (computer.uptime() - startTime))
|
||||
completed = completed + 1
|
||||
end, function ()
|
||||
print("Gave up trying to ping " .. v)
|
||||
completed = completed + 1
|
||||
end)
|
||||
end
|
||||
|
||||
while completed < #args do
|
||||
event.pull(5)
|
||||
end
|
24
oc/app/protocol.chat
Normal file
24
oc/app/protocol.chat
Normal file
@ -0,0 +1,24 @@
|
||||
The chat.lua Protocol
|
||||
- 20kdc, 2017
|
||||
|
||||
The chat.lua protocol is a simple chat protocol built on Copper with the reliability layer in use.
|
||||
All packets/messages referred to here are Reliability Layer reliable packets.
|
||||
Note that this protocol, assuming no more than one connection between two peers has to be maintained at a time,
|
||||
should be extremely versatile.
|
||||
However, IRC on TCP Emulation Over Copper should be the go-to standard when that becomes a thing.
|
||||
|
||||
|
||||
The server will receive messages on port 2 and send them back out on port 3.
|
||||
|
||||
All messages received by the client from the server on port 3 should be displayed as-is.
|
||||
The client must ignore messages received on port 2, due to the possibility of a client and server on the same machine.
|
||||
|
||||
The client should, every so often, send a blank string, to confirm the client is alive and wants to hear from the server.
|
||||
When sending a message, the client should send just the message text, with no annotation,
|
||||
and should not display the message locally.
|
||||
The message, with modifications applied, will be returned by the server if all goes well.
|
||||
|
||||
The server should keep a 'subscription pool' of all clients that have sent blank strings "recently".
|
||||
The server can implement the subscription pool - and it's definition of "recently" - however it likes,
|
||||
though the standard should be to wait until at least 16 seconds after the last packet from the client.
|
||||
When the server sends a message, it sends an individual packet to each subscriber.
|
63
oc/app/protocol.iot
Normal file
63
oc/app/protocol.iot
Normal file
@ -0,0 +1,63 @@
|
||||
The IoT Protocol
|
||||
- 20kdc, 2017
|
||||
|
||||
One of the core uses of Copper is network-connected small devices.
|
||||
These have a set list of requirements:
|
||||
1. The device itself can't be forced to do much in the way of work,
|
||||
at least for simple cases.
|
||||
2. The protocol needs to be relatively flexible.
|
||||
|
||||
The protocol is multi-part, each part noting a different aspect of the system.
|
||||
|
||||
Part 1. General Mainport Packet Description
|
||||
|
||||
The "Main port" is port 4.
|
||||
This port is the port via which most data exchange happens.
|
||||
|
||||
First things first - in case of error, do nothing at all.
|
||||
If it's possible to just ignore a protocol violation, do so.
|
||||
For example, a get request with data should be treated as a get request without it.
|
||||
|
||||
In all packets on the main port,
|
||||
the first byte's upper 2 bits indicate what kind of packet this is -
|
||||
the lower 6 bits is the 'variable number'.
|
||||
The remainder of the packet is the parameter data.
|
||||
|
||||
00: Request: Get (This has no further data.)
|
||||
01: Request: Set (The data is the new variable contents.
|
||||
Success is determined by checking acknowledgement, then performing a Get.)
|
||||
10: Perform Action: (The data is the parameter.)
|
||||
11: Response: Get (The data is the variable contents.)
|
||||
|
||||
Part 2. Variable Types
|
||||
|
||||
Variable types indicate to the program what values are expected in variables.
|
||||
Firstly, the upper bit being set indicates an Action - only the lower bits actually give the variable type-code.
|
||||
Secondly, if it's not an Action, the second-to-upper bit indicates if it can be set.
|
||||
|
||||
They are, as follows:
|
||||
|
||||
0: Void (Only useful for Actions. Think 'ignore anything out of here'.
|
||||
1: Human-Readable String (Generic string that a user could feasibly type. No special characters.)
|
||||
2: Boolean (Length > 0 means true, otherwise false. Odd definition, but also convenient.)
|
||||
3: Float (See Human-Readable String, but translated to a number by the device.)
|
||||
4: Descriptor (See Part 3.)
|
||||
|
||||
Part 3. Discovery & Description
|
||||
|
||||
Upon the receipt of any unreliable packet on port 1 that is addressed to "*" or the IoT device name,
|
||||
it should send a packet back on port 4 formatted as a Get response packet for variable 0.
|
||||
|
||||
Variable 0 is always the Descriptor, which describes the variables and actions available.
|
||||
|
||||
The Descriptor is simply a list of variable types and 7-byte variable names,
|
||||
starting from variable index 1 (as 0 is always the descriptor)
|
||||
It's recommended the variable names are in camel-case.
|
||||
|
||||
Example descriptor for a networked lightbulb:
|
||||
|
||||
"\x42lActive"
|
||||
|
||||
Example descriptor for a networked turtle (Lua-escaped):
|
||||
|
||||
"\x80turnLft\x80turnRgt\x80forward\x80backwrd\x02fwd!air"
|
11
oc/app/protocol.ping
Normal file
11
oc/app/protocol.ping
Normal file
@ -0,0 +1,11 @@
|
||||
The Copper Ping Protocol
|
||||
- 20kdc, 2017
|
||||
|
||||
The Ping protocol for Copper is very simple:
|
||||
|
||||
For the server:
|
||||
Run a Reliability Layer node.
|
||||
|
||||
For the client:
|
||||
Send a Reliability Layer packet to port 0, no data.
|
||||
The time from sending to acknowledgement is the ping.
|
131
oc/hierarchi.lua
131
oc/hierarchi.lua
@ -2,46 +2,62 @@
|
||||
-- No warranty is provided, implied or otherwise.
|
||||
|
||||
-- Copper Hierarchial Gateway implementation for OpenComputers.
|
||||
-- Should be run in a Server Rack, with two servers, connected by Linked Cards.
|
||||
-- Each should be responsible for "it's" side.
|
||||
-- This is a piece of dedicated hardware for a specific purpose.
|
||||
-- The only reason it's not run on two microcontrollers is because
|
||||
-- Best run in a Server with two modems,
|
||||
-- but can be run with a support program on another computer with a modem,
|
||||
-- one modem in the main computer, and a LC pair.
|
||||
|
||||
-- The only reason this isn't run on two microcontrollers is because
|
||||
-- they're inconvenient to use and cdlib needs to be there -
|
||||
-- I'm sure you can port it yourself.
|
||||
|
||||
local args = {...}
|
||||
|
||||
-- Does the single modem connected to this server connect to the outside world?
|
||||
local outbound = false
|
||||
|
||||
if #args ~= 2 then error("Expecting args: outbound ('true'/'false'), network-name") end
|
||||
|
||||
if args[1] == "true" then
|
||||
outbound = true
|
||||
elseif args[1] ~= "false" then
|
||||
error("Only 'true' or 'false' are allowed for the 'outbound' argument.")
|
||||
if #args ~= 2 then
|
||||
error("Expecting args: outboundModem (or 'relay' to use Linked Cards), network-name")
|
||||
end
|
||||
|
||||
-- What is the name of this division, including forward-slash?
|
||||
local netname = args[2] .. "/"
|
||||
|
||||
local outboundModemAdr = args[1]
|
||||
|
||||
-- These names are chosen by sending direction.
|
||||
local outboundModem, inboundModem
|
||||
|
||||
if outboundModemAdr == "relay" then outboundModemAdr = nil end
|
||||
|
||||
local event = require("event")
|
||||
local component = require("component")
|
||||
local cdlib = require("cdlib")
|
||||
|
||||
local modem = component.modem
|
||||
local tunnel = component.tunnel
|
||||
|
||||
-- It is possible that this is meant to be used
|
||||
-- on public wireless infrastructure -
|
||||
-- for example, if this was a server-level domain,
|
||||
-- perhaps solely connected via wireless...
|
||||
-- Oh well. It's the sysadmin's decision to connect it this way.
|
||||
-- Any wireless-abuse is the local regulator's decision.
|
||||
if modem.isWireless() then
|
||||
modem.setStrength(400)
|
||||
for a, _ in component.list("modem") do
|
||||
if outboundModemAdr and (a:sub(1, outboundModemAdr:len()) == outboundModemAdr) then
|
||||
if outboundModem then error("Outbound modem ambiguous.") end
|
||||
outboundModem = component.proxy(a)
|
||||
else
|
||||
if inboundModem then error("More than one internal-side modem.") end
|
||||
inboundModem = component.proxy(a)
|
||||
end
|
||||
end
|
||||
modem.open(4957)
|
||||
|
||||
inboundModem.open(4957)
|
||||
if not outboundModem then
|
||||
local tunnel = component.tunnel
|
||||
-- Implement just enough of an outbound modem to be useful.
|
||||
outboundModem = {
|
||||
address = tunnel.address,
|
||||
broadcast = function (port, ...)
|
||||
tunnel.send(...)
|
||||
end
|
||||
}
|
||||
else
|
||||
outboundModem.open(4957)
|
||||
end
|
||||
|
||||
------ By this point, inboundModem and outboundModem must be:
|
||||
-- 1. non-nil
|
||||
-- 2. have the address and broadcast(port, ...) fields
|
||||
-- (Also, if outboundModemAdr == nil then the port will be ignored for it.)
|
||||
|
||||
-- Rules used on messages coming in from the 'modem' side.
|
||||
-- (This implies Tunnel packets are trusted absolutely - which is correct.)
|
||||
@ -49,27 +65,27 @@ local processFrom, processTo
|
||||
|
||||
-- Implementation of the rules described in protocol.1 for more or
|
||||
-- less unambiguous name translation.
|
||||
if outbound then
|
||||
processFrom = function (from)
|
||||
-- "incoming" is parent-side, incoming being false means child-side.
|
||||
processFrom = function (incoming, from)
|
||||
if incoming then
|
||||
if from:sub(1, netname:len()) == netname then
|
||||
return
|
||||
end
|
||||
return "<" .. from
|
||||
end
|
||||
processTo = function (nto)
|
||||
if nto:sub(1, netname:len()) ~= netname then
|
||||
return
|
||||
end
|
||||
return nto:sub(netname:len() + 1)
|
||||
end
|
||||
else
|
||||
processFrom = function (from)
|
||||
else
|
||||
if from:sub(1, 1) == "<" then
|
||||
return
|
||||
end
|
||||
return netname .. from
|
||||
end
|
||||
processTo = function (nto)
|
||||
end
|
||||
processTo = function (incoming, nto)
|
||||
if incoming then
|
||||
if nto:sub(1, netname:len()) ~= netname then
|
||||
return
|
||||
end
|
||||
return nto:sub(netname:len() + 1)
|
||||
else
|
||||
if nto:sub(1, 1) ~= "<" then
|
||||
return
|
||||
end
|
||||
@ -84,28 +100,31 @@ local function checkLen(s)
|
||||
return s
|
||||
end
|
||||
|
||||
local function handlePacket(incoming, dat)
|
||||
local hops, nfrom, nto, data = cdlib.decode(dat)
|
||||
if not data then return end -- corrupt packet
|
||||
if hops == 255 then return end
|
||||
|
||||
local tfrom, tto = checkLen(processFrom(nfrom)), checkLen(processTo(nto))
|
||||
if tfrom and tto then
|
||||
local resdata = cdlib.encode(hops + 1, tfrom, tto, data)
|
||||
if incoming then
|
||||
inboundModem.broadcast(4957, "copper", resdata)
|
||||
else
|
||||
outboundModem.broadcast(4957, "copper", resdata)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
while true do
|
||||
local e = {event.pull("modem_message")}
|
||||
if e[1] == "modem_message" then
|
||||
-- type, to, from, port, dist, magic[6], data[7]
|
||||
if ((e[2] == tunnel.address) or (e[4] == 4957)) then
|
||||
if e[6] == "copper" then
|
||||
if type(e[7]) == "string" then
|
||||
local hops, nfrom, nto, data = cdlib.decode(e[7])
|
||||
if data then
|
||||
if e[2] == tunnel.address then
|
||||
-- Pass it on as given.
|
||||
modem.broadcast(4957, "copper", e[7])
|
||||
elseif e[2] == modem.address then
|
||||
-- Process it, then give to tunnel
|
||||
if hops ~= 255 then
|
||||
local tfrom, tto = checkLen(processFrom(nfrom)), checkLen(processTo(nto))
|
||||
if tfrom and tto then
|
||||
tunnel.send("copper", cdlib.encode(hops + 1, tfrom, tto, data))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- type, receiver, sender, port, dist, magic, data
|
||||
if (e[2] == inboundModem.address) or (e[2] == outboundModem.address) then
|
||||
local incoming = e[2] == outboundModem.address
|
||||
if (e[4] == 4957) or (incoming and (outboundModemAdr == nil)) then
|
||||
if (e[6] == "copper") and e[7] then
|
||||
handlePacket(incoming, e[7])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user