Practical Security II: The Mafia IRC Bot (WIP)¶
To demonstrate secure distributed programming in Monte, let’s take the mafia game code developed earlier and make it into an IRC bot.
mafiabot.mt module begins by importing the
mafia module, an
irc/client library, and the same modules for dealing
with entropy that we saw before:
1 2 3 4 5 6
import "mafia" =~ [=> makeMafia :DeepFrozen] import "irc/client" =~ [=> makeIRCClient :DeepFrozen, => connectIRCClient :DeepFrozen] import "lib/entropy/entropy" =~ [=> makeEntropy :DeepFrozen] import "lib/entropy/pcg" =~ [=> makePCG :DeepFrozen] exports (main)
main entry point is provided with a number of powerful references as
- To seed our random number generator, we use
currentRuntimeto get a source of true randomness, i.e. secure entropy.
- To give
makeIRCServiceaccess to TCP/IP networking and event scheduling, we use
192 193 194 195 196 197 198 199 200 201 202
def main(argv, => makeTCP4ClientEndpoint, => Timer, => currentRuntime, => getAddrInfo) as DeepFrozen: def [_, seed] := currentRuntime.getCrypt().makeSecureEntropy().getEntropy() def rng := makeEntropy(makePCG(seed, 0)) def [hostname] := argv def irc := makeIRCService(makeTCP4ClientEndpoint, getAddrInfo, Timer, hostname) irc.connect(makeMafiaBot(rng))
We can go ahead and run this code from a file by using the
monte eval mafiabot.mt chat.freenode.net
Everything after the source filename is passed to main in
argv as a list of
Unlike many other contemporary programming languages, Monte does not need an additional networking library to provide solid primitive and high-level networking operations. This is because Monte was designed to handle networking as easily as any other kind of input or output.
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
def makeIRCService(makeTCP4ClientEndpoint, getAddrInfo, Timer, hostname :Str) as DeepFrozen: def port := 6667 # TODO: named arg with default value return object IRC: to _printOn(out): out.print(`IRC($hostname)`) to connect(handler): def client := makeIRCClient(handler, Timer) def addrs := getAddrInfo(b`$hostname`, b``) return when (addrs) -> def choices := [ for addr in (addrs) ? (addr.getFamily() == "INET" && addr.getSocketType() == "stream") addr.getAddress()] def [address] + _ := choices def ep := makeTCP4ClientEndpoint(address, port) connectIRCClient(client, ep) client
Monte comes with builtin explicit parallelism suitable for scaling to arbitrary numbers of processes or machines, and a well-defined concurrency system that simplifies and streamlines the task of writing event-driven code.
Monte has one concurrent operation. Monte permits messages to be passed as eventual sends. An eventually-sent message will be passed to the target object at a later time, generating a promise which can have more messages sent to it. Unlike similar mechanisms in Twisted, Node.js, etc., Monte builds promises and eventual sending directly into the language and runtime, removing the need for extraneous libraries.
Monte also has a single primitive for combining isolation and parallelism, the vat. Each vat isolates a collection of objects from objects in other vats. Each eventual send in a vat becomes a distinct turn of execution, and vats execute concurrently with one another. During a turn, a vat delivers a single queued send, which could result in more sends being queued up for subsequent turns.
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
def makeChannelVow(client, name) as DeepFrozen: "Return a vow because say() won't work until we have joined." def [wait, done] := Ref.promise() var waitingFor :NullOk[Set[Str]]:= null object chan: to _printOn(out): out.print(`<channel $name>`) to getName(): return name to hasJoined(): return client.hasJoined(name) to say(message) :Void: client.say(name, message) to getUsers(notReady): return client.getUsers(name, notReady) to waitFor(them :Set[Str]): waitingFor := them return wait to notify(): if (waitingFor != null): escape oops: def present := chan.getUsers(oops).getKeys().asSet() traceln("notify present:", present, waitingFor, waitingFor - present) if ((waitingFor - present).size() == 0): waitingFor := null done.resolve(present) to tell(whom, what, notInChannel): if (chan.getUsers(notInChannel).contains(whom)): client.say(whom, what) else: notInChannel(`cannot tell $whom: not in $name`) to part(message): client.part(name, message) return when(chan.hasJoined()) -> chan