summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndreas Kling <awesomekling@gmail.com>2019-03-18 14:09:58 +0100
committerAndreas Kling <awesomekling@gmail.com>2019-03-18 14:09:58 +0100
commit8e3d0a23d555b2464aba5a07a90092535cec19e1 (patch)
tree551456726750a407e82f76d9b626fa1d78775e70
parentd466f2634d94df44402c5779395a8f3798151d45 (diff)
downloadserenity-8e3d0a23d555b2464aba5a07a90092535cec19e1.zip
LibGUI: Add GTCPSocket and base class GSocket (inherits from GIODevice.)
And use these to do the line-by-line reading automagically instead of having that logic in IRCClient. This will definitely come in handy.
-rw-r--r--Applications/IRCClient/IRCClient.cpp80
-rw-r--r--Applications/IRCClient/IRCClient.h14
-rw-r--r--Kernel/IPv4.h6
-rw-r--r--LibGUI/GFile.cpp14
-rw-r--r--LibGUI/GFile.h1
-rw-r--r--LibGUI/GIODevice.cpp68
-rw-r--r--LibGUI/GIODevice.h7
-rw-r--r--LibGUI/GSocket.cpp64
-rw-r--r--LibGUI/GSocket.h68
-rw-r--r--LibGUI/GTCPSocket.cpp19
-rw-r--r--LibGUI/GTCPSocket.h10
-rw-r--r--LibGUI/Makefile2
12 files changed, 267 insertions, 86 deletions
diff --git a/Applications/IRCClient/IRCClient.cpp b/Applications/IRCClient/IRCClient.cpp
index 33e112d63d..9d09a31988 100644
--- a/Applications/IRCClient/IRCClient.cpp
+++ b/Applications/IRCClient/IRCClient.cpp
@@ -26,6 +26,7 @@ IRCClient::IRCClient(const String& address, int port)
, m_nickname("anon")
, m_log(IRCLogBuffer::create())
{
+ m_socket = new GTCPSocket(this);
m_client_window_list_model = new IRCWindowListModel(*this);
}
@@ -35,37 +36,15 @@ IRCClient::~IRCClient()
bool IRCClient::connect()
{
- if (m_socket_fd != -1) {
+ if (m_socket->is_connected())
ASSERT_NOT_REACHED();
- }
-
- m_socket_fd = socket(AF_INET, SOCK_STREAM, 0);
- if (m_socket_fd < 0) {
- perror("socket");
- exit(1);
- }
-
- struct sockaddr_in addr;
- memset(&addr, 0, sizeof(addr));
-
- addr.sin_family = AF_INET;
- addr.sin_port = htons(m_port);
- int rc = inet_pton(AF_INET, m_hostname.characters(), &addr.sin_addr);
- if (rc < 0) {
- perror("inet_pton");
- exit(1);
- }
- printf("Connecting to %s...", m_hostname.characters());
- fflush(stdout);
- rc = ::connect(m_socket_fd, (struct sockaddr*)&addr, sizeof(addr));
- if (rc < 0) {
- perror("connect");
- exit(1);
- }
- printf("ok!\n");
+ IPv4Address ipv4_address(127, 0, 0, 1);
+ bool success = m_socket->connect(GSocketAddress(ipv4_address), m_port);
+ if (!success)
+ return false;
- m_notifier = make<GNotifier>(m_socket_fd, GNotifier::Read);
+ m_notifier = make<GNotifier>(m_socket->fd(), GNotifier::Read);
m_notifier->on_ready_to_read = [this] (GNotifier&) { receive_from_server(); };
send_user();
@@ -78,35 +57,20 @@ bool IRCClient::connect()
void IRCClient::receive_from_server()
{
- char buffer[4096];
- int nread = recv(m_socket_fd, buffer, sizeof(buffer) - 1, 0);
- if (nread < 0) {
- perror("recv");
- exit(1);
- }
- if (nread == 0) {
- printf("IRCClient: Connection closed!\n");
- exit(1);
- }
- buffer[nread] = '\0';
-#if 0
- printf("Received: '%s'\n", buffer);
-#endif
-
- for (int i = 0; i < nread; ++i) {
- char ch = buffer[i];
- if (ch == '\r')
- continue;
- if (ch == '\n') {
- process_line();
- m_line_buffer.clear_with_capacity();
- continue;
+ while (m_socket->can_read_line()) {
+ auto line = m_socket->read_line(4096);
+ if (line.is_null()) {
+ if (!m_socket->is_connected()) {
+ printf("IRCClient: Connection closed!\n");
+ exit(1);
+ }
+ ASSERT_NOT_REACHED();
}
- m_line_buffer.append(ch);
+ process_line(move(line));
}
}
-void IRCClient::process_line()
+void IRCClient::process_line(ByteBuffer&& line)
{
Message msg;
Vector<char> prefix;
@@ -121,7 +85,12 @@ void IRCClient::process_line()
InTrailingParameter,
} state = Start;
- for (char ch : m_line_buffer) {
+ for (int i = 0; i < line.size(); ++i) {
+ char ch = line[i];
+ if (ch == '\r')
+ continue;
+ if (ch == '\n')
+ break;
switch (state) {
case Start:
if (ch == ':') {
@@ -175,8 +144,7 @@ void IRCClient::process_line()
void IRCClient::send(const String& text)
{
- int rc = ::send(m_socket_fd, text.characters(), text.length(), 0);
- if (rc < 0) {
+ if (!m_socket->send(ByteBuffer::wrap((void*)text.characters(), text.length()))) {
perror("send");
exit(1);
}
diff --git a/Applications/IRCClient/IRCClient.h b/Applications/IRCClient/IRCClient.h
index 304353171e..c35f3da10a 100644
--- a/Applications/IRCClient/IRCClient.h
+++ b/Applications/IRCClient/IRCClient.h
@@ -4,6 +4,7 @@
#include <AK/HashMap.h>
#include <AK/CircularQueue.h>
#include <AK/Function.h>
+#include <LibGUI/GTCPSocket.h>
#include "IRCLogBuffer.h"
#include "IRCWindow.h"
@@ -12,12 +13,12 @@ class IRCQuery;
class IRCWindowListModel;
class GNotifier;
-class IRCClient {
+class IRCClient final : public GObject {
friend class IRCChannel;
friend class IRCQuery;
public:
IRCClient(const String& address, int port = 6667);
- ~IRCClient();
+ virtual ~IRCClient() override;
bool connect();
@@ -59,6 +60,8 @@ public:
IRCQuery& ensure_query(const String& name);
IRCChannel& ensure_channel(const String& name);
+ const char* class_name() const override { return "IRCClient"; }
+
private:
struct Message {
String prefix;
@@ -72,7 +75,7 @@ private:
void send_nick();
void send_pong(const String& server);
void send_privmsg(const String& target, const String&);
- void process_line();
+ void process_line(ByteBuffer&&);
void handle_join(const Message&);
void handle_part(const Message&);
void handle_ping(const Message&);
@@ -84,8 +87,9 @@ private:
void handle_user_command(const String&);
String m_hostname;
- int m_port { 0 };
- int m_socket_fd { -1 };
+ int m_port { 6667 };
+
+ GTCPSocket* m_socket { nullptr };
String m_nickname;
Vector<char> m_line_buffer;
diff --git a/Kernel/IPv4.h b/Kernel/IPv4.h
index 55cac8d78a..dc4e4dff3c 100644
--- a/Kernel/IPv4.h
+++ b/Kernel/IPv4.h
@@ -4,7 +4,6 @@
#include <AK/Assertions.h>
#include <AK/Types.h>
#include <Kernel/NetworkOrdered.h>
-#include <Kernel/StdLib.h>
enum class IPv4Protocol : word {
ICMP = 1,
@@ -19,7 +18,10 @@ public:
IPv4Address() { }
IPv4Address(const byte data[4])
{
- memcpy(m_data, data, 4);
+ m_data[0] = data[0];
+ m_data[1] = data[1];
+ m_data[2] = data[2];
+ m_data[3] = data[3];
}
IPv4Address(byte a, byte b, byte c, byte d)
{
diff --git a/LibGUI/GFile.cpp b/LibGUI/GFile.cpp
index 45b083191c..e0ff42e0bb 100644
--- a/LibGUI/GFile.cpp
+++ b/LibGUI/GFile.cpp
@@ -40,17 +40,3 @@ bool GFile::open(GIODevice::OpenMode mode)
set_mode(mode);
return true;
}
-
-bool GFile::close()
-{
- if (fd() < 0 || mode() == NotOpen)
- return false;
- int rc = ::close(fd());
- if (rc < 0) {
- set_error(rc);
- return false;
- }
- set_fd(-1);
- set_mode(GIODevice::NotOpen);
- return true;
-}
diff --git a/LibGUI/GFile.h b/LibGUI/GFile.h
index de7325fb93..89aadf0aad 100644
--- a/LibGUI/GFile.h
+++ b/LibGUI/GFile.h
@@ -13,7 +13,6 @@ public:
void set_filename(const String& filename) { m_filename = filename; }
virtual bool open(GIODevice::OpenMode) override;
- virtual bool close() override;
virtual const char* class_name() const override { return "GFile"; }
diff --git a/LibGUI/GIODevice.cpp b/LibGUI/GIODevice.cpp
index d62d36622f..0b0f8de1d1 100644
--- a/LibGUI/GIODevice.cpp
+++ b/LibGUI/GIODevice.cpp
@@ -1,5 +1,7 @@
#include <LibGUI/GIODevice.h>
#include <unistd.h>
+#include <sys/select.h>
+#include <stdio.h>
GIODevice::GIODevice(GObject* parent)
: GObject(parent)
@@ -44,26 +46,63 @@ ByteBuffer GIODevice::read(int max_size)
return buffer;
}
+bool GIODevice::can_read() const
+{
+ // FIXME: Can we somehow remove this once GSocket is implemented using non-blocking sockets?
+ fd_set rfds;
+ FD_ZERO(&rfds);
+ FD_SET(m_fd, &rfds);
+ struct timeval timeout { 0, 0 };
+ int rc = select(m_fd + 1, &rfds, nullptr, nullptr, &timeout);
+ if (rc < 0) {
+ // NOTE: We don't set m_error here.
+ perror("GIODevice::can_read: select");
+ return false;
+ }
+ return FD_ISSET(m_fd, &rfds);
+}
+
+bool GIODevice::can_read_line()
+{
+ if (m_eof && !m_buffered_data.is_empty())
+ return true;
+ if (m_buffered_data.contains_slow('\n'))
+ return true;
+ if (!can_read())
+ return false;
+ populate_read_buffer();
+ return m_buffered_data.contains_slow('\n');
+}
+
ByteBuffer GIODevice::read_line(int max_size)
{
if (m_fd < 0)
return { };
if (!max_size)
return { };
- auto line = ByteBuffer::create_uninitialized(max_size);
- int line_index = 0;
- while (line_index < line.size()) {
- if (line_index >= m_buffered_data.size()) {
- if (!populate_read_buffer())
- return { };
+ if (!can_read_line())
+ return { };
+ if (m_eof) {
+ if (m_buffered_data.size() > max_size) {
+ dbgprintf("GIODevice::read_line: At EOF but there's more than max_size(%d) buffered\n", max_size);
+ return { };
}
+ auto buffer = ByteBuffer::copy(m_buffered_data.data(), m_buffered_data.size());
+ m_buffered_data.clear();
+ return buffer;
+ }
+ auto line = ByteBuffer::create_uninitialized(max_size + 1);
+ int line_index = 0;
+ while (line_index < max_size) {
byte ch = m_buffered_data[line_index];
line[line_index++] = ch;
if (ch == '\n') {
Vector<byte> new_buffered_data;
new_buffered_data.append(m_buffered_data.data() + line_index, m_buffered_data.size() - line_index);
m_buffered_data = move(new_buffered_data);
- line.trim(line_index);
+ line[line_index] = '\0';
+ line.trim(line_index + 1);
+ dbgprintf("GIODevice::read_line: '%s'\n", line.pointer());
return line;
}
}
@@ -84,6 +123,21 @@ bool GIODevice::populate_read_buffer()
set_eof(true);
return false;
}
+ buffer.trim(nread);
m_buffered_data.append(buffer.pointer(), buffer.size());
return true;
}
+
+bool GIODevice::close()
+{
+ if (fd() < 0 || mode() == NotOpen)
+ return false;
+ int rc = ::close(fd());
+ if (rc < 0) {
+ set_error(rc);
+ return false;
+ }
+ set_fd(-1);
+ set_mode(GIODevice::NotOpen);
+ return true;
+}
diff --git a/LibGUI/GIODevice.h b/LibGUI/GIODevice.h
index 744e1997d0..d7d7cd094c 100644
--- a/LibGUI/GIODevice.h
+++ b/LibGUI/GIODevice.h
@@ -29,8 +29,13 @@ public:
ByteBuffer read(int max_size);
ByteBuffer read_line(int max_size);
+ // FIXME: I would like this to be const but currently it needs to call populate_read_buffer().
+ bool can_read_line();
+
+ bool can_read() const;
+
virtual bool open(GIODevice::OpenMode) = 0;
- virtual bool close() = 0;
+ virtual bool close();
virtual const char* class_name() const override { return "GIODevice"; }
diff --git a/LibGUI/GSocket.cpp b/LibGUI/GSocket.cpp
new file mode 100644
index 0000000000..78343f2225
--- /dev/null
+++ b/LibGUI/GSocket.cpp
@@ -0,0 +1,64 @@
+#include <LibGUI/GSocket.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdio.h>
+
+GSocket::GSocket(Type type, GObject* parent)
+ : GIODevice(parent)
+ , m_type(type)
+{
+}
+
+GSocket::~GSocket()
+{
+}
+
+bool GSocket::connect(const GSocketAddress& address, int port)
+{
+ ASSERT(!is_connected());
+ ASSERT(address.type() == GSocketAddress::Type::IPv4);
+ ASSERT(port > 0 && port <= 65535);
+
+ struct sockaddr_in addr;
+ memset(&addr, 0, sizeof(addr));
+ auto ipv4_address = address.ipv4_address();
+ memcpy(&addr.sin_addr.s_addr, &ipv4_address, sizeof(IPv4Address));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(port);
+
+ printf("Connecting to %s...", address.to_string().characters());
+ fflush(stdout);
+ int rc = ::connect(fd(), (struct sockaddr*)&addr, sizeof(addr));
+ if (rc < 0) {
+ perror("connect");
+ exit(1);
+ }
+ printf("ok!\n");
+
+ m_destination_address = address;
+ m_destination_port = port;
+ m_connected = true;
+ return true;
+}
+
+ByteBuffer GSocket::receive(int max_size)
+{
+ auto buffer = read(max_size);
+ if (eof()) {
+ dbgprintf("GSocket{%p}: Connection appears to have closed in receive().\n", this);
+ m_connected = false;
+ }
+ return buffer;
+}
+
+bool GSocket::send(const ByteBuffer& data)
+{
+ int nsent = ::send(fd(), data.pointer(), data.size(), 0);
+ if (nsent < 0) {
+ set_error(nsent);
+ return false;
+ }
+ ASSERT(nsent == data.size());
+ return true;
+}
diff --git a/LibGUI/GSocket.h b/LibGUI/GSocket.h
new file mode 100644
index 0000000000..a4871f6480
--- /dev/null
+++ b/LibGUI/GSocket.h
@@ -0,0 +1,68 @@
+#pragma once
+
+#include <LibGUI/GIODevice.h>
+#include <AK/AKString.h>
+#include <Kernel/IPv4.h>
+
+class GSocketAddress {
+public:
+ enum class Type { Invalid, IPv4, Local };
+
+ GSocketAddress() { }
+ GSocketAddress(const IPv4Address& address)
+ : m_type(Type::IPv4)
+ , m_ipv4_address(address)
+ {
+ }
+
+ Type type() const { return m_type; }
+ bool is_valid() const { return m_type != Type::Invalid; }
+ IPv4Address ipv4_address() const { return m_ipv4_address; }
+
+ String to_string() const
+ {
+ switch (m_type) {
+ case Type::IPv4: return m_ipv4_address.to_string();
+ default: return "[GSocketAddress]";
+ }
+ }
+
+private:
+ Type m_type { Type::Invalid };
+ IPv4Address m_ipv4_address;
+};
+
+class GSocket : public GIODevice {
+public:
+ enum class Type { Invalid, TCP, UDP };
+ virtual ~GSocket() override;
+
+ bool connect(const GSocketAddress&, int port);
+
+ ByteBuffer receive(int max_size);
+ bool send(const ByteBuffer&);
+
+ bool is_connected() const { return m_connected; }
+
+ GSocketAddress source_address() const { return m_source_address; }
+ int source_port() const { return m_source_port; }
+
+ GSocketAddress destination_address() const { return m_source_address; }
+ int destination_port() const { return m_destination_port; }
+
+ virtual const char* class_name() const override { return "GSocket"; }
+
+protected:
+ GSocket(Type, GObject* parent);
+
+ GSocketAddress m_source_address;
+ GSocketAddress m_destination_address;
+ int m_source_port { -1 };
+ int m_destination_port { -1 };
+ bool m_connected { false };
+
+private:
+ virtual bool open(GIODevice::OpenMode) override { ASSERT_NOT_REACHED(); }
+ Type m_type { Type::Invalid };
+
+};
diff --git a/LibGUI/GTCPSocket.cpp b/LibGUI/GTCPSocket.cpp
new file mode 100644
index 0000000000..1194e8adf0
--- /dev/null
+++ b/LibGUI/GTCPSocket.cpp
@@ -0,0 +1,19 @@
+#include <LibGUI/GTCPSocket.h>
+#include <sys/socket.h>
+
+GTCPSocket::GTCPSocket(GObject* parent)
+ : GSocket(GSocket::Type::TCP, parent)
+{
+ int fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (fd < 0) {
+ set_error(fd);
+ } else {
+ set_fd(fd);
+ set_mode(GIODevice::ReadWrite);
+ set_error(0);
+ }
+}
+
+GTCPSocket::~GTCPSocket()
+{
+}
diff --git a/LibGUI/GTCPSocket.h b/LibGUI/GTCPSocket.h
new file mode 100644
index 0000000000..691da1731e
--- /dev/null
+++ b/LibGUI/GTCPSocket.h
@@ -0,0 +1,10 @@
+#include <LibGUI/GSocket.h>
+
+class GTCPSocket final : public GSocket {
+public:
+ explicit GTCPSocket(GObject* parent);
+ virtual ~GTCPSocket() override;
+
+private:
+};
+
diff --git a/LibGUI/Makefile b/LibGUI/Makefile
index c39478d130..098c3cf779 100644
--- a/LibGUI/Makefile
+++ b/LibGUI/Makefile
@@ -40,6 +40,8 @@ LIBGUI_OBJS = \
GStackWidget.o \
GEvent.o \
GScrollableWidget.o \
+ GSocket.o \
+ GTCPSocket.o \
GWindow.o
OBJS = $(SHAREDGRAPHICS_OBJS) $(LIBGUI_OBJS)