WIP start of IRC app

This commit is contained in:
Travis Geiselbrecht
2021-05-26 02:08:14 -07:00
committed by Travis Geiselbrecht
parent fe28bd8a95
commit 8b81805b0e
4 changed files with 443 additions and 0 deletions

338
app/irc/irc.cpp Normal file
View File

@@ -0,0 +1,338 @@
/*
* Copyright (c) 2021 Travis Geiselbrecht
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT
*/
#include <string.h>
#include <stdio.h>
#include <lk/debug.h>
#include <lk/err.h>
#include <lk/trace.h>
#include <app.h>
#include <lib/minip.h>
#include <kernel/thread.h>
#include <kernel/mutex.h>
#include "lktl.h"
#define LOCAL_TRACE 1
//#define IRC_SERVER IPV4(176,58,122,119) // irc.libera.chat
#define IRC_SERVER IPV4(88,99,244,30) // irc.sortix.org
//#define IRC_SERVER IPV4(192,168,1,110) // localhost
#define IRC_PORT 6667
#define IRC_USER "geist"
#define IRC_NICK "geist-lk"
#define IRC_CHAN "#sortix"
//#define IRC_CHAN "#osdev"
class irc_client {
public:
irc_client();
~irc_client();
void init();
void shutdown();
status_t connect();
status_t handshake();
status_t read_loop();
status_t console_input_line(const char *line, bool &exit);
void set_server(uint32_t server) { server_ip_ = server; }
void set_server_port(uint16_t port) { server_port_ = port; }
private:
mutex_t lock_ = MUTEX_INITIAL_VALUE(lock_);
tcp_socket_t *sock_ = nullptr;
uint32_t server_ip_ = 0;
uint16_t server_port_ = 0;
enum {
INITIAL,
CONNECTED,
HANDSHOOK,
} state_ = INITIAL;
};
irc_client::irc_client() = default;
irc_client::~irc_client() = default;
void irc_client::init() {
}
void irc_client::shutdown() {
lktl::auto_lock al(&lock_);
if (sock_) {
if (state_ == HANDSHOOK) {
// send quit
tcp_write(sock_, "QUIT\r\n", strlen("QUIT\r\n"));
// XXX flush socket
thread_sleep(1000);
}
tcp_close(sock_);
sock_ = nullptr;
}
}
status_t irc_client::connect() {
lktl::auto_lock al(&lock_);
if (server_ip_ == 0 || server_port_ == 0) {
return ERR_NOT_CONFIGURED;
}
auto err = tcp_connect(&sock_, server_ip_, server_port_);
if (err < 0) {
printf("err %d connecting to server\n", err);
return ERR_CHANNEL_CLOSED; // TODO: better one
}
state_ = CONNECTED;
return NO_ERROR;
}
status_t irc_client::read_loop() {
char line[1024];
int pos = 0;
for (;;) {
char c;
ssize_t r = tcp_read(sock_, &c, sizeof(c));
if (r < 0) {
return r;
}
if (r == 0) {
TRACEF("tcp_read returns 0?\n");
return ERR_GENERIC;
}
// TODO: make sure we dont overwrite the line
// append the char to our accumulated line
if (c == '\r') {
// consume \r
continue;
}
// store the char
line[pos++] = c;
if (c != '\n') {
// we're done, loop around
continue;
}
// we've completed a line, process it
line[pos] = 0; // terminate the string
pos = 0; // next time around we start over
lktl::auto_lock al(&lock_);
if (strncmp(line, "PING", strlen("PING"))== 0) {
// handle a PONG
tcp_write(sock_, "PONG\r\n", strlen("PONG\r\n"));
printf("%s", line);
printf("PING/PONG\n");
} else {
printf("%s", line);
}
}
return NO_ERROR;
}
status_t irc_client::console_input_line(const char *line, bool &exit) {
//printf("CONSOLE LINE '%s'\n", line);
if (strlen(line) == 0) {
return NO_ERROR;
}
// see if it starts with /
if (line[0] == '/') {
if (line[1] == 0) {
// malformed command
return NO_ERROR;
}
// look for quit command
if (strncmp(&line[1], "quit", strlen("quit")) == 0) {
shutdown();
exit = true;
return NO_ERROR;
} else {
printf("bad command\n");
return NO_ERROR;
}
}
lktl::auto_lock al(&lock_);
// send it as a privmsg
char buf[256];
snprintf(buf, sizeof(buf), "PRIVMSG #sortix :%s\r\n", line);
status_t err = tcp_write(sock_, buf, strlen(buf));
return err;
}
status_t irc_client::handshake() {
lktl::auto_lock al(&lock_);
// send USER and NICK
char buf[128];
snprintf(buf, sizeof(buf), "USER %s host server :geist\r\n", IRC_USER);
status_t err = tcp_write(sock_, buf, strlen(buf));
if (err < 0) {
printf("error %d writing to server\n", err);
return err;
}
snprintf(buf, sizeof(buf), "NICK %s\r\n", IRC_NICK);
err = tcp_write(sock_, buf, strlen(buf));
if (err < 0) {
printf("error %d writing to server\n", err);
return err;
}
snprintf(buf, sizeof(buf), "JOIN %s\r\n", IRC_CHAN);
err = tcp_write(sock_, buf, strlen(buf));
if (err < 0) {
printf("error %d writing to server\n", err);
return err;
}
state_ = HANDSHOOK;
return NO_ERROR;
}
// console worker thread
static int console_thread_worker(void *arg) {
irc_client *irc = static_cast<irc_client *>(arg);
LTRACEF("top of console thread\n");
// read a line from the console, giving it to the irc client object at EOL
status_t err;
char line[256];
int pos = 0;
bool exit = false;
while (!exit) {
int c = getchar();
if (c <= 0) {
break;
}
if (pos == sizeof(line)) {
printf("line too long, discarding\n");
pos = 0;
}
//printf("char %c (%d)\n", c, c);
switch (c) {
case '\n':
case '\r':
if (pos > 0) {
putchar('\n');
// end of a line with characters in it, feed it to the irc client
line[pos++] = 0; // null terminate
err = irc->console_input_line(line, exit);
if (err < 0) {
exit = true;
}
pos = 0;
}
break;
case '\b': // backspace
case 127: // DEL
if (pos > 0) {
pos--;
fputs("\b \b", stdout); // wipe out a character
}
break;
default:
line[pos++] = (char)c;
putchar(c);
break;
}
}
LTRACEF("console thread exiting\n");
return 0;
};
static void irc_app_entry(const struct app_descriptor *app, void *args) {
LTRACE_ENTRY;
printf("welcome to IRC!\n");
// create a local state object
irc_client *irc = new irc_client();
if (!irc) {
return;
}
status_t err;
irc->init();
// clean up and delete the object on the way out
auto cleanup = [&irc]() {
printf("cleaning up IRC\n");
irc->shutdown();
delete irc;
};
auto ac = lktl::make_auto_call(cleanup);
// configure the parameters
irc->set_server(IRC_SERVER);
irc->set_server_port(IRC_PORT);
err = irc->connect();
if (err < 0) {
return;
}
err = irc->handshake();
if (err < 0) {
return;
}
// start two threads
thread_t *console_thread;
thread_t *server_thread;
console_thread = thread_create("irc console", console_thread_worker, irc, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
thread_resume(console_thread);
auto server_thread_worker = [](void *arg) -> int {
irc_client *_irc = static_cast<irc_client *>(arg);
printf("top of server thread\n");
_irc->read_loop();
return 0;
};
server_thread = thread_create("irc socket", server_thread_worker, irc, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
thread_resume(server_thread);
thread_join(server_thread, nullptr, INFINITE_TIME);
thread_join(console_thread, nullptr, INFINITE_TIME);
LTRACE_EXIT;
}
APP_START(irc)
.init = nullptr,
.entry = irc_app_entry,
.flags = APP_FLAG_NO_AUTOSTART,
.stack_size = 0,
APP_END

92
app/irc/lktl.h Normal file
View File

@@ -0,0 +1,92 @@
/*
* Copyright (c) 2021 Travis Geiselbrecht
*
* Use of this source code is governed by a MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT
*/
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <kernel/thread.h>
#include <kernel/mutex.h>
// Macro used to simplify the task of deleting all of the default copy
// constructors and assignment operators.
#define DISALLOW_COPY_ASSIGN_AND_MOVE(_class_name) \
_class_name(const _class_name&) = delete; \
_class_name(_class_name&&) = delete; \
_class_name& operator=(const _class_name&) = delete; \
_class_name& operator=(_class_name&&) = delete
// Macro used to simplify the task of deleting the non rvalue reference copy
// constructors and assignment operators. (IOW - forcing move semantics)
#define DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(_class_name) \
_class_name(const _class_name&) = delete; \
_class_name& operator=(const _class_name&) = delete
// Macro used to simplify the task of deleting the new and new[]
// operators. (IOW - disallow heap allocations)
#define DISALLOW_NEW \
static void* operator new(size_t) = delete; \
static void* operator new[](size_t) = delete
#pragma once
namespace lktl {
// call a routine when the object goes out of scope
template <typename T>
class auto_call {
public:
constexpr explicit auto_call(T call) : call_(call) {}
~auto_call() {
if (armed_) {
call_();
}
}
// move
auto_call(auto_call &&ac) : armed_(ac.armed_), call_(ac.call_) {
ac.cancel();
}
void cancel() {
armed_ = false;
}
private:
DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(auto_call);
bool armed_ = true;
T call_;
};
// create an auto caller with implicit template specialization.
//
// example:
// auto ac = make_auto_call([]() { printf("a lambda!\n"); });
template <typename T>
inline auto_call<T> make_auto_call(T c) {
return auto_call<T>(c);
}
// auto mutex scope guard
class auto_lock {
public:
explicit auto_lock(mutex_t *m) : m_(m) {
mutex_acquire(m_);
}
~auto_lock() {
mutex_release(m_);
}
private:
DISALLOW_COPY_ASSIGN_AND_MOVE(auto_lock);
mutex_t *m_ = nullptr;
};
} // namespace lktl

12
app/irc/rules.mk Normal file
View File

@@ -0,0 +1,12 @@
LOCAL_DIR := $(GET_LOCAL_DIR)
MODULE := $(LOCAL_DIR)
MODULE_SRCS += \
$(LOCAL_DIR)/irc.cpp \
MODULE_DEPS := \
lib/libcpp \
lib/minip
include make/module.mk

View File

@@ -1,5 +1,6 @@
# main project for qemu-riscv64-supervisor
MODULES += \
app/irc \
app/shell
SUBARCH := 64
RISCV_MODE := supervisor