diff options
author | Andreas Kling <awesomekling@gmail.com> | 2019-03-18 14:09:58 +0100 |
---|---|---|
committer | Andreas Kling <awesomekling@gmail.com> | 2019-03-18 14:09:58 +0100 |
commit | 8e3d0a23d555b2464aba5a07a90092535cec19e1 (patch) | |
tree | 551456726750a407e82f76d9b626fa1d78775e70 /LibGUI | |
parent | d466f2634d94df44402c5779395a8f3798151d45 (diff) | |
download | serenity-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.
Diffstat (limited to 'LibGUI')
-rw-r--r-- | LibGUI/GFile.cpp | 14 | ||||
-rw-r--r-- | LibGUI/GFile.h | 1 | ||||
-rw-r--r-- | LibGUI/GIODevice.cpp | 68 | ||||
-rw-r--r-- | LibGUI/GIODevice.h | 7 | ||||
-rw-r--r-- | LibGUI/GSocket.cpp | 64 | ||||
-rw-r--r-- | LibGUI/GSocket.h | 68 | ||||
-rw-r--r-- | LibGUI/GTCPSocket.cpp | 19 | ||||
-rw-r--r-- | LibGUI/GTCPSocket.h | 10 | ||||
-rw-r--r-- | LibGUI/Makefile | 2 |
9 files changed, 230 insertions, 23 deletions
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) |