Convert build system to automake per Anders Kaseorg Makefile.am

This commit is contained in:
Keith Winstein
2012-02-04 23:56:39 -05:00
parent 5800cb7965
commit da2f481eea
70 changed files with 1118 additions and 79 deletions
+12
View File
@@ -0,0 +1,12 @@
AM_CPPFLAGS = -I$(top_builddir)/protobufs/ -D_XOPEN_SOURCE=500 -D_GNU_SOURCE -D_BSD_SOURCE
AM_CXXFLAGS = -g -O2 --std=c++0x -pedantic -Werror -Wall -Wextra -Weffc++ -fno-default-inline -pipe
LIBS = `pkg-config --libs protobuf-lite`
LDADD = -lutil -lcrypto -lrt -lm $(top_builddir)/protobufs/libmoshprotos.a
bin_PROGRAMS = mosh-client mosh-server
source = parser.cpp parserstate.cpp terminal.cpp parseraction.cpp terminalfunctions.cpp swrite.cpp terminalframebuffer.cpp terminaldispatcher.cpp terminaluserinput.cpp terminaldisplay.cpp network.cpp ocb.cpp base64.cpp crypto.cpp networktransport.cpp transportfragment.cpp user.cpp completeterminal.cpp transportsender.cpp stmclient.cpp terminaloverlay.cpp
mosh_client_SOURCES = mosh-client.cpp $(source)
mosh_server_SOURCES = mosh-server.cpp $(source)
+182
View File
@@ -0,0 +1,182 @@
/* ---------------------------------------------------------------------------
*
* AEAD API 0.12 - 13 July 2011
*
* This file gives an interface appropriate for many authenticated
* encryption with associated data (AEAD) implementations. It does not try
* to accommodate all possible options or limitations that an implementation
* might have -- you should consult the documentation of your chosen
* implementation to find things like RFC 5116 constants, alignment
* requirements, whether the incremental interface is supported, etc.
*
* This file is in the public domain. It is provided "as is", without
* warranty of any kind. Use at your own risk.
*
* Comments are welcome: Ted Krovetz <ted@krovetz>.
*
* ------------------------------------------------------------------------ */
#ifndef _AE_H_
#define _AE_H_
#ifdef __cplusplus
extern "C" {
#endif
/* --------------------------------------------------------------------------
*
* Constants
*
* ----------------------------------------------------------------------- */
/* Return status codes: Negative return values indicate an error occurred.
* For full explanations of error values, consult the implementation's
* documentation. */
#define AE_SUCCESS ( 0) /* Indicates successful completion of call */
#define AE_INVALID (-1) /* Indicates bad tag during decryption */
#define AE_NOT_SUPPORTED (-2) /* Indicates unsupported option requested */
/* Flags: When data can be processed "incrementally", these flags are used
* to indicate whether the submitted data is the last or not. */
#define AE_FINALIZE (1) /* This is the last of data */
#define AE_PENDING (0) /* More data of is coming */
/* --------------------------------------------------------------------------
*
* AEAD opaque structure definition
*
* ----------------------------------------------------------------------- */
typedef struct _ae_ctx ae_ctx;
/* --------------------------------------------------------------------------
*
* Data Structure Routines
*
* ----------------------------------------------------------------------- */
ae_ctx* ae_allocate (void *misc); /* Allocate ae_ctx, set optional ptr */
void ae_free (ae_ctx *ctx); /* Deallocate ae_ctx struct */
int ae_clear (ae_ctx *ctx); /* Undo initialization */
int ae_ctx_sizeof(void); /* Return sizeof(ae_ctx) */
/* ae_allocate() allocates an ae_ctx structure, but does not initialize it.
* ae_free() deallocates an ae_ctx structure, but does not zeroize it.
* ae_clear() zeroes sensitive values associated with an ae_ctx structure
* and deallocates any auxiliary structures allocated during ae_init().
* ae_ctx_sizeof() returns sizeof(ae_ctx), to aid in any static allocations.
*/
/* --------------------------------------------------------------------------
*
* AEAD Routines
*
* ----------------------------------------------------------------------- */
int ae_init(ae_ctx *ctx,
const void *key,
int key_len,
int nonce_len,
int tag_len);
/* --------------------------------------------------------------------------
*
* Initialize an ae_ctx context structure.
*
* Parameters:
* ctx - Pointer to an ae_ctx structure to be initialized
* key - Pointer to user-supplied key
* key_len - Length of key supplied, in bytes
* nonce_len - Length of nonces to be used for this key, in bytes
* tag_len - Length of tags to be produced for this key, in bytes
*
* Returns:
* AE_SUCCESS - Success. Ctx ready for use.
* AE_NOT_SUPPORTED - An unsupported length was supplied. Ctx is untouched.
* Otherwise - Error. Check implementation documentation for codes.
*
* ----------------------------------------------------------------------- */
int ae_encrypt(ae_ctx *ctx,
const void *nonce,
const void *pt,
int pt_len,
const void *ad,
int ad_len,
void *ct,
void *tag,
int final);
/* --------------------------------------------------------------------------
*
* Encrypt plaintext; provide for authentication of ciphertext/associated data.
*
* Parameters:
* ctx - Pointer to an ae_ctx structure initialized by ae_init.
* nonce - Pointer to a nonce_len (defined in ae_init) byte nonce.
* pt - Pointer to plaintext bytes to be encrypted.
* pt_len - number of bytes pointed to by pt.
* ad - Pointer to associated data.
* ad_len - number of bytes pointed to by ad.
* ct - Pointer to buffer to receive ciphertext encryption.
* tag - Pointer to receive authentication tag; or NULL
* if tag is to be bundled into the ciphertext.
* final - Non-zero if this call completes the plaintext being encrypted.
*
* If nonce!=NULL then a message is being initiated. If final!=0
* then a message is being finalized. If final==0 or nonce==NULL
* then the incremental interface is being used. If nonce!=NULL and
* ad_len<0, then use same ad as last message.
*
* Returns:
* non-negative - Number of bytes written to ct.
* AE_NOT_SUPPORTED - Usage mode unsupported (eg, incremental and/or sticky).
* Otherwise - Error. Check implementation documentation for codes.
*
* ----------------------------------------------------------------------- */
int ae_decrypt(ae_ctx *ctx,
const void *nonce,
const void *ct,
int ct_len,
const void *ad,
int ad_len,
void *pt,
const void *tag,
int final);
/* --------------------------------------------------------------------------
*
* Decrypt ciphertext; provide authenticity of plaintext and associated data.
*
* Parameters:
* ctx - Pointer to an ae_ctx structure initialized by ae_init.
* nonce - Pointer to a nonce_len (defined in ae_init) byte nonce.
* ct - Pointer to ciphertext bytes to be decrypted.
* ct_len - number of bytes pointed to by ct.
* ad - Pointer to associated data.
* ad_len - number of bytes pointed to by ad.
* pt - Pointer to buffer to receive plaintext decryption.
* tag - Pointer to tag_len (defined in ae_init) bytes; or NULL
* if tag is bundled into the ciphertext. Non-NULL tag is only
* read when final is non-zero.
* final - Non-zero if this call completes the ciphertext being decrypted.
*
* If nonce!=NULL then "ct" points to the start of a ciphertext. If final!=0
* then "in" points to the final piece of ciphertext. If final==0 or nonce==
* NULL then the incremental interface is being used. If nonce!=NULL and
* ad_len<0, then use same ad as last message.
*
* Returns:
* non-negative - Number of bytes written to pt.
* AE_INVALID - Authentication failure.
* AE_NOT_SUPPORTED - Usage mode unsupported (eg, incremental and/or sticky).
* Otherwise - Error. Check implementation documentation for codes.
*
* NOTE !!! NOTE !!! -- The ciphertext should be assumed possibly inauthentic
* until it has been completely written and it is
* verified that this routine did not return AE_INVALID.
*
* ----------------------------------------------------------------------- */
#ifdef __cplusplus
} /* closing brace for extern "C" */
#endif
#endif /* _AE_H_ */
+577
View File
@@ -0,0 +1,577 @@
/* Taken from GNU coreutils */
/* base64.c -- Encode binary data using printable characters.
Copyright (C) 1999-2001, 2004-2006, 2009-2011 Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
/* Written by Simon Josefsson. Partially adapted from GNU MailUtils
* (mailbox/filter_trans.c, as of 2004-11-28). Improved by review
* from Paul Eggert, Bruno Haible, and Stepan Kasal.
*
* See also RFC 3548 <http://www.ietf.org/rfc/rfc3548.txt>.
*
* Be careful with error checking. Here is how you would typically
* use these functions:
*
* bool ok = base64_decode_alloc (in, inlen, &out, &outlen);
* if (!ok)
* FAIL: input was not valid base64
* if (out == NULL)
* FAIL: memory allocation error
* OK: data in OUT/OUTLEN
*
* size_t outlen = base64_encode_alloc (in, inlen, &out);
* if (out == NULL && outlen == 0 && inlen != 0)
* FAIL: input too long
* if (out == NULL)
* FAIL: memory allocation error
* OK: data in OUT/OUTLEN.
*
*/
// #include <config.h>
/* Get prototype. */
#include "base64.h"
/* Get malloc. */
#include <stdlib.h>
/* Get UCHAR_MAX. */
#include <limits.h>
#include <string.h>
/* C89 compliant way to cast 'char' to 'unsigned char'. */
static inline unsigned char
to_uchar (char ch)
{
return ch;
}
/* Base64 encode IN array of size INLEN into OUT array of size OUTLEN.
If OUTLEN is less than BASE64_LENGTH(INLEN), write as many bytes as
possible. If OUTLEN is larger than BASE64_LENGTH(INLEN), also zero
terminate the output buffer. */
void
base64_encode (const char *restrict in, size_t inlen,
char *restrict out, size_t outlen)
{
static const char b64str[65] = /* KJW */
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
while (inlen && outlen)
{
*out++ = b64str[(to_uchar (in[0]) >> 2) & 0x3f];
if (!--outlen)
break;
*out++ = b64str[((to_uchar (in[0]) << 4)
+ (--inlen ? to_uchar (in[1]) >> 4 : 0))
& 0x3f];
if (!--outlen)
break;
*out++ =
(inlen
? b64str[((to_uchar (in[1]) << 2)
+ (--inlen ? to_uchar (in[2]) >> 6 : 0))
& 0x3f]
: '=');
if (!--outlen)
break;
*out++ = inlen ? b64str[to_uchar (in[2]) & 0x3f] : '=';
if (!--outlen)
break;
if (inlen)
inlen--;
if (inlen)
in += 3;
}
if (outlen)
*out = '\0';
}
/* Allocate a buffer and store zero terminated base64 encoded data
from array IN of size INLEN, returning BASE64_LENGTH(INLEN), i.e.,
the length of the encoded data, excluding the terminating zero. On
return, the OUT variable will hold a pointer to newly allocated
memory that must be deallocated by the caller. If output string
length would overflow, 0 is returned and OUT is set to NULL. If
memory allocation failed, OUT is set to NULL, and the return value
indicates length of the requested memory block, i.e.,
BASE64_LENGTH(inlen) + 1. */
size_t
base64_encode_alloc (const char *in, size_t inlen, char **out)
{
size_t outlen = 1 + BASE64_LENGTH (inlen);
/* Check for overflow in outlen computation.
*
* If there is no overflow, outlen >= inlen.
*
* If the operation (inlen + 2) overflows then it yields at most +1, so
* outlen is 0.
*
* If the multiplication overflows, we lose at least half of the
* correct value, so the result is < ((inlen + 2) / 3) * 2, which is
* less than (inlen + 2) * 0.66667, which is less than inlen as soon as
* (inlen > 4).
*/
if (inlen > outlen)
{
*out = NULL;
return 0;
}
*out = (char *) malloc (outlen); /* KJW */
if (!*out)
return outlen;
base64_encode (in, inlen, *out, outlen);
return outlen - 1;
}
/* With this approach this file works independent of the charset used
(think EBCDIC). However, it does assume that the characters in the
Base64 alphabet (A-Za-z0-9+/) are encoded in 0..255. POSIX
1003.1-2001 require that char and unsigned char are 8-bit
quantities, though, taking care of that problem. But this may be a
potential problem on non-POSIX C99 platforms.
IBM C V6 for AIX mishandles "#define B64(x) ...'x'...", so use "_"
as the formal parameter rather than "x". */
#define B64(_) \
((_) == 'A' ? 0 \
: (_) == 'B' ? 1 \
: (_) == 'C' ? 2 \
: (_) == 'D' ? 3 \
: (_) == 'E' ? 4 \
: (_) == 'F' ? 5 \
: (_) == 'G' ? 6 \
: (_) == 'H' ? 7 \
: (_) == 'I' ? 8 \
: (_) == 'J' ? 9 \
: (_) == 'K' ? 10 \
: (_) == 'L' ? 11 \
: (_) == 'M' ? 12 \
: (_) == 'N' ? 13 \
: (_) == 'O' ? 14 \
: (_) == 'P' ? 15 \
: (_) == 'Q' ? 16 \
: (_) == 'R' ? 17 \
: (_) == 'S' ? 18 \
: (_) == 'T' ? 19 \
: (_) == 'U' ? 20 \
: (_) == 'V' ? 21 \
: (_) == 'W' ? 22 \
: (_) == 'X' ? 23 \
: (_) == 'Y' ? 24 \
: (_) == 'Z' ? 25 \
: (_) == 'a' ? 26 \
: (_) == 'b' ? 27 \
: (_) == 'c' ? 28 \
: (_) == 'd' ? 29 \
: (_) == 'e' ? 30 \
: (_) == 'f' ? 31 \
: (_) == 'g' ? 32 \
: (_) == 'h' ? 33 \
: (_) == 'i' ? 34 \
: (_) == 'j' ? 35 \
: (_) == 'k' ? 36 \
: (_) == 'l' ? 37 \
: (_) == 'm' ? 38 \
: (_) == 'n' ? 39 \
: (_) == 'o' ? 40 \
: (_) == 'p' ? 41 \
: (_) == 'q' ? 42 \
: (_) == 'r' ? 43 \
: (_) == 's' ? 44 \
: (_) == 't' ? 45 \
: (_) == 'u' ? 46 \
: (_) == 'v' ? 47 \
: (_) == 'w' ? 48 \
: (_) == 'x' ? 49 \
: (_) == 'y' ? 50 \
: (_) == 'z' ? 51 \
: (_) == '0' ? 52 \
: (_) == '1' ? 53 \
: (_) == '2' ? 54 \
: (_) == '3' ? 55 \
: (_) == '4' ? 56 \
: (_) == '5' ? 57 \
: (_) == '6' ? 58 \
: (_) == '7' ? 59 \
: (_) == '8' ? 60 \
: (_) == '9' ? 61 \
: (_) == '+' ? 62 \
: (_) == '/' ? 63 \
: -1)
static const signed char b64[0x100] = {
B64 (0), B64 (1), B64 (2), B64 (3),
B64 (4), B64 (5), B64 (6), B64 (7),
B64 (8), B64 (9), B64 (10), B64 (11),
B64 (12), B64 (13), B64 (14), B64 (15),
B64 (16), B64 (17), B64 (18), B64 (19),
B64 (20), B64 (21), B64 (22), B64 (23),
B64 (24), B64 (25), B64 (26), B64 (27),
B64 (28), B64 (29), B64 (30), B64 (31),
B64 (32), B64 (33), B64 (34), B64 (35),
B64 (36), B64 (37), B64 (38), B64 (39),
B64 (40), B64 (41), B64 (42), B64 (43),
B64 (44), B64 (45), B64 (46), B64 (47),
B64 (48), B64 (49), B64 (50), B64 (51),
B64 (52), B64 (53), B64 (54), B64 (55),
B64 (56), B64 (57), B64 (58), B64 (59),
B64 (60), B64 (61), B64 (62), B64 (63),
B64 (64), B64 (65), B64 (66), B64 (67),
B64 (68), B64 (69), B64 (70), B64 (71),
B64 (72), B64 (73), B64 (74), B64 (75),
B64 (76), B64 (77), B64 (78), B64 (79),
B64 (80), B64 (81), B64 (82), B64 (83),
B64 (84), B64 (85), B64 (86), B64 (87),
B64 (88), B64 (89), B64 (90), B64 (91),
B64 (92), B64 (93), B64 (94), B64 (95),
B64 (96), B64 (97), B64 (98), B64 (99),
B64 (100), B64 (101), B64 (102), B64 (103),
B64 (104), B64 (105), B64 (106), B64 (107),
B64 (108), B64 (109), B64 (110), B64 (111),
B64 (112), B64 (113), B64 (114), B64 (115),
B64 (116), B64 (117), B64 (118), B64 (119),
B64 (120), B64 (121), B64 (122), B64 (123),
B64 (124), B64 (125), B64 (126), B64 (127),
B64 (128), B64 (129), B64 (130), B64 (131),
B64 (132), B64 (133), B64 (134), B64 (135),
B64 (136), B64 (137), B64 (138), B64 (139),
B64 (140), B64 (141), B64 (142), B64 (143),
B64 (144), B64 (145), B64 (146), B64 (147),
B64 (148), B64 (149), B64 (150), B64 (151),
B64 (152), B64 (153), B64 (154), B64 (155),
B64 (156), B64 (157), B64 (158), B64 (159),
B64 (160), B64 (161), B64 (162), B64 (163),
B64 (164), B64 (165), B64 (166), B64 (167),
B64 (168), B64 (169), B64 (170), B64 (171),
B64 (172), B64 (173), B64 (174), B64 (175),
B64 (176), B64 (177), B64 (178), B64 (179),
B64 (180), B64 (181), B64 (182), B64 (183),
B64 (184), B64 (185), B64 (186), B64 (187),
B64 (188), B64 (189), B64 (190), B64 (191),
B64 (192), B64 (193), B64 (194), B64 (195),
B64 (196), B64 (197), B64 (198), B64 (199),
B64 (200), B64 (201), B64 (202), B64 (203),
B64 (204), B64 (205), B64 (206), B64 (207),
B64 (208), B64 (209), B64 (210), B64 (211),
B64 (212), B64 (213), B64 (214), B64 (215),
B64 (216), B64 (217), B64 (218), B64 (219),
B64 (220), B64 (221), B64 (222), B64 (223),
B64 (224), B64 (225), B64 (226), B64 (227),
B64 (228), B64 (229), B64 (230), B64 (231),
B64 (232), B64 (233), B64 (234), B64 (235),
B64 (236), B64 (237), B64 (238), B64 (239),
B64 (240), B64 (241), B64 (242), B64 (243),
B64 (244), B64 (245), B64 (246), B64 (247),
B64 (248), B64 (249), B64 (250), B64 (251),
B64 (252), B64 (253), B64 (254), B64 (255)
};
#if UCHAR_MAX == 255
# define uchar_in_range(c) true
#else
# define uchar_in_range(c) ((c) <= 255)
#endif
/* Return true if CH is a character from the Base64 alphabet, and
false otherwise. Note that '=' is padding and not considered to be
part of the alphabet. */
bool
isbase64 (char ch)
{
return uchar_in_range (to_uchar (ch)) && 0 <= b64[to_uchar (ch)];
}
/* Initialize decode-context buffer, CTX. */
void
base64_decode_ctx_init (struct base64_decode_context *ctx)
{
ctx->i = 0;
}
/* If CTX->i is 0 or 4, there are four or more bytes in [*IN..IN_END), and
none of those four is a newline, then return *IN. Otherwise, copy up to
4 - CTX->i non-newline bytes from that range into CTX->buf, starting at
index CTX->i and setting CTX->i to reflect the number of bytes copied,
and return CTX->buf. In either case, advance *IN to point to the byte
after the last one processed, and set *N_NON_NEWLINE to the number of
verified non-newline bytes accessible through the returned pointer. */
static inline char *
get_4 (struct base64_decode_context *ctx,
char const *restrict *in, char const *restrict in_end,
size_t *n_non_newline)
{
if (ctx->i == 4)
ctx->i = 0;
if (ctx->i == 0)
{
char const *t = *in;
if (4 <= in_end - *in && memchr (t, '\n', 4) == NULL)
{
/* This is the common case: no newline. */
*in += 4;
*n_non_newline = 4;
return (char *) t;
}
}
{
/* Copy non-newline bytes into BUF. */
char const *p = *in;
while (p < in_end)
{
char c = *p++;
if (c != '\n')
{
ctx->buf[ctx->i++] = c;
if (ctx->i == 4)
break;
}
}
*in = p;
*n_non_newline = ctx->i;
return ctx->buf;
}
}
#define return_false \
do \
{ \
*outp = out; \
return false; \
} \
while (false)
/* Decode up to four bytes of base64-encoded data, IN, of length INLEN
into the output buffer, *OUT, of size *OUTLEN bytes. Return true if
decoding is successful, false otherwise. If *OUTLEN is too small,
as many bytes as possible are written to *OUT. On return, advance
*OUT to point to the byte after the last one written, and decrement
*OUTLEN to reflect the number of bytes remaining in *OUT. */
static inline bool
decode_4 (char const *restrict in, size_t inlen,
char *restrict *outp, size_t *outleft)
{
char *out = *outp;
if (inlen < 2)
return false;
if (!isbase64 (in[0]) || !isbase64 (in[1]))
return false;
if (*outleft)
{
*out++ = ((b64[to_uchar (in[0])] << 2)
| (b64[to_uchar (in[1])] >> 4));
--*outleft;
}
if (inlen == 2)
return_false;
if (in[2] == '=')
{
if (inlen != 4)
return_false;
if (in[3] != '=')
return_false;
}
else
{
if (!isbase64 (in[2]))
return_false;
if (*outleft)
{
*out++ = (((b64[to_uchar (in[1])] << 4) & 0xf0)
| (b64[to_uchar (in[2])] >> 2));
--*outleft;
}
if (inlen == 3)
return_false;
if (in[3] == '=')
{
if (inlen != 4)
return_false;
}
else
{
if (!isbase64 (in[3]))
return_false;
if (*outleft)
{
*out++ = (((b64[to_uchar (in[2])] << 6) & 0xc0)
| b64[to_uchar (in[3])]);
--*outleft;
}
}
}
*outp = out;
return true;
}
/* Decode base64-encoded input array IN of length INLEN to output array
OUT that can hold *OUTLEN bytes. The input data may be interspersed
with newlines. Return true if decoding was successful, i.e. if the
input was valid base64 data, false otherwise. If *OUTLEN is too
small, as many bytes as possible will be written to OUT. On return,
*OUTLEN holds the length of decoded bytes in OUT. Note that as soon
as any non-alphabet, non-newline character is encountered, decoding
is stopped and false is returned. If INLEN is zero, then process
only whatever data is stored in CTX.
Initially, CTX must have been initialized via base64_decode_ctx_init.
Subsequent calls to this function must reuse whatever state is recorded
in that buffer. It is necessary for when a quadruple of base64 input
bytes spans two input buffers.
If CTX is NULL then newlines are treated as garbage and the input
buffer is processed as a unit. */
bool
base64_decode_ctx (struct base64_decode_context *ctx,
const char *restrict in, size_t inlen,
char *restrict out, size_t *outlen)
{
size_t outleft = *outlen;
bool ignore_newlines = ctx != NULL;
bool flush_ctx = false;
unsigned int ctx_i = 0;
if (ignore_newlines)
{
ctx_i = ctx->i;
flush_ctx = inlen == 0;
}
while (true)
{
size_t outleft_save = outleft;
if (ctx_i == 0 && !flush_ctx)
{
while (true)
{
/* Save a copy of outleft, in case we need to re-parse this
block of four bytes. */
outleft_save = outleft;
if (!decode_4 (in, inlen, &out, &outleft))
break;
in += 4;
inlen -= 4;
}
}
if (inlen == 0 && !flush_ctx)
break;
/* Handle the common case of 72-byte wrapped lines.
This also handles any other multiple-of-4-byte wrapping. */
if (inlen && *in == '\n' && ignore_newlines)
{
++in;
--inlen;
continue;
}
/* Restore OUT and OUTLEFT. */
out -= outleft_save - outleft;
outleft = outleft_save;
{
char const *in_end = in + inlen;
char const *non_nl;
if (ignore_newlines)
non_nl = get_4 (ctx, &in, in_end, &inlen);
else
non_nl = in; /* Might have nl in this case. */
/* If the input is empty or consists solely of newlines (0 non-newlines),
then we're done. Likewise if there are fewer than 4 bytes when not
flushing context and not treating newlines as garbage. */
if (inlen == 0 || (inlen < 4 && !flush_ctx && ignore_newlines))
{
inlen = 0;
break;
}
if (!decode_4 (non_nl, inlen, &out, &outleft))
break;
inlen = in_end - in;
}
}
*outlen -= outleft;
return inlen == 0;
}
/* Allocate an output buffer in *OUT, and decode the base64 encoded
data stored in IN of size INLEN to the *OUT buffer. On return, the
size of the decoded data is stored in *OUTLEN. OUTLEN may be NULL,
if the caller is not interested in the decoded length. *OUT may be
NULL to indicate an out of memory error, in which case *OUTLEN
contains the size of the memory block needed. The function returns
true on successful decoding and memory allocation errors. (Use the
*OUT and *OUTLEN parameters to differentiate between successful
decoding and memory error.) The function returns false if the
input was invalid, in which case *OUT is NULL and *OUTLEN is
undefined. */
bool
base64_decode_alloc_ctx (struct base64_decode_context *ctx,
const char *in, size_t inlen, char **out,
size_t *outlen)
{
/* This may allocate a few bytes too many, depending on input,
but it's not worth the extra CPU time to compute the exact size.
The exact size is 3 * inlen / 4, minus 1 if the input ends
with "=" and minus another 1 if the input ends with "==".
Dividing before multiplying avoids the possibility of overflow. */
size_t needlen = 3 * (inlen / 4) + 2;
*out = (char *) malloc (needlen);
if (!*out)
return true;
if (!base64_decode_ctx (ctx, in, inlen, *out, &needlen))
{
free (*out);
*out = NULL;
return false;
}
if (outlen)
*outlen = needlen;
return true;
}
+65
View File
@@ -0,0 +1,65 @@
/* Taken from GNU coreutils */
#define restrict
/* base64.h -- Encode binary data using printable characters.
Copyright (C) 2004-2006, 2009-2011 Free Software Foundation, Inc.
Written by Simon Josefsson.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
#ifndef BASE64_H
# define BASE64_H
/* Get size_t. */
# include <stddef.h>
/* Get bool. */
# include <stdbool.h>
/* This uses that the expression (n+(k-1))/k means the smallest
integer >= n/k, i.e., the ceiling of n/k. */
# define BASE64_LENGTH(inlen) ((((inlen) + 2) / 3) * 4)
struct base64_decode_context
{
unsigned int i;
char buf[4];
};
extern bool isbase64 (char ch);
extern void base64_encode (const char *restrict in, size_t inlen,
char *restrict out, size_t outlen);
extern size_t base64_encode_alloc (const char *in, size_t inlen, char **out);
extern void base64_decode_ctx_init (struct base64_decode_context *ctx);
extern bool base64_decode_ctx (struct base64_decode_context *ctx,
const char *restrict in, size_t inlen,
char *restrict out, size_t *outlen);
extern bool base64_decode_alloc_ctx (struct base64_decode_context *ctx,
const char *in, size_t inlen,
char **out, size_t *outlen);
#define base64_decode(in, inlen, out, outlen) \
base64_decode_ctx (NULL, in, inlen, out, outlen)
#define base64_decode_alloc(in, inlen, out, outlen) \
base64_decode_alloc_ctx (NULL, in, inlen, out, outlen)
#endif /* BASE64_H */
+75
View File
@@ -0,0 +1,75 @@
#include "completeterminal.hpp"
#include "hostinput.pb.h"
using namespace std;
using namespace Parser;
using namespace Terminal;
using namespace HostBuffers;
string Complete::act( const string &str )
{
for ( unsigned int i = 0; i < str.size(); i++ ) {
/* parse octet into up to three actions */
list<Action *> actions( parser.input( str[ i ] ) );
/* apply actions to terminal and delete them */
for ( list<Action *>::iterator it = actions.begin();
it != actions.end();
it++ ) {
Action *act = *it;
act->act_on_terminal( &terminal );
delete act;
}
}
return terminal.read_octets_to_host();
}
string Complete::act( const Action *act )
{
/* apply action to terminal */
act->act_on_terminal( &terminal );
return terminal.read_octets_to_host();
}
/* interface for Network::Transport */
string Complete::diff_from( const Complete &existing )
{
HostBuffers::HostMessage output;
if ( !(existing.get_fb() == get_fb()) ) {
if ( (existing.get_fb().ds.get_width() != terminal.get_fb().ds.get_width())
|| (existing.get_fb().ds.get_height() != terminal.get_fb().ds.get_height()) ) {
Instruction *new_res = output.add_instruction();
new_res->MutableExtension( resize )->set_width( terminal.get_fb().ds.get_width() );
new_res->MutableExtension( resize )->set_height( terminal.get_fb().ds.get_height() );
}
Instruction *new_inst = output.add_instruction();
new_inst->MutableExtension( hostbytes )->set_hoststring( Terminal::Display::new_frame( true, existing.get_fb(), terminal.get_fb() ) );
}
return output.SerializeAsString();
}
void Complete::apply_string( string diff )
{
HostBuffers::HostMessage input;
assert( input.ParseFromString( diff ) );
for ( int i = 0; i < input.instruction_size(); i++ ) {
if ( input.instruction( i ).HasExtension( hostbytes ) ) {
string terminal_to_host = act( input.instruction( i ).GetExtension( hostbytes ).hoststring() );
assert( terminal_to_host.empty() ); /* server never interrogates client terminal */
} else if ( input.instruction( i ).HasExtension( resize ) ) {
act( new Resize( input.instruction( i ).GetExtension( resize ).width(),
input.instruction( i ).GetExtension( resize ).height() ) );
}
}
}
bool Complete::operator==( Complete const &x ) const
{
// assert( parser == x.parser ); /* parser state is irrelevant for us */
return terminal == x.terminal;
}
+32
View File
@@ -0,0 +1,32 @@
#ifndef COMPLETE_TERMINAL_HPP
#define COMPLETE_TERMINAL_HPP
#include "parser.hpp"
#include "terminal.hpp"
/* This class represents the complete terminal -- a UTF8Parser feeding Actions to an Emulator. */
namespace Terminal {
class Complete {
private:
Parser::UTF8Parser parser;
Terminal::Emulator terminal;
public:
Complete( size_t width, size_t height ) : parser(), terminal( width, height ) {}
std::string act( const std::string &str );
std::string act( const Parser::Action *act );
const Framebuffer & get_fb( void ) const { return terminal.get_fb(); }
bool parser_grounded( void ) const { return parser.is_grounded(); }
/* interface for Network::Transport */
void subtract( const Complete * ) {}
std::string diff_from( const Complete &existing );
void apply_string( std::string diff );
bool operator==( const Complete &x ) const;
};
}
#endif
+227
View File
@@ -0,0 +1,227 @@
#include <string.h>
#include <stdio.h>
#include "crypto.hpp"
#include "base64.h"
using namespace std;
using namespace Crypto;
const char rdev[] = "/dev/urandom";
long int myatoi( char *str )
{
char *end;
errno = 0;
long int ret = strtol( str, &end, 10 );
if ( ( errno != 0 )
|| ( end != str + strlen( str ) ) ) {
throw CryptoException( "Bad integer." );
}
return ret;
}
static void * sse_alloc( int len )
{
void *ptr = NULL;
if( (0 != posix_memalign( (void **)&ptr, 16, len )) || (ptr == NULL) ) {
throw std::bad_alloc();
}
return ptr;
}
Base64Key::Base64Key( string printable_key )
{
if ( printable_key.length() != 22 ) {
throw CryptoException( "Key must be 22 letters long." );
}
string base64 = printable_key + "==";
size_t len = 16;
if ( !base64_decode( base64.data(), 24, (char *)&key[ 0 ], &len ) ) {
throw CryptoException( "Key must be well-formed base64." );
}
if ( len != 16 ) {
throw CryptoException( "Key must represent 16 octets." );
}
/* to catch changes after the first 128 bits */
if ( printable_key != this->printable_key() ) {
throw CryptoException( "Base64 key was not encoded 128-bit key." );
}
}
Base64Key::Base64Key()
{
FILE *devrandom = fopen( rdev, "r" );
if ( devrandom == NULL ) {
throw CryptoException( string( rdev ) + ": " + strerror( errno ) );
}
if ( 1 != fread( key, 16, 1, devrandom ) ) {
throw CryptoException( "Could not read from " + string( rdev ) );
}
if ( 0 != fclose( devrandom ) ) {
throw CryptoException( string( rdev ) + ": " + strerror( errno ) );
}
}
string Base64Key::printable_key( void ) const
{
char base64[ 25 ];
base64_encode( (char *)key, 16, base64, 25 );
if ( (base64[ 24 ] != 0)
|| (base64[ 23 ] != '=')
|| (base64[ 22 ] != '=') ) {
throw CryptoException( "Unexpected output from base64_encode." );
}
base64[ 22 ] = 0;
return string( base64 );
}
Session::Session( Base64Key s_key )
: key( s_key ), ctx( NULL )
{
ctx = ae_allocate( NULL );
if ( ctx == NULL ) {
throw CryptoException( "Could not allocate AES-OCB context." );
}
if ( AE_SUCCESS != ae_init( ctx, key.data(), 16, 12, 16 ) ) {
throw CryptoException( "Could not initialize AES-OCB context." );
}
}
Session::~Session()
{
if ( ae_clear( ctx ) != AE_SUCCESS ) {
throw CryptoException( "Could not clear AES-OCB context." );
}
ae_free( ctx );
}
Nonce::Nonce( uint64_t val )
{
uint64_t val_net = htobe64( val );
memset( bytes, 0, 4 );
memcpy( bytes + 4, &val_net, 8 );
}
uint64_t Nonce::val( void )
{
uint64_t ret;
memcpy( &ret, bytes + 4, 8 );
return be64toh( ret );
}
Nonce::Nonce( char *s_bytes, size_t len )
{
if ( len != 8 ) {
throw CryptoException( "Nonce representation must be 8 octets long." );
}
memset( bytes, 0, 4 );
memcpy( bytes + 4, s_bytes, 8 );
}
Message::Message( char *nonce_bytes, size_t nonce_len,
char *text_bytes, size_t text_len )
: nonce( nonce_bytes, nonce_len ),
text( (char *)text_bytes, text_len )
{}
Message::Message( Nonce s_nonce, string s_text )
: nonce( s_nonce ),
text( s_text )
{}
string Session::encrypt( Message plaintext )
{
const size_t pt_len = plaintext.text.size();
const int ciphertext_len = pt_len + 16;
char *ciphertext = (char *)sse_alloc( ciphertext_len );
char *pt = (char *)sse_alloc( pt_len );
memcpy( pt, plaintext.text.data(), plaintext.text.size() );
if ( (uint64_t( plaintext.nonce.data() ) & 0xf) != 0 ) {
throw CryptoException( "Bad alignment." );
}
if ( ciphertext_len != ae_encrypt( ctx, /* ctx */
plaintext.nonce.data(), /* nonce */
pt, /* pt */
pt_len, /* pt_len */
NULL, /* ad */
0, /* ad_len */
ciphertext, /* ct */
NULL, /* tag */
AE_FINALIZE ) ) { /* final */
free( pt );
free( ciphertext );
throw CryptoException( "ae_encrypt() returned error." );
}
string text( (char *)ciphertext, ciphertext_len );
free( pt );
free( ciphertext );
return plaintext.nonce.cpp_str() + text;
}
Message Session::decrypt( string ciphertext )
{
if ( ciphertext.size() < 24 ) {
throw CryptoException( "Ciphertext must contain nonce and tag." );
}
char *str = (char *)ciphertext.data();
int body_len = ciphertext.size() - 8;
int pt_len = body_len - 16;
if ( pt_len < 0 ) { /* super-assertion that pt_len does not equal AE_INVALID */
fprintf( stderr, "BUG.\n" );
exit( 1 );
}
Nonce __attribute__((__aligned__ (16))) nonce( str, 8 );
char *body = (char *)sse_alloc( body_len );
memcpy( body, str + 8, body_len );
char *plaintext = (char *)sse_alloc( pt_len );
if ( pt_len != ae_decrypt( ctx, /* ctx */
nonce.data(), /* nonce */
body, /* ct */
body_len, /* ct_len */
NULL, /* ad */
0, /* ad_len */
plaintext, /* pt */
NULL, /* tag */
AE_FINALIZE ) ) { /* final */
free( plaintext );
free( body );
throw CryptoException( "Packet failed integrity check." );
}
Message ret( nonce, string( plaintext, pt_len ) );
free( plaintext );
free( body );
return ret;
}
+70
View File
@@ -0,0 +1,70 @@
#ifndef CRYPTO_HPP
#define CRYPTO_HPP
#include "ae.hpp"
#include <string>
#include <string.h>
using namespace std;
long int myatoi( char *str );
namespace Crypto {
class CryptoException {
public:
string text;
CryptoException( string s_text ) : text( s_text ) {};
};
class Base64Key {
private:
unsigned char key[ 16 ];
public:
Base64Key(); /* random key */
Base64Key( string printable_key );
string printable_key( void ) const;
unsigned char *data( void ) { return key; }
};
class Nonce {
private:
char bytes[ 12 ];
public:
Nonce( uint64_t val );
Nonce( char *s_bytes, size_t len );
string cpp_str( void ) { return string( (char *)( bytes + 4 ), 8 ); }
char *data( void ) { return bytes; }
uint64_t val( void );
};
class Message {
public:
Nonce nonce;
string text;
Message( char *nonce_bytes, size_t nonce_len,
char *text_bytes, size_t text_len );
Message( Nonce s_nonce, string s_text );
};
class Session {
private:
Base64Key key;
ae_ctx *ctx;
public:
Session( Base64Key s_key );
~Session();
string encrypt( Message plaintext );
Message decrypt( string ciphertext );
Session( const Session & );
Session & operator=( const Session & );
};
}
#endif
+22
View File
@@ -0,0 +1,22 @@
#ifndef DOS_ASSERT_HPP
#define DOS_ASSERT_HPP
#include <stdio.h>
#include <stdlib.h>
#include "crypto.hpp"
static void dos_detected( const char *expression, const char *file, int line, const char *function )
{
char buffer[ 2048 ];
snprintf( buffer, 2048, "Illegal counterparty input (possible denial of service) in function %s at %s:%d, failed test: %s\n",
function, file, line, expression );
throw Crypto::CryptoException( buffer );
}
#define dos_assert(expr) \
((expr) \
? (void)0 \
: dos_detected (__STRING(expr), __FILE__, __LINE__, __PRETTY_FUNCTION__ ))
#endif
+58
View File
@@ -0,0 +1,58 @@
#include <stdlib.h>
#include <string.h>
#include "stmclient.hpp"
#include "crypto.hpp"
int main( int argc, char *argv[] )
{
/* Get arguments */
char *ip;
int port;
if ( argc != 3 ) {
fprintf( stderr, "Usage: %s IP PORT\n", argv[ 0 ] );
exit( 1 );
}
ip = argv[ 1 ];
port = myatoi( argv[ 2 ] );
/* Read key from environment */
char *env_key = getenv( "MOSH_KEY" );
if ( env_key == NULL ) {
fprintf( stderr, "MOSH_KEY environment variable not found.\n" );
exit( 1 );
}
char *key = strdup( env_key );
if ( key == NULL ) {
perror( "strdup" );
exit( 1 );
}
if ( unsetenv( "MOSH_KEY" ) < 0 ) {
perror( "unsetenv" );
exit( 1 );
}
/* Adopt native locale */
if ( NULL == setlocale( LC_ALL, "" ) ) {
perror( "setlocale" );
exit( 1 );
}
STMClient client( ip, port, key );
client.init();
client.main();
client.shutdown();
printf( "\n[mosh is exiting.]\n" );
free( key );
return 0;
}
+325
View File
@@ -0,0 +1,325 @@
#include <locale.h>
#include <string.h>
#include <langinfo.h>
#include <termios.h>
#include <unistd.h>
#include <stdio.h>
#include <pty.h>
#include <stdlib.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <pwd.h>
#include <typeinfo>
#include <signal.h>
#include <sys/signalfd.h>
#include "completeterminal.hpp"
#include "swrite.hpp"
#include "user.hpp"
#include "networktransport.cpp"
void serve( int host_fd, const char *desired_ip );
using namespace std;
int main( int argc, char *argv[] )
{
char *desired_ip = NULL;
if ( argc == 1 ) {
desired_ip = NULL;
} else if ( argc == 2 ) {
desired_ip = argv[ 1 ];
} else {
fprintf( stderr, "Usage: %s [LOCALADDR]\n", argv[ 0 ] );
exit( 1 );
}
int master;
struct termios child_termios;
/* Adopt implementation locale */
if ( NULL == setlocale( LC_ALL, "" ) ) {
perror( "setlocale" );
exit( 1 );
}
/* Verify locale calls for UTF-8 */
if ( strcmp( nl_langinfo( CODESET ), "UTF-8" ) != 0 ) {
fprintf( stderr, "mosh requires a UTF-8 locale.\n" );
exit( 1 );
}
/* Verify terminal configuration */
if ( tcgetattr( STDIN_FILENO, &child_termios ) < 0 ) {
perror( "tcgetattr" );
exit( 1 );
}
if ( !(child_termios.c_iflag & IUTF8) ) {
/* SSH should also convey IUTF8 across connection. */
// fprintf( stderr, "Warning: Locale is UTF-8 but termios IUTF8 flag not set. Setting IUTF8 flag.\n" );
child_termios.c_iflag |= IUTF8;
}
/* Fork child process */
pid_t child = forkpty( &master, NULL, &child_termios, NULL );
if ( child == -1 ) {
perror( "forkpty" );
exit( 1 );
}
if ( child == 0 ) {
/* child */
if ( setenv( "TERM", "xterm", true ) < 0 ) {
perror( "setenv" );
exit( 1 );
}
/* ask ncurses to send UTF-8 instead of ISO 2022 for line-drawing chars */
if ( setenv( "NCURSES_NO_UTF8_ACS", "1", true ) < 0 ) {
perror( "setenv" );
exit( 1 );
}
/* clear STY environment variable so GNU screen regards us as top level */
if ( unsetenv( "STY" ) < 0 ) {
perror( "unsetenv" );
exit( 1 );
}
/* get shell name */
struct passwd *pw = getpwuid( geteuid() );
if ( pw == NULL ) {
perror( "getpwuid" );
exit( 1 );
}
char *my_argv[ 2 ];
my_argv[ 0 ] = strdup( pw->pw_shell );
assert( my_argv[ 0 ] );
my_argv[ 1 ] = NULL;
if ( execve( pw->pw_shell, my_argv, environ ) < 0 ) {
perror( "execve" );
exit( 1 );
}
exit( 0 );
} else {
/* parent */
serve( master, desired_ip );
if ( close( master ) < 0 ) {
perror( "close" );
exit( 1 );
}
}
printf( "\n[mosh-server is exiting.]\n" );
return 0;
}
void serve( int host_fd, const char *desired_ip )
{
/* establish fd for shutdown signals */
sigset_t signal_mask;
assert( sigemptyset( &signal_mask ) == 0 );
assert( sigaddset( &signal_mask, SIGTERM ) == 0 );
assert( sigaddset( &signal_mask, SIGINT ) == 0 );
sigset_t signals_to_block = signal_mask;
assert( sigaddset( &signals_to_block, SIGHUP ) == 0 );
assert( sigaddset( &signals_to_block, SIGPIPE ) == 0 );
/* don't let signals kill us */
assert( sigprocmask( SIG_BLOCK, &signals_to_block, NULL ) == 0 );
int shutdown_signal_fd = signalfd( -1, &signal_mask, 0 );
if ( shutdown_signal_fd < 0 ) {
perror( "signalfd" );
return;
}
/* get initial window size */
struct winsize window_size;
if ( ioctl( STDIN_FILENO, TIOCGWINSZ, &window_size ) < 0 ) {
perror( "ioctl TIOCGWINSZ" );
return;
}
/* tell child process */
if ( ioctl( host_fd, TIOCSWINSZ, &window_size ) < 0 ) {
perror( "ioctl TIOCSWINSZ" );
return;
}
/* open parser and terminal */
Terminal::Complete terminal( window_size.ws_col, window_size.ws_row );
/* open network */
Network::UserStream blank;
Network::Transport< Terminal::Complete, Network::UserStream > network( terminal, blank, desired_ip );
/* network.set_verbose(); */
printf( "MOSH CONNECT %d %s\n", network.port(), network.get_key().c_str() );
fflush( stdout );
/* detach from terminal */
pid_t the_pid = fork();
if ( the_pid < 0 ) {
perror( "fork" );
} else if ( the_pid > 0 ) {
_exit( 0 );
}
fprintf( stderr, "[mosh-server detached, pid=%d.]\n", (int)getpid() );
/* prepare to poll for events */
struct pollfd pollfds[ 3 ];
pollfds[ 0 ].fd = network.fd();
pollfds[ 0 ].events = POLLIN;
pollfds[ 1 ].fd = host_fd;
pollfds[ 1 ].events = POLLIN;
pollfds[ 2 ].fd = shutdown_signal_fd;
pollfds[ 2 ].events = POLLIN;
uint64_t last_remote_num = network.get_remote_state_num();
while ( 1 ) {
try {
int active_fds = poll( pollfds, 3, network.wait_time() );
if ( active_fds < 0 ) {
perror( "poll" );
break;
}
if ( pollfds[ 0 ].revents & POLLIN ) {
/* packet received from the network */
network.recv();
/* is new user input available for the terminal? */
if ( network.get_remote_state_num() != last_remote_num ) {
string terminal_to_host;
Network::UserStream us;
us.apply_string( network.get_remote_diff() );
/* apply userstream to terminal */
for ( size_t i = 0; i < us.size(); i++ ) {
terminal_to_host += terminal.act( us.get_action( i ) );
if ( typeid( *us.get_action( i ) ) == typeid( Parser::Resize ) ) {
/* tell child process of resize */
const Parser::Resize *res = static_cast<const Parser::Resize *>( us.get_action( i ) );
window_size.ws_col = res->width;
window_size.ws_row = res->height;
if ( ioctl( host_fd, TIOCSWINSZ, &window_size ) < 0 ) {
perror( "ioctl TIOCSWINSZ" );
return;
}
}
}
/* update client with new state of terminal */
if ( !network.shutdown_in_progress() ) {
network.set_current_state( terminal );
}
/* write any writeback octets back to the host */
if ( swrite( host_fd, terminal_to_host.c_str(), terminal_to_host.length() ) < 0 ) {
break;
}
}
}
if ( pollfds[ 1 ].revents & POLLIN ) {
/* input from the host needs to be fed to the terminal */
const int buf_size = 16384;
char buf[ buf_size ];
/* fill buffer if possible */
ssize_t bytes_read = read( pollfds[ 1 ].fd, buf, buf_size );
if ( bytes_read == 0 ) { /* EOF */
return;
} else if ( bytes_read < 0 ) {
perror( "read" );
return;
}
string terminal_to_host = terminal.act( string( buf, bytes_read ) );
/* update client with new state of terminal */
if ( !network.shutdown_in_progress() ) {
network.set_current_state( terminal );
}
/* write any writeback octets back to the host */
if ( swrite( host_fd, terminal_to_host.c_str(), terminal_to_host.length() ) < 0 ) {
break;
}
}
if ( pollfds[ 2 ].revents & POLLIN ) {
/* shutdown signal */
struct signalfd_siginfo the_siginfo;
ssize_t bytes_read = read( pollfds[ 2 ].fd, &the_siginfo, sizeof( the_siginfo ) );
if ( bytes_read == 0 ) {
break;
} else if ( bytes_read < 0 ) {
perror( "read" );
break;
}
if ( network.attached() && (!network.shutdown_in_progress()) ) {
network.start_shutdown();
} else {
break;
}
}
if ( (pollfds[ 0 ].revents)
& (POLLERR | POLLHUP | POLLNVAL) ) {
/* network problem */
break;
}
if ( (pollfds[ 1 ].revents)
& (POLLERR | POLLHUP | POLLNVAL) ) {
/* host problem */
if ( network.attached() ) {
network.start_shutdown();
} else {
break;
}
}
/* quit if our shutdown has been acknowledged */
if ( network.shutdown_in_progress() && network.shutdown_acknowledged() ) {
break;
}
/* quit after shutdown acknowledgement timeout */
if ( network.shutdown_in_progress() && network.shutdown_ack_timed_out() ) {
break;
}
/* quit if we received and acknowledged a shutdown request */
if ( network.counterparty_shutdown_ack_sent() ) {
break;
}
network.tick();
} catch ( Network::NetworkException e ) {
fprintf( stderr, "%s: %s\n", e.function.c_str(), strerror( e.the_errno ) );
sleep( 1 );
} catch ( Crypto::CryptoException e ) {
fprintf( stderr, "Crypto exception: %s\n", e.text.c_str() );
}
}
}
+359
View File
@@ -0,0 +1,359 @@
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <endian.h>
#include "dos_assert.hpp"
#include "network.hpp"
#include "crypto.hpp"
using namespace std;
using namespace Network;
using namespace Crypto;
const uint64_t DIRECTION_MASK = uint64_t(1) << 63;
const uint64_t SEQUENCE_MASK = uint64_t(-1) ^ DIRECTION_MASK;
/* Read in packet from coded string */
Packet::Packet( string coded_packet, Session *session )
: seq( -1 ),
direction( TO_SERVER ),
timestamp( -1 ),
timestamp_reply( -1 ),
payload()
{
Message message = session->decrypt( coded_packet );
direction = (message.nonce.val() & DIRECTION_MASK) ? TO_CLIENT : TO_SERVER;
seq = message.nonce.val() & SEQUENCE_MASK;
dos_assert( message.text.size() >= 2 * sizeof( uint16_t ) );
uint16_t *data = (uint16_t *)message.text.data();
timestamp = be16toh( data[ 0 ] );
timestamp_reply = be16toh( data[ 1 ] );
payload = string( message.text.begin() + 2 * sizeof( uint16_t ), message.text.end() );
}
/* Output coded string from packet */
string Packet::tostring( Session *session )
{
uint64_t direction_seq = (uint64_t( direction == TO_CLIENT ) << 63) | (seq & SEQUENCE_MASK);
uint16_t ts_net[ 2 ] = { htobe16( timestamp ), htobe16( timestamp_reply ) };
string timestamps = string( (char *)ts_net, 2 * sizeof( uint16_t ) );
return session->encrypt( Message( Nonce( direction_seq ), timestamps + payload ) );
}
Packet Connection::new_packet( string &s_payload )
{
uint16_t outgoing_timestamp_reply = -1;
uint64_t now = timestamp();
if ( now - saved_timestamp_received_at < 1000 ) { /* we have a recent received timestamp */
/* send "corrected" timestamp advanced by how long we held it */
outgoing_timestamp_reply = saved_timestamp + (now - saved_timestamp_received_at);
saved_timestamp = -1;
saved_timestamp_received_at = 0;
}
Packet p( next_seq++, direction, timestamp16(), outgoing_timestamp_reply, s_payload );
return p;
}
void Connection::setup( void )
{
/* create socket */
sock = socket( AF_INET, SOCK_DGRAM, 0 );
if ( sock < 0 ) {
throw NetworkException( "socket", errno );
}
/* Enable path MTU discovery */
char flag = IP_PMTUDISC_DO;
socklen_t optlen = sizeof( flag );
if ( setsockopt( sock, IPPROTO_IP, IP_MTU_DISCOVER, &flag, optlen ) < 0 ) {
throw NetworkException( "setsockopt", errno );
}
}
Connection::Connection( const char *desired_ip ) /* server */
: sock( -1 ),
remote_addr(),
server( true ),
attached( false ),
MTU( SEND_MTU ),
key(),
session( key ),
direction( TO_CLIENT ),
next_seq( 0 ),
saved_timestamp( -1 ),
saved_timestamp_received_at( 0 ),
expected_receiver_seq( 0 ),
RTT_hit( false ),
SRTT( 1000 ),
RTTVAR( 500 )
{
setup();
/* Attempt to bind free local port, with
address client used to connect to us.
This usage does not seem to be endorsed by POSIX. */
struct sockaddr_in local_addr;
local_addr.sin_family = AF_INET;
local_addr.sin_port = htons( 0 );
if ( desired_ip
&& inet_aton( desired_ip, &local_addr.sin_addr )
&& (bind( sock, (sockaddr *)&local_addr, sizeof( local_addr ) ) == 0) ) {
return;
}
if ( desired_ip ) {
fprintf( stderr, "Could not bind to desired local address %s.\n", desired_ip );
}
/* Could not bind to that IP (maybe we are behind NAT).
Try again with any IP. */
local_addr.sin_addr.s_addr = INADDR_ANY;
if ( bind( sock, (sockaddr *)&local_addr, sizeof( local_addr ) ) < 0 ) {
throw NetworkException( "bind", errno );
}
}
Connection::Connection( const char *key_str, const char *ip, int port ) /* client */
: sock( -1 ),
remote_addr(),
server( false ),
attached( false ),
MTU( SEND_MTU ),
key( key_str ),
session( key ),
direction( TO_SERVER ),
next_seq( 0 ),
saved_timestamp( -1 ),
saved_timestamp_received_at( 0 ),
expected_receiver_seq( 0 ),
RTT_hit( false ),
SRTT( 1000 ),
RTTVAR( 500 )
{
setup();
/* associate socket with remote host and port */
remote_addr.sin_family = AF_INET;
remote_addr.sin_port = htons( port );
if ( !inet_aton( ip, &remote_addr.sin_addr ) ) {
int saved_errno = errno;
char buffer[ 2048 ];
snprintf( buffer, 2048, "Bad IP address (%s)", ip );
throw NetworkException( buffer, saved_errno );
}
attached = true;
}
void Connection::send( string s )
{
assert( attached );
Packet px = new_packet( s );
string p = px.tostring( &session );
ssize_t bytes_sent = sendto( sock, p.data(), p.size(), 0,
(sockaddr *)&remote_addr, sizeof( remote_addr ) );
if ( (bytes_sent < 0) && (errno == EMSGSIZE) ) {
update_MTU();
throw NetworkException( "Path MTU Discovery", EMSGSIZE );
} else if ( bytes_sent == static_cast<int>( p.size() ) ) {
return;
} else {
throw NetworkException( "sendto", errno );
}
}
string Connection::recv( void )
{
struct sockaddr_in packet_remote_addr;
char buf[ RECEIVE_MTU ];
socklen_t addrlen = sizeof( packet_remote_addr );
ssize_t received_len = recvfrom( sock, buf, RECEIVE_MTU, 0, (sockaddr *)&packet_remote_addr, &addrlen );
if ( received_len < 0 ) {
throw NetworkException( "recvfrom", errno );
}
if ( received_len > RECEIVE_MTU ) {
char buffer[ 2048 ];
snprintf( buffer, 2048, "Received oversize datagram (size %d) and limit is %d\n",
static_cast<int>( received_len ), RECEIVE_MTU );
throw NetworkException( buffer, errno );
}
Packet p( string( buf, received_len ), &session );
dos_assert( p.direction == (server ? TO_SERVER : TO_CLIENT) ); /* prevent malicious playback to sender */
if ( p.seq >= expected_receiver_seq ) { /* don't use out-of-order packets for timestamp or targeting */
expected_receiver_seq = p.seq + 1; /* this is security-sensitive because a replay attack could otherwise
screw up the timestamp and targeting */
if ( p.timestamp != uint16_t(-1) ) {
saved_timestamp = p.timestamp;
saved_timestamp_received_at = timestamp();
}
if ( p.timestamp_reply != uint16_t(-1) ) {
uint16_t now = timestamp16();
double R = timestamp_diff( now, p.timestamp_reply );
if ( R < 5000 ) { /* ignore large values, e.g. server was Ctrl-Zed */
if ( !RTT_hit ) { /* first measurement */
SRTT = R;
RTTVAR = R / 2;
RTT_hit = true;
} else {
const double alpha = 1.0 / 8.0;
const double beta = 1.0 / 4.0;
RTTVAR = (1 - beta) * RTTVAR + ( beta * fabs( SRTT - R ) );
SRTT = (1 - alpha) * SRTT + ( alpha * R );
}
}
}
/* auto-adjust to remote host */
attached = true;
if ( (remote_addr.sin_addr.s_addr != packet_remote_addr.sin_addr.s_addr)
|| (remote_addr.sin_port != packet_remote_addr.sin_port) ) {
remote_addr = packet_remote_addr;
if ( server ) {
fprintf( stderr, "Server now attached to client at %s:%d\n",
inet_ntoa( remote_addr.sin_addr ),
ntohs( remote_addr.sin_port ) );
}
}
}
return p.payload; /* we do return out-of-order or duplicated packets to caller */
}
int Connection::port( void ) const
{
struct sockaddr_in local_addr;
socklen_t addrlen = sizeof( local_addr );
if ( getsockname( sock, (sockaddr *)&local_addr, &addrlen ) < 0 ) {
throw NetworkException( "getsockname", errno );
}
return ntohs( local_addr.sin_port );
}
uint64_t Network::timestamp( void )
{
struct timespec tp;
if ( clock_gettime( CLOCK_MONOTONIC, &tp ) < 0 ) {
throw NetworkException( "clock_gettime", errno );
}
uint64_t millis = tp.tv_nsec / 1000000;
millis += uint64_t( tp.tv_sec ) * 1000;
return millis;
}
uint16_t Network::timestamp16( void )
{
uint16_t ts = timestamp() % 65536;
if ( ts == uint16_t(-1) ) {
ts++;
}
return ts;
}
uint16_t Network::timestamp_diff( uint16_t tsnew, uint16_t tsold )
{
int diff = tsnew - tsold;
if ( diff < 0 ) {
diff += 65536;
}
assert( diff >= 0 );
assert( diff <= 65535 );
return diff;
}
uint64_t Connection::timeout( void ) const
{
uint64_t RTO = lrint( ceil( SRTT + 4 * RTTVAR ) );
if ( RTO < MIN_RTO ) {
RTO = MIN_RTO;
} else if ( RTO > MAX_RTO ) {
RTO = MAX_RTO;
}
return RTO;
}
class Socket {
public:
int fd;
Socket( int domain, int type, int protocol )
: fd( socket( domain, type, protocol ) )
{
if ( fd < 0 ) {
throw NetworkException( "socket", errno );
}
}
~Socket()
{
if ( close( fd ) < 0 ) {
throw NetworkException( "close", errno );
}
}
};
void Connection::update_MTU( void )
{
if ( !attached ) {
return;
}
/* We don't want to use our main socket because we don't want to have to connect it */
Socket path_MTU_socket( AF_INET, SOCK_DGRAM, 0 );
/* Connect socket so we can retrieve path MTU */
if ( connect( path_MTU_socket.fd, (sockaddr *)&remote_addr, sizeof( remote_addr ) ) < 0 ) {
throw NetworkException( "connect", errno );
}
int PMTU;
socklen_t optlen = sizeof( PMTU );
if ( getsockopt( path_MTU_socket.fd, IPPROTO_IP, IP_MTU, &PMTU, &optlen ) < 0 ) {
throw NetworkException( "getsockopt", errno );
}
if ( optlen != sizeof( PMTU ) ) {
throw NetworkException( "Error getting path MTU", errno );
}
MTU = min( PMTU, int(SEND_MTU) ); /* need cast to compile without optimization! XXX */
}
+105
View File
@@ -0,0 +1,105 @@
#ifndef NETWORK_HPP
#define NETWORK_HPP
#include <stdint.h>
#include <deque>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string>
#include <math.h>
#include "crypto.hpp"
using namespace std;
using namespace Crypto;
namespace Network {
static const unsigned int MOSH_PROTOCOL_VERSION = 1;
uint64_t timestamp( void );
uint16_t timestamp16( void );
uint16_t timestamp_diff( uint16_t tsnew, uint16_t tsold );
class NetworkException {
public:
string function;
int the_errno;
NetworkException( string s_function, int s_errno ) : function( s_function ), the_errno( s_errno ) {}
};
enum Direction {
TO_SERVER = 0,
TO_CLIENT = 1
};
class Packet {
public:
uint64_t seq;
Direction direction;
uint16_t timestamp, timestamp_reply;
string payload;
Packet( uint64_t s_seq, Direction s_direction,
uint16_t s_timestamp, uint16_t s_timestamp_reply, string s_payload )
: seq( s_seq ), direction( s_direction ),
timestamp( s_timestamp ), timestamp_reply( s_timestamp_reply ), payload( s_payload )
{}
Packet( string coded_packet, Session *session );
string tostring( Session *session );
};
class Connection {
private:
static const int RECEIVE_MTU = 2048;
static const int SEND_MTU = 1400;
static const uint64_t MIN_RTO = 50; /* ms */
static const uint64_t MAX_RTO = 1000; /* ms */
int sock;
struct sockaddr_in remote_addr;
bool server;
bool attached;
int MTU;
Base64Key key;
Session session;
void setup( void );
Direction direction;
uint64_t next_seq;
uint16_t saved_timestamp;
uint64_t saved_timestamp_received_at;
uint64_t expected_receiver_seq;
bool RTT_hit;
double SRTT;
double RTTVAR;
Packet new_packet( string &s_payload );
void update_MTU( void );
public:
Connection( const char *desired_ip ); /* server */
Connection( const char *key_str, const char *ip, int port ); /* client */
void send( string s );
string recv( void );
int fd( void ) const { return sock; }
int get_MTU( void ) const { return MTU; }
int port( void ) const;
string get_key( void ) const { return key.printable_key(); }
bool get_attached( void ) const { return attached; }
uint64_t timeout( void ) const;
double get_SRTT( void ) const { return SRTT; }
};
}
#endif
+154
View File
@@ -0,0 +1,154 @@
#include <assert.h>
#include <iostream>
#include "networktransport.hpp"
#include "transportsender.cpp"
using namespace Network;
using namespace std;
template <class MyState, class RemoteState>
Transport<MyState, RemoteState>::Transport( MyState &initial_state, RemoteState &initial_remote,
const char *desired_ip )
: connection( desired_ip ),
sender( &connection, initial_state ),
received_states( 1, TimestampedState<RemoteState>( timestamp(), 0, initial_remote ) ),
last_receiver_state( initial_remote ),
sent_state_late_acked( 0 ),
fragments(),
verbose( false )
{
/* server */
}
template <class MyState, class RemoteState>
Transport<MyState, RemoteState>::Transport( MyState &initial_state, RemoteState &initial_remote,
const char *key_str, const char *ip, int port )
: connection( key_str, ip, port ),
sender( &connection, initial_state ),
received_states( 1, TimestampedState<RemoteState>( timestamp(), 0, initial_remote ) ),
last_receiver_state( initial_remote ),
sent_state_late_acked( 0 ),
fragments(),
verbose( false )
{
/* client */
}
template <class MyState, class RemoteState>
void Transport<MyState, RemoteState>::recv( void )
{
string s( connection.recv() );
Fragment frag( s );
if ( fragments.add_fragment( frag ) ) { /* complete packet */
Instruction inst = fragments.get_assembly();
if ( inst.protocol_version() != MOSH_PROTOCOL_VERSION ) {
throw NetworkException( "mosh protocol version mismatch", 0 );
}
sender.process_acknowledgment_through( inst.ack_num() );
if ( inst.late_ack_num() > sent_state_late_acked ) {
sent_state_late_acked = inst.late_ack_num();
}
/* first, make sure we don't already have the new state */
for ( typename list< TimestampedState<RemoteState> >::iterator i = received_states.begin();
i != received_states.end();
i++ ) {
if ( inst.new_num() == i->num ) {
return;
}
}
/* now, make sure we do have the old state */
bool found = 0;
typename list< TimestampedState<RemoteState> >::iterator reference_state = received_states.begin();
while ( reference_state != received_states.end() ) {
if ( inst.old_num() == reference_state->num ) {
found = true;
break;
}
reference_state++;
}
if ( !found ) {
// fprintf( stderr, "Ignoring out-of-order packet. Reference state %d has been discarded or hasn't yet been received.\n", int(inst.old_num) );
return; /* this is security-sensitive and part of how we enforce idempotency */
}
/* apply diff to reference state */
TimestampedState<RemoteState> new_state = *reference_state;
new_state.timestamp = timestamp();
new_state.num = inst.new_num();
if ( !inst.diff().empty() ) {
new_state.state.apply_string( inst.diff() );
}
process_throwaway_until( inst.throwaway_num() );
/* Insert new state in sorted place */
for ( typename list< TimestampedState<RemoteState> >::iterator i = received_states.begin();
i != received_states.end();
i++ ) {
if ( i->num > new_state.num ) {
received_states.insert( i, new_state );
if ( verbose ) {
fprintf( stderr, "[%u] Received OUT-OF-ORDER state %d [ack %d]\n",
(unsigned int)(timestamp() % 100000), (int)new_state.num, (int)inst.ack_num() );
}
return;
}
}
if ( verbose ) {
fprintf( stderr, "[%u] Received state %d [ack %d]\n",
(unsigned int)(timestamp() % 100000), (int)new_state.num, (int)inst.ack_num() );
}
received_states.push_back( new_state );
sender.set_ack_num( received_states.back().num );
if ( !inst.diff().empty() ) {
sender.set_data_ack();
}
}
}
/* The sender uses throwaway_num to tell us the earliest received state that we need to keep around */
template <class MyState, class RemoteState>
void Transport<MyState, RemoteState>::process_throwaway_until( uint64_t throwaway_num )
{
typename list< TimestampedState<RemoteState> >::iterator i = received_states.begin();
while ( i != received_states.end() ) {
typename list< TimestampedState<RemoteState> >::iterator inext = i;
inext++;
if ( i->num < throwaway_num ) {
received_states.erase( i );
}
i = inext;
}
assert( received_states.size() > 0 );
}
template <class MyState, class RemoteState>
string Transport<MyState, RemoteState>::get_remote_diff( void )
{
/* find diff between last receiver state and current remote state, then rationalize states */
string ret( received_states.back().state.diff_from( last_receiver_state ) );
const RemoteState *oldest_receiver_state = &received_states.front().state;
for ( typename list< TimestampedState<RemoteState> >::reverse_iterator i = received_states.rbegin();
i != received_states.rend();
i++ ) {
i->state.subtract( oldest_receiver_state );
}
last_receiver_state = received_states.back().state;
return ret;
}
+89
View File
@@ -0,0 +1,89 @@
#ifndef NETWORK_TRANSPORT_HPP
#define NETWORK_TRANSPORT_HPP
#include <string>
#include <signal.h>
#include <time.h>
#include <list>
#include <vector>
#include "network.hpp"
#include "transportsender.hpp"
#include "transportfragment.hpp"
using namespace std;
namespace Network {
template <class MyState, class RemoteState>
class Transport
{
private:
/* the underlying, encrypted network connection */
Connection connection;
/* sender side */
TransportSender<MyState> sender;
/* helper methods for recv() */
void process_throwaway_until( uint64_t throwaway_num );
/* simple receiver */
list< TimestampedState<RemoteState> > received_states;
RemoteState last_receiver_state; /* the state we were in when user last queried state */
uint64_t sent_state_late_acked;
FragmentAssembly fragments;
bool verbose;
public:
Transport( MyState &initial_state, RemoteState &initial_remote, const char *desired_ip );
Transport( MyState &initial_state, RemoteState &initial_remote,
const char *key_str, const char *ip, int port );
/* Send data or an ack if necessary. */
void tick( void ) { sender.tick(); }
/* Returns the number of ms to wait until next possible event. */
int wait_time( void ) { return sender.wait_time(); }
/* Blocks waiting for a packet. */
void recv( void );
/* Find diff between last receiver state and current remote state, then rationalize states. */
string get_remote_diff( void );
/* Shut down other side of connection. */
/* Illegal to change current_state after this. */
void start_shutdown( void ) { sender.start_shutdown(); }
bool shutdown_in_progress( void ) const { return sender.get_shutdown_in_progress(); }
bool shutdown_acknowledged( void ) const { return sender.get_shutdown_acknowledged(); }
bool shutdown_ack_timed_out( void ) const { return sender.shutdown_ack_timed_out(); }
bool attached( void ) const { return connection.get_attached(); }
/* Other side has requested shutdown and we have sent one ACK */
bool counterparty_shutdown_ack_sent( void ) const { return sender.get_counterparty_shutdown_acknowledged(); }
int port( void ) const { return connection.port(); }
string get_key( void ) const { return connection.get_key(); }
MyState &get_current_state( void ) { return sender.get_current_state(); }
void set_current_state( const MyState &x ) { sender.set_current_state( x ); }
uint64_t get_remote_state_num( void ) const { return received_states.back().num; }
const TimestampedState<RemoteState> & get_latest_remote_state( void ) const { return received_states.back(); }
int fd( void ) const { return connection.fd(); }
void set_verbose( void ) { sender.set_verbose(); verbose = true; }
void set_send_delay( int new_delay ) { sender.set_send_delay( new_delay ); }
uint64_t get_sent_state_acked( void ) const { return sender.get_sent_state_acked(); }
uint64_t get_sent_state_last( void ) const { return sender.get_sent_state_last(); }
uint64_t get_sent_state_late_acked( void ) const { return sent_state_late_acked; }
unsigned int send_interval( void ) const { return sender.send_interval(); }
};
}
#endif
+1226
View File
File diff suppressed because it is too large Load Diff
+127
View File
@@ -0,0 +1,127 @@
#include <assert.h>
#include <typeinfo>
#include <langinfo.h>
#include "parser.hpp"
static void append_or_delete( Parser::Action *act,
std::list<Parser::Action *>&vec )
{
assert( act );
if ( typeid( *act ) != typeid( Parser::Ignore ) ) {
vec.push_back( act );
} else {
delete act;
}
}
std::list<Parser::Action *> Parser::Parser::input( wchar_t ch )
{
std::list<Action *> ret;
Transition tx = state->input( ch );
if ( tx.next_state != NULL ) {
append_or_delete( state->exit(), ret );
}
append_or_delete( tx.action, ret );
if ( tx.next_state != NULL ) {
append_or_delete( tx.next_state->enter(), ret );
state = tx.next_state;
}
return ret;
}
Parser::UTF8Parser::UTF8Parser()
: parser(), buf_len( 0 )
{
assert( BUF_SIZE >= MB_CUR_MAX );
}
std::list<Parser::Action *> Parser::UTF8Parser::input( char c )
{
assert( buf_len < BUF_SIZE );
buf[ buf_len++ ] = c;
/* This function will only work in a UTF-8 locale. */
/* This is asserted in the constructor. */
wchar_t pwc;
mbstate_t ps;
memset( &ps, 0, sizeof( ps ) );
size_t total_bytes_parsed = 0;
size_t orig_buf_len = buf_len;
std::list<Action *> ret;
/* this routine is somewhat complicated in order to comply with
Unicode 6.0, section 3.9, "Best Practices for using U+FFFD" */
while ( total_bytes_parsed != orig_buf_len ) {
assert( total_bytes_parsed < orig_buf_len );
assert( buf_len > 0 );
size_t bytes_parsed = mbrtowc( &pwc, buf, buf_len, &ps );
/* this returns 0 when n = 0! */
/* This function annoying returns a size_t so we have to check
the negative values first before the "> 0" branch */
if ( bytes_parsed == 0 ) {
/* character was NUL, accept and clear buffer */
assert( buf_len == 1 );
buf_len = 0;
pwc = L'\0';
bytes_parsed = 1;
} else if ( bytes_parsed == (size_t) -1 ) {
/* invalid sequence, use replacement character and try again with last char */
assert( errno == EILSEQ );
if ( buf_len > 1 ) {
buf[ 0 ] = buf[ buf_len - 1 ];
bytes_parsed = buf_len - 1;
buf_len = 1;
} else {
buf_len = 0;
bytes_parsed = 1;
}
pwc = (wchar_t) 0xFFFD;
} else if ( bytes_parsed == (size_t) -2 ) {
/* can't parse incomplete multibyte character */
total_bytes_parsed += buf_len;
continue;
} else if ( bytes_parsed > 0 ) {
/* parsed into pwc, accept */
assert( bytes_parsed <= buf_len );
memcpy( buf, buf + bytes_parsed, buf_len - bytes_parsed );
buf_len = buf_len - bytes_parsed;
} else {
throw std::string( "Unknown return value from mbrtowc" );
}
if ( (pwc < 0) || (pwc > 0x10FFFF) ) { /* outside Unicode range */
pwc = (wchar_t) 0xFFFD;
}
std::list<Action *> vec = parser.input( pwc );
ret.insert( ret.end(), vec.begin(), vec.end() );
total_bytes_parsed += bytes_parsed;
}
return ret;
}
Parser::Parser::Parser( const Parser &other )
: state( other.state )
{}
Parser::Parser & Parser::Parser::operator=( const Parser &other )
{
state = other.state;
return *this;
}
+67
View File
@@ -0,0 +1,67 @@
#ifndef PARSER_HPP
#define PARSER_HPP
/* Based on Paul Williams's parser,
http://www.vt100.net/emu/dec_ansi_parser */
#include <wchar.h>
#include <list>
#include <string.h>
#include "parsertransition.hpp"
#include "parseraction.hpp"
#include "parserstate.hpp"
#include "parserstatefamily.hpp"
#ifndef __STDC_ISO_10646__
#error "Must have __STDC_ISO_10646__"
#endif
namespace Parser {
static const StateFamily family;
class Parser {
private:
State const *state;
public:
Parser() : state( &family.s_Ground ) {}
Parser( const Parser &other );
Parser & operator=( const Parser & );
~Parser() {}
std::list<Action *> input( wchar_t ch );
bool operator==( const Parser &x ) const
{
return state == x.state;
}
bool is_grounded( void ) const { return state == &family.s_Ground; }
};
static const size_t BUF_SIZE = 8;
class UTF8Parser {
private:
Parser parser;
char buf[ BUF_SIZE ];
size_t buf_len;
public:
UTF8Parser();
std::list<Action *> input( char c );
bool operator==( const UTF8Parser &x ) const
{
return parser == x.parser;
}
bool is_grounded( void ) const { return parser.is_grounded(); }
};
}
#endif
+86
View File
@@ -0,0 +1,86 @@
#include <stdio.h>
#include <wctype.h>
#include "parseraction.hpp"
#include "terminal.hpp"
using namespace Parser;
std::string Action::str( void )
{
char thechar[ 10 ] = { 0 };
if ( char_present ) {
snprintf( thechar, 10, iswprint( ch ) ? "(%lc)" : "(0x%x)", ch );
}
return name() + std::string( thechar );
}
void Print::act_on_terminal( Terminal::Emulator *emu ) const
{
emu->print( this );
}
void Execute::act_on_terminal( Terminal::Emulator *emu ) const
{
emu->execute( this );
}
void Clear::act_on_terminal( Terminal::Emulator *emu ) const
{
emu->dispatch.clear( this );
}
void Param::act_on_terminal( Terminal::Emulator *emu ) const
{
emu->dispatch.newparamchar( this );
}
void Collect::act_on_terminal( Terminal::Emulator *emu ) const
{
emu->dispatch.collect( this );
}
void CSI_Dispatch::act_on_terminal( Terminal::Emulator *emu ) const
{
emu->CSI_dispatch( this );
}
void Esc_Dispatch::act_on_terminal( Terminal::Emulator *emu ) const
{
emu->Esc_dispatch( this );
}
void OSC_Put::act_on_terminal( Terminal::Emulator *emu ) const
{
emu->dispatch.OSC_put( this );
}
void OSC_Start::act_on_terminal( Terminal::Emulator *emu ) const
{
emu->dispatch.OSC_start( this );
}
void OSC_End::act_on_terminal( Terminal::Emulator *emu ) const
{
emu->OSC_end( this );
}
void UserByte::act_on_terminal( Terminal::Emulator *emu ) const
{
emu->dispatch.terminal_to_host.append( emu->user.input( this,
emu->fb.ds.application_mode_cursor_keys ) );
}
void Resize::act_on_terminal( Terminal::Emulator *emu ) const
{
emu->resize( width, height );
handled = true;
}
bool Action::operator==( const Action &other ) const
{
return ( char_present == other.char_present )
&& ( ch == other.ch )
&& ( handled == other.handled );
}
+129
View File
@@ -0,0 +1,129 @@
#ifndef PARSERACTION_HPP
#define PARSERACTION_HPP
#include <string>
namespace Terminal {
class Emulator;
}
namespace Parser {
class Action
{
public:
bool char_present;
wchar_t ch;
mutable bool handled;
std::string str( void );
virtual std::string name( void ) = 0;
virtual void act_on_terminal( Terminal::Emulator * ) const {};
Action() : char_present( false ), ch( -1 ), handled( false ) {};
virtual ~Action() {};
virtual bool operator==( const Action &other ) const;
};
class Ignore : public Action {
public: std::string name( void ) { return std::string( "Ignore" ); }
};
class Print : public Action {
public:
std::string name( void ) { return std::string( "Print" ); }
void act_on_terminal( Terminal::Emulator *emu ) const;
};
class Execute : public Action {
public:
std::string name( void ) { return std::string( "Execute" ); }
void act_on_terminal( Terminal::Emulator *emu ) const;
};
class Clear : public Action {
public:
std::string name( void ) { return std::string( "Clear" ); }
void act_on_terminal( Terminal::Emulator *emu ) const;
};
class Collect : public Action {
public:
std::string name( void ) { return std::string( "Collect" ); }
void act_on_terminal( Terminal::Emulator *emu ) const;
};
class Param : public Action {
public:
std::string name( void ) { return std::string( "Param" ); }
void act_on_terminal( Terminal::Emulator *emu ) const;
};
class Esc_Dispatch : public Action {
public:
std::string name( void ) { return std::string( "Esc_Dispatch" ); }
void act_on_terminal( Terminal::Emulator *emu ) const;
};
class CSI_Dispatch : public Action {
public:
std::string name( void ) { return std::string( "CSI_Dispatch" ); }
void act_on_terminal( Terminal::Emulator *emu ) const;
};
class Hook : public Action {
public: std::string name( void ) { return std::string( "Hook" ); }
};
class Put : public Action {
public: std::string name( void ) { return std::string( "Put" ); }
};
class Unhook : public Action {
public: std::string name( void ) { return std::string( "Unhook" ); }
};
class OSC_Start : public Action {
public:
std::string name( void ) { return std::string( "OSC_Start" ); }
void act_on_terminal( Terminal::Emulator *emu ) const;
};
class OSC_Put : public Action {
public:
std::string name( void ) { return std::string( "OSC_Put" ); }
void act_on_terminal( Terminal::Emulator *emu ) const;
};
class OSC_End : public Action {
public:
std::string name( void ) { return std::string( "OSC_End" ); }
void act_on_terminal( Terminal::Emulator *emu ) const;
};
class UserByte : public Action {
/* user keystroke -- not part of the host-source state machine*/
public:
char c; /* The user-source byte. We don't try to interpret the charset */
std::string name( void ) { return std::string( "UserByte" ); }
void act_on_terminal( Terminal::Emulator *emu ) const;
UserByte( int s_c ) : c( s_c ) {}
bool operator==( const UserByte &other ) const
{
return c == other.c;
}
};
class Resize : public Action {
/* resize event -- not part of the host-source state machine*/
public:
size_t width, height;
std::string name( void ) { return std::string( "Resize" ); }
void act_on_terminal( Terminal::Emulator *emu ) const;
Resize( size_t s_width, size_t s_height )
: width( s_width ),
height( s_height )
{}
bool operator==( const Resize &other ) const
{
return ( width == other.width ) && ( height == other.height );
}
};
}
#endif
+356
View File
@@ -0,0 +1,356 @@
#include "parserstate.hpp"
#include "parserstatefamily.hpp"
using namespace Parser;
Transition State::anywhere_rule( wchar_t ch ) const
{
if ( (ch == 0x18) || (ch == 0x1A)
|| ((0x80 <= ch) && (ch <= 0x8F))
|| ((0x91 <= ch) && (ch <= 0x97))
|| (ch == 0x99) || (ch == 0x9A) ) {
return Transition( new Execute, &family->s_Ground );
} else if ( ch == 0x9C ) {
return Transition( &family->s_Ground );
} else if ( ch == 0x1B ) {
return Transition( &family->s_Escape );
} else if ( (ch == 0x98) || (ch == 0x9E) || (ch == 0x9F) ) {
return Transition( &family->s_SOS_PM_APC_String );
} else if ( ch == 0x90 ) {
return Transition( &family->s_DCS_Entry );
} else if ( ch == 0x9D ) {
return Transition( &family->s_OSC_String );
} else if ( ch == 0x9B ) {
return Transition( &family->s_CSI_Entry );
}
return Transition( NULL, NULL ); /* don't allocate an Ignore action */
}
Transition State::input( wchar_t ch ) const
{
Transition ret = anywhere_rule( ch );
if ( !ret.next_state ) {
if ( ch >= 0xA0 ) {
ret = this->input_state_rule( 0x41 );
} else {
ret = this->input_state_rule( ch );
}
}
ret.action->char_present = true;
ret.action->ch = ch;
return ret;
}
static bool C0_prime( wchar_t ch )
{
return ( (ch <= 0x17)
|| (ch == 0x19)
|| ( (0x1C <= ch) && (ch <= 0x1F) ) );
}
static bool GLGR ( wchar_t ch )
{
return ( ( (0x20 <= ch) && (ch <= 0x7F) ) /* GL area */
|| ( (0xA0 <= ch) && (ch <= 0xFF) ) ); /* GR area */
}
Transition Ground::input_state_rule( wchar_t ch ) const
{
if ( C0_prime( ch ) ) {
return Transition( new Execute );
}
if ( GLGR( ch ) ) {
return Transition( new Print );
}
return Transition();
}
Action *Escape::enter( void ) const
{
return new Clear;
}
Transition Escape::input_state_rule( wchar_t ch ) const
{
if ( C0_prime( ch ) ) {
return Transition( new Execute );
}
if ( (0x20 <= ch) && (ch <= 0x2F) ) {
return Transition( new Collect, &family->s_Escape_Intermediate );
}
if ( ( (0x30 <= ch) && (ch <= 0x4F) )
|| ( (0x51 <= ch) && (ch <= 0x57) )
|| ( ch == 0x59 )
|| ( ch == 0x5A )
|| ( ch == 0x5C )
|| ( (0x60 <= ch) && (ch <= 0x7E) ) ) {
return Transition( new Esc_Dispatch, &family->s_Ground );
}
if ( ch == 0x5B ) {
return Transition( &family->s_CSI_Entry );
}
if ( ch == 0x5D ) {
return Transition( &family->s_OSC_String );
}
if ( ch == 0x50 ) {
return Transition( &family->s_DCS_Entry );
}
if ( (ch == 0x58) || (ch == 0x5E) || (ch == 0x5F) ) {
return Transition( &family->s_SOS_PM_APC_String );
}
return Transition();
}
Transition Escape_Intermediate::input_state_rule( wchar_t ch ) const
{
if ( C0_prime( ch ) ) {
return Transition( new Execute );
}
if ( (0x20 <= ch) && (ch <= 0x2F) ) {
return Transition( new Collect );
}
if ( (0x30 <= ch) && (ch <= 0x7E) ) {
return Transition( new Esc_Dispatch, &family->s_Ground );
}
return Transition();
}
Action *CSI_Entry::enter( void ) const
{
return new Clear;
}
Transition CSI_Entry::input_state_rule( wchar_t ch ) const
{
if ( C0_prime( ch ) ) {
return Transition( new Execute );
}
if ( (0x40 <= ch) && (ch <= 0x7E) ) {
return Transition( new CSI_Dispatch, &family->s_Ground );
}
if ( ( (0x30 <= ch) && (ch <= 0x39) )
|| ( ch == 0x3B ) ) {
return Transition( new Param, &family->s_CSI_Param );
}
if ( (0x3C <= ch) && (ch <= 0x3F) ) {
return Transition( new Collect, &family->s_CSI_Param );
}
if ( ch == 0x3A ) {
return Transition( &family->s_CSI_Ignore );
}
if ( (0x20 <= ch) && (ch <= 0x2F) ) {
return Transition( new Collect, &family->s_CSI_Intermediate );
}
return Transition();
}
Transition CSI_Param::input_state_rule( wchar_t ch ) const
{
if ( C0_prime( ch ) ) {
return Transition( new Execute );
}
if ( ( (0x30 <= ch) && (ch <= 0x39) ) || ( ch == 0x3B ) ) {
return Transition( new Param );
}
if ( ( ch == 0x3A ) || ( (0x3C <= ch) && (ch <= 0x3F) ) ) {
return Transition( &family->s_CSI_Ignore );
}
if ( (0x20 <= ch) && (ch <= 0x2F) ) {
return Transition( new Collect, &family->s_CSI_Intermediate );
}
if ( (0x40 <= ch) && (ch <= 0x7E) ) {
return Transition( new CSI_Dispatch, &family->s_Ground );
}
return Transition();
}
Transition CSI_Intermediate::input_state_rule( wchar_t ch ) const
{
if ( C0_prime( ch ) ) {
return Transition( new Execute );
}
if ( (0x20 <= ch) && (ch <= 0x2F) ) {
return Transition( new Collect );
}
if ( (0x40 <= ch) && (ch <= 0x7E) ) {
return Transition( new CSI_Dispatch, &family->s_Ground );
}
if ( (0x30 <= ch) && (ch <= 0x3F) ) {
return Transition( &family->s_CSI_Ignore );
}
return Transition();
}
Transition CSI_Ignore::input_state_rule( wchar_t ch ) const
{
if ( C0_prime( ch ) ) {
return Transition( new Execute );
}
if ( (0x40 <= ch) && (ch <= 0x7E) ) {
return Transition( &family->s_Ground );
}
return Transition();
}
Action *DCS_Entry::enter( void ) const
{
return new Clear;
}
Transition DCS_Entry::input_state_rule( wchar_t ch ) const
{
if ( (0x20 <= ch) && (ch <= 0x2F) ) {
return Transition( new Collect, &family->s_DCS_Intermediate );
}
if ( ch == 0x3A ) {
return Transition( &family->s_DCS_Ignore );
}
if ( ( (0x30 <= ch) && (ch <= 0x39) ) || ( ch == 0x3B ) ) {
return Transition( new Param, &family->s_DCS_Param );
}
if ( (0x3C <= ch) && (ch <= 0x3F) ) {
return Transition( new Collect, &family->s_DCS_Param );
}
if ( (0x40 <= ch) && (ch <= 0x7E) ) {
return Transition( &family->s_DCS_Passthrough );
}
return Transition();
}
Transition DCS_Param::input_state_rule( wchar_t ch ) const
{
if ( ( (0x30 <= ch) && (ch <= 0x39) ) || ( ch == 0x3B ) ) {
return Transition( new Param );
}
if ( ( ch == 0x3A ) || ( (0x3C <= ch) && (ch <= 0x3F) ) ) {
return Transition( &family->s_DCS_Ignore );
}
if ( (0x20 <= ch) && (ch <= 0x2F) ) {
return Transition( new Collect, &family->s_DCS_Intermediate );
}
if ( (0x40 <= ch) && (ch <= 0x7E) ) {
return Transition( &family->s_DCS_Passthrough );
}
return Transition();
}
Transition DCS_Intermediate::input_state_rule( wchar_t ch ) const
{
if ( (0x20 <= ch) && (ch <= 0x2F) ) {
return Transition( new Collect );
}
if ( (0x40 <= ch) && (ch <= 0x7E) ) {
return Transition( &family->s_DCS_Passthrough );
}
if ( (0x30 <= ch) && (ch <= 0x3F) ) {
return Transition( &family->s_DCS_Ignore );
}
return Transition();
}
Action *DCS_Passthrough::enter( void ) const
{
return new Hook;
}
Action *DCS_Passthrough::exit( void ) const
{
return new Unhook;
}
Transition DCS_Passthrough::input_state_rule( wchar_t ch ) const
{
if ( C0_prime( ch ) || ( (0x20 <= ch) && (ch <= 0x7E) ) ) {
return Transition( new Put );
}
if ( ch == 0x9C ) {
return Transition( &family->s_Ground );
}
return Transition();
}
Transition DCS_Ignore::input_state_rule( wchar_t ch ) const
{
if ( ch == 0x9C ) {
return Transition( &family->s_Ground );
}
return Transition();
}
Action *OSC_String::enter( void ) const
{
return new OSC_Start;
}
Action *OSC_String::exit( void ) const
{
return new OSC_End;
}
Transition OSC_String::input_state_rule( wchar_t ch ) const
{
if ( (0x20 <= ch) && (ch <= 0x7F) ) {
return Transition( new OSC_Put );
}
if ( (ch == 0x9C) || (ch == 0x07) ) { /* 0x07 is xterm non-ANSI variant */
return Transition( &family->s_Ground );
}
return Transition();
}
Transition SOS_PM_APC_String::input_state_rule( wchar_t ch ) const
{
if ( ch == 0x9C ) {
return Transition( &family->s_Ground );
}
return Transition();
}
+87
View File
@@ -0,0 +1,87 @@
#ifndef PARSERSTATE_HPP
#define PARSERSTATE_HPP
#include "parsertransition.hpp"
namespace Parser {
class StateFamily;
class State
{
protected:
virtual Transition input_state_rule( wchar_t ch ) const = 0;
StateFamily *family;
private:
Transition anywhere_rule( wchar_t ch ) const;
public:
void setfamily( StateFamily *s_family ) { family = s_family; }
Transition input( wchar_t ch ) const;
virtual Action *enter( void ) const { return new Ignore; }
virtual Action *exit( void ) const { return new Ignore; }
State() : family( NULL ) {};
virtual ~State() {};
State( const State & );
State & operator=( const State & );
};
class Ground : public State {
Transition input_state_rule( wchar_t ch ) const;
};
class Escape : public State {
Action *enter( void ) const;
Transition input_state_rule( wchar_t ch ) const;
};
class Escape_Intermediate : public State {
Transition input_state_rule( wchar_t ch ) const;
};
class CSI_Entry : public State {
Action *enter( void ) const;
Transition input_state_rule( wchar_t ch ) const;
};
class CSI_Param : public State {
Transition input_state_rule( wchar_t ch ) const;
};
class CSI_Intermediate : public State {
Transition input_state_rule( wchar_t ch ) const;
};
class CSI_Ignore : public State {
Transition input_state_rule( wchar_t ch ) const;
};
class DCS_Entry : public State {
Action *enter( void ) const;
Transition input_state_rule( wchar_t ch ) const;
};
class DCS_Param : public State {
Transition input_state_rule( wchar_t ch ) const;
};
class DCS_Intermediate : public State {
Transition input_state_rule( wchar_t ch ) const;
};
class DCS_Passthrough : public State {
Action *enter( void ) const;
Transition input_state_rule( wchar_t ch ) const;
Action *exit( void ) const;
};
class DCS_Ignore : public State {
Transition input_state_rule( wchar_t ch ) const;
};
class OSC_String : public State {
Action *enter( void ) const;
Transition input_state_rule( wchar_t ch ) const;
Action *exit( void ) const;
};
class SOS_PM_APC_String : public State {
Transition input_state_rule( wchar_t ch ) const;
};
}
#endif
+54
View File
@@ -0,0 +1,54 @@
#ifndef PARSERSTATEFAMILY_HPP
#define PARSERSTATEFAMILY_HPP
#include "parserstate.hpp"
namespace Parser {
class StateFamily
{
public:
Ground s_Ground;
Escape s_Escape;
Escape_Intermediate s_Escape_Intermediate;
CSI_Entry s_CSI_Entry;
CSI_Param s_CSI_Param;
CSI_Intermediate s_CSI_Intermediate;
CSI_Ignore s_CSI_Ignore;
DCS_Entry s_DCS_Entry;
DCS_Param s_DCS_Param;
DCS_Intermediate s_DCS_Intermediate;
DCS_Passthrough s_DCS_Passthrough;
DCS_Ignore s_DCS_Ignore;
OSC_String s_OSC_String;
SOS_PM_APC_String s_SOS_PM_APC_String;
StateFamily()
: s_Ground(), s_Escape(), s_Escape_Intermediate(),
s_CSI_Entry(), s_CSI_Param(), s_CSI_Intermediate(), s_CSI_Ignore(),
s_DCS_Entry(), s_DCS_Param(), s_DCS_Intermediate(),
s_DCS_Passthrough(), s_DCS_Ignore(),
s_OSC_String(), s_SOS_PM_APC_String()
{
s_Ground.setfamily( this );
s_Escape.setfamily( this );
s_Escape_Intermediate.setfamily( this );
s_CSI_Entry.setfamily( this );
s_CSI_Param.setfamily( this );
s_CSI_Intermediate.setfamily( this );
s_CSI_Ignore.setfamily( this );
s_DCS_Entry.setfamily( this );
s_DCS_Param.setfamily( this );
s_DCS_Intermediate.setfamily( this );
s_DCS_Passthrough.setfamily( this );
s_DCS_Ignore.setfamily( this );
s_OSC_String.setfamily( this );
s_SOS_PM_APC_String.setfamily( this );
}
};
}
#endif
+39
View File
@@ -0,0 +1,39 @@
#ifndef PARSERTRANSITION_HPP
#define PARSERTRANSITION_HPP
#include <stdlib.h>
#include "parseraction.hpp"
namespace Parser {
class State;
class Transition
{
public:
Action *action;
State *next_state;
Transition( const Transition &x )
: action( x.action ),
next_state( x.next_state ) {}
Transition & operator=( const Transition &t )
{
action = t.action;
next_state = t.next_state;
return *this;
}
virtual ~Transition() {}
Transition( Action *s_action=new Ignore, State *s_next_state=NULL )
: action( s_action ), next_state( s_next_state )
{}
Transition( State *s_next_state )
: action( new Ignore ), next_state( s_next_state )
{}
};
}
#endif
+386
View File
@@ -0,0 +1,386 @@
#include <locale.h>
#include <string.h>
#include <langinfo.h>
#include <unistd.h>
#include <stdio.h>
#include <pty.h>
#include <stdlib.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <pwd.h>
#include <signal.h>
#include <sys/signalfd.h>
#include <time.h>
#include "stmclient.hpp"
#include "swrite.hpp"
#include "completeterminal.hpp"
#include "user.hpp"
#include "networktransport.cpp"
void STMClient::init( void )
{
/* Verify locale calls for UTF-8 */
if ( strcmp( nl_langinfo( CODESET ), "UTF-8" ) != 0 ) {
fprintf( stderr, "mosh requires a UTF-8 locale.\n" );
exit( 1 );
}
/* Verify terminal configuration */
if ( tcgetattr( STDIN_FILENO, &saved_termios ) < 0 ) {
perror( "tcgetattr" );
exit( 1 );
}
/* Put terminal driver in raw mode */
raw_termios = saved_termios;
if ( !(raw_termios.c_iflag & IUTF8) ) {
/* SSH should also convey IUTF8 across connection. */
// fprintf( stderr, "Warning: Locale is UTF-8 but termios IUTF8 flag not set. Setting IUTF8 flag.\n" );
raw_termios.c_iflag |= IUTF8;
}
cfmakeraw( &raw_termios );
if ( tcsetattr( STDIN_FILENO, TCSANOW, &raw_termios ) < 0 ) {
perror( "tcsetattr" );
exit( 1 );
}
/* Put terminal in application-cursor-key mode */
swrite( STDOUT_FILENO, Terminal::Emulator::open().c_str() );
/* Add our name to window title */
overlays.set_title_prefix( wstring( L"[mosh] " ) );
}
void STMClient::shutdown( void )
{
/* Restore screen state */
overlays.get_notification_engine().set_notification_string( wstring( L"" ) );
overlays.get_notification_engine().server_heard( timestamp() );
overlays.set_title_prefix( wstring( L"" ) );
output_new_frame();
/* Restore terminal and terminal-driver state */
swrite( STDOUT_FILENO, Terminal::Emulator::close().c_str() );
if ( tcsetattr( STDIN_FILENO, TCSANOW, &saved_termios ) < 0 ) {
perror( "tcsetattr" );
exit( 1 );
}
}
void STMClient::main_init( void )
{
/* establish WINCH fd and start listening for signal */
sigset_t signal_mask;
assert( sigemptyset( &signal_mask ) == 0 );
assert( sigaddset( &signal_mask, SIGWINCH ) == 0 );
/* stop "ignoring" WINCH signal */
assert( sigprocmask( SIG_BLOCK, &signal_mask, NULL ) == 0 );
winch_fd = signalfd( -1, &signal_mask, 0 );
if ( winch_fd < 0 ) {
perror( "signalfd" );
return;
}
/* establish fd for shutdown signals */
assert( sigemptyset( &signal_mask ) == 0 );
assert( sigaddset( &signal_mask, SIGTERM ) == 0 );
assert( sigaddset( &signal_mask, SIGINT ) == 0 );
assert( sigaddset( &signal_mask, SIGHUP ) == 0 );
assert( sigaddset( &signal_mask, SIGPIPE ) == 0 );
assert( sigaddset( &signal_mask, SIGTSTP ) == 0 );
assert( sigaddset( &signal_mask, SIGSTOP ) == 0 );
assert( sigaddset( &signal_mask, SIGCONT ) == 0 );
/* don't let signals kill us */
assert( sigprocmask( SIG_BLOCK, &signal_mask, NULL ) == 0 );
shutdown_signal_fd = signalfd( -1, &signal_mask, 0 );
if ( shutdown_signal_fd < 0 ) {
perror( "signalfd" );
return;
}
/* get initial window size */
if ( ioctl( STDIN_FILENO, TIOCGWINSZ, &window_size ) < 0 ) {
perror( "ioctl TIOCGWINSZ" );
return;
}
/* local state */
local_framebuffer = new Terminal::Framebuffer( window_size.ws_col, window_size.ws_row );
/* initialize screen */
string init = Terminal::Display::new_frame( false, *local_framebuffer, *local_framebuffer );
swrite( STDOUT_FILENO, init.data(), init.size() );
/* open network */
Network::UserStream blank;
Terminal::Complete local_terminal( window_size.ws_col, window_size.ws_row );
network = new Network::Transport< Network::UserStream, Terminal::Complete >( blank, local_terminal,
key.c_str(), ip.c_str(), port );
network->set_send_delay( 1 ); /* minimal delay on outgoing keystrokes */
/* tell server the size of the terminal */
network->get_current_state().push_back( Parser::Resize( window_size.ws_col, window_size.ws_row ) );
}
void STMClient::output_new_frame( void )
{
/* fetch target state */
Terminal::Framebuffer new_state( network->get_latest_remote_state().state.get_fb() );
/* apply local overlays */
overlays.apply( new_state );
/* calculate minimal difference from where we are */
const string diff( Terminal::Display::new_frame( !repaint_requested,
*local_framebuffer,
new_state ) );
swrite( STDOUT_FILENO, diff.data(), diff.size() );
*local_framebuffer = new_state;
repaint_requested = false;
}
bool STMClient::process_network_input( void )
{
network->recv();
/* Now give hints to the overlays */
overlays.get_notification_engine().server_heard( network->get_latest_remote_state().timestamp );
overlays.get_prediction_engine().set_local_frame_acked( network->get_sent_state_acked() );
overlays.get_prediction_engine().set_send_interval( network->send_interval() );
overlays.get_prediction_engine().set_local_frame_late_acked( network->get_sent_state_late_acked() );
return true;
}
bool STMClient::process_user_input( int fd )
{
const int buf_size = 16384;
char buf[ buf_size ];
/* fill buffer if possible */
ssize_t bytes_read = read( fd, buf, buf_size );
if ( bytes_read == 0 ) { /* EOF */
return false;
} else if ( bytes_read < 0 ) {
perror( "read" );
return false;
}
if ( !network->shutdown_in_progress() ) {
overlays.get_prediction_engine().set_local_frame_sent( network->get_sent_state_last() );
for ( int i = 0; i < bytes_read; i++ ) {
char the_byte = buf[ i ];
overlays.get_prediction_engine().new_user_byte( the_byte, *local_framebuffer );
if ( quit_sequence_started ) {
if ( the_byte == '.' ) { /* Quit sequence is Ctrl-^ . */
if ( network->attached() && (!network->shutdown_in_progress()) ) {
overlays.get_notification_engine().set_notification_string( wstring( L"Exiting on user request..." ) );
network->start_shutdown();
return true;
} else {
return false;
}
} else if ( the_byte == '^' ) {
/* Emulation sequence to type Ctrl-^ is Ctrl-^ ^ */
network->get_current_state().push_back( Parser::UserByte( 0x1E ) );
} else {
/* Ctrl-^ followed by anything other than . and ^ gets sent literally */
network->get_current_state().push_back( Parser::UserByte( 0x1E ) );
network->get_current_state().push_back( Parser::UserByte( the_byte ) );
}
quit_sequence_started = false;
continue;
}
quit_sequence_started = (the_byte == 0x1E);
if ( quit_sequence_started ) {
continue;
}
if ( the_byte == 0x0C ) { /* Ctrl-L */
repaint_requested = true;
}
network->get_current_state().push_back( Parser::UserByte( the_byte ) );
}
}
return true;
}
bool STMClient::process_resize( void )
{
struct signalfd_siginfo info;
assert( read( winch_fd, &info, sizeof( info ) ) == sizeof( info ) );
assert( info.ssi_signo == SIGWINCH );
/* get new size */
if ( ioctl( STDIN_FILENO, TIOCGWINSZ, &window_size ) < 0 ) {
perror( "ioctl TIOCGWINSZ" );
return false;
}
/* tell remote emulator */
Parser::Resize res( window_size.ws_col, window_size.ws_row );
if ( !network->shutdown_in_progress() ) {
network->get_current_state().push_back( res );
}
/* note remote emulator will probably reply with its own Resize to adjust our state */
/* tell prediction engine */
overlays.get_prediction_engine().reset();
return true;
}
void STMClient::main( void )
{
/* initialize signal handling and structures */
main_init();
/* prepare to poll for events */
struct pollfd pollfds[ 4 ];
pollfds[ 0 ].fd = network->fd();
pollfds[ 0 ].events = POLLIN;
pollfds[ 1 ].fd = STDIN_FILENO;
pollfds[ 1 ].events = POLLIN;
pollfds[ 2 ].fd = winch_fd;
pollfds[ 2 ].events = POLLIN;
pollfds[ 3 ].fd = shutdown_signal_fd;
pollfds[ 3 ].events = POLLIN;
while ( 1 ) {
try {
output_new_frame();
int active_fds = poll( pollfds, 4, min( network->wait_time(), overlays.wait_time() ) );
if ( active_fds < 0 ) {
perror( "poll" );
break;
}
if ( pollfds[ 0 ].revents & POLLIN ) {
/* packet received from the network */
if ( !process_network_input() ) { return; }
}
if ( pollfds[ 1 ].revents & POLLIN ) {
/* input from the user needs to be fed to the network */
if ( !process_user_input( pollfds[ 1 ].fd ) ) {
if ( !network->attached() ) {
break;
} else if ( !network->shutdown_in_progress() ) {
overlays.get_notification_engine().set_notification_string( wstring( L"Exiting..." ) );
network->start_shutdown();
}
}
}
if ( pollfds[ 2 ].revents & POLLIN ) {
/* resize */
if ( !process_resize() ) { return; }
}
if ( pollfds[ 3 ].revents & POLLIN ) {
/* shutdown signal */
struct signalfd_siginfo the_siginfo;
ssize_t bytes_read = read( pollfds[ 3 ].fd, &the_siginfo, sizeof( the_siginfo ) );
if ( bytes_read == 0 ) {
break;
} else if ( bytes_read < 0 ) {
perror( "read" );
break;
}
if ( !network->attached() ) {
break;
} else if ( !network->shutdown_in_progress() ) {
overlays.get_notification_engine().set_notification_string( wstring( L"Signal received, shutting down..." ) );
network->start_shutdown();
}
}
if ( (pollfds[ 0 ].revents)
& (POLLERR | POLLHUP | POLLNVAL) ) {
/* network problem */
break;
}
if ( (pollfds[ 1 ].revents)
& (POLLERR | POLLHUP | POLLNVAL) ) {
/* user problem */
if ( !network->attached() ) {
break;
} else if ( !network->shutdown_in_progress() ) {
overlays.get_notification_engine().set_notification_string( wstring( L"Exiting..." ) );
network->start_shutdown();
}
}
/* quit if our shutdown has been acknowledged */
if ( network->shutdown_in_progress() && network->shutdown_acknowledged() ) {
break;
}
/* quit after shutdown acknowledgement timeout */
if ( network->shutdown_in_progress() && network->shutdown_ack_timed_out() ) {
break;
}
/* quit if we received and acknowledged a shutdown request */
if ( network->counterparty_shutdown_ack_sent() ) {
break;
}
static const wstring connecting_notification( L"Connecting..." );
if ( (network->get_remote_state_num() == 0) && (!network->shutdown_in_progress()) ) {
overlays.get_notification_engine().set_notification_string( connecting_notification );
} else if ( (network->get_remote_state_num() != 0)
&& (overlays.get_notification_engine().get_notification_string()
== connecting_notification) ) {
overlays.get_notification_engine().set_notification_string( L"" );
}
network->tick();
} catch ( Network::NetworkException e ) {
if ( !network->shutdown_in_progress() ) {
wchar_t tmp[ 128 ];
swprintf( tmp, 128, L"%s: %s", e.function.c_str(), strerror( e.the_errno ) );
overlays.get_notification_engine().set_notification_string( wstring( tmp ) );
}
struct timespec req;
req.tv_sec = 0;
req.tv_nsec = 200000000; /* 0.2 sec */
nanosleep( &req, NULL );
} catch ( Crypto::CryptoException e ) {
wchar_t tmp[ 128 ];
swprintf( tmp, 128, L"Crypto exception: %s", e.text.c_str() );
overlays.get_notification_engine().set_notification_string( wstring( tmp ) );
}
}
}
+70
View File
@@ -0,0 +1,70 @@
#ifndef STM_CLIENT_HPP
#define STM_CLIENT_HPP
#include <sys/ioctl.h>
#include <termios.h>
#include <string>
#include "completeterminal.hpp"
#include "networktransport.hpp"
#include "user.hpp"
#include "terminaloverlay.hpp"
class STMClient {
private:
std::string ip;
int port;
std::string key;
struct termios saved_termios, raw_termios;
int winch_fd, shutdown_signal_fd;
struct winsize window_size;
Terminal::Framebuffer *local_framebuffer;
Overlay::OverlayManager overlays;
Network::Transport< Network::UserStream, Terminal::Complete > *network;
bool repaint_requested, quit_sequence_started;
void main_init( void );
bool process_network_input( void );
bool process_user_input( int fd );
bool process_resize( void );
void output_new_frame( void );
public:
STMClient( const char *s_ip, int s_port, const char *s_key )
: ip( s_ip ), port( s_port ), key( s_key ),
saved_termios(), raw_termios(),
winch_fd(), shutdown_signal_fd(),
window_size(),
local_framebuffer( NULL ),
overlays(),
network( NULL ),
repaint_requested( false ),
quit_sequence_started( false )
{}
void init( void );
void shutdown( void );
void main( void );
~STMClient()
{
if ( local_framebuffer != NULL ) {
delete local_framebuffer;
}
if ( network != NULL ) {
delete network;
}
}
/* unused */
STMClient( const STMClient & );
STMClient & operator=( const STMClient & );
};
#endif
+23
View File
@@ -0,0 +1,23 @@
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include "swrite.hpp"
int swrite( int fd, const char *str, ssize_t len )
{
ssize_t total_bytes_written = 0;
ssize_t bytes_to_write = ( len >= 0 ) ? len : strlen( str );
while ( total_bytes_written < bytes_to_write ) {
ssize_t bytes_written = write( fd, str + total_bytes_written,
bytes_to_write - total_bytes_written );
if ( bytes_written <= 0 ) {
perror( "write" );
return -1;
} else {
total_bytes_written += bytes_written;
}
}
return 0;
}
+6
View File
@@ -0,0 +1,6 @@
#ifndef SWRITE_HPP
#define SWRITE_HPP
int swrite( int fd, const char *str, ssize_t len = -1 );
#endif
+155
View File
@@ -0,0 +1,155 @@
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <typeinfo>
#include "terminal.hpp"
#include "swrite.hpp"
using namespace Terminal;
Emulator::Emulator( size_t s_width, size_t s_height )
: fb( s_width, s_height ), dispatch(), user()
{}
std::string Emulator::read_octets_to_host( void )
{
std::string ret = dispatch.terminal_to_host;
dispatch.terminal_to_host.clear();
return ret;
}
void Emulator::execute( const Parser::Execute *act )
{
dispatch.dispatch( CONTROL, act, &fb );
}
void Emulator::print( const Parser::Print *act )
{
assert( act->char_present );
int chwidth = act->ch == L'\0' ? -1 : wcwidth( act->ch );
Cell *this_cell = fb.get_mutable_cell();
Cell *combining_cell = fb.get_combining_cell(); /* can be null if we were resized */
switch ( chwidth ) {
case 1: /* normal character */
case 2: /* wide character */
if ( fb.ds.auto_wrap_mode && fb.ds.next_print_will_wrap ) {
fb.get_mutable_row( -1 )->wrap = true;
fb.ds.move_col( 0 );
fb.move_rows_autoscroll( 1 );
}
/* wrap 2-cell chars if no room, even without will-wrap flag */
if ( fb.ds.auto_wrap_mode
&& (chwidth == 2)
&& (fb.ds.get_cursor_col() == fb.ds.get_width() - 1) ) {
fb.reset_cell( this_cell );
fb.get_mutable_row( -1 )->wrap = false;
/* There doesn't seem to be a consistent way to get the
downstream terminal emulator to set the wrap-around
copy-and-paste flag on a row that ends with an empty cell
because a wide char was wrapped to the next line. */
fb.ds.move_col( 0 );
fb.move_rows_autoscroll( 1 );
}
if ( fb.ds.insert_mode ) {
for ( int i = 0; i < chwidth; i++ ) {
fb.insert_cell( fb.ds.get_cursor_row(), fb.ds.get_cursor_col() );
}
}
this_cell = fb.get_mutable_cell();
fb.reset_cell( this_cell );
this_cell->contents.push_back( act->ch );
this_cell->width = chwidth;
fb.apply_renditions_to_current_cell();
if ( chwidth == 2 ) { /* erase overlapped cell */
if ( fb.ds.get_cursor_col() + 1 < fb.ds.get_width() ) {
fb.reset_cell( fb.get_mutable_cell( fb.ds.get_cursor_row(), fb.ds.get_cursor_col() + 1 ) );
}
}
fb.ds.move_col( chwidth, true, true );
act->handled = true;
break;
case 0: /* combining character */
if ( combining_cell == NULL ) { /* character is now offscreen */
act->handled = true;
break;
}
if ( combining_cell->contents.size() == 0 ) {
/* cell starts with combining character */
assert( this_cell == combining_cell );
assert( combining_cell->width == 1 );
combining_cell->fallback = true;
fb.ds.move_col( 1, true, true );
}
if ( combining_cell->contents.size() < 16 ) {
/* seems like a reasonable limit on combining characters */
combining_cell->contents.push_back( act->ch );
}
act->handled = true;
break;
case -1: /* unprintable character */
break;
default:
assert( false );
}
}
void Emulator::CSI_dispatch( const Parser::CSI_Dispatch *act )
{
dispatch.dispatch( CSI, act, &fb );
}
void Emulator::OSC_end( const Parser::OSC_End *act )
{
dispatch.OSC_dispatch( act, &fb );
}
void Emulator::Esc_dispatch( const Parser::Esc_Dispatch *act )
{
/* handle 7-bit ESC-encoding of C1 control characters */
if ( (dispatch.get_dispatch_chars().size() == 0)
&& (0x40 <= act->ch)
&& (act->ch <= 0x5F) ) {
Parser::Esc_Dispatch act2 = *act;
act2.ch += 0x40;
dispatch.dispatch( CONTROL, &act2, &fb );
} else {
dispatch.dispatch( ESCAPE, act, &fb );
}
}
std::string Emulator::open( void )
{
char appmode[ 6 ] = { 0x1b, '[', '?', '1', 'h', 0 };
return std::string( appmode );
}
std::string Emulator::close( void )
{
return std::string( "\033[?1l\033[!p" );
}
void Emulator::resize( size_t s_width, size_t s_height )
{
fb.resize( s_width, s_height );
}
bool Emulator::operator==( Emulator const &x ) const
{
/* dispatcher and user are irrelevant for us */
return ( fb == x.fb );
}
+58
View File
@@ -0,0 +1,58 @@
#ifndef TERMINAL_CPP
#define TERMINAL_CPP
#include <wchar.h>
#include <stdio.h>
#include <vector>
#include <deque>
#include "parseraction.hpp"
#include "terminalframebuffer.hpp"
#include "terminaldispatcher.hpp"
#include "terminaluserinput.hpp"
#include "terminaldisplay.hpp"
namespace Terminal {
class Emulator {
friend void Parser::Print::act_on_terminal( Emulator * ) const;
friend void Parser::Execute::act_on_terminal( Emulator * ) const;
friend void Parser::Clear::act_on_terminal( Emulator * ) const;
friend void Parser::Param::act_on_terminal( Emulator * ) const;
friend void Parser::Collect::act_on_terminal( Emulator * ) const;
friend void Parser::CSI_Dispatch::act_on_terminal( Emulator * ) const;
friend void Parser::Esc_Dispatch::act_on_terminal( Emulator * ) const;
friend void Parser::OSC_Start::act_on_terminal( Emulator * ) const;
friend void Parser::OSC_Put::act_on_terminal( Emulator * ) const;
friend void Parser::OSC_End::act_on_terminal( Emulator * ) const;
friend void Parser::UserByte::act_on_terminal( Emulator * ) const;
friend void Parser::Resize::act_on_terminal( Emulator * ) const;
private:
Framebuffer fb;
Dispatcher dispatch;
UserInput user;
/* action methods */
void print( const Parser::Print *act );
void execute( const Parser::Execute *act );
void CSI_dispatch( const Parser::CSI_Dispatch *act );
void Esc_dispatch( const Parser::Esc_Dispatch *act );
void OSC_end( const Parser::OSC_End *act );
void resize( size_t s_width, size_t s_height );
public:
Emulator( size_t s_width, size_t s_height );
std::string read_octets_to_host( void );
static std::string open( void ); /* put user cursor keys in application mode */
static std::string close( void ); /* restore user cursor keys */
const Framebuffer & get_fb( void ) const { return fb; }
bool operator==( Emulator const &x ) const;
};
}
#endif
+211
View File
@@ -0,0 +1,211 @@
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include "terminaldispatcher.hpp"
#include "parseraction.hpp"
#include "terminalframebuffer.hpp"
using namespace Terminal;
Dispatcher::Dispatcher()
: params(), parsed_params(), parsed( false ), dispatch_chars(),
OSC_string(), terminal_to_host()
{}
void Dispatcher::newparamchar( const Parser::Param *act )
{
assert( act->char_present );
assert( (act->ch == ';') || ( (act->ch >= '0') && (act->ch <= '9') ) );
if ( params.length() < 100 ) {
/* enough for 16 five-char params plus 15 semicolons */
params.push_back( act->ch );
act->handled = true;
}
parsed = false;
}
void Dispatcher::collect( const Parser::Collect *act )
{
assert( act->char_present );
if ( ( dispatch_chars.length() < 8 ) /* never should need more than 2 */
&& ( act->ch <= 255 ) ) { /* ignore non-8-bit */
dispatch_chars.push_back( act->ch );
act->handled = true;
}
}
void Dispatcher::clear( const Parser::Clear *act )
{
params.clear();
dispatch_chars.clear();
parsed = false;
act->handled = true;
}
void Dispatcher::parse_params( void )
{
if ( parsed ) {
return;
}
parsed_params.clear();
const char *str = params.c_str();
const char *segment_begin = str;
while ( 1 ) {
const char *segment_end = strchr( segment_begin, ';' );
if ( segment_end == NULL ) {
break;
}
errno = 0;
char *endptr;
int val = strtol( segment_begin, &endptr, 10 );
if ( endptr == segment_begin ) {
val = -1;
}
if ( errno == 0 ) {
parsed_params.push_back( val );
}
segment_begin = segment_end + 1;
}
/* get last param */
errno = 0;
char *endptr;
int val = strtol( segment_begin, &endptr, 10 );
if ( endptr == segment_begin ) {
val = -1;
}
if ( errno == 0 ) {
parsed_params.push_back( val );
}
parsed = true;
}
int Dispatcher::getparam( size_t N, int defaultval )
{
int ret = defaultval;
if ( !parsed ) {
parse_params();
}
if ( parsed_params.size() > N ) {
ret = parsed_params[ N ];
}
if ( ret < 1 ) ret = defaultval;
return ret;
}
int Dispatcher::param_count( void )
{
if ( !parsed ) {
parse_params();
}
return parsed_params.size();
}
std::string Dispatcher::str( void )
{
char assum[ 64 ];
snprintf( assum, 64, "[dispatch=\"%s\" params=\"%s\"]",
dispatch_chars.c_str(), params.c_str() );
return std::string( assum );
}
/* construct on first use to avoid static initialization order crash */
DispatchRegistry & Terminal::get_global_dispatch_registry( void )
{
static DispatchRegistry global_dispatch_registry;
return global_dispatch_registry;
}
static void register_function( Function_Type type,
std::string dispatch_chars,
Function f )
{
switch ( type ) {
case ESCAPE:
get_global_dispatch_registry().escape.insert( dispatch_map_t::value_type( dispatch_chars, f ) );
break;
case CSI:
get_global_dispatch_registry().CSI.insert( dispatch_map_t::value_type( dispatch_chars, f ) );
break;
case CONTROL:
get_global_dispatch_registry().control.insert( dispatch_map_t::value_type( dispatch_chars, f ) );
break;
}
}
Function::Function( Function_Type type, std::string dispatch_chars,
void (*s_function)( Framebuffer *, Dispatcher * ),
bool s_clears_wrap_state )
: function( s_function ), clears_wrap_state( s_clears_wrap_state )
{
register_function( type, dispatch_chars, *this );
}
void Dispatcher::dispatch( Function_Type type, const Parser::Action *act, Framebuffer *fb )
{
/* add final char to dispatch key */
if ( (type == ESCAPE) || (type == CSI) ) {
assert( act->char_present );
Parser::Collect act2;
act2.char_present = true;
act2.ch = act->ch;
collect( &act2 );
}
dispatch_map_t *map = NULL;
switch ( type ) {
case ESCAPE: map = &get_global_dispatch_registry().escape; break;
case CSI: map = &get_global_dispatch_registry().CSI; break;
case CONTROL: map = &get_global_dispatch_registry().control; break;
}
std::string key = dispatch_chars;
if ( type == CONTROL ) {
assert( act->ch <= 255 );
char ctrlstr[ 2 ] = { (char)act->ch, 0 };
key = std::string( ctrlstr, 1 );
}
dispatch_map_t::const_iterator i = map->find( key );
if ( i == map->end() ) {
/* unknown function */
fb->ds.next_print_will_wrap = false;
return;
} else {
act->handled = true;
if ( i->second.clears_wrap_state ) {
fb->ds.next_print_will_wrap = false;
}
return i->second.function( fb, this );
}
}
void Dispatcher::OSC_put( const Parser::OSC_Put *act )
{
assert( act->char_present );
if ( OSC_string.size() < 256 ) { /* should be a long enough window title */
OSC_string.push_back( act->ch );
act->handled = true;
}
}
void Dispatcher::OSC_start( const Parser::OSC_Start *act )
{
OSC_string.clear();
act->handled = true;
}
bool Dispatcher::operator==( const Dispatcher &x ) const
{
return ( params == x.params ) && ( parsed_params == x.parsed_params ) && ( parsed == x.parsed )
&& ( dispatch_chars == x.dispatch_chars ) && ( OSC_string == x.OSC_string ) && ( terminal_to_host == x.terminal_to_host );
}
+86
View File
@@ -0,0 +1,86 @@
#ifndef TERMINALDISPATCHER_HPP
#define TERMINALDISPATCHER_HPP
#include <vector>
#include <string>
#include <map>
namespace Parser {
class Action;
class Param;
class Collect;
class Clear;
class Esc_Dispatch;
class CSI_Dispatch;
class Execute;
class OSC_Start;
class OSC_Put;
class OSC_End;
}
namespace Terminal {
class Framebuffer;
class Dispatcher;
enum Function_Type { ESCAPE, CSI, CONTROL };
class Function {
public:
Function() : function( NULL ), clears_wrap_state( true ) {}
Function( Function_Type type, std::string dispatch_chars,
void (*s_function)( Framebuffer *, Dispatcher * ),
bool s_clears_wrap_state = true );
void (*function)( Framebuffer *, Dispatcher * );
bool clears_wrap_state;
};
typedef std::map<std::string, Function> dispatch_map_t;
class DispatchRegistry {
public:
dispatch_map_t escape;
dispatch_map_t CSI;
dispatch_map_t control;
DispatchRegistry() : escape(), CSI(), control() {}
};
DispatchRegistry & get_global_dispatch_registry( void );
class Dispatcher {
private:
std::string params;
std::vector<int> parsed_params;
bool parsed;
std::string dispatch_chars;
std::vector<wchar_t> OSC_string; /* only used to set the window title */
void parse_params( void );
public:
std::string terminal_to_host; /* this is the reply string */
Dispatcher();
int getparam( size_t N, int defaultval );
int param_count( void );
void newparamchar( const Parser::Param *act );
void collect( const Parser::Collect *act );
void clear( const Parser::Clear *act );
std::string str( void );
void dispatch( Function_Type type, const Parser::Action *act, Framebuffer *fb );
std::string get_dispatch_chars( void ) { return dispatch_chars; }
std::vector<wchar_t> get_OSC_string( void ) { return OSC_string; }
void OSC_put( const Parser::OSC_Put *act );
void OSC_start( const Parser::OSC_Start *act );
void OSC_dispatch( const Parser::OSC_End *act, Framebuffer *fb );
bool operator==( const Dispatcher &x ) const;
};
}
#endif
+259
View File
@@ -0,0 +1,259 @@
#include <assert.h>
#include "terminaldisplay.hpp"
using namespace Terminal;
/* Print a new "frame" to the terminal, using ANSI/ECMA-48 escape codes. */
std::string Display::new_frame( bool initialized, const Framebuffer &last, const Framebuffer &f )
{
FrameState frame( last );
char tmp[ 64 ];
/* has bell been rung? */
if ( f.get_bell_count() != frame.last_frame.get_bell_count() ) {
frame.append( "\x07" );
}
/* has window title changed? */
if ( (!initialized)
|| (f.get_window_title() != frame.last_frame.get_window_title()) ) {
/* set window title */
frame.append( "\033]0;" );
const std::deque<wchar_t> &window_title( f.get_window_title() );
for ( auto i = window_title.begin();
i != window_title.end();
i++ ) {
snprintf( tmp, 64, "%lc", *i );
frame.append( tmp );
}
frame.append( "\033\\" );
}
/* has reverse video state changed? */
if ( (!initialized)
|| (f.ds.reverse_video != frame.last_frame.ds.reverse_video) ) {
/* set reverse video */
snprintf( tmp, 64, "\033[?5%c", (f.ds.reverse_video ? 'h' : 'l') );
frame.append( tmp );
}
/* has size changed? */
if ( (!initialized)
|| (f.ds.get_width() != frame.last_frame.ds.get_width())
|| (f.ds.get_height() != frame.last_frame.ds.get_height()) ) {
/* clear screen */
frame.append( "\033[0m\033[H\033[2J" );
initialized = false;
frame.cursor_x = frame.cursor_y = 0;
frame.current_rendition_string = "\033[0m";
} else {
frame.cursor_x = frame.last_frame.ds.get_cursor_col();
frame.cursor_y = frame.last_frame.ds.get_cursor_row();
frame.current_rendition_string = frame.last_frame.ds.get_renditions().sgr();
}
/* shortcut -- has display moved up by a certain number of lines? */
frame.y = 0;
if ( initialized ) {
int lines_scrolled = 0;
int scroll_height = 0;
for ( int row = 0; row < f.ds.get_height(); row++ ) {
if ( *(f.get_row( 0 )) == *(frame.last_frame.get_row( row )) ) {
/* found a scroll */
lines_scrolled = row;
scroll_height = 1;
/* how big is the region that was scrolled? */
for ( int region_height = 1;
lines_scrolled + region_height < f.ds.get_height();
region_height++ ) {
if ( *(f.get_row( region_height ))
== *(frame.last_frame.get_row( lines_scrolled + region_height )) ) {
scroll_height = region_height + 1;
} else {
break;
}
}
break;
}
}
if ( scroll_height ) {
frame.y = scroll_height;
if ( lines_scrolled ) {
if ( frame.cursor_y != f.ds.get_height() - 1 ) {
frame.append_silent_move( f.ds.get_height() - 1, 0 );
}
if ( frame.current_rendition_string != "\033[0m" ) {
frame.append( "\033[0m" );
frame.current_rendition_string = "\033[0m";
}
for ( int i = 0; i < lines_scrolled; i++ ) {
frame.append( "\n" );
}
for ( int i = 0; i < f.ds.get_height(); i++ ) {
if ( i + lines_scrolled < f.ds.get_height() ) {
*(frame.last_frame.get_mutable_row( i )) = *(frame.last_frame.get_row( i + lines_scrolled ));
} else {
frame.last_frame.get_mutable_row( i )->reset( 0 );
}
}
}
}
}
/* iterate for every cell */
for ( ; frame.y < f.ds.get_height(); frame.y++ ) {
int last_x = 0;
for ( frame.x = 0;
frame.x < f.ds.get_width(); /* let put_cell() handle advance */ ) {
last_x = frame.x;
put_cell( initialized, frame, f );
/* To hint that a word-select should group the end of one line
with the beginning of the next, we let the real cursor
actually wrap around in cases where it wrapped around for us. */
if ( (frame.cursor_x >= f.ds.get_width())
&& (frame.y < f.ds.get_height() - 1)
&& f.get_row( frame.y )->wrap
&& (!initialized || !frame.last_frame.get_row( frame.y )->wrap) ) {
/* next write will wrap */
frame.cursor_x = 0;
frame.cursor_y++;
}
}
/* Turn off wrap */
if ( (frame.y < f.ds.get_height() - 1)
&& (!f.get_row( frame.y )->wrap)
&& (!initialized || frame.last_frame.get_row( frame.y )->wrap) ) {
frame.x = last_x;
if ( initialized ) {
frame.last_frame.reset_cell( frame.last_frame.get_mutable_cell( frame.y, frame.x ) );
}
snprintf( tmp, 64, "\033[%d;%dH\033[K", frame.y + 1, frame.x + 1 );
frame.append( tmp );
frame.cursor_x = frame.x;
put_cell( initialized, frame, f );
}
}
/* has cursor location changed? */
if ( (!initialized)
|| (f.ds.get_cursor_row() != frame.cursor_y)
|| (f.ds.get_cursor_col() != frame.cursor_x) ) {
snprintf( tmp, 64, "\033[%d;%dH", f.ds.get_cursor_row() + 1,
f.ds.get_cursor_col() + 1 );
frame.append( tmp );
frame.cursor_x = f.ds.get_cursor_col();
frame.cursor_y = f.ds.get_cursor_row();
}
/* has cursor visibility changed? */
if ( (!initialized)
|| (f.ds.cursor_visible != frame.last_frame.ds.cursor_visible) ) {
if ( f.ds.cursor_visible ) {
frame.append( "\033[?25h" );
} else {
frame.append( "\033[?25l" );
}
}
/* have renditions changed? */
if ( (!initialized)
|| (f.ds.get_renditions().sgr() != frame.current_rendition_string) ) {
frame.appendstring( f.ds.get_renditions().sgr() );
frame.current_rendition_string = f.ds.get_renditions().sgr();
}
return frame.str;
}
void Display::put_cell( bool initialized, FrameState &frame, const Framebuffer &f )
{
char tmp[ 64 ];
const Cell *cell = f.get_cell( frame.y, frame.x );
if ( initialized
&& ( *cell == *(frame.last_frame.get_cell( frame.y, frame.x )) ) ) {
frame.x += cell->width;
return;
}
if ( (frame.x != frame.cursor_x) || (frame.y != frame.cursor_y) ) {
frame.append_silent_move( frame.y, frame.x );
}
std::string rendition_str = cell->renditions.sgr();
if ( frame.current_rendition_string != rendition_str ) {
/* print renditions */
frame.appendstring( rendition_str );
frame.current_rendition_string = rendition_str;
}
if ( cell->contents.empty() ) {
/* see how far we can stretch a clear */
int clear_count = 0;
for ( int col = frame.x; col < f.ds.get_width(); col++ ) {
const Cell *other_cell = f.get_cell( frame.y, col );
if ( (cell->renditions == other_cell->renditions)
&& (other_cell->contents.empty()) ) {
clear_count++;
} else {
break;
}
}
snprintf( tmp, 64, "\033[%dX", clear_count );
frame.append( tmp );
frame.x += clear_count;
return;
}
/* cells that begin with combining character get combiner attached to no-break space */
if ( cell->fallback ) {
snprintf( tmp, 64, "%lc", 0xA0 );
frame.append( tmp );
}
for ( std::vector<wchar_t>::const_iterator i = cell->contents.begin();
i != cell->contents.end();
i++ ) {
snprintf( tmp, 64, "%lc", *i );
frame.append( tmp );
}
frame.x += cell->width;
frame.cursor_x += cell->width;
}
void FrameState::append_silent_move( int y, int x )
{
char tmp[ 64 ];
/* turn off cursor if necessary before moving cursor */
if ( last_frame.ds.cursor_visible ) {
append( "\033[?25l" );
last_frame.ds.cursor_visible = false;
}
snprintf( tmp, 64, "\033[%d;%dH", y + 1, x + 1 );
append( tmp );
cursor_x = x;
cursor_y = y;
}
+41
View File
@@ -0,0 +1,41 @@
#ifndef TERMINALDISPLAY_HPP
#define TERMINALDISPLAY_HPP
#include "terminalframebuffer.hpp"
namespace Terminal {
/* variables used within a new_frame */
class FrameState {
public:
int x, y;
std::string str;
int cursor_x, cursor_y;
std::string current_rendition_string;
Framebuffer last_frame;
FrameState( const Framebuffer &s_last )
: x(0), y(0),
str(), cursor_x(0), cursor_y(0), current_rendition_string(),
last_frame( s_last )
{
str.reserve( 1024 );
}
void append( const char * s ) { str.append( s ); }
void appendstring( const std::string s ) { str.append( s ); }
void append_silent_move( int y, int x );
};
class Display {
private:
static void put_cell( bool initialized, FrameState &frame, const Framebuffer &f );
public:
static std::string new_frame( bool initialized, const Framebuffer &last, const Framebuffer &f );
};
}
#endif
+425
View File
@@ -0,0 +1,425 @@
#include <assert.h>
#include "terminalframebuffer.hpp"
using namespace Terminal;
void Cell::reset( int background_color )
{
contents.clear();
fallback = false;
width = 1;
renditions = Renditions( background_color );
}
DrawState::DrawState( int s_width, int s_height )
: width( s_width ), height( s_height ),
cursor_col( 0 ), cursor_row( 0 ),
combining_char_col( 0 ), combining_char_row( 0 ), tabs( s_width ),
scrolling_region_top_row( 0 ), scrolling_region_bottom_row( height - 1 ),
renditions( 0 ), save(),
next_print_will_wrap( false ), origin_mode( false ), auto_wrap_mode( true ),
insert_mode( false ), cursor_visible( true ), reverse_video( false ),
application_mode_cursor_keys( false )
{
for ( int i = 0; i < width; i++ ) {
tabs[ i ] = ( (i % 8) == 0 );
}
}
Framebuffer::Framebuffer( int s_width, int s_height )
: rows( s_height, Row( s_width, 0 ) ), window_title(), bell_count( 0 ), ds( s_width, s_height )
{
assert( s_height > 0 );
assert( s_width > 0 );
}
void Framebuffer::scroll( int N )
{
if ( N >= 0 ) {
for ( int i = 0; i < N; i++ ) {
delete_line( ds.get_scrolling_region_top_row() );
ds.move_row( -1, true );
}
} else {
N = -N;
for ( int i = 0; i < N; i++ ) {
rows.insert( rows.begin() + ds.get_scrolling_region_top_row(), newrow() );
rows.erase( rows.begin() + ds.get_scrolling_region_bottom_row() + 1 );
ds.move_row( 1, true );
}
}
}
void DrawState::new_grapheme( void )
{
combining_char_col = cursor_col;
combining_char_row = cursor_row;
}
void DrawState::snap_cursor_to_border( void )
{
if ( cursor_row < limit_top() ) cursor_row = limit_top();
if ( cursor_row > limit_bottom() ) cursor_row = limit_bottom();
if ( cursor_col < 0 ) cursor_col = 0;
if ( cursor_col >= width ) cursor_col = width - 1;
}
void DrawState::move_row( int N, bool relative )
{
if ( relative ) {
cursor_row += N;
} else {
cursor_row = N + limit_top();
}
snap_cursor_to_border();
new_grapheme();
next_print_will_wrap = false;
}
void DrawState::move_col( int N, bool relative, bool implicit )
{
if ( implicit ) {
new_grapheme();
}
if ( relative ) {
cursor_col += N;
} else {
cursor_col = N;
}
if ( implicit && (cursor_col >= width) ) {
next_print_will_wrap = true;
}
snap_cursor_to_border();
if ( !implicit ) {
new_grapheme();
next_print_will_wrap = false;
}
}
void Framebuffer::move_rows_autoscroll( int rows )
{
/* don't scroll if outside the scrolling region */
if ( (ds.get_cursor_row() < ds.get_scrolling_region_top_row())
|| (ds.get_cursor_row() > ds.get_scrolling_region_bottom_row()) ) {
ds.move_row( rows, true );
return;
}
if ( ds.get_cursor_row() + rows > ds.get_scrolling_region_bottom_row() ) {
scroll( ds.get_cursor_row() + rows - ds.get_scrolling_region_bottom_row() );
} else if ( ds.get_cursor_row() + rows < ds.get_scrolling_region_top_row() ) {
scroll( ds.get_cursor_row() + rows - ds.get_scrolling_region_top_row() );
}
ds.move_row( rows, true );
}
Cell *Framebuffer::get_combining_cell( void )
{
if ( (ds.get_combining_char_col() < 0)
|| (ds.get_combining_char_row() < 0)
|| (ds.get_combining_char_col() >= ds.get_width())
|| (ds.get_combining_char_row() >= ds.get_height()) ) {
return NULL;
} /* can happen if a resize came in between */
return &rows[ ds.get_combining_char_row() ].cells[ ds.get_combining_char_col() ];
}
void DrawState::set_tab( void )
{
tabs[ cursor_col ] = true;
}
void DrawState::clear_tab( int col )
{
tabs[ col ] = false;
}
int DrawState::get_next_tab( void )
{
for ( int i = cursor_col + 1; i < width; i++ ) {
if ( tabs[ i ] ) {
return i;
}
}
return -1;
}
void DrawState::set_scrolling_region( int top, int bottom )
{
if ( height < 1 ) {
return;
}
scrolling_region_top_row = top;
scrolling_region_bottom_row = bottom;
if ( scrolling_region_top_row < 0 ) scrolling_region_top_row = 0;
if ( scrolling_region_bottom_row >= height ) scrolling_region_bottom_row = height - 1;
if ( scrolling_region_bottom_row < scrolling_region_top_row )
scrolling_region_bottom_row = scrolling_region_top_row;
/* real rule requires TWO-line scrolling region */
if ( origin_mode ) {
snap_cursor_to_border();
new_grapheme();
}
}
int DrawState::limit_top( void )
{
return origin_mode ? scrolling_region_top_row : 0;
}
int DrawState::limit_bottom( void )
{
return origin_mode ? scrolling_region_bottom_row : height - 1;
}
std::vector<int> DrawState::get_tabs( void )
{
std::vector<int> ret;
for ( int i = 0; i < width; i++ ) {
if ( tabs[ i ] ) {
ret.push_back( i );
}
}
return ret;
}
void Framebuffer::apply_renditions_to_current_cell( void )
{
get_mutable_cell()->renditions = ds.get_renditions();
}
SavedCursor::SavedCursor()
: cursor_col( 0 ), cursor_row( 0 ),
renditions( 0 ),
auto_wrap_mode( true ),
origin_mode( false )
{}
void DrawState::save_cursor( void )
{
save.cursor_col = cursor_col;
save.cursor_row = cursor_row;
save.renditions = renditions;
save.auto_wrap_mode = auto_wrap_mode;
save.origin_mode = origin_mode;
}
void DrawState::restore_cursor( void )
{
cursor_col = save.cursor_col;
cursor_row = save.cursor_row;
renditions = save.renditions;
auto_wrap_mode = save.auto_wrap_mode;
origin_mode = save.origin_mode;
snap_cursor_to_border(); /* we could have resized in between */
new_grapheme();
}
void Framebuffer::insert_line( int before_row )
{
if ( (before_row < ds.get_scrolling_region_top_row())
|| (before_row > ds.get_scrolling_region_bottom_row() + 1) ) {
return;
}
rows.insert( rows.begin() + before_row, newrow() );
rows.erase( rows.begin() + ds.get_scrolling_region_bottom_row() + 1 );
}
void Framebuffer::delete_line( int row )
{
if ( (row < ds.get_scrolling_region_top_row())
|| (row > ds.get_scrolling_region_bottom_row()) ) {
return;
}
int insertbefore = ds.get_scrolling_region_bottom_row() + 1;
if ( insertbefore == ds.get_height() ) {
rows.push_back( newrow() );
} else {
rows.insert( rows.begin() + insertbefore, newrow() );
}
rows.erase( rows.begin() + row );
}
void Row::insert_cell( int col, int background_color )
{
cells.insert( cells.begin() + col, Cell( background_color ) );
cells.pop_back();
}
void Row::delete_cell( int col, int background_color )
{
cells.push_back( Cell( background_color ) );
cells.erase( cells.begin() + col );
}
void Framebuffer::insert_cell( int row, int col )
{
rows[ row ].insert_cell( col, ds.get_background_rendition() );
}
void Framebuffer::delete_cell( int row, int col )
{
rows[ row ].delete_cell( col, ds.get_background_rendition() );
}
void Framebuffer::reset( void )
{
int width = ds.get_width(), height = ds.get_height();
ds = DrawState( width, height );
rows = std::deque<Row>( height, newrow() );
window_title.clear();
/* do not reset bell_count */
}
void Framebuffer::soft_reset( void )
{
ds.insert_mode = false;
ds.origin_mode = false;
ds.cursor_visible = true; /* per xterm and gnome-terminal */
ds.application_mode_cursor_keys = false;
ds.set_scrolling_region( 0, ds.get_height() - 1 );
ds.add_rendition( 0 );
ds.clear_saved_cursor();
}
void Framebuffer::resize( int s_width, int s_height )
{
assert( s_width > 0 );
assert( s_height > 0 );
rows.resize( s_height, newrow() );
for ( std::deque<Row>::iterator i = rows.begin();
i != rows.end();
i++ ) {
(*i).cells.resize( s_width, Cell( ds.get_background_rendition() ) );
}
ds.resize( s_width, s_height );
}
void DrawState::resize( int s_width, int s_height )
{
if ( (width != s_width)
|| (height != s_height) ) {
/* reset entire scrolling region on any resize */
/* xterm and rxvt-unicode do this. gnome-terminal only
resets scrolling region if it has to become smaller in resize */
scrolling_region_top_row = 0;
scrolling_region_bottom_row = s_height - 1;
}
width = s_width;
height = s_height;
snap_cursor_to_border();
tabs.resize( width );
/* saved cursor will be snapped to border on restore */
/* invalidate combining char cell if necessary */
if ( (combining_char_col >= width)
|| (combining_char_row >= height) ) {
combining_char_col = combining_char_row = -1;
}
}
Renditions::Renditions( int s_background )
: bold( false ), underlined( false ), blink( false ),
inverse( false ), invisible( false ), foreground_color( 0 ),
background_color( s_background )
{}
void Renditions::set_rendition( int num )
{
if ( num == 0 ) {
bold = underlined = blink = inverse = invisible = false;
foreground_color = background_color = 0;
return;
}
if ( (30 <= num) && (num <= 39) ) { /* foreground color */
foreground_color = num;
return;
} else if ( (40 <= num) && (num <= 49) ) { /* background color */
background_color = num;
return;
}
switch ( num ) {
case 1: case 22: bold = (num == 1); break;
case 4: case 24: underlined = (num == 4); break;
case 5: case 25: blink = (num == 5); break;
case 7: case 27: inverse = (num == 7); break;
case 8: case 28: invisible = (num == 8); break;
}
}
std::string Renditions::sgr( void ) const
{
std::string ret;
ret.append( "\033[0" );
if ( bold ) ret.append( ";1" );
if ( underlined ) ret.append( ";4" );
if ( blink ) ret.append( ";5" );
if ( inverse ) ret.append( ";7" );
if ( invisible ) ret.append( ";8" );
if ( foreground_color ) {
char col[ 8 ];
snprintf( col, 8, ";%d", foreground_color );
ret.append( col );
}
if ( background_color ) {
char col[ 8 ];
snprintf( col, 8, ";%d", background_color );
ret.append( col );
}
ret.append( "m" );
return ret;
}
void Row::reset( int background_color )
{
for ( std::vector<Cell>::iterator i = cells.begin();
i != cells.end();
i++ ) {
i->reset( background_color );
}
}
void Framebuffer::prefix_window_title( const std::deque<wchar_t> &s )
{
for ( auto i = s.rbegin(); i != s.rend(); i++ ) {
window_title.push_front( *i );
}
}
wchar_t Cell::debug_contents( void ) const
{
if ( contents.empty() ) {
return '_';
} else {
return contents.front();
}
}
+276
View File
@@ -0,0 +1,276 @@
#ifndef TERMINALFB_HPP
#define TERMINALFB_HPP
#include <vector>
#include <deque>
#include <string>
#include <list>
#include <assert.h>
/* Terminal framebuffer */
namespace Terminal {
class Renditions {
public:
bool bold, underlined, blink, inverse, invisible;
int foreground_color;
int background_color;
Renditions( int s_background );
void set_rendition( int num );
std::string sgr( void ) const;
bool operator==( const Renditions &x ) const
{
return (bold == x.bold) && (underlined == x.underlined)
&& (blink == x.blink) && (inverse == x.inverse)
&& (invisible == x.invisible) && (foreground_color == x.foreground_color)
&& (background_color == x.background_color);
}
};
class Cell {
public:
std::vector<wchar_t> contents;
char fallback; /* first character is combining character */
int width;
Renditions renditions;
Cell( int background_color )
: contents(),
fallback( false ),
width( 1 ),
renditions( background_color )
{}
Cell() /* default constructor required by C++11 STL */
: contents(),
fallback( false ),
width( 1 ),
renditions( 0 )
{
assert( false );
}
void reset( int background_color );
bool operator==( const Cell &x ) const
{
return ( (contents == x.contents)
&& (fallback == x.fallback)
&& (width == x.width)
&& (renditions == x.renditions) );
}
wchar_t debug_contents( void ) const;
bool is_blank( void ) const
{
return ( contents.empty()
|| ( (contents.size() == 1) && ( (contents.front() == 0x20)
|| (contents.front() == 0xA0) ) ) );
}
};
class Row {
public:
std::vector<Cell> cells;
bool wrap;
Row( size_t s_width, int background_color )
: cells( s_width, Cell( background_color ) ), wrap( false )
{}
Row() /* default constructor required by C++11 STL */
: cells( 1, Cell() ), wrap( false )
{
assert( false );
}
void insert_cell( int col, int background_color );
void delete_cell( int col, int background_color );
void reset( int background_color );
bool operator==( const Row &x ) const
{
return ( (cells == x.cells) && (wrap == x.wrap) );
}
};
class SavedCursor {
public:
int cursor_col, cursor_row;
Renditions renditions;
/* not implemented: character set shift state */
bool auto_wrap_mode;
bool origin_mode;
/* not implemented: state of selective erase */
SavedCursor();
};
class DrawState {
private:
int width, height;
void new_grapheme( void );
void snap_cursor_to_border( void );
int cursor_col, cursor_row;
int combining_char_col, combining_char_row;
std::vector<bool> tabs;
int scrolling_region_top_row, scrolling_region_bottom_row;
Renditions renditions;
SavedCursor save;
public:
bool next_print_will_wrap;
bool origin_mode;
bool auto_wrap_mode;
bool insert_mode;
bool cursor_visible;
bool reverse_video;
bool application_mode_cursor_keys;
/* bold, etc. */
void move_row( int N, bool relative = false );
void move_col( int N, bool relative = false, bool implicit = false );
int get_cursor_col( void ) const { return cursor_col; }
int get_cursor_row( void ) const { return cursor_row; }
int get_combining_char_col( void ) const { return combining_char_col; }
int get_combining_char_row( void ) const { return combining_char_row; }
int get_width( void ) const { return width; }
int get_height( void ) const { return height; }
void set_tab( void );
void clear_tab( int col );
int get_next_tab( void );
std::vector<int> get_tabs( void );
void set_scrolling_region( int top, int bottom );
int get_scrolling_region_top_row( void ) const { return scrolling_region_top_row; }
int get_scrolling_region_bottom_row( void ) const { return scrolling_region_bottom_row; }
int limit_top( void );
int limit_bottom( void );
void add_rendition( int x ) { renditions.set_rendition( x ); }
Renditions get_renditions( void ) const { return renditions; }
int get_background_rendition( void ) { return renditions.background_color; }
void save_cursor( void );
void restore_cursor( void );
void clear_saved_cursor( void ) { save = SavedCursor(); }
void resize( int s_width, int s_height );
DrawState( int s_width, int s_height );
bool operator==( const DrawState &x ) const
{
/* only compare fields that affect display */
return ( width == x.width ) && ( height == x.height ) && ( cursor_col == x.cursor_col )
&& ( cursor_row == x.cursor_row ) && ( cursor_visible == x.cursor_visible ) &&
( reverse_video == x.reverse_video ) && ( renditions == x.renditions );
}
};
class Framebuffer {
private:
std::deque<Row> rows;
std::deque<wchar_t> window_title;
unsigned int bell_count;
Row newrow( void ) { return Row( ds.get_width(), ds.get_background_rendition() ); }
public:
Framebuffer( int s_width, int s_height );
DrawState ds;
void scroll( int N );
void move_rows_autoscroll( int rows );
const Row *get_row( int row ) const
{
if ( row == -1 ) row = ds.get_cursor_row();
return &rows[ row ];
}
inline const Cell *get_cell( void ) const
{
return &rows[ ds.get_cursor_row() ].cells[ ds.get_cursor_col() ];
}
inline const Cell *get_cell( int row, int col ) const
{
if ( row == -1 ) row = ds.get_cursor_row();
if ( col == -1 ) col = ds.get_cursor_col();
return &rows[ row ].cells[ col ];
}
Row *get_mutable_row( int row )
{
if ( row == -1 ) row = ds.get_cursor_row();
return &rows[ row ];
}
inline Cell *get_mutable_cell( void )
{
return &rows[ ds.get_cursor_row() ].cells[ ds.get_cursor_col() ];
}
inline Cell *get_mutable_cell( int row, int col )
{
if ( row == -1 ) row = ds.get_cursor_row();
if ( col == -1 ) col = ds.get_cursor_col();
return &rows[ row ].cells[ col ];
}
Cell *get_combining_cell( void );
void apply_renditions_to_current_cell( void );
void insert_line( int before_row );
void delete_line( int row );
void insert_cell( int row, int col );
void delete_cell( int row, int col );
void reset( void );
void soft_reset( void );
void set_window_title( const std::deque<wchar_t> &s ) { window_title = s; }
const std::deque<wchar_t> & get_window_title( void ) const { return window_title; }
void prefix_window_title( const std::deque<wchar_t> &s );
void resize( int s_width, int s_height );
void reset_cell( Cell *c ) { c->reset( ds.get_background_rendition() ); }
void reset_row( Row *r ) { r->reset( ds.get_background_rendition() ); }
void ring_bell( void ) { bell_count++; }
unsigned int get_bell_count( void ) const { return bell_count; }
bool operator==( const Framebuffer &x ) const
{
return ( rows == x.rows ) && ( window_title == x.window_title ) && ( bell_count == x.bell_count ) && ( ds == x.ds );
}
};
}
#endif
+493
View File
@@ -0,0 +1,493 @@
#include <unistd.h>
#include <string>
#include "terminaldispatcher.hpp"
#include "terminalframebuffer.hpp"
#include "parseraction.hpp"
using namespace Terminal;
/* Terminal functions -- routines activated by CSI, escape or a control char */
static void clearline( Framebuffer *fb, int row, int start, int end )
{
for ( int col = start; col <= end; col++ ) {
fb->reset_cell( fb->get_mutable_cell( row, col ) );
}
}
/* erase in line */
void CSI_EL( Framebuffer *fb, Dispatcher *dispatch )
{
switch ( dispatch->getparam( 0, 0 ) ) {
case 0: /* default: active position to end of line, inclusive */
clearline( fb, -1, fb->ds.get_cursor_col(), fb->ds.get_width() - 1 );
break;
case 1: /* start of screen to active position, inclusive */
clearline( fb, -1, 0, fb->ds.get_cursor_col() );
break;
case 2: /* all of line */
fb->reset_row( fb->get_mutable_row( -1 ) );
break;
}
}
static Function func_CSI_EL( CSI, "K", CSI_EL );
/* erase in display */
void CSI_ED( Framebuffer *fb, Dispatcher *dispatch ) {
switch ( dispatch->getparam( 0, 0 ) ) {
case 0: /* active position to end of screen, inclusive */
clearline( fb, -1, fb->ds.get_cursor_col(), fb->ds.get_width() - 1 );
for ( int y = fb->ds.get_cursor_row() + 1; y < fb->ds.get_height(); y++ ) {
fb->reset_row( fb->get_mutable_row( y ) );
}
break;
case 1: /* start of screen to active position, inclusive */
for ( int y = 0; y < fb->ds.get_cursor_row(); y++ ) {
fb->reset_row( fb->get_mutable_row( y ) );
}
clearline( fb, -1, 0, fb->ds.get_cursor_col() );
break;
case 2: /* entire screen */
for ( int y = 0; y < fb->ds.get_height(); y++ ) {
fb->reset_row( fb->get_mutable_row( y ) );
}
break;
}
}
static Function func_CSI_ED( CSI, "J", CSI_ED );
/* cursor movement -- relative and absolute */
void CSI_cursormove( Framebuffer *fb, Dispatcher *dispatch )
{
int num = dispatch->getparam( 0, 1 );
switch ( dispatch->get_dispatch_chars()[ 0 ] ) {
case 'A':
fb->ds.move_row( -num, true );
break;
case 'B':
fb->ds.move_row( num, true );
break;
case 'C':
fb->ds.move_col( num, true );
break;
case 'D':
fb->ds.move_col( -num, true );
break;
case 'H':
case 'f':
int x = dispatch->getparam( 0, 1 );
int y = dispatch->getparam( 1, 1 );
fb->ds.move_row( x - 1 );
fb->ds.move_col( y - 1 );
}
}
static Function func_CSI_cursormove_A( CSI, "A", CSI_cursormove );
static Function func_CSI_cursormove_B( CSI, "B", CSI_cursormove );
static Function func_CSI_cursormove_C( CSI, "C", CSI_cursormove );
static Function func_CSI_cursormove_D( CSI, "D", CSI_cursormove );
static Function func_CSI_cursormove_H( CSI, "H", CSI_cursormove );
static Function func_CSI_cursormove_f( CSI, "f", CSI_cursormove );
/* device attributes */
void CSI_DA( Framebuffer *fb __attribute((unused)), Dispatcher *dispatch )
{
dispatch->terminal_to_host.append( "\033[?62c" ); /* plain vt220 */
}
static Function func_CSI_DA( CSI, "c", CSI_DA );
/* secondary device attributes */
void CSI_SDA( Framebuffer *fb __attribute((unused)), Dispatcher *dispatch )
{
dispatch->terminal_to_host.append( "\033[>1;10;0c" ); /* plain vt220 */
}
static Function func_CSI_SDA( CSI, ">c", CSI_SDA );
/* screen alignment diagnostic */
void Esc_DECALN( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
{
for ( int y = 0; y < fb->ds.get_height(); y++ ) {
for ( int x = 0; x < fb->ds.get_width(); x++ ) {
fb->reset_cell( fb->get_mutable_cell( y, x ) );
fb->get_mutable_cell( y, x )->contents.push_back( L'E' );
}
}
}
static Function func_Esc_DECALN( ESCAPE, "#8", Esc_DECALN );
/* line feed */
void Ctrl_LF( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
{
fb->move_rows_autoscroll( 1 );
}
/* same procedure for index, vertical tab, and form feed control codes */
static Function func_Ctrl_LF( CONTROL, "\x0a", Ctrl_LF );
static Function func_Ctrl_IND( CONTROL, "\x84", Ctrl_LF );
static Function func_Ctrl_VT( CONTROL, "\x0b", Ctrl_LF );
static Function func_Ctrl_FF( CONTROL, "\x0c", Ctrl_LF );
/* carriage return */
void Ctrl_CR( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
{
fb->ds.move_col( 0 );
}
static Function func_Ctrl_CR( CONTROL, "\x0d", Ctrl_CR );
/* backspace */
void Ctrl_BS( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
{
fb->ds.move_col( -1, true );
}
static Function func_Ctrl_BS( CONTROL, "\x08", Ctrl_BS );
/* reverse index -- like a backwards line feed */
void Ctrl_RI( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
{
fb->move_rows_autoscroll( -1 );
}
static Function func_Ctrl_RI( CONTROL, "\x8D", Ctrl_RI );
/* newline */
void Ctrl_NEL( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
{
fb->ds.move_col( 0 );
fb->move_rows_autoscroll( 1 );
}
static Function func_Ctrl_NEL( CONTROL, "\x85", Ctrl_NEL );
/* horizontal tab */
void Ctrl_HT( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
{
int col = fb->ds.get_next_tab();
if ( col == -1 ) { /* no tabs, go to end of line */
fb->ds.move_col( fb->ds.get_width() - 1 );
} else {
fb->ds.move_col( col );
}
}
static Function func_Ctrl_HT( CONTROL, "\x09", Ctrl_HT );
/* horizontal tab set */
void Ctrl_HTS( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
{
fb->ds.set_tab();
}
static Function func_Ctrl_HTS( CONTROL, "\x88", Ctrl_HTS );
/* tabulation clear */
void CSI_TBC( Framebuffer *fb, Dispatcher *dispatch )
{
int param = dispatch->getparam( 0, 0 );
switch ( param ) {
case 0: /* clear this tab stop */
fb->ds.clear_tab( fb->ds.get_cursor_col() );
break;
case 3: /* clear all tab stops */
for ( int x = 0; x < fb->ds.get_width(); x++ ) {
fb->ds.clear_tab( x );
}
break;
}
}
static Function func_CSI_TBC( CSI, "g", CSI_TBC );
static bool *get_DEC_mode( int param, Framebuffer *fb ) {
switch ( param ) {
case 1: /* cursor key mode */
return &(fb->ds.application_mode_cursor_keys);
case 3: /* 80/132. Ignore but clear screen. */
/* clear screen */
fb->ds.move_row( 0 );
fb->ds.move_col( 0 );
for ( int y = 0; y < fb->ds.get_height(); y++ ) {
fb->reset_row( fb->get_mutable_row( y ) );
}
return NULL;
case 5: /* reverse video */
return &(fb->ds.reverse_video);
case 6: /* origin */
fb->ds.move_row( 0 );
fb->ds.move_col( 0 );
return &(fb->ds.origin_mode);
case 7: /* auto wrap */
return &(fb->ds.auto_wrap_mode);
case 25:
return &(fb->ds.cursor_visible);
}
return NULL;
}
/* set private mode */
void CSI_DECSM( Framebuffer *fb, Dispatcher *dispatch )
{
for ( int i = 0; i < dispatch->param_count(); i++ ) {
bool *mode = get_DEC_mode( dispatch->getparam( i, 0 ), fb );
if ( mode ) {
*mode = true;
}
}
}
/* clear private mode */
void CSI_DECRM( Framebuffer *fb, Dispatcher *dispatch )
{
for ( int i = 0; i < dispatch->param_count(); i++ ) {
bool *mode = get_DEC_mode( dispatch->getparam( i, 0 ), fb );
if ( mode ) {
*mode = false;
}
}
}
static Function func_CSI_DECSM( CSI, "?h", CSI_DECSM );
static Function func_CSI_DECRM( CSI, "?l", CSI_DECRM );
static bool *get_ANSI_mode( int param, Framebuffer *fb ) {
switch ( param ) {
case 4: /* insert/replace mode */
return &(fb->ds.insert_mode);
}
return NULL;
}
/* set mode */
void CSI_SM( Framebuffer *fb, Dispatcher *dispatch )
{
for ( int i = 0; i < dispatch->param_count(); i++ ) {
bool *mode = get_ANSI_mode( dispatch->getparam( i, 0 ), fb );
if ( mode ) {
*mode = true;
}
}
}
/* clear mode */
void CSI_RM( Framebuffer *fb, Dispatcher *dispatch )
{
for ( int i = 0; i < dispatch->param_count(); i++ ) {
bool *mode = get_ANSI_mode( dispatch->getparam( i, 0 ), fb );
if ( mode ) {
*mode = false;
}
}
}
static Function func_CSI_SM( CSI, "h", CSI_SM );
static Function func_CSI_RM( CSI, "l", CSI_RM );
/* set top and bottom margins */
void CSI_DECSTBM( Framebuffer *fb, Dispatcher *dispatch )
{
int top = dispatch->getparam( 0, 1 );
int bottom = dispatch->getparam( 1, fb->ds.get_height() );
fb->ds.set_scrolling_region( top - 1, bottom - 1 );
fb->ds.move_row( 0 );
fb->ds.move_col( 0 );
}
static Function func_CSI_DECSTMB( CSI, "r", CSI_DECSTBM );
/* terminal bell */
void Ctrl_BEL( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) ) {
fb->ring_bell();
}
static Function func_Ctrl_BEL( CONTROL, "\x07", Ctrl_BEL );
/* select graphics rendition -- e.g., bold, blinking, etc. */
void CSI_SGR( Framebuffer *fb, Dispatcher *dispatch )
{
for ( int i = 0; i < dispatch->param_count(); i++ ) {
int rendition = dispatch->getparam( i, 0 );
fb->ds.add_rendition( rendition );
}
}
static Function func_CSI_SGR( CSI, "m", CSI_SGR, false ); /* changing renditions doesn't clear wrap flag */
/* save and restore cursor */
void Esc_DECSC( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
{
fb->ds.save_cursor();
}
void Esc_DECRC( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
{
fb->ds.restore_cursor();
}
static Function func_Esc_DECSC( ESCAPE, "7", Esc_DECSC );
static Function func_Esc_DECRC( ESCAPE, "8", Esc_DECRC );
/* device status report -- e.g., cursor position (used by resize) */
void CSI_DSR( Framebuffer *fb, Dispatcher *dispatch )
{
int param = dispatch->getparam( 0, 0 );
switch ( param ) {
case 5: /* device status report requested */
dispatch->terminal_to_host.append( "\033[0n" );
break;
case 6: /* report of active position requested */
char cpr[ 32 ];
snprintf( cpr, 32, "\033[%d;%dR",
fb->ds.get_cursor_row() + 1,
fb->ds.get_cursor_col() + 1 );
dispatch->terminal_to_host.append( cpr );
break;
}
}
static Function func_CSI_DSR( CSI, "n", CSI_DSR );
/* insert line */
void CSI_IL( Framebuffer *fb, Dispatcher *dispatch )
{
int lines = dispatch->getparam( 0, 1 );
for ( int i = 0; i < lines; i++ ) {
fb->insert_line( fb->ds.get_cursor_row() );
}
/* vt220 manual and Ecma-48 say to move to first column */
/* but xterm and gnome-terminal don't */
fb->ds.move_col( 0 );
}
static Function func_CSI_IL( CSI, "L", CSI_IL );
/* delete line */
void CSI_DL( Framebuffer *fb, Dispatcher *dispatch )
{
int lines = dispatch->getparam( 0, 1 );
for ( int i = 0; i < lines; i++ ) {
fb->delete_line( fb->ds.get_cursor_row() );
}
/* same story -- xterm and gnome-terminal don't
move to first column */
fb->ds.move_col( 0 );
}
static Function func_CSI_DL( CSI, "M", CSI_DL );
/* insert characters */
void CSI_ICH( Framebuffer *fb, Dispatcher *dispatch )
{
int cells = dispatch->getparam( 0, 1 );
for ( int i = 0; i < cells; i++ ) {
fb->insert_cell( fb->ds.get_cursor_row(), fb->ds.get_cursor_col() );
}
}
static Function func_CSI_ICH( CSI, "@", CSI_ICH );
/* delete character */
void CSI_DCH( Framebuffer *fb, Dispatcher *dispatch )
{
int cells = dispatch->getparam( 0, 1 );
for ( int i = 0; i < cells; i++ ) {
fb->delete_cell( fb->ds.get_cursor_row(), fb->ds.get_cursor_col() );
}
}
static Function func_CSI_DCH( CSI, "P", CSI_DCH );
/* line position absolute */
void CSI_VPA( Framebuffer *fb, Dispatcher *dispatch )
{
int row = dispatch->getparam( 0, 1 );
fb->ds.move_row( row - 1 );
}
static Function func_CSI_VPA( CSI, "d", CSI_VPA );
/* character position absolute */
void CSI_HPA( Framebuffer *fb, Dispatcher *dispatch )
{
int col = dispatch->getparam( 0, 1 );
fb->ds.move_col( col - 1 );
}
static Function func_CSI_CHA( CSI, "G", CSI_HPA ); /* ECMA-48 name: CHA */
static Function func_CSI_HPA( CSI, "\x60", CSI_HPA ); /* ECMA-48 name: HPA */
/* erase character */
void CSI_ECH( Framebuffer *fb, Dispatcher *dispatch )
{
int num = dispatch->getparam( 0, 1 );
int limit = fb->ds.get_cursor_col() + num - 1;
if ( limit >= fb->ds.get_width() ) {
limit = fb->ds.get_width() - 1;
}
clearline( fb, -1, fb->ds.get_cursor_col(), limit );
}
static Function func_CSI_ECH( CSI, "X", CSI_ECH );
/* reset to initial state */
void Esc_RIS( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
{
fb->reset();
}
static Function func_Esc_RIS( ESCAPE, "c", Esc_RIS );
/* soft reset */
void CSI_DECSTR( Framebuffer *fb, Dispatcher *dispatch __attribute((unused)) )
{
fb->soft_reset();
}
static Function func_CSI_DECSTR( CSI, "!p", CSI_DECSTR );
/* xterm uses an Operating System Command to set the window title */
void Dispatcher::OSC_dispatch( const Parser::OSC_End *act, Framebuffer *fb )
{
if ( OSC_string.size() >= 2 ) {
if ( (OSC_string[ 0 ] == L'0')
&& (OSC_string[ 1 ] == L';') ) {
std::deque<wchar_t> newtitle( OSC_string.begin(), OSC_string.end() );
newtitle.erase( newtitle.begin() );
newtitle.erase( newtitle.begin() );
fb->set_window_title( newtitle );
act->handled = true;
}
}
}
/* scroll down or terminfo indn */
void CSI_SD( Framebuffer *fb, Dispatcher *dispatch )
{
fb->scroll( dispatch->getparam( 0, 1 ) );
}
static Function func_CSI_SD( CSI, "S", CSI_SD );
/* scroll up or terminfo rin */
void CSI_SU( Framebuffer *fb, Dispatcher *dispatch )
{
fb->scroll( -dispatch->getparam( 0, 1 ) );
}
static Function func_CSI_SU( CSI, "T", CSI_SU );
+720
View File
@@ -0,0 +1,720 @@
#include <algorithm>
#include <wchar.h>
#include <list>
#include <typeinfo>
#include <limits.h>
#include "terminaloverlay.hpp"
using namespace Overlay;
bool ConditionalOverlay::start_clock( uint64_t local_frame_acked, uint64_t now, unsigned int send_interval )
{
if ( (local_frame_acked >= expiration_frame) && (expiration_time == uint64_t(-1)) ) {
expiration_time = now + 50 + 125 * send_interval / 100;
return true;
}
return false;
}
void ConditionalOverlayCell::apply( Framebuffer &fb, uint64_t confirmed_epoch, int row, bool flag ) const
{
if ( (!active)
|| (row >= fb.ds.get_height())
|| (col >= fb.ds.get_width()) ) {
return;
}
if ( tentative( confirmed_epoch ) ) {
return;
}
if ( unknown ) {
if ( flag && ( col != fb.ds.get_width() - 1 ) ) {
fb.get_mutable_cell( row, col )->renditions.underlined = true;
}
return;
}
if ( !(*(fb.get_cell( row, col )) == replacement) ) {
if ( replacement.is_blank() && fb.get_cell( row, col )->is_blank() ) {
return;
}
*(fb.get_mutable_cell( row, col )) = replacement;
if ( flag ) {
fb.get_mutable_cell( row, col )->renditions.underlined = true;
}
}
}
Validity ConditionalOverlayCell::get_validity( const Framebuffer &fb, int row,
uint64_t sent_frame, uint64_t early_ack, uint64_t late_ack,
uint64_t now ) const
{
if ( !active ) {
return Inactive;
}
if ( (row >= fb.ds.get_height())
|| (col >= fb.ds.get_width()) ) {
return IncorrectOrExpired;
}
if ( unknown ) {
return CorrectNoCredit;
}
const Cell &current = *( fb.get_cell( row, col ) );
/* see if it hasn't been updated yet */
if ( early_ack < expiration_frame ) {
return Pending;
}
assert( expiration_time != uint64_t(-1) );
if ( (late_ack >= expiration_frame)
|| ( (sent_frame <= early_ack) && (expiration_time <= now) ) ) {
/* special case deletion */
if ( current.is_blank() && replacement.is_blank() ) {
return CorrectNoCredit;
}
if ( current.contents == replacement.contents ) {
auto it = find_if( original_contents.begin(), original_contents.end(),
[&]( const Cell &x ) { return replacement.contents == x.contents; } );
if ( it == original_contents.end() ) {
return Correct;
} else {
return CorrectNoCredit;
}
} else {
return IncorrectOrExpired;
}
}
return Pending;
}
Validity ConditionalCursorMove::get_validity( const Framebuffer &fb, uint64_t sent_frame, uint64_t early_ack, uint64_t late_ack, uint64_t now ) const
{
if ( !active ) {
return Inactive;
}
if ( (row >= fb.ds.get_height())
|| (col >= fb.ds.get_width()) ) {
assert( false );
// fprintf( stderr, "Crazy cursor (%d,%d)!\n", row, col );
return IncorrectOrExpired;
}
if ( early_ack < expiration_frame ) {
return Pending;
}
assert( expiration_time != uint64_t(-1) );
if ( (late_ack >= expiration_frame)
|| ( (sent_frame <= early_ack) && (expiration_time <= now) ) ) {
if ( (fb.ds.get_cursor_col() == col)
&& (fb.ds.get_cursor_row() == row) ) {
return Correct;
} else {
return IncorrectOrExpired;
}
}
return Pending;
}
void ConditionalCursorMove::apply( Framebuffer &fb, uint64_t confirmed_epoch ) const
{
if ( !active ) {
return;
}
if ( tentative( confirmed_epoch ) ) {
return;
}
assert( row < fb.ds.get_height() );
assert( col < fb.ds.get_width() );
assert( !fb.ds.origin_mode );
fb.ds.move_row( row, false );
fb.ds.move_col( col, false, false );
}
NotificationEngine::NotificationEngine()
: last_word_from_server( timestamp() ),
message(),
message_expiration( -1 )
{}
void NotificationEngine::apply( Framebuffer &fb ) const
{
uint64_t now = timestamp();
bool time_expired = need_countup( now );
if ( message.empty() && !time_expired ) {
return;
}
assert( fb.ds.get_width() > 0 );
assert( fb.ds.get_height() > 0 );
/* hide cursor if necessary */
if ( fb.ds.get_cursor_row() == 0 ) {
fb.ds.cursor_visible = false;
}
/* draw bar across top of screen */
Cell notification_bar( 0 );
notification_bar.renditions.foreground_color = 37;
notification_bar.renditions.background_color = 44;
notification_bar.contents.push_back( 0x20 );
for ( int i = 0; i < fb.ds.get_width(); i++ ) {
*(fb.get_mutable_cell( 0, i )) = notification_bar;
}
/* write message */
wchar_t tmp[ 128 ];
if ( message.empty() && (!time_expired) ) {
return;
} else if ( message.empty() && time_expired ) {
swprintf( tmp, 128, L"mosh: Last contact %.0f seconds ago. [To quit: Ctrl-^ .]", (double)(now - last_word_from_server) / 1000.0 );
} else if ( (!message.empty()) && (!time_expired) ) {
swprintf( tmp, 128, L"mosh: %ls [To quit: Ctrl-^ .]", message.c_str() );
} else {
swprintf( tmp, 128, L"mosh: %ls (%.0f s without contact.) [To quit: Ctrl-^ .]", message.c_str(),
(double)(now - last_word_from_server) / 1000.0 );
}
wstring string_to_draw( tmp );
int overlay_col = 0;
Cell *combining_cell = fb.get_mutable_cell( 0, 0 );
/* We unfortunately duplicate the terminal's logic for how to render a Unicode sequence into graphemes */
for ( wstring::const_iterator i = string_to_draw.begin(); i != string_to_draw.end(); i++ ) {
if ( overlay_col >= fb.ds.get_width() ) {
break;
}
wchar_t ch = *i;
int chwidth = ch == L'\0' ? -1 : wcwidth( ch );
Cell *this_cell = nullptr;
switch ( chwidth ) {
case 1: /* normal character */
case 2: /* wide character */
this_cell = fb.get_mutable_cell( 0, overlay_col );
fb.reset_cell( this_cell );
this_cell->renditions.bold = true;
this_cell->renditions.foreground_color = 37;
this_cell->renditions.background_color = 44;
this_cell->contents.push_back( ch );
this_cell->width = chwidth;
combining_cell = this_cell;
overlay_col += chwidth;
break;
case 0: /* combining character */
if ( !combining_cell ) {
break;
}
if ( combining_cell->contents.size() == 0 ) {
assert( combining_cell->width == 1 );
combining_cell->fallback = true;
overlay_col++;
}
if ( combining_cell->contents.size() < 16 ) {
combining_cell->contents.push_back( ch );
}
break;
case -1: /* unprintable character */
break;
default:
assert( false );
}
}
}
void NotificationEngine::adjust_message( void )
{
if ( timestamp() >= message_expiration ) {
message.clear();
}
}
void OverlayManager::apply( Framebuffer &fb )
{
predictions.cull( fb );
predictions.apply( fb );
notifications.adjust_message();
notifications.apply( fb );
title.apply( fb );
}
int OverlayManager::wait_time( void )
{
uint64_t next_expiry = INT_MAX;
uint64_t now = timestamp();
uint64_t message_delay = notifications.get_message_expiration() - now;
if ( message_delay < next_expiry ) {
next_expiry = message_delay;
}
if ( notifications.need_countup( now ) && ( next_expiry > 1000 ) ) {
next_expiry = 1000;
}
if ( predictions.active() && ( next_expiry > 10 ) ) {
next_expiry = 10;
}
return next_expiry;
}
void TitleEngine::set_prefix( const wstring s )
{
prefix = deque<wchar_t>( s.begin(), s.end() );
}
void ConditionalOverlayRow::apply( Framebuffer &fb, uint64_t confirmed_epoch, bool flag ) const
{
for_each( overlay_cells.begin(), overlay_cells.end(), [&]( const ConditionalOverlayCell &x ) { x.apply( fb, confirmed_epoch, row_num, flag ); } );
}
void PredictionEngine::apply( Framebuffer &fb ) const
{
for_each( cursors.begin(), cursors.end(), [&]( const ConditionalCursorMove &x ) { x.apply( fb, confirmed_epoch ); } );
for_each( overlays.begin(), overlays.end(), [&]( const ConditionalOverlayRow &x ){ x.apply( fb, confirmed_epoch, flagging ); } );
}
void PredictionEngine::kill_epoch( uint64_t epoch, const Framebuffer &fb )
{
cursors.remove_if( [&]( ConditionalCursorMove &x ) { return x.tentative( epoch - 1 ); } );
cursors.push_back( ConditionalCursorMove( local_frame_sent + 1,
fb.ds.get_cursor_row(),
fb.ds.get_cursor_col(),
prediction_epoch ) );
cursor().active = true;
for ( auto i = overlays.begin(); i != overlays.end(); i++ ) {
for ( auto j = i->overlay_cells.begin(); j != i->overlay_cells.end(); j++ ) {
if ( j->tentative( epoch - 1 ) ) {
j->reset();
}
}
}
become_tentative();
}
void PredictionEngine::reset( void )
{
cursors.clear();
overlays.clear();
become_tentative();
// fprintf( stderr, "RESETTING\n" );
}
void PredictionEngine::init_cursor( const Framebuffer &fb )
{
if ( cursors.empty() ) {
/* initialize new cursor prediction */
cursors.push_back( ConditionalCursorMove( local_frame_sent + 1,
fb.ds.get_cursor_row(),
fb.ds.get_cursor_col(),
prediction_epoch ) );
cursor().active = true;
} else if ( cursor().tentative_until_epoch != prediction_epoch ) {
cursors.push_back( ConditionalCursorMove( local_frame_sent + 1,
cursor().row,
cursor().col,
prediction_epoch ) );
cursor().active = true;
}
}
void PredictionEngine::cull( const Framebuffer &fb )
{
uint64_t now = timestamp();
/* control flagging with hysteresis */
if ( send_interval > 80 ) {
flagging = true;
} else if ( send_interval < 50 ) {
flagging = false;
}
/* go through cell predictions */
auto i = overlays.begin();
while ( i != overlays.end() ) {
auto inext = i;
inext++;
if ( (i->row_num < 0) || (i->row_num >= fb.ds.get_height()) ) {
overlays.erase( i );
i = inext;
continue;
}
for ( auto j = i->overlay_cells.begin(); j != i->overlay_cells.end(); j++ ) {
if ( j->start_clock( local_frame_acked, now, send_interval ) ) {
last_scheduled_timeout = max( last_scheduled_timeout, j->expiration_time );
}
switch ( j->get_validity( fb, i->row_num,
local_frame_sent, local_frame_acked, local_frame_late_acked,
now ) ) {
case IncorrectOrExpired:
if ( j->tentative( confirmed_epoch ) ) {
/*
fprintf( stderr, "Bad tentative prediction in row %d, col %d (think %lc, actually %lc)\n",
i->row_num, j->col,
j->replacement.debug_contents(),
fb.get_cell( i->row_num, j->col )->debug_contents()
);
*/
kill_epoch( j->tentative_until_epoch, fb );
/*
if ( j->display_time != uint64_t(-1) ) {
fprintf( stderr, "TIMING %ld - %ld (TENT)\n", time(NULL), now - j->display_time );
}
*/
} else {
/*
fprintf( stderr, "[%d=>%d] Killing prediction in row %d, col %d (think %lc, actually %lc)\n",
(int)local_frame_acked, (int)j->expiration_frame,
i->row_num, j->col,
j->replacement.debug_contents(),
fb.get_cell( i->row_num, j->col )->debug_contents() );
*/
/*
if ( j->display_time != uint64_t(-1) ) {
fprintf( stderr, "TIMING %ld - %ld\n", time(NULL), now - j->display_time );
}
*/
reset();
return;
}
break;
case Correct:
/*
if ( j->display_time != uint64_t(-1) ) {
fprintf( stderr, "TIMING %ld + %ld\n", now, now - j->display_time );
}
*/
if ( j->tentative_until_epoch > confirmed_epoch ) {
confirmed_epoch = j->tentative_until_epoch;
/*
fprintf( stderr, "%lc in (%d,%d) confirms epoch %lu (predicting in epoch %lu)\n",
j->replacement.debug_contents(), i->row_num, j->col,
confirmed_epoch, prediction_epoch );
*/
}
/* no break */
case CorrectNoCredit:
j->reset();
break;
case Pending:
break;
default:
break;
}
}
i = inext;
}
/* go through cursor predictions */
for ( auto it = cursors.begin(); it != cursors.end(); it++ ) {
if ( it->start_clock( local_frame_acked, now, send_interval ) ) {
last_scheduled_timeout = max( last_scheduled_timeout, it->expiration_time );
}
}
if ( !cursors.empty() ) {
if ( cursor().get_validity( fb,
local_frame_sent, local_frame_acked, local_frame_late_acked,
now ) == IncorrectOrExpired ) {
/*
fprintf( stderr, "Sadly, we're predicting (%d,%d) vs. (%d,%d) [tau: %ld, expiration_time=%ld, now=%ld]\n",
cursor().row, cursor().col,
fb.ds.get_cursor_row(),
fb.ds.get_cursor_col(),
cursor().tentative_until_epoch,
cursor().expiration_time,
now );
*/
reset();
return;
}
}
cursors.remove_if( [&]( const ConditionalCursorMove &x ) {
return (x.get_validity( fb,
local_frame_sent, local_frame_acked, local_frame_late_acked,
now ) != Pending); } );
}
ConditionalOverlayRow & PredictionEngine::get_or_make_row( int row_num, int num_cols )
{
auto it = find_if( overlays.begin(), overlays.end(),
[&]( const ConditionalOverlayRow &x ) { return x.row_num == row_num; } );
if ( it != overlays.end() ) {
return *it;
} else {
/* make row */
ConditionalOverlayRow r( row_num );
r.overlay_cells.reserve( num_cols );
for ( int i = 0; i < num_cols; i++ ) {
r.overlay_cells.push_back( ConditionalOverlayCell( 0, i, prediction_epoch ) );
assert( r.overlay_cells[ i ].col == i );
}
overlays.push_back( r );
return overlays.back();
}
}
void PredictionEngine::new_user_byte( char the_byte, const Framebuffer &fb )
{
cull( fb );
/* translate application-mode cursor control function to ANSI cursor control sequence */
if ( (last_byte == 0x1b)
&& (the_byte == 'O') ) {
the_byte = '[';
}
last_byte = the_byte;
list<Parser::Action *> actions( parser.input( the_byte ) );
for ( auto it = actions.begin(); it != actions.end(); it++ ) {
Parser::Action *act = *it;
/*
fprintf( stderr, "Action: %s (%lc)\n",
act->name().c_str(), act->char_present ? act->ch : L'_' );
*/
if ( typeid( *act ) == typeid( Parser::Print ) ) {
/* make new prediction */
init_cursor( fb );
assert( act->char_present );
wchar_t ch = act->ch;
/* XXX handle wide characters */
if ( ch == 0x7f ) { /* backspace */
// fprintf( stderr, "Backspace.\n" );
ConditionalOverlayRow &the_row = get_or_make_row( cursor().row, fb.ds.get_width() );
if ( cursor().col > 0 ) {
cursor().col--;
cursor().expire( local_frame_sent + 1 );
for ( int i = cursor().col; i < fb.ds.get_width(); i++ ) {
ConditionalOverlayCell &cell = the_row.overlay_cells[ i ];
cell.reset_with_orig();
cell.active = true;
cell.tentative_until_epoch = prediction_epoch;
cell.expire( local_frame_sent + 1 );
cell.original_contents.push_back( *fb.get_cell( cursor().row, i ) );
if ( i + 2 < fb.ds.get_width() ) {
ConditionalOverlayCell &next_cell = the_row.overlay_cells[ i + 1 ];
const Cell *next_cell_actual = fb.get_cell( cursor().row, i + 1 );
if ( next_cell.active ) {
if ( next_cell.unknown ) {
cell.unknown = true;
} else {
cell.unknown = false;
cell.replacement = next_cell.replacement;
}
} else {
cell.unknown = false;
cell.replacement = *next_cell_actual;
}
} else {
cell.unknown = true;
}
}
}
} else if ( (ch < 0x20) || (wcwidth( ch ) != 1) ) {
/* unknown print */
become_tentative();
// fprintf( stderr, "Unknown print 0x%x\n", ch );
} else {
assert( cursor().row >= 0 );
assert( cursor().col >= 0 );
assert( cursor().row < fb.ds.get_height() );
assert( cursor().col < fb.ds.get_width() );
ConditionalOverlayRow &the_row = get_or_make_row( cursor().row, fb.ds.get_width() );
if ( cursor().col + 1 >= fb.ds.get_width() ) {
/* prediction in the last column is tricky */
/* e.g., emacs will show wrap character, shell will just put the character there */
become_tentative();
}
/* do the insert */
for ( int i = fb.ds.get_width() - 1; i > cursor().col; i-- ) {
ConditionalOverlayCell &cell = the_row.overlay_cells[ i ];
cell.reset_with_orig();
cell.active = true;
cell.tentative_until_epoch = prediction_epoch;
cell.expire( local_frame_sent + 1 );
cell.original_contents.push_back( *fb.get_cell( cursor().row, i ) );
ConditionalOverlayCell &prev_cell = the_row.overlay_cells[ i - 1 ];
const Cell *prev_cell_actual = fb.get_cell( cursor().row, i - 1 );
if ( i == fb.ds.get_width() - 1 ) {
cell.unknown = true;
} else if ( prev_cell.active ) {
if ( prev_cell.unknown ) {
cell.unknown = true;
} else {
cell.unknown = false;
cell.replacement = prev_cell.replacement;
}
} else {
cell.unknown = false;
cell.replacement = *prev_cell_actual;
}
}
ConditionalOverlayCell &cell = the_row.overlay_cells[ cursor().col ];
cell.reset_with_orig();
cell.active = true;
cell.tentative_until_epoch = prediction_epoch;
cell.expire( local_frame_sent + 1 );
cell.replacement.renditions = fb.ds.get_renditions();
cell.replacement.contents.clear();
cell.replacement.contents.push_back( ch );
cell.original_contents.push_back( *fb.get_cell( cursor().row, cursor().col ) );
/*
fprintf( stderr, "[%d=>%d] Predicting %lc in row %d, col %d [tue: %lu]\n",
(int)local_frame_acked, (int)cell.expiration_frame,
ch, cursor().row, cursor().col,
cell.tentative_until_epoch );
*/
cursor().expire( local_frame_sent + 1 );
/* do we need to wrap? */
if ( cursor().col < fb.ds.get_width() - 1 ) {
cursor().col++;
} else {
become_tentative();
newline_carriage_return( fb );
}
}
} else if ( typeid( *act ) == typeid( Parser::Execute ) ) {
if ( act->char_present && (act->ch == 0x0d) /* CR */ ) {
become_tentative();
newline_carriage_return( fb );
} else {
// fprintf( stderr, "Execute 0x%x\n", act->ch );
become_tentative();
}
} else if ( typeid( *act ) == typeid( Parser::Esc_Dispatch ) ) {
// fprintf( stderr, "Escape sequence\n" );
become_tentative();
} else if ( typeid( *act ) == typeid( Parser::CSI_Dispatch ) ) {
if ( act->char_present && (act->ch == L'C') ) { /* right arrow */
init_cursor( fb );
if ( cursor().col < fb.ds.get_width() - 1 ) {
cursor().col++;
cursor().expire( local_frame_sent + 1 );
}
} else if ( act->char_present && (act->ch == L'D') ) { /* left arrow */
init_cursor( fb );
if ( cursor().col > 0 ) {
cursor().col--;
cursor().expire( local_frame_sent + 1 );
}
} else {
// fprintf( stderr, "CSI sequence %lc\n", act->ch );
become_tentative();
}
} else if ( typeid( *act ) == typeid( Parser::Clear ) ) {
}
delete act;
}
}
void PredictionEngine::newline_carriage_return( const Framebuffer &fb )
{
init_cursor( fb );
cursor().col = 0;
if ( cursor().row == fb.ds.get_height() - 1 ) {
for ( auto i = overlays.begin(); i != overlays.end(); i++ ) {
i->row_num--;
for ( auto j = i->overlay_cells.begin(); j != i->overlay_cells.end(); j++ ) {
if ( j->active ) {
j->expire( local_frame_sent + 1 );
}
}
}
/* make blank prediction for last row */
ConditionalOverlayRow &the_row = get_or_make_row( cursor().row, fb.ds.get_width() );
for ( auto j = the_row.overlay_cells.begin(); j != the_row.overlay_cells.end(); j++ ) {
j->active = true;
j->tentative_until_epoch = prediction_epoch;
j->expire( local_frame_sent + 1 );
j->replacement.contents.clear();
}
} else {
cursor().row++;
}
}
void PredictionEngine::become_tentative( void )
{
prediction_epoch++;
/*
fprintf( stderr, "Now tentative in epoch %lu (confirmed=%lu)\n",
prediction_epoch, confirmed_epoch );
*/
}
+208
View File
@@ -0,0 +1,208 @@
#ifndef TERMINAL_OVERLAY_HPP
#define TERMINAL_OVERLAY_HPP
#include "terminalframebuffer.hpp"
#include "network.hpp"
#include "parser.hpp"
#include <vector>
namespace Overlay {
using namespace Terminal;
using namespace Network;
using namespace std;
enum Validity {
Pending,
Correct,
CorrectNoCredit,
IncorrectOrExpired,
Inactive
};
class ConditionalOverlay {
public:
uint64_t expiration_frame;
uint64_t expiration_time; /* after frame is hit */
int col;
bool active; /* represents a prediction at all */
uint64_t tentative_until_epoch; /* when to show */
ConditionalOverlay( uint64_t s_exp, int s_col, uint64_t s_tentative )
: expiration_frame( s_exp ), expiration_time( -1 ), col( s_col ),
active( false ),
tentative_until_epoch( s_tentative )
{}
virtual ~ConditionalOverlay() {}
bool tentative( uint64_t confirmed_epoch ) const { return tentative_until_epoch > confirmed_epoch; }
void reset( void ) { expiration_frame = expiration_time = tentative_until_epoch = -1; active = false; }
bool start_clock( uint64_t local_frame_acked, uint64_t now, unsigned int send_interval );
void expire( uint64_t s_exp ) { expiration_frame = s_exp; expiration_time = uint64_t(-1); }
};
class ConditionalCursorMove : public ConditionalOverlay {
public:
int row;
void apply( Framebuffer &fb, uint64_t confirmed_epoch ) const;
Validity get_validity( const Framebuffer &fb, uint64_t sent_frame, uint64_t early_ack, uint64_t late_ack, uint64_t now ) const;
ConditionalCursorMove( uint64_t s_exp, int s_row, int s_col, uint64_t s_tentative )
: ConditionalOverlay( s_exp, s_col, s_tentative ), row( s_row )
{}
};
class ConditionalOverlayCell : public ConditionalOverlay {
public:
Cell replacement;
bool unknown;
vector<Cell> original_contents; /* we don't give credit for correct predictions
that match the original contents */
void apply( Framebuffer &fb, uint64_t confirmed_epoch, int row, bool flag ) const;
Validity get_validity( const Framebuffer &fb, int row, uint64_t sent_frame, uint64_t early_ack, uint64_t late_ack, uint64_t now ) const;
ConditionalOverlayCell( uint64_t s_exp, int s_col, uint64_t s_tentative )
: ConditionalOverlay( s_exp, s_col, s_tentative ),
replacement( 0 ),
unknown( false ),
original_contents()
{}
void reset( void ) { unknown = false; original_contents.clear(); ConditionalOverlay::reset(); }
void reset_with_orig( void ) {
if ( (!active) || unknown ) {
reset();
return;
}
vector<Cell> new_orig( original_contents );
new_orig.push_back( replacement );
reset();
original_contents = new_orig;
}
};
class ConditionalOverlayRow {
public:
int row_num;
vector<ConditionalOverlayCell> overlay_cells;
void apply( Framebuffer &fb, uint64_t confirmed_epoch, bool flag ) const;
ConditionalOverlayRow( int s_row_num ) : row_num( s_row_num ), overlay_cells() {}
};
/* the various overlays */
class NotificationEngine {
private:
uint64_t last_word_from_server;
wstring message;
uint64_t message_expiration;
public:
bool need_countup( uint64_t ts ) const { return ts - last_word_from_server > 4500; }
void adjust_message( void );
void apply( Framebuffer &fb ) const;
void set_notification_string( const wstring s_message ) { message = s_message; message_expiration = timestamp() + 1000; }
const wstring &get_notification_string( void ) const { return message; }
void server_heard( uint64_t s_last_word ) { last_word_from_server = s_last_word; }
uint64_t get_message_expiration( void ) const { return message_expiration; }
NotificationEngine();
};
class PredictionEngine {
private:
char last_byte;
Parser::UTF8Parser parser;
list<ConditionalOverlayRow> overlays;
list<ConditionalCursorMove> cursors;
uint64_t local_frame_sent, local_frame_acked, local_frame_late_acked;
ConditionalOverlayRow & get_or_make_row( int row_num, int num_cols );
uint64_t prediction_epoch;
uint64_t confirmed_epoch;
void become_tentative( void );
void newline_carriage_return( const Framebuffer &fb );
bool flagging;
ConditionalCursorMove & cursor( void ) { assert( !cursors.empty() ); return cursors.back(); }
void kill_epoch( uint64_t epoch, const Framebuffer &fb );
void init_cursor( const Framebuffer &fb );
uint64_t last_scheduled_timeout;
unsigned int send_interval;
public:
void apply( Framebuffer &fb ) const;
void new_user_byte( char the_byte, const Framebuffer &fb );
void cull( const Framebuffer &fb );
void reset( void );
bool active( void ) { return timestamp() <= last_scheduled_timeout; }
void set_local_frame_sent( uint64_t x ) { local_frame_sent = x; }
void set_local_frame_acked( uint64_t x ) { local_frame_acked = x; }
void set_local_frame_late_acked( uint64_t x ) { local_frame_late_acked = x; }
void set_send_interval( unsigned int x ) { send_interval = x; }
PredictionEngine( void ) : last_byte( 0 ), parser(), overlays(), cursors(),
local_frame_sent( 0 ), local_frame_acked( 0 ),
local_frame_late_acked( 0 ),
prediction_epoch( 1 ), confirmed_epoch( 0 ),
flagging( false ), last_scheduled_timeout( 0 ),
send_interval( 250 )
{
}
};
class TitleEngine {
private:
deque<wchar_t> prefix;
public:
void apply( Framebuffer &fb ) const { fb.prefix_window_title( prefix ); }
void set_prefix( const wstring s );
TitleEngine() : prefix() {}
};
/* the overlay manager */
class OverlayManager {
private:
NotificationEngine notifications;
PredictionEngine predictions;
TitleEngine title;
public:
void apply( Framebuffer &fb );
NotificationEngine & get_notification_engine( void ) { return notifications; }
PredictionEngine & get_prediction_engine( void ) { return predictions; }
void set_title_prefix( const wstring s ) { title.set_prefix( s ); }
OverlayManager() : notifications(), predictions(), title() {}
int wait_time( void );
};
}
#endif
+30
View File
@@ -0,0 +1,30 @@
#include "terminaluserinput.hpp"
using namespace Terminal;
std::string UserInput::input( const Parser::UserByte *act,
bool application_mode_cursor_keys )
{
char translated_str[ 2 ] = { act->c, 0 };
/* The user will always be in application mode. If stm is not in
application mode, convert user's cursor control function to an
ANSI cursor control sequence */
/* We don't need lookahead to do this for 7-bit. */
if ( (!application_mode_cursor_keys)
&& (last_byte == 0x1b) /* ESC */
&& (act->c == 'O') ) { /* ESC O = 7-bit SS3 = application mode */
translated_str[ 0 ] = '[';
}
/* This doesn't handle the 8-bit SS3 C1 control, which would be
two octets in UTF-8. Fortunately nobody seems to send this. */
last_byte = act->c;
act->handled = true;
return std::string( translated_str, 1 );
}
+24
View File
@@ -0,0 +1,24 @@
#ifndef TERMINALUSERINPUT_HPP
#define TERMINALUSERINPUT_HPP
#include <string>
#include "parseraction.hpp"
namespace Terminal {
class UserInput {
private:
wchar_t last_byte;
public:
UserInput()
: last_byte( -1 )
{}
std::string input( const Parser::UserByte *act,
bool application_mode_cursor_keys );
bool operator==( const UserInput &x ) const { return last_byte == x.last_byte; }
};
}
#endif
+162
View File
@@ -0,0 +1,162 @@
#include <endian.h>
#include <assert.h>
#include "transportfragment.hpp"
#include "transportinstruction.pb.h"
using namespace Network;
using namespace TransportBuffers;
static string network_order_string( uint16_t host_order )
{
uint16_t net_int = htobe16( host_order );
return string( (char *)&net_int, sizeof( net_int ) );
}
static string network_order_string( uint64_t host_order )
{
uint64_t net_int = htobe64( host_order );
return string( (char *)&net_int, sizeof( net_int ) );
}
string Fragment::tostring( void )
{
assert( initialized );
string ret;
ret += network_order_string( id );
assert( !( fragment_num & 0x8000 ) ); /* effective limit on size of a terminal screen change or buffered user input */
uint16_t combined_fragment_num = ( final << 15 ) | fragment_num;
ret += network_order_string( combined_fragment_num );
assert( ret.size() == frag_header_len );
ret += contents;
return ret;
}
Fragment::Fragment( string &x )
: id( -1 ), fragment_num( -1 ), final( false ), initialized( true ),
contents( x.begin() + frag_header_len, x.end() )
{
assert( x.size() >= frag_header_len );
uint64_t *data64 = (uint64_t *)x.data();
uint16_t *data16 = (uint16_t *)x.data();
id = be64toh( data64[ 0 ] );
fragment_num = be16toh( data16[ 4 ] );
final = ( fragment_num & 0x8000 ) >> 15;
fragment_num &= 0x7FFF;
}
bool FragmentAssembly::add_fragment( Fragment &frag )
{
/* see if this is a totally new packet */
if ( current_id != frag.id ) {
fragments.clear();
fragments.resize( frag.fragment_num + 1 );
fragments.at( frag.fragment_num ) = frag;
fragments_arrived = 1;
fragments_total = -1; /* unknown */
current_id = frag.id;
} else { /* not a new packet */
/* see if we already have this fragment */
if ( (fragments.size() > frag.fragment_num)
&& (fragments.at( frag.fragment_num ).initialized) ) {
/* make sure new version is same as what we already have */
assert( fragments.at( frag.fragment_num ) == frag );
} else {
if ( (int)fragments.size() < frag.fragment_num + 1 ) {
fragments.resize( frag.fragment_num + 1 );
}
fragments.at( frag.fragment_num ) = frag;
fragments_arrived++;
}
}
if ( frag.final ) {
fragments_total = frag.fragment_num + 1;
assert( (int)fragments.size() <= fragments_total );
fragments.resize( fragments_total );
}
if ( fragments_total != -1 ) {
assert( fragments_arrived <= fragments_total );
}
/* see if we're done */
return ( fragments_arrived == fragments_total );
}
Instruction FragmentAssembly::get_assembly( void )
{
assert( fragments_arrived == fragments_total );
string encoded;
for ( int i = 0; i < fragments_total; i++ ) {
assert( fragments.at( i ).initialized );
encoded += fragments.at( i ).contents;
}
Instruction ret;
assert( ret.ParseFromString( encoded ) );
fragments.clear();
fragments_arrived = 0;
fragments_total = -1;
return ret;
}
bool Fragment::operator==( const Fragment &x )
{
return ( id == x.id ) && ( fragment_num == x.fragment_num ) && ( final == x.final )
&& ( initialized == x.initialized ) && ( contents == x.contents );
}
vector<Fragment> Fragmenter::make_fragments( Instruction &inst, int MTU )
{
if ( (inst.old_num() != last_instruction.old_num())
|| (inst.new_num() != last_instruction.new_num())
|| (inst.ack_num() != last_instruction.ack_num())
|| (inst.throwaway_num() != last_instruction.throwaway_num())
|| (inst.late_ack_num() != last_instruction.late_ack_num())
|| (inst.protocol_version() != last_instruction.protocol_version())
|| (last_MTU != MTU) ) {
next_instruction_id++;
}
if ( (inst.old_num() == last_instruction.old_num())
&& (inst.new_num() == last_instruction.new_num()) ) {
assert( inst.diff() == last_instruction.diff() );
}
last_instruction = inst;
last_MTU = MTU;
string payload = inst.SerializeAsString();
uint16_t fragment_num = 0;
vector<Fragment> ret;
while ( !payload.empty() ) {
string this_fragment;
bool final = false;
if ( int( payload.size() + HEADER_LEN ) > MTU ) {
this_fragment = string( payload.begin(), payload.begin() + MTU - HEADER_LEN );
payload = string( payload.begin() + MTU - HEADER_LEN, payload.end() );
} else {
this_fragment = payload;
payload.clear();
final = true;
}
ret.push_back( Fragment( next_instruction_id, fragment_num++, final, this_fragment ) );
}
return ret;
}
+78
View File
@@ -0,0 +1,78 @@
#ifndef TRANSPORT_FRAGMENT_HPP
#define TRANSPORT_FRAGMENT_HPP
#include <stdint.h>
#include <vector>
#include <string>
#include "transportinstruction.pb.h"
using namespace std;
using namespace TransportBuffers;
namespace Network {
static const int HEADER_LEN = 66;
class Fragment
{
private:
static const size_t frag_header_len = sizeof( uint64_t ) + sizeof( uint16_t );
public:
uint64_t id;
uint16_t fragment_num;
bool final;
bool initialized;
string contents;
Fragment()
: id( -1 ), fragment_num( -1 ), final( false ), initialized( false ), contents()
{}
Fragment( uint64_t s_id, uint16_t s_fragment_num, bool s_final, string s_contents )
: id( s_id ), fragment_num( s_fragment_num ), final( s_final ), initialized( true ),
contents( s_contents )
{}
Fragment( string &x );
string tostring( void );
bool operator==( const Fragment &x );
};
class FragmentAssembly
{
private:
vector<Fragment> fragments;
uint64_t current_id;
int fragments_arrived, fragments_total;
public:
FragmentAssembly() : fragments(), current_id( -1 ), fragments_arrived( 0 ), fragments_total( -1 ) {}
bool add_fragment( Fragment &inst );
Instruction get_assembly( void );
};
class Fragmenter
{
private:
uint64_t next_instruction_id;
Instruction last_instruction;
int last_MTU;
public:
Fragmenter() : next_instruction_id( 0 ), last_instruction(), last_MTU( -1 )
{
last_instruction.set_old_num( -1 );
last_instruction.set_new_num( -1 );
}
vector<Fragment> make_fragments( Instruction &inst, int MTU );
uint64_t last_ack_sent( void ) const { return last_instruction.ack_num(); }
};
}
#endif
+301
View File
@@ -0,0 +1,301 @@
#include <algorithm>
#include <list>
#include "transportsender.hpp"
#include "transportfragment.hpp"
using namespace Network;
template <class MyState>
TransportSender<MyState>::TransportSender( Connection *s_connection, MyState &initial_state )
: connection( s_connection ),
current_state( initial_state ),
sent_states( 1, TimestampedState<MyState>( timestamp(), 0, initial_state ) ),
assumed_receiver_state( sent_states.begin() ),
fragmenter(),
next_ack_time( timestamp() ),
next_send_time( timestamp() ),
verbose( false ),
shutdown_in_progress( false ),
shutdown_tries( 0 ),
ack_num( 0 ),
pending_data_ack( false ),
ack_timestamp( 0 ),
ack_history(),
SEND_MINDELAY( 15 )
{
}
/* Try to send roughly two frames per RTT, bounded by limits on frame rate */
template <class MyState>
unsigned int TransportSender<MyState>::send_interval( void ) const
{
int SEND_INTERVAL = lrint( ceil( connection->get_SRTT() / 2.0 ) );
if ( SEND_INTERVAL < SEND_INTERVAL_MIN ) {
SEND_INTERVAL = SEND_INTERVAL_MIN;
} else if ( SEND_INTERVAL > SEND_INTERVAL_MAX ) {
SEND_INTERVAL = SEND_INTERVAL_MAX;
}
return SEND_INTERVAL;
}
/* How many ms can the caller wait before we will have an event (empty ack or next frame)? */
template <class MyState>
int TransportSender<MyState>::wait_time( void )
{
if ( pending_data_ack && (next_ack_time > timestamp() + ACK_DELAY) ) {
next_ack_time = timestamp() + ACK_DELAY;
}
if ( !(current_state == sent_states.back().state) ) { /* pending data to send */
if ( next_send_time > timestamp() + SEND_MINDELAY ) {
next_send_time = timestamp() + SEND_MINDELAY;
}
if ( next_send_time < sent_states.back().timestamp + send_interval() ) {
next_send_time = sent_states.back().timestamp + send_interval();
}
}
/* speed up shutdown sequence */
if ( shutdown_in_progress || (ack_num == uint64_t(-1)) ) {
next_ack_time = sent_states.back().timestamp + send_interval();
}
uint64_t next_wakeup = next_ack_time;
if ( next_send_time < next_wakeup ) {
next_wakeup = next_send_time;
}
if ( !connection->get_attached() ) {
return -1;
}
if ( next_wakeup > timestamp() ) {
return next_wakeup - timestamp();
} else {
return 0;
}
}
/* Send data or an empty ack if necessary */
template <class MyState>
void TransportSender<MyState>::tick( void )
{
wait_time();
if ( !connection->get_attached() ) {
return;
}
if ( (timestamp() < next_ack_time)
&& (timestamp() < next_send_time) ) {
return;
}
/* Determine if a new diff or empty ack needs to be sent */
/* Update assumed receiver state */
update_assumed_receiver_state();
/* Cut out common prefix of all states */
rationalize_states();
string diff = current_state.diff_from( assumed_receiver_state->state );
if ( diff.empty() && (timestamp() >= next_ack_time) ) {
send_empty_ack();
return;
}
if ( !diff.empty() && ( (timestamp() >= next_send_time)
|| (timestamp() >= next_ack_time) ) ) {
/* Send diffs or ack */
send_to_receiver( diff );
}
}
template <class MyState>
void TransportSender<MyState>::send_empty_ack( void )
{
assert ( timestamp() >= next_ack_time );
uint64_t new_num = sent_states.back().num + 1;
/* special case for shutdown sequence */
if ( shutdown_in_progress ) {
new_num = uint64_t( -1 );
}
// sent_states.push_back( TimestampedState<MyState>( sent_states.back().timestamp, new_num, current_state ) );
add_sent_state( sent_states.back().timestamp, new_num, current_state );
send_in_fragments( "", new_num );
next_ack_time = timestamp() + ACK_INTERVAL;
}
template <class MyState>
void TransportSender<MyState>::add_sent_state( uint64_t the_timestamp, uint64_t num, MyState &state )
{
sent_states.push_back( TimestampedState<MyState>( the_timestamp, num, state ) );
if ( sent_states.size() > 32 ) { /* limit on state queue */
auto last = sent_states.end();
for ( int i = 0; i < 16; i++ ) { last--; }
sent_states.erase( last ); /* erase state from middle of queue */
}
}
template <class MyState>
void TransportSender<MyState>::send_to_receiver( string diff )
{
uint64_t new_num;
if ( current_state == sent_states.back().state ) { /* previously sent */
new_num = sent_states.back().num;
} else { /* new state */
new_num = sent_states.back().num + 1;
}
/* special case for shutdown sequence */
if ( shutdown_in_progress ) {
new_num = uint64_t( -1 );
}
if ( new_num == sent_states.back().num ) {
sent_states.back().timestamp = timestamp();
} else {
add_sent_state( timestamp(), new_num, current_state );
}
send_in_fragments( diff, new_num ); // Can throw NetworkException
/* successfully sent, probably */
/* ("probably" because the FIRST size-exceeded datagram doesn't get an error) */
assumed_receiver_state = sent_states.end();
assumed_receiver_state--;
next_ack_time = timestamp() + ACK_INTERVAL;
next_send_time = uint64_t(-1);
}
template <class MyState>
void TransportSender<MyState>::update_assumed_receiver_state( void )
{
uint64_t now = timestamp();
/* start from what is known and give benefit of the doubt to unacknowledged states
transmitted recently enough ago */
assumed_receiver_state = sent_states.begin();
typename list< TimestampedState<MyState> >::iterator i = sent_states.begin();
i++;
while ( i != sent_states.end() ) {
assert( now >= i->timestamp );
if ( uint64_t(now - i->timestamp) < connection->timeout() + ACK_DELAY ) {
assumed_receiver_state = i;
} else {
return;
}
i++;
}
}
template <class MyState>
void TransportSender<MyState>::rationalize_states( void )
{
const MyState * known_receiver_state = &sent_states.front().state;
current_state.subtract( known_receiver_state );
for ( typename list< TimestampedState<MyState> >::reverse_iterator i = sent_states.rbegin();
i != sent_states.rend();
i++ ) {
i->state.subtract( known_receiver_state );
}
}
template <class MyState>
void TransportSender<MyState>::send_in_fragments( string diff, uint64_t new_num )
{
Instruction inst;
uint64_t now = timestamp();
inst.set_protocol_version( MOSH_PROTOCOL_VERSION );
inst.set_old_num( assumed_receiver_state->num );
inst.set_new_num( new_num );
inst.set_ack_num( ack_num );
inst.set_throwaway_num( sent_states.front().num );
inst.set_late_ack_num( get_late_ack( now ) );
inst.set_diff( diff );
if ( new_num == uint64_t(-1) ) {
shutdown_tries++;
}
vector<Fragment> fragments = fragmenter.make_fragments( inst, connection->get_MTU() );
for ( auto i = fragments.begin(); i != fragments.end(); i++ ) {
connection->send( i->tostring() );
if ( verbose ) {
fprintf( stderr, "[%u] Sent [%d=>%d] id %d, frag %d ack=%d, late_ack=%d, throwaway=%d, len=%d, frame rate=%.2f, timeout=%d, srtt=%.1f age=%llu\n",
(unsigned int)(timestamp() % 100000), (int)inst.old_num(), (int)inst.new_num(), (int)i->id, (int)i->fragment_num,
(int)inst.ack_num(), (int)inst.late_ack_num(), (int)inst.throwaway_num(), (int)i->contents.size(),
1000.0 / (double)send_interval(),
(int)connection->timeout(), connection->get_SRTT(),
(long long)(now - ack_timestamp) );
}
}
pending_data_ack = false;
}
template <class MyState>
void TransportSender<MyState>::process_acknowledgment_through( uint64_t ack_num )
{
/* Ignore ack if we have culled the state it's acknowledging */
if ( sent_states.end() != find_if( sent_states.begin(), sent_states.end(),
[&]( const TimestampedState<MyState> &x ) { return x.num == ack_num; } ) ) {
sent_states.remove_if( [&]( const TimestampedState<MyState> &x ) { return x.num < ack_num; } );
}
assert( !sent_states.empty() );
}
/* give up on getting acknowledgement for shutdown */
template <class MyState>
bool TransportSender<MyState>::shutdown_ack_timed_out( void ) const
{
return shutdown_tries >= SHUTDOWN_RETRIES;
}
/* Executed upon entry to new receiver state */
template <class MyState>
void TransportSender<MyState>::set_ack_num( uint64_t s_ack_num )
{
ack_num = s_ack_num;
ack_timestamp = timestamp();
ack_history.push_back( make_pair( ack_num, ack_timestamp ) );
}
/* The "late" ack is for the input state that has had enough time on the host to have been echoed */
template <class MyState>
uint64_t TransportSender<MyState>::get_late_ack( uint64_t now )
{
uint64_t newest_echo_ack = 0;
for ( auto i = ack_history.begin(); i != ack_history.end(); i++ ) {
if ( i->second < now - ECHO_TIMEOUT ) {
newest_echo_ack = i->first;
}
}
ack_history.remove_if( [&]( const pair<uint64_t, uint64_t> &x ) { return x.first < newest_echo_ack; } );
return newest_echo_ack;
}
+116
View File
@@ -0,0 +1,116 @@
#ifndef TRANSPORT_SENDER_HPP
#define TRANSPORT_SENDER_HPP
#include <string>
#include <list>
#include "network.hpp"
#include "transportinstruction.pb.h"
#include "transportstate.hpp"
#include "transportfragment.hpp"
using namespace std;
using namespace TransportBuffers;
namespace Network {
template <class MyState>
class TransportSender
{
private:
/* timing parameters */
static const int SEND_INTERVAL_MIN = 20; /* ms between frames */
static const int SEND_INTERVAL_MAX = 250; /* ms between frames */
static const int ACK_INTERVAL = 3000; /* ms between empty acks */
static const int ACK_DELAY = 100; /* ms before delayed ack */
static const int SHUTDOWN_RETRIES = 3; /* number of shutdown packets to send before giving up */
static const int ECHO_TIMEOUT = 50; /* for late ack */
/* helper methods for tick() */
void update_assumed_receiver_state( void );
void rationalize_states( void );
void send_to_receiver( string diff );
void send_empty_ack( void );
void send_in_fragments( string diff, uint64_t new_num );
void add_sent_state( uint64_t the_timestamp, uint64_t num, MyState &state );
/* state of sender */
Connection *connection;
MyState current_state;
list< TimestampedState<MyState> > sent_states;
/* first element: known, acknowledged receiver state */
/* last element: last sent state */
/* somewhere in the middle: the assumed state of the receiver */
typename list< TimestampedState<MyState> >::iterator assumed_receiver_state;
/* for fragment creation */
Fragmenter fragmenter;
/* timing state */
uint64_t next_ack_time;
uint64_t next_send_time;
bool verbose;
bool shutdown_in_progress;
int shutdown_tries;
/* information about receiver state */
uint64_t ack_num;
bool pending_data_ack;
uint64_t ack_timestamp;
list< pair<uint64_t, uint64_t> > ack_history;
uint64_t get_late_ack( uint64_t now ); /* calculate delayed "echo" acknowledgment */
unsigned int SEND_MINDELAY; /* ms to collect all input */
public:
/* constructor */
TransportSender( Connection *s_connection, MyState &initial_state );
/* Send data or an ack if necessary */
void tick( void );
/* Returns the number of ms to wait until next possible event. */
int wait_time( void );
/* Executed upon receipt of ack */
void process_acknowledgment_through( uint64_t ack_num );
/* Executed upon entry to new receiver state */
void set_ack_num( uint64_t s_ack_num );
/* Accelerate reply ack */
void set_data_ack( void ) { pending_data_ack = true; }
/* Starts shutdown sequence */
void start_shutdown( void ) { shutdown_in_progress = true; }
/* Misc. getters and setters */
/* Cannot modify current_state while shutdown in progress */
MyState &get_current_state( void ) { assert( !shutdown_in_progress ); return current_state; }
void set_current_state( const MyState &x ) { assert( !shutdown_in_progress ); current_state = x; }
void set_verbose( void ) { verbose = true; }
bool get_shutdown_in_progress( void ) const { return shutdown_in_progress; }
bool get_shutdown_acknowledged( void ) const { return sent_states.front().num == uint64_t(-1); }
bool get_counterparty_shutdown_acknowledged( void ) const { return fragmenter.last_ack_sent() == uint64_t(-1); }
uint64_t get_sent_state_acked( void ) const { return sent_states.front().num; }
uint64_t get_sent_state_last( void ) const { return sent_states.back().num; }
bool shutdown_ack_timed_out( void ) const;
void set_send_delay( int new_delay ) { SEND_MINDELAY = new_delay; }
unsigned int send_interval( void ) const;
/* nonexistent methods to satisfy -Weffc++ */
TransportSender( const TransportSender &x );
TransportSender & operator=( const TransportSender &x );
};
}
#endif
+19
View File
@@ -0,0 +1,19 @@
#ifndef TRANSPORT_STATE_HPP
#define TRANSPORT_STATE_HPP
namespace Network {
template <class State>
class TimestampedState
{
public:
uint64_t timestamp;
uint64_t num;
State state;
TimestampedState( uint64_t s_timestamp, uint64_t s_num, State &s_state )
: timestamp( s_timestamp ), num( s_num ), state( s_state )
{}
};
}
#endif
+97
View File
@@ -0,0 +1,97 @@
#include <assert.h>
#include <typeinfo>
#include "user.hpp"
#include "userinput.pb.h"
using namespace Parser;
using namespace Network;
using namespace ClientBuffers;
void UserStream::subtract( const UserStream *prefix )
{
for ( deque<UserEvent>::const_iterator i = prefix->actions.begin();
i != prefix->actions.end();
i++ ) {
assert( !actions.empty() );
assert( *i == actions.front() );
actions.pop_front();
}
}
string UserStream::diff_from( const UserStream &existing )
{
deque<UserEvent>::iterator my_it = actions.begin();
for ( deque<UserEvent>::const_iterator i = existing.actions.begin();
i != existing.actions.end();
i++ ) {
assert( my_it != actions.end() );
assert( *i == *my_it );
my_it++;
}
ClientBuffers::UserMessage output;
while ( my_it != actions.end() ) {
switch ( my_it->type ) {
case UserByteType:
{
char the_byte = my_it->userbyte.c;
/* can we combine this with a previous Keystroke? */
if ( (output.instruction_size() > 0)
&& (output.instruction( output.instruction_size() - 1 ).HasExtension( keystroke )) ) {
output.mutable_instruction( output.instruction_size() - 1 )->MutableExtension( keystroke )->mutable_keys()->append( string( &the_byte, 1 ) );
} else {
Instruction *new_inst = output.add_instruction();
new_inst->MutableExtension( keystroke )->set_keys( &the_byte, 1 );
}
}
break;
case ResizeType:
{
Instruction *new_inst = output.add_instruction();
new_inst->MutableExtension( resize )->set_width( my_it->resize.width );
new_inst->MutableExtension( resize )->set_height( my_it->resize.height );
}
break;
default:
assert( false );
}
my_it++;
}
return output.SerializeAsString();
}
void UserStream::apply_string( string diff )
{
ClientBuffers::UserMessage input;
assert( input.ParseFromString( diff ) );
for ( int i = 0; i < input.instruction_size(); i++ ) {
if ( input.instruction( i ).HasExtension( keystroke ) ) {
string the_bytes = input.instruction( i ).GetExtension( keystroke ).keys();
for ( unsigned int loc = 0; loc < the_bytes.size(); loc++ ) {
actions.push_back( UserEvent( UserByte( the_bytes.at( loc ) ) ) );
}
} else if ( input.instruction( i ).HasExtension( resize ) ) {
actions.push_back( UserEvent( Resize( input.instruction( i ).GetExtension( resize ).width(),
input.instruction( i ).GetExtension( resize ).height() ) ) );
}
}
}
const Parser::Action *UserStream::get_action( unsigned int i )
{
switch( actions[ i ].type ) {
case UserByteType:
return &( actions[ i ].userbyte );
case ResizeType:
return &( actions[ i ].resize );
default:
assert( false );
return NULL;
}
}
+62
View File
@@ -0,0 +1,62 @@
#ifndef USER_HPP
#define USER_HPP
#include <deque>
#include <list>
#include <string>
#include <assert.h>
#include "parseraction.hpp"
using namespace std;
namespace Network {
enum UserEventType {
UserByteType = 0,
ResizeType = 1
};
class UserEvent
{
public:
UserEventType type;
Parser::UserByte userbyte;
Parser::Resize resize;
UserEvent( Parser::UserByte s_userbyte ) : type( UserByteType ), userbyte( s_userbyte ), resize( -1, -1 ) {}
UserEvent( Parser::Resize s_resize ) : type( ResizeType ), userbyte( 0 ), resize( s_resize ) {}
UserEvent() /* default constructor required by C++11 STL */
: type( UserByteType ),
userbyte( 0 ),
resize( -1, -1 )
{
assert( false );
}
bool operator==( const UserEvent &x ) const { return ( type == x.type ) && ( userbyte == x.userbyte ) && ( resize == x.resize ); }
};
class UserStream
{
private:
deque<UserEvent> actions;
public:
UserStream() : actions() {}
void push_back( Parser::UserByte s_userbyte ) { actions.push_back( UserEvent( s_userbyte ) ); }
void push_back( Parser::Resize s_resize ) { actions.push_back( UserEvent( s_resize ) ); }
size_t size( void ) { return actions.size(); }
const Parser::Action *get_action( unsigned int i );
/* interface for Network::Transport */
void subtract( const UserStream *prefix );
string diff_from( const UserStream &existing );
void apply_string( string diff );
bool operator==( const UserStream &x ) const { return actions == x.actions; }
};
}
#endif