-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit f90d46d
Showing
5 changed files
with
243 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
# Package | ||
|
||
version = "0.1.0" | ||
author = "zedeus" | ||
description = "A chat application" | ||
license = "GPL-3.0" | ||
srcDir = "src" | ||
|
||
|
||
# Dependencies | ||
|
||
requires "nim >= 0.19.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import asyncdispatch, asyncnet, os, threadpool, strutils, strformat | ||
import ./protocol | ||
|
||
type | ||
Client = ref object | ||
socket: AsyncSocket | ||
server: string | ||
port: int | ||
username: string | ||
|
||
proc displayMessage(message: Message) = | ||
echo &"{message.username}: {message.text}" | ||
|
||
proc connect(client: Client) {.async.} = | ||
echo "Connecting to ", client.server | ||
await connect(client.socket, client.server, Port(client.port)) | ||
echo "Connected!" | ||
|
||
proc login(client: Client) {.async.} = | ||
echo "Logging in to ", client.server | ||
let message = createMessage(client.username, "login", ConnectionMessage) | ||
await client.socket.send(message) | ||
|
||
let response = await client.socket.recvLine() | ||
let parsed = parseMessage(response) | ||
|
||
if parsed.mtype == ServiceMessage: | ||
displayMessage(parsed) | ||
elif parsed.mtype == ErrorMessage: | ||
quit("ERROR: " & parsed.text) | ||
|
||
proc processMessages(client: Client) {.async.} = | ||
while true: | ||
let line = await client.socket.recvLine() | ||
let parsed = parseMessage(line) | ||
displayMessage(parsed) | ||
|
||
proc processInput(client: Client) {.async.} = | ||
var messageFlowVar = spawn stdin.readLine() | ||
while true: | ||
if messageFlowVar.isReady(): | ||
let message = createMessage(client.username, ^messageFlowVar, TextMessage) | ||
asyncCheck client.socket.send(message) | ||
messageFlowVar = spawn stdin.readLine() | ||
poll() | ||
|
||
proc newClient(): Client = | ||
if paramCount() < 3: | ||
quit("Please specify server address, port and username\nExample: ./client localhost 7687 user") | ||
|
||
new(result) | ||
result.server = paramStr(1) | ||
result.port = parseInt(paramStr(2)) | ||
result.username = paramStr(3) | ||
result.socket = newAsyncSocket() | ||
|
||
proc main() = | ||
let client = newClient() | ||
waitFor client.connect() | ||
waitFor client.login() | ||
|
||
asyncCheck client.processMessages() | ||
waitFor client.processInput() | ||
|
||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
--threads:on |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import json, hashes, strutils | ||
|
||
type | ||
MessageType* = enum | ||
TextMessage, ServiceMessage, ConnectionMessage, | ||
WarningMessage, ErrorMessage | ||
|
||
Message* = object | ||
username*: string | ||
mtype*: MessageType | ||
text*: string | ||
hash*: int | ||
|
||
proc getHash*(username, text: string): int = | ||
return hash(username & text) | ||
|
||
proc getHash*(message: Message): int = | ||
return hash(message.username & message.text) | ||
|
||
proc verifyMessage*(message: Message): bool = | ||
return getHash(message) == message.hash | ||
|
||
proc parseMessage*(data: string): Message = | ||
let dataJson = parseJson(data) | ||
result.username = dataJson["username"].getStr() | ||
result.text = dataJson["text"].getStr() | ||
result.mtype = parseEnum[MessageType](dataJson["mtype"].getStr()) | ||
result.hash = dataJson["hash"].getInt() | ||
|
||
proc createMessage*(username, text: string; mtype: MessageType): string = | ||
result = $(%{ | ||
"username": %username, | ||
"text": %text, | ||
"mtype": %mtype, | ||
"hash": %getHash(username, text) | ||
}) & "\c\l" | ||
|
||
|
||
when isMainModule: | ||
let hash = getHash("John" & "Hi!") | ||
let data = """{"username": "John", "text": "Hi!", "hash": 6635146331310275049, "mtype": 0}""" | ||
|
||
let parsed = parseMessage(data) | ||
doAssert parsed.username == "John" | ||
doAssert parsed.text == "Hi!" | ||
doAssert parsed.hash == getHash("John" & "Hi!") | ||
echo "All tests passed" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import asyncdispatch, asyncnet, os, strformat, strutils | ||
import ./protocol | ||
|
||
type | ||
Client = ref object | ||
socket: AsyncSocket | ||
netAddr: string | ||
connected: bool | ||
username: string | ||
id: int | ||
|
||
Server = ref object | ||
socket: AsyncSocket | ||
clients: seq[Client] | ||
|
||
proc newServer(): Server = | ||
Server(socket: newAsyncSocket(), clients: @[]) | ||
|
||
proc `$`(client: Client): string = | ||
&"{client.id} ({client.netAddr})" | ||
|
||
proc sendMessage(client: Client; text: string; mtype: MessageType) {.async.} = | ||
let message = createMessage("server", text, mtype) | ||
await client.socket.send(message) | ||
|
||
proc sendError(client: Client; text: string) {.async.} = | ||
await sendMessage(client, text, ErrorMessage) | ||
|
||
proc sendWarning(client: Client; text: string) {.async.} = | ||
await sendMessage(client, text, WarningMessage) | ||
|
||
proc sendService(client: Client; text: string) {.async.} = | ||
if not client.connected: return | ||
await sendMessage(client, text, ServiceMessage) | ||
|
||
proc relayMessage(server: Server; message: string; skip=(-1)) {.async.} = | ||
for c in server.clients: | ||
if c.connected and (skip == -1 or c.id != skip): | ||
await c.socket.send(message) | ||
|
||
proc broadcast(server: Server; text: string; skip=(-1); mtype=ServiceMessage) {.async.} = | ||
let message = createMessage("server", text, mtype) | ||
await relayMessage(server, message, skip) | ||
|
||
proc disconnect(client: Client) = | ||
echo client, " disconnected!" | ||
client.connected = false | ||
client.username = "" | ||
client.socket.close() | ||
|
||
proc handleLogin(server: Server; client: Client; message: Message) {.async.} = | ||
if client.connected: | ||
return | ||
|
||
for c in server.clients: | ||
if c.username == message.username: | ||
await sendError(client, "Username already taken.") | ||
return | ||
|
||
client.username = message.username | ||
client.connected = true | ||
|
||
await sendService(client, "Login succesful.") | ||
await server.broadcast(&"user \"{client.username}\" has joined") | ||
|
||
proc processMessage(server: Server; client: Client; message: string) {.async.} = | ||
let parsed = parseMessage(message) | ||
echo client, " sent: ", message | ||
|
||
case parsed.mtype | ||
of TextMessage: | ||
if verifyMessage(parsed): | ||
await relayMessage(server, message & "\c\l", skip=client.id) | ||
else: | ||
await sendWarning(client, "Message hash verification failed.") | ||
of ConnectionMessage: | ||
if parsed.text == "login": | ||
await handleLogin(server, client, parsed) | ||
else: | ||
discard | ||
|
||
proc processClient(server: Server, client: Client) {.async.} = | ||
while true: | ||
let line = await client.socket.recvLine() | ||
if line.len == 0: | ||
if client.username != "": | ||
await server.broadcast(&"user \"{client.username}\" disconnected", skip=client.id) | ||
|
||
disconnect(client) | ||
return | ||
|
||
await processMessage(server, client, line) | ||
|
||
proc loop(server: Server) {.async.} = | ||
while true: | ||
let (netAddr, clientSocket) = await server.socket.acceptAddr() | ||
let client = Client( | ||
socket: clientSocket, | ||
netAddr: netAddr, | ||
connected: false, | ||
id: server.clients.len | ||
) | ||
server.clients.add(client) | ||
asyncCheck server.processClient(client) | ||
echo "Accepted connection from ", netAddr, ", client id: ", client.id | ||
|
||
proc main() = | ||
let server = newServer() | ||
var port = 7687 | ||
|
||
if paramCount() == 1: | ||
port = paramStr(1).parseInt | ||
|
||
server.socket.bindAddr(Port(port)) | ||
server.socket.listen() | ||
waitFor server.loop() | ||
|
||
main() |