plptools
Loading...
Searching...
No Matches
ncp_session.cc
Go to the documentation of this file.
1/*
2 * This file is part of plptools.
3 *
4 * Copyright (C) 1999 Philip Proudman <philip.proudman@btinternet.com>
5 * Copyright (C) 1999-2001 Fritz Elfert <felfert@to.com>
6 * Copyright (C) 2026 Jason Morley <hello@jbmorley.co.uk>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License along
19 * along with this program; if not, see <https://www.gnu.org/licenses/>.
20 *
21 */
22#include "config.h"
23#include "ncp_session.h"
24
25#include <algorithm>
26#include <cassert>
27#include <cstring>
28#include <iostream>
29
30#include <plpintl.h>
31#include <pthread.h>
32
33#include "ncp_log.h"
34#include "socketchannel.h"
35
36using namespace std;
37
51void *link_thread(void *arg) {
52 NCPSession *session = (NCPSession *)arg;
53 while (!session->isCancelled()) {
54 // psion
55 session->socketChannelWatch_.watch(1, 0);
56 if (session->ncp_->hasFailed()) {
57 if (session->autoexit_) {
58 session->cancel();
59 break;
60 }
61 session->socketChannelWatch_.watch(5, 0);
62 if (session->isCancelled()) {
63 break;
64 }
65 if (session->nverbose_ & NCP_SESSION_LOG)
66 lout << "ncp: restarting\n";
67 session->ncp_->reset();
68 }
69 }
70 return NULL;
71}
72
79 NCPSession *session = (NCPSession *)arg;
80 while (true) {
81
82 // Wait for events on our sockets, or cancellation.
83 session->socketChannelWatch_.watch(0, 10000);
84 if (session->isCancelled()) {
85 break;
86 }
87
88 // Poll all the socket channels to drive things forwards.
89 // We take a copy of the socket channels array while holding a lock as this can be mutated on other threads. We
90 // know that it's safe to operate on a copy and that nothing will get cleaned up under our feet as we're also
91 // responsible for clean up (see later).
92 {
93 std::vector<SocketChannel *> socketChannels;
94 {
95 std::lock_guard<std::mutex> lock(session->socketChannelLock_);
96 socketChannels = session->socketChannels_;
97 }
98 for (auto &socketChannel : socketChannels) {
99 socketChannel->socketPoll();
100 }
101 }
102
103 // Clean up the terminated sockets while holding a lock.
104 {
105 std::lock_guard<std::mutex> lock(session->socketChannelLock_);
106 session->socketChannels_.erase(std::remove_if(
107 session->socketChannels_.begin(),
108 session->socketChannels_.end(),
109 [](SocketChannel *socketChannel) {
110 if (!socketChannel->shouldTerminate()) {
111 return false;
112 }
113 delete socketChannel;
114 return true;
115 }),
116 session->socketChannels_.end());
117 }
118
119 }
120 return nullptr;
121}
122
124
125 // Accept the incoming socket.
126 string peer;
127 TCPSocket *next = session->skt_.accept(&peer, session->cancellationPipe_[0]);
128 if (!next) {
129 // NULL here can indicate an error, or cancellation, so we return control to our calling code to allow it to
130 // decide what to do.
131 return;
132 }
133 if (session->nverbose_ & NCP_SESSION_LOG) {
134 lout << "New socket connection from " << peer << endl;
135 }
136
137 // Check to see if we can service the incoming socket.
138 // We don't perform socket rejection while holding the lock since it can take some time.
139 bool didAddSocket = false;
140 {
141 std::lock_guard<std::mutex> lock(session->socketChannelLock_);
142 if ((session->socketChannels_.size() < session->ncp_->maxLinks()) && (session->ncp_->gotLinkChannel())) {
143 session->socketChannels_.push_back(new SocketChannel(next, session->ncp_));
144 didAddSocket = true;
145 }
146 }
147
148 // If we accepted the socket, start watching it and return.
149 if (didAddSocket) {
150 next->setWatch(&session->socketChannelWatch_);
151 return;
152 }
153
154 // If we weren't able to accept the socket, then we need to clean it up.
155
157
158 // Give the client time to send its version request.
159 next->dataToGet(1, 0);
160 next->getBufferStore(a, false);
161
162 a.init();
163 a.addStringT("No Psion Connected\n");
164 next->sendBufferStore(a);
165 delete next;
166 if (session->nverbose_ & NCP_SESSION_LOG) {
167 lout << "rejected" << endl;
168 }
169}
170
171void *ncp_session_main_thread(void *arg) {
172 NCPSession *session = (NCPSession *)arg;
173
174 if (!session->skt_.listen(session->host_.c_str(), session->portNumber_)) {
175 lerr << "listen on " << session->host_ << ":" << session->portNumber_ << ": " << strerror(errno) << endl;
176 return nullptr;
177 }
178 linf
179 << _("Listening at ") << session->host_ << ":" << session->portNumber_
180 << _(" using device ") << session->serialDevice_ << endl;
181
182 session->ncp_ = new NCP(session->serialDevice_.c_str(),
183 session->baudRate_,
184 session->nverbose_,
185 session->cancellationPipe_[0]);
186 pthread_t thr_a, thr_b;
187 if (pthread_create(&thr_a, NULL, link_thread, session) != 0) {
188 lerr << "Could not create Link thread" << endl;
189 exit(-1);
190 }
191 if (pthread_create(&thr_b, NULL, socket_connection_polling_thread, session) != 0) {
192 lerr << "Could not create Socket thread" << endl;
193 exit(-1);
194 }
195 while (!session->isCancelled()) {
197 }
198 linf << _("terminating") << endl;
199 void *ret;
200 pthread_join(thr_a, &ret);
201 linf << _("joined Link thread") << endl;
202 pthread_join(thr_b, &ret);
203 linf << _("joined Socket thread") << endl;
204 delete session->ncp_;
205 linf << _("shut down NCP") << endl;
206 session->skt_.closeSocket();
207 linf << _("socket closed") << endl;
208
209 return nullptr;
210}
211
213 close(cancellationPipe_[0]);
214 close(cancellationPipe_[1]);
215 cancellationPipe_[0] = -1;
216 cancellationPipe_[1] = -1;
217}
218
220 assert(sessionMainThreadId_ == 0);
221 int result = pipe(cancellationPipe_);
222 if (result != 0) {
223 return result;
224 }
226 return pthread_create(&sessionMainThreadId_, NULL, ncp_session_main_thread, this);
227}
228
230 char b = 0;
231 write(cancellationPipe_[1], &b, 1);
232}
233
235 if (cancellationPipe_[0] == -1) {
236 return false;
237 }
238 fd_set fds;
239 FD_ZERO(&fds);
240 FD_SET(cancellationPipe_[0], &fds);
241 struct timeval t = {0, 0};
242 return select(cancellationPipe_[0] + 1, &fds, NULL, NULL, &t) > 0;
243}
244
246 pthread_join(sessionMainThreadId_, 0);
247}
void addIO(const int fd)
Adds a file descriptor to the set of descriptors.
Definition: iowatch.cc:42
bool watch(const long secs, const long usecs)
Performs a select() call.
Definition: iowatch.cc:64
Responsible for orchestrating the high-level life cycle of a daemon-side NCP server and multiplexing ...
Definition: ncp_session.h:41
IOWatch socketChannelWatch_
Used to watch all active SocketChannel instances (stored in socketChannels_) to see if they're readab...
Definition: ncp_session.h:116
int baudRate_
Definition: ncp_session.h:98
TCPSocket skt_
Definition: ncp_session.h:118
int cancellationPipe_[2]
Definition: ncp_session.h:121
std::string host_
Definition: ncp_session.h:99
bool isCancelled()
Definition: ncp_session.cc:234
void cancel()
Mark the session as cancelled.
Definition: ncp_session.cc:229
std::string serialDevice_
Definition: ncp_session.h:100
int portNumber_
Definition: ncp_session.h:97
NCP * ncp_
NCP instance.
Definition: ncp_session.h:111
bool autoexit_
Definition: ncp_session.h:101
std::mutex socketChannelLock_
Definition: ncp_session.h:119
unsigned short nverbose_
Definition: ncp_session.h:102
pthread_t sessionMainThreadId_
Definition: ncp_session.h:106
void wait()
Wait for the session to terminate.
Definition: ncp_session.cc:245
std::vector< SocketChannel * > socketChannels_
Definition: ncp_session.h:120
int start()
Creates and manages all the threads necessary to run a full session for communicating with a Psion an...
Definition: ncp_session.cc:219
friend void * ncp_session_main_thread(void *arg)
Definition: ncp_session.cc:171
Definition: ncp.h:54
int maxLinks()
Definition: ncp.cc:79
bool hasFailed()
Definition: ncp.cc:487
bool gotLinkChannel()
Definition: ncp.cc:506
void reset()
Definition: ncp.cc:84
A class for dealing with sockets.
Definition: tcpsocket.h:38
bool closeSocket(void)
Closes the connection.
Definition: tcpsocket.cc:374
void setWatch(IOWatch *watch)
Registers an IOWatch for this socket.
Definition: tcpsocket.cc:89
virtual bool listen(const char *const Host, int Port)
Starts listening.
Definition: tcpsocket.cc:183
int getBufferStore(bufferStore &a, bool wait=true)
Receive data into a bufferStore .
Definition: tcpsocket.cc:289
bool sendBufferStore(const bufferStore &a)
Sends data from a bufferStore .
Definition: tcpsocket.cc:325
TCPSocket * accept(std::string *Peer)
Accept a connection; blocking, non-cancellable.
Definition: tcpsocket.cc:208
bool dataToGet(int sec, int usec) const
Check and optionally wait for incoming data.
Definition: tcpsocket.cc:277
A generic container for an array of bytes.
Definition: bufferstore.h:37
Definition: doctest.h:522
std::ostream lerr
std::ostream lout
#define NCP_SESSION_LOG
Definition: ncp_log.h:36
std::ostream linf
void check_for_new_socket_connection(NCPSession *session)
Definition: ncp_session.cc:123
void * link_thread(void *arg)
Definition: ncp_session.cc:51
void * socket_connection_polling_thread(void *arg)
Responsible for driving the SocketChannel instances (incoming TCP connections) by means of SocketChan...
Definition: ncp_session.cc:78
void * ncp_session_main_thread(void *arg)
Definition: ncp_session.cc:171
static rfsv * a
Definition: main.cc:53
#define _(String)
Definition: plpintl.h:35