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.
|
-- No warranty is provided, implied or otherwise.
|
||||||
|
|
||||||
-- Copper Hierarchial Gateway implementation for OpenComputers.
|
-- Copper Hierarchial Gateway implementation for OpenComputers.
|
||||||
-- Should be run in a Server Rack, with two servers, connected by Linked Cards.
|
-- Best run in a Server with two modems,
|
||||||
-- Each should be responsible for "it's" side.
|
-- but can be run with a support program on another computer with a modem,
|
||||||
-- This is a piece of dedicated hardware for a specific purpose.
|
-- one modem in the main computer, and a LC pair.
|
||||||
-- The only reason it's not run on two microcontrollers is because
|
|
||||||
|
-- The only reason this isn't run on two microcontrollers is because
|
||||||
-- they're inconvenient to use and cdlib needs to be there -
|
-- they're inconvenient to use and cdlib needs to be there -
|
||||||
-- I'm sure you can port it yourself.
|
-- I'm sure you can port it yourself.
|
||||||
|
|
||||||
local args = {...}
|
local args = {...}
|
||||||
|
|
||||||
-- Does the single modem connected to this server connect to the outside world?
|
if #args ~= 2 then
|
||||||
local outbound = false
|
error("Expecting args: outboundModem (or 'relay' to use Linked Cards), network-name")
|
||||||
|
|
||||||
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.")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- What is the name of this division, including forward-slash?
|
-- What is the name of this division, including forward-slash?
|
||||||
local netname = args[2] .. "/"
|
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 event = require("event")
|
||||||
local component = require("component")
|
local component = require("component")
|
||||||
local cdlib = require("cdlib")
|
local cdlib = require("cdlib")
|
||||||
|
|
||||||
local modem = component.modem
|
for a, _ in component.list("modem") do
|
||||||
local tunnel = component.tunnel
|
if outboundModemAdr and (a:sub(1, outboundModemAdr:len()) == outboundModemAdr) then
|
||||||
|
if outboundModem then error("Outbound modem ambiguous.") end
|
||||||
-- It is possible that this is meant to be used
|
outboundModem = component.proxy(a)
|
||||||
-- on public wireless infrastructure -
|
else
|
||||||
-- for example, if this was a server-level domain,
|
if inboundModem then error("More than one internal-side modem.") end
|
||||||
-- perhaps solely connected via wireless...
|
inboundModem = component.proxy(a)
|
||||||
-- Oh well. It's the sysadmin's decision to connect it this way.
|
end
|
||||||
-- Any wireless-abuse is the local regulator's decision.
|
|
||||||
if modem.isWireless() then
|
|
||||||
modem.setStrength(400)
|
|
||||||
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.
|
-- Rules used on messages coming in from the 'modem' side.
|
||||||
-- (This implies Tunnel packets are trusted absolutely - which is correct.)
|
-- (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
|
-- Implementation of the rules described in protocol.1 for more or
|
||||||
-- less unambiguous name translation.
|
-- less unambiguous name translation.
|
||||||
if outbound then
|
-- "incoming" is parent-side, incoming being false means child-side.
|
||||||
processFrom = function (from)
|
processFrom = function (incoming, from)
|
||||||
|
if incoming then
|
||||||
if from:sub(1, netname:len()) == netname then
|
if from:sub(1, netname:len()) == netname then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
return "<" .. from
|
return "<" .. from
|
||||||
end
|
else
|
||||||
processTo = function (nto)
|
|
||||||
if nto:sub(1, netname:len()) ~= netname then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
return nto:sub(netname:len() + 1)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
processFrom = function (from)
|
|
||||||
if from:sub(1, 1) == "<" then
|
if from:sub(1, 1) == "<" then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
return netname .. from
|
return netname .. from
|
||||||
end
|
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
|
if nto:sub(1, 1) ~= "<" then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@ -84,28 +100,31 @@ local function checkLen(s)
|
|||||||
return s
|
return s
|
||||||
end
|
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
|
while true do
|
||||||
local e = {event.pull("modem_message")}
|
local e = {event.pull("modem_message")}
|
||||||
if e[1] == "modem_message" then
|
if e[1] == "modem_message" then
|
||||||
-- type, to, from, port, dist, magic[6], data[7]
|
-- type, receiver, sender, port, dist, magic, data
|
||||||
if ((e[2] == tunnel.address) or (e[4] == 4957)) then
|
if (e[2] == inboundModem.address) or (e[2] == outboundModem.address) then
|
||||||
if e[6] == "copper" then
|
local incoming = e[2] == outboundModem.address
|
||||||
if type(e[7]) == "string" then
|
if (e[4] == 4957) or (incoming and (outboundModemAdr == nil)) then
|
||||||
local hops, nfrom, nto, data = cdlib.decode(e[7])
|
if (e[6] == "copper") and e[7] then
|
||||||
if data then
|
handlePacket(incoming, e[7])
|
||||||
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
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user