diff --git a/.gitignore b/.gitignore index 2b880085b..6f9356d05 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ Thumbs.db *.pro.user .kdev4 *.kdev4 + +!supportlibs/libsam3/Makefile diff --git a/libretroshare/src/util/i2pcommon.cpp b/libretroshare/src/util/i2pcommon.cpp index ec2ebfd6b..bca7ae110 100644 --- a/libretroshare/src/util/i2pcommon.cpp +++ b/libretroshare/src/util/i2pcommon.cpp @@ -70,13 +70,14 @@ std::string publicKeyFromPrivate(std::string const &priv) uint8_t certType = 0; uint16_t len = 0; uint16_t signingKeyType = 0; - uint16_t cryptKey = 0; + uint16_t cryptKeyType = 0; // only used for easy break do { try { // jump to certificate p += publicKeyLen; + // try to read type and length certType = *p++; len = readTwoBytesBE(p); @@ -87,7 +88,7 @@ std::string publicKeyFromPrivate(std::string const &priv) /* * CertType.Null * type null is followed by 0x00 0x00 - * so has to be 0! + * so len has to be 0! */ RS_DBG("cert is CertType.Null"); publicKeyLen += 3; // add 0x00 0x00 0x00 @@ -119,7 +120,7 @@ std::string publicKeyFromPrivate(std::string const &priv) // likely 7 signingKeyType = readTwoBytesBE(p); - RS_DBG("signing pubkey type ", certType); + RS_DBG("signing pubkey type ", signingKeyType); if (signingKeyType >= 3 && signingKeyType <= 6) { RS_DBG("signing pubkey type ", certType, " has oversize"); // calculate oversize @@ -137,18 +138,18 @@ std::string publicKeyFromPrivate(std::string const &priv) return std::string(); } - publicKeyLen += values.first - 128; // 128 = default DSA key length = the space than can be used before the key must be splitted + publicKeyLen += values.first - 128; // 128 = default DSA key length = the space that can be used before the key must be splitted } // Crypto Public Key // likely 0 - cryptKey = readTwoBytesBE(p); - RS_DBG("crypto pubkey type ", cryptKey); + cryptKeyType = readTwoBytesBE(p); + RS_DBG("crypto pubkey type ", cryptKeyType); // info: these are all smaller than the default 256 bytes, so no oversize calculation is needed break; } catch (const std::out_of_range &e) { - RS_DBG("hit exception! ", e.what()); + RS_DBG("hit an exception! ", e.what()); return std::string(); } } while(false); @@ -160,4 +161,102 @@ std::string publicKeyFromPrivate(std::string const &priv) return pub; } +bool getKeyTypes(const std::string &key, std::string &signingKey, std::string &cryptoKey) +{ + // creat a copy to work on, need to convert it to standard base64 + auto key_copy(key); + std::replace(key_copy.begin(), key_copy.end(), '~', '/'); + // replacing the - with a + is not necessary, as RsBase64 can handle base64url encoding, too + // std::replace(copy.begin(), copy.end(), '-', '+'); + + // get raw data + std::vector data; + RsBase64::decode(key_copy, data); + + auto p = data.cbegin(); + + constexpr size_t publicKeyLen = 256 + 128; // default length (bytes) + uint8_t certType = 0; + uint16_t signingKeyType = 0; + uint16_t cryptKeyType = 0; + + // try to read types + try { + // jump to certificate + p += publicKeyLen; + + // try to read type and skip length + certType = *p++; + p += 2; + + // only 0 and 5 are used / valid at this point + // check for == 0 + if (certType == static_cast::type>(CertType::Null)) { + RS_DBG("cert is CertType.Null"); + + signingKey = "DSA_SHA1"; + cryptoKey = "ElGamal"; + return true; + } + + // check for != 5 + if (certType != static_cast::type>(CertType::Key)) { + // unsupported + RS_DBG("cert type ", certType, " is unsupported"); + return false; + } + + RS_DBG("cert is CertType.Key"); + + // Signing Public Key + // likely 7 + signingKeyType = readTwoBytesBE(p); + RS_DBG("signing pubkey type ", signingKeyType); + + // Crypto Public Key + // likely 0 + cryptKeyType = readTwoBytesBE(p); + RS_DBG("crypto pubkey type ", cryptKeyType); + } catch (const std::out_of_range &e) { + RS_DBG("hit an exception! ", e.what()); + return false; + } + + // now convert to string (this would be easier with c++17) +#define HELPER(a, b, c) \ + case static_cast::type>(a::c): \ + b = "c"; \ + break; + + switch (signingKeyType) { + HELPER(SigningKeyType, signingKey, DSA_SHA1) + HELPER(SigningKeyType, signingKey, ECDSA_SHA256_P256) + HELPER(SigningKeyType, signingKey, ECDSA_SHA384_P384) + HELPER(SigningKeyType, signingKey, ECDSA_SHA512_P521) + HELPER(SigningKeyType, signingKey, RSA_SHA256_2048) + HELPER(SigningKeyType, signingKey, RSA_SHA384_3072) + HELPER(SigningKeyType, signingKey, RSA_SHA512_4096) + HELPER(SigningKeyType, signingKey, EdDSA_SHA512_Ed25519) + HELPER(SigningKeyType, signingKey, EdDSA_SHA512_Ed25519ph) + HELPER(SigningKeyType, signingKey, RedDSA_SHA512_Ed25519) + default: + RsWarn("unkown signing key type:", signingKeyType); + return false; + } + + switch (cryptKeyType) { + HELPER(CryptoKeyType, cryptoKey, ElGamal) + HELPER(CryptoKeyType, cryptoKey, P256) + HELPER(CryptoKeyType, cryptoKey, P384) + HELPER(CryptoKeyType, cryptoKey, P521) + HELPER(CryptoKeyType, cryptoKey, X25519) + default: + RsWarn("unkown crypto key type:", cryptKeyType); + return false; + } +#undef HELPER + + return true; +} + } // namespace i2p diff --git a/libretroshare/src/util/i2pcommon.h b/libretroshare/src/util/i2pcommon.h index 1fd152079..e41b61010 100644 --- a/libretroshare/src/util/i2pcommon.h +++ b/libretroshare/src/util/i2pcommon.h @@ -208,6 +208,15 @@ std::string keyToBase32Addr(const std::string &key); */ std::string publicKeyFromPrivate(const std::string &priv); +/** + * @brief getKeyTypes returns the name of the utilized algorithms used by the key + * @param key public key (private works, too) + * @param signingKey name of the signing key, e.g. DSA_SHA1 + * @param cryptoKey name of the crpyto key, e.g. ElGamal + * @return true on success, false otherwise + */ +bool getKeyTypes(const std::string &key, std::string &signingKey, std::string &cryptoKey); + } // namespace i2p #endif // I2PCOMMON_H diff --git a/supportlibs/libsam3/Makefile b/supportlibs/libsam3/Makefile new file mode 100644 index 000000000..fc6f52245 --- /dev/null +++ b/supportlibs/libsam3/Makefile @@ -0,0 +1,41 @@ +CFLAGS := -Wall -g -O2 -std=gnu99 + +SRCS := \ + src/libsam3/libsam3.c \ + src/libsam3a/libsam3a.c + +TESTS := \ + src/ext/tinytest.c \ + test/test.c \ + test/libsam3/test_b32.c + +LIB_OBJS := ${SRCS:.c=.o} +TEST_OBJS := ${TESTS:.c=.o} + +OBJS := ${LIB_OBJS} ${TEST_OBJS} + +LIB := libsam3.a + + +all: build check + +check: libsam3-tests + ./libsam3-tests + +build: ${LIB} + +${LIB}: ${LIB_OBJS} + ${AR} -sr ${LIB} ${LIB_OBJS} + +libsam3-tests: ${TEST_OBJS} ${LIB} + ${CC} $^ -o $@ + +clean: + rm -f libsam3-tests ${LIB} ${OBJS} examples/sam3/samtest + +%.o: %.c Makefile + ${CC} ${CFLAGS} -c $< -o $@ + +fmt: + find . -name '*.c' -exec clang-format -i {} \; + find . -name '*.h' -exec clang-format -i {} \; diff --git a/supportlibs/libsam3/README.md b/supportlibs/libsam3/README.md new file mode 100644 index 000000000..98dfe4205 --- /dev/null +++ b/supportlibs/libsam3/README.md @@ -0,0 +1,18 @@ +# libsam3 + +[![Build Status](https://travis-ci.org/i2p/libsam3.svg?branch=master)](https://travis-ci.org/i2p/libsam3) + +A C library for the [SAM v3 API](https://geti2p.net/en/docs/api/samv3). + +## Development Status + +Unmaintained, but PRs welcome! + +## Usage + +Copy the two files from one of the following locations into your codebase: + +- `src/libsam3` - Synchronous implementation. +- `src/libsam3a` - Asynchronous implementation. + +See `examples/` for how to use various parts of the API. diff --git a/supportlibs/libsam3/examples/libsam3 b/supportlibs/libsam3/examples/libsam3 new file mode 120000 index 000000000..136138896 --- /dev/null +++ b/supportlibs/libsam3/examples/libsam3 @@ -0,0 +1 @@ +../src/libsam3 \ No newline at end of file diff --git a/supportlibs/libsam3/examples/sam3/README.md b/supportlibs/libsam3/examples/sam3/README.md new file mode 100644 index 000000000..1228e4984 --- /dev/null +++ b/supportlibs/libsam3/examples/sam3/README.md @@ -0,0 +1,26 @@ +Examples +======== + +These examples show various ways of using libsam3 to enable i2p in your +application, and are also useful in other ways. If you implement an i2p +application library in another language, making variants basic tools wouldn't be +the worst way to make sure that it works. + +building +-------- + +Once you have build the library in the root of this repository by running make +all, you can build all these examples at once by running + + make + +in this directory. I think it makes things easier to experiment with quickly. + +namelookup +---------- + +Namelookup uses the SAM API to find the base64 destination of an readable "jump" +or base32 i2p address. You can use it like this: + + ./lookup i2p-projekt.i2p + diff --git a/supportlibs/libsam3/examples/sam3/dgramc.c b/supportlibs/libsam3/examples/sam3/dgramc.c new file mode 100644 index 000000000..898a9aec1 --- /dev/null +++ b/supportlibs/libsam3/examples/sam3/dgramc.c @@ -0,0 +1,116 @@ +/* This program is free software. It comes without any warranty, to + * the extent permitted by applicable law. You can redistribute it + * and/or modify it under the terms of the Do What The Fuck You Want + * To Public License, Version 2, as published by Sam Hocevar. See + * http://sam.zoy.org/wtfpl/COPYING for more details. + * + * I2P-Bote: + * 5m77dFKGEq6~7jgtrfw56q3t~SmfwZubmGdyOLQOPoPp8MYwsZ~pfUCwud6LB1EmFxkm4C3CGlzq-hVs9WnhUV + * we are the Borg. */ +#include +#include +#include +#include +#include + +#include "../libsam3/libsam3.h" + +// comment the following if you don't want to stress UDP with 'big' datagram +// seems that up to 32000 bytes can be used for localhost +// note that we need 516+6+? bytes for header; lets reserve 1024 bytes for it +#define BIG (32000 - 1024) + +#define KEYFILE "dgrams.key" + +int main(int argc, char *argv[]) { + Sam3Session ses; + char buf[1024]; + char destkey[517] = {0}; // 516 chars + \0 + int sz; + // + libsam3_debug = 1; + // + if (argc < 2) { + FILE *fl = fopen(KEYFILE, "rb"); + // + if (fl != NULL) { + if (fread(destkey, 516, 1, fl) == 1) { + fclose(fl); + goto ok; + } + fclose(fl); + } + printf("usage: dgramc PUBKEY\n"); + return 1; + } else { + if (strlen(argv[1]) != 516) { + fprintf(stderr, "FATAL: invalid key length!\n"); + return 1; + } + strcpy(destkey, argv[1]); + } + // +ok: + printf("creating session...\n"); + /* create TRANSIENT session with temporary disposible destination */ + if (sam3CreateSession(&ses, SAM3_HOST_DEFAULT, SAM3_PORT_DEFAULT, + SAM3_DESTINATION_TRANSIENT, SAM3_SESSION_DGRAM, 4, + NULL) < 0) { + fprintf(stderr, "FATAL: can't create session\n"); + return 1; + } + /* send datagram */ + printf("sending test datagram...\n"); + if (sam3DatagramSend(&ses, destkey, "test", 4) < 0) { + fprintf(stderr, "ERROR: %s\n", ses.error); + goto error; + } + /** receive reply */ + if ((sz = sam3DatagramReceive(&ses, buf, sizeof(buf) - 1)) < 0) { + fprintf(stderr, "ERROR: %s\n", ses.error); + goto error; + } + /** null terminated string */ + buf[sz] = 0; + printf("received: [%s]\n", buf); + // +#ifdef BIG + { + char *big = calloc(BIG + 1024, sizeof(char)); + /** generate random string */ + sam3GenChannelName(big, BIG + 1023, BIG + 1023); + printf("sending BIG datagram...\n"); + if (sam3DatagramSend(&ses, destkey, big, BIG) < 0) { + free(big); + fprintf(stderr, "ERROR: %s\n", ses.error); + goto error; + } + if ((sz = sam3DatagramReceive(&ses, big, BIG + 512)) < 0) { + free(big); + fprintf(stderr, "ERROR: %s\n", ses.error); + goto error; + } + big[sz] = 0; + printf("received (%d): [%s]\n", sz, big); + free(big); + } +#endif + // + printf("sending quit datagram...\n"); + if (sam3DatagramSend(&ses, destkey, "quit", 4) < 0) { + fprintf(stderr, "ERROR: %s\n", ses.error); + goto error; + } + if ((sz = sam3DatagramReceive(&ses, buf, sizeof(buf) - 1)) < 0) { + fprintf(stderr, "ERROR: %s\n", ses.error); + goto error; + } + buf[sz] = 0; + printf("received: [%s]\n", buf); + // + sam3CloseSession(&ses); + return 0; +error: + sam3CloseSession(&ses); + return 1; +} diff --git a/supportlibs/libsam3/examples/sam3/dgrams.c b/supportlibs/libsam3/examples/sam3/dgrams.c new file mode 100644 index 000000000..1fe0fc0f7 --- /dev/null +++ b/supportlibs/libsam3/examples/sam3/dgrams.c @@ -0,0 +1,113 @@ +/* This program is free software. It comes without any warranty, to + * the extent permitted by applicable law. You can redistribute it + * and/or modify it under the terms of the Do What The Fuck You Want + * To Public License, Version 2, as published by Sam Hocevar. See + * http://sam.zoy.org/wtfpl/COPYING for more details. + * + * I2P-Bote: + * 5m77dFKGEq6~7jgtrfw56q3t~SmfwZubmGdyOLQOPoPp8MYwsZ~pfUCwud6LB1EmFxkm4C3CGlzq-hVs9WnhUV + * we are the Borg. */ +#include +#include +#include +#include +#include + +#include "../libsam3/libsam3.h" + +#define KEYFILE "dgrams.key" + +int main(int argc, char *argv[]) { + Sam3Session ses; + char privkey[1024], pubkey[1024], buf[33 * 1024]; + + /** quit command */ + const char *quitstr = "quit"; + const size_t quitlen = strlen(quitstr); + + /** reply response */ + const char *replystr = "reply: "; + const size_t replylen = strlen(replystr); + + FILE *fl; + // + libsam3_debug = 1; + // + + /** generate new destination keypair */ + printf("generating keys...\n"); + if (sam3GenerateKeys(&ses, SAM3_HOST_DEFAULT, SAM3_PORT_DEFAULT, 4) < 0) { + fprintf(stderr, "FATAL: can't generate keys\n"); + return 1; + } + /** copy keypair into local buffer */ + strncpy(pubkey, ses.pubkey, sizeof(pubkey)); + strncpy(privkey, ses.privkey, sizeof(privkey)); + /** create sam session */ + printf("creating session...\n"); + if (sam3CreateSession(&ses, SAM3_HOST_DEFAULT, SAM3_PORT_DEFAULT, privkey, + SAM3_SESSION_DGRAM, 5, NULL) < 0) { + fprintf(stderr, "FATAL: can't create session\n"); + return 1; + } + /** make sure we have the right destination */ + // FIXME: probably not needed + if (strcmp(pubkey, ses.pubkey) != 0) { + fprintf(stderr, "FATAL: destination keys don't match\n"); + sam3CloseSession(&ses); + return 1; + } + /** print destination to stdout */ + printf("PUB KEY\n=======\n%s\n=======\n", ses.pubkey); + if ((fl = fopen(KEYFILE, "wb")) != NULL) { + /** write public key to keyfile */ + fwrite(pubkey, strlen(pubkey), 1, fl); + fclose(fl); + } + + /* now listen for UDP packets */ + printf("starting main loop...\n"); + for (;;) { + /** save replylen bytes for out reply at begining */ + char *datagramBuf = buf + replylen; + const size_t datagramMaxLen = sizeof(buf) - replylen; + int sz, isquit; + printf("waiting for datagram...\n"); + /** blocks until we get a UDP packet */ + if ((sz = sam3DatagramReceive(&ses, datagramBuf, datagramMaxLen) < 0)) { + fprintf(stderr, "ERROR: %s\n", ses.error); + goto error; + } + /** ensure null terminated string */ + datagramBuf[sz] = 0; + /** print out datagram payload to user */ + printf("FROM\n====\n%s\n====\n", ses.destkey); + printf("SIZE=%d\n", sz); + printf("data: [%s]\n", datagramBuf); + /** check for "quit" */ + isquit = (sz == quitlen && memcmp(datagramBuf, quitstr, quitlen) == 0); + /** echo datagram back to sender with "reply: " at the beginning */ + memcpy(buf, replystr, replylen); + + if (sam3DatagramSend(&ses, ses.destkey, buf, sz + replylen) < 0) { + fprintf(stderr, "ERROR: %s\n", ses.error); + goto error; + } + /** if we got a quit command wait for 10 seconds and break out of the + * mainloop */ + if (isquit) { + printf("shutting down...\n"); + sleep(10); /* let dgram reach it's destination */ + break; + } + } + /** close session and delete keyfile */ + sam3CloseSession(&ses); + unlink(KEYFILE); + return 0; +error: + /** error case, close session, delete keyfile and return exit code 1 */ + sam3CloseSession(&ses); + unlink(KEYFILE); + return 1; +} diff --git a/supportlibs/libsam3/examples/sam3/namelookup.c b/supportlibs/libsam3/examples/sam3/namelookup.c new file mode 100644 index 000000000..772e4aceb --- /dev/null +++ b/supportlibs/libsam3/examples/sam3/namelookup.c @@ -0,0 +1,43 @@ +/* This program is free software. It comes without any warranty, to + * the extent permitted by applicable law. You can redistribute it + * and/or modify it under the terms of the Do What The Fuck You Want + * To Public License, Version 2, as published by Sam Hocevar. See + * http://sam.zoy.org/wtfpl/COPYING for more details. + * + * I2P-Bote: + * 5m77dFKGEq6~7jgtrfw56q3t~SmfwZubmGdyOLQOPoPp8MYwsZ~pfUCwud6LB1EmFxkm4C3CGlzq-hVs9WnhUV + * we are the Borg. */ +#include +#include +#include +#include + +#include "../libsam3/libsam3.h" + +int main(int argc, char *argv[]) { + Sam3Session ses; + // + // + libsam3_debug = 1; + // + if (argc < 2) { + printf("usage: %s name [name...]\n", argv[0]); + return 1; + } + /** for each name in arguments ... */ + for (int n = 1; n < argc; ++n) { + if (!getenv("I2P_LOOKUP_QUIET")) { + fprintf(stdout, "%s ... ", argv[n]); + fflush(stdout); + } + /** do oneshot name lookup */ + if (sam3NameLookup(&ses, SAM3_HOST_DEFAULT, SAM3_PORT_DEFAULT, argv[n]) >= + 0) { + fprintf(stdout, "%s\n\n", ses.destkey); + } else { + fprintf(stdout, "FAILED [%s]\n", ses.error); + } + } + // + return 0; +} diff --git a/supportlibs/libsam3/examples/sam3/samtest.c b/supportlibs/libsam3/examples/sam3/samtest.c new file mode 100644 index 000000000..b2d0b9983 --- /dev/null +++ b/supportlibs/libsam3/examples/sam3/samtest.c @@ -0,0 +1,51 @@ +/* This program is free software. It comes without any warranty, to + * the extent permitted by applicable law. You can redistribute it + * and/or modify it under the terms of the Do What The Fuck You Want + * To Public License, Version 2, as published by Sam Hocevar. See + * http://sam.zoy.org/wtfpl/COPYING for more details. + * + * I2P-Bote: + * 5m77dFKGEq6~7jgtrfw56q3t~SmfwZubmGdyOLQOPoPp8MYwsZ~pfUCwud6LB1EmFxkm4C3CGlzq-hVs9WnhUV + * we are the Borg. */ +#include +#include +#include +#include +#include + +#include "../libsam3/libsam3.h" + +int main(int argc, char *argv[]) { + int fd; + SAMFieldList *rep = NULL; + const char *v; + // + libsam3_debug = 1; + // + // + if ((fd = sam3Handshake(NULL, 0, NULL)) < 0) + return 1; + // + if (sam3tcpPrintf(fd, "DEST GENERATE\n") < 0) + goto error; + rep = sam3ReadReply(fd); + // sam3DumpFieldList(rep); + if (!sam3IsGoodReply(rep, "DEST", "REPLY", "PUB", NULL)) + goto error; + if (!sam3IsGoodReply(rep, "DEST", "REPLY", "PRIV", NULL)) + goto error; + v = sam3FindField(rep, "PUB"); + printf("PUB KEY\n=======\n%s\n", v); + v = sam3FindField(rep, "PRIV"); + printf("PRIV KEY\n========\n%s\n", v); + sam3FreeFieldList(rep); + rep = NULL; + // + sam3FreeFieldList(rep); + sam3tcpDisconnect(fd); + return 0; +error: + sam3FreeFieldList(rep); + sam3tcpDisconnect(fd); + return 1; +} diff --git a/supportlibs/libsam3/examples/sam3/streamc.c b/supportlibs/libsam3/examples/sam3/streamc.c new file mode 100644 index 000000000..672831c3a --- /dev/null +++ b/supportlibs/libsam3/examples/sam3/streamc.c @@ -0,0 +1,87 @@ +/* This program is free software. It comes without any warranty, to + * the extent permitted by applicable law. You can redistribute it + * and/or modify it under the terms of the Do What The Fuck You Want + * To Public License, Version 2, as published by Sam Hocevar. See + * http://sam.zoy.org/wtfpl/COPYING for more details. + * + * I2P-Bote: + * 5m77dFKGEq6~7jgtrfw56q3t~SmfwZubmGdyOLQOPoPp8MYwsZ~pfUCwud6LB1EmFxkm4C3CGlzq-hVs9WnhUV + * we are the Borg. */ +#include +#include +#include +#include +#include + +#include "../libsam3/libsam3.h" + +#define KEYFILE "streams.key" + +int main(int argc, char *argv[]) { + Sam3Session ses; + Sam3Connection *conn; + char cmd[1024], destkey[617]; // 616 chars + \0 + // + libsam3_debug = 1; + // + memset(destkey, 0, sizeof(destkey)); + // + if (argc < 2) { + FILE *fl = fopen(KEYFILE, "rb"); + // + if (fl != NULL) { + if (fread(destkey, 616, 1, fl) == 1) { + fclose(fl); + goto ok; + } + fclose(fl); + } + printf("usage: streamc PUBKEY\n"); + return 1; + } else { + if (!sam3CheckValidKeyLength(argv[1])) { + fprintf(stderr, "FATAL: invalid key length! %s %lu\n", argv[1], + strlen(argv[1])); + return 1; + } + strcpy(destkey, argv[1]); + } + // +ok: + printf("creating session...\n"); + // create TRANSIENT session + if (sam3CreateSession(&ses, SAM3_HOST_DEFAULT, SAM3_PORT_DEFAULT, + SAM3_DESTINATION_TRANSIENT, SAM3_SESSION_STREAM, 4, + NULL) < 0) { + fprintf(stderr, "FATAL: can't create session\n"); + return 1; + } + // + printf("connecting...\n"); + if ((conn = sam3StreamConnect(&ses, destkey)) == NULL) { + fprintf(stderr, "FATAL: can't connect: %s\n", ses.error); + sam3CloseSession(&ses); + return 1; + } + // + // now waiting for incoming connection + printf("sending test command...\n"); + if (sam3tcpPrintf(conn->fd, "test\n") < 0) + goto error; + if (sam3tcpReceiveStr(conn->fd, cmd, sizeof(cmd)) < 0) + goto error; + printf("echo: %s\n", cmd); + // + printf("sending quit command...\n"); + if (sam3tcpPrintf(conn->fd, "quit\n") < 0) + goto error; + // + sam3CloseConnection(conn); + sam3CloseSession(&ses); + return 0; +error: + fprintf(stderr, "FATAL: some error occured!\n"); + sam3CloseConnection(conn); + sam3CloseSession(&ses); + return 1; +} diff --git a/supportlibs/libsam3/examples/sam3/streams.c b/supportlibs/libsam3/examples/sam3/streams.c new file mode 100644 index 000000000..2fa000c1c --- /dev/null +++ b/supportlibs/libsam3/examples/sam3/streams.c @@ -0,0 +1,72 @@ +/* This program is free software. It comes without any warranty, to + * the extent permitted by applicable law. You can redistribute it + * and/or modify it under the terms of the Do What The Fuck You Want + * To Public License, Version 2, as published by Sam Hocevar. See + * http://sam.zoy.org/wtfpl/COPYING for more details. + * + * I2P-Bote: + * 5m77dFKGEq6~7jgtrfw56q3t~SmfwZubmGdyOLQOPoPp8MYwsZ~pfUCwud6LB1EmFxkm4C3CGlzq-hVs9WnhUV + * we are the Borg. */ +#include +#include +#include +#include +#include + +#include "../libsam3/libsam3.h" + +#define KEYFILE "streams.key" + +int main(int argc, char *argv[]) { + Sam3Session ses; + Sam3Connection *conn; + FILE *fl; + // + libsam3_debug = 1; + // + printf("creating session...\n"); + // create TRANSIENT session + if (sam3CreateSession(&ses, SAM3_HOST_DEFAULT, SAM3_PORT_DEFAULT, + SAM3_DESTINATION_TRANSIENT, SAM3_SESSION_STREAM, 4, + NULL) < 0) { + fprintf(stderr, "FATAL: can't create session\n"); + return 1; + } + // + printf("PUB KEY\n=======\n%s\n=======\n", ses.pubkey); + if ((fl = fopen(KEYFILE, "wb")) != NULL) { + fwrite(ses.pubkey, strlen(ses.pubkey), 1, fl); + fclose(fl); + } + // + printf("starting stream acceptor...\n"); + if ((conn = sam3StreamAccept(&ses)) == NULL) { + fprintf(stderr, "FATAL: can't accept: %s\n", ses.error); + sam3CloseSession(&ses); + return 1; + } + printf("FROM\n====\n%s\n====\n", conn->destkey); + // + printf("starting main loop...\n"); + for (;;) { + char cmd[256]; + // + if (sam3tcpReceiveStr(conn->fd, cmd, sizeof(cmd)) < 0) + goto error; + printf("cmd: [%s]\n", cmd); + if (strcmp(cmd, "quit") == 0) + break; + // echo command + if (sam3tcpPrintf(conn->fd, "re: %s\n", cmd) < 0) + goto error; + } + // + sam3CloseSession(&ses); + unlink(KEYFILE); + return 0; +error: + fprintf(stderr, "FATAL: some error occured!\n"); + sam3CloseSession(&ses); + unlink(KEYFILE); + return 1; +} diff --git a/supportlibs/libsam3/examples/sam3a/test00.c b/supportlibs/libsam3/examples/sam3a/test00.c new file mode 100644 index 000000000..5596053e9 --- /dev/null +++ b/supportlibs/libsam3/examples/sam3a/test00.c @@ -0,0 +1,178 @@ +/* This program is free software. It comes without any warranty, to + * the extent permitted by applicable law. You can redistribute it + * and/or modify it under the terms of the Do What The Fuck You Want + * To Public License, Version 2, as published by Sam Hocevar. See + * http://sam.zoy.org/wtfpl/COPYING for more details. + * + * I2P-Bote: + * 5m77dFKGEq6~7jgtrfw56q3t~SmfwZubmGdyOLQOPoPp8MYwsZ~pfUCwud6LB1EmFxkm4C3CGlzq-hVs9WnhUV + * we are the Borg. */ +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../libsam3a/libsam3a.h" + +//////////////////////////////////////////////////////////////////////////////// +static void scbErrorClose(Sam3ASession *ses) { + fprintf(stderr, + "\n===============================\nSESION_ERROR: " + "[%s]\n===============================\n", + ses->error); + sam3aCloseSession(ses); // it's safe here +} + +static void scbNRCreated(Sam3ASession *ses) { + fprintf(stderr, "\n===============================\nNAME RESOLVED: [%s]\n", + ses->params); + fprintf(stderr, "PUB: %s\n===============================\n", ses->destkey); + sam3aCloseSession(ses); // it's safe here +} + +static const Sam3ASessionCallbacks scbNR = { + .cbError = scbErrorClose, + .cbCreated = scbNRCreated, + .cbDisconnected = NULL, + .cbDatagramRead = NULL, + .cbDestroy = NULL, +}; + +//////////////////////////////////////////////////////////////////////////////// +static void scbKGCreated(Sam3ASession *ses) { + fprintf(stderr, "\n===============================\nKEYS GENERATED\n"); + fprintf(stderr, "\rPRIV: %s\n", ses->privkey); + fprintf(stderr, "\nPUB: %s\n===============================\n", ses->pubkey); + sam3aCloseSession(ses); // it's safe here +} + +static const Sam3ASessionCallbacks scbKG = { + .cbError = scbErrorClose, + .cbCreated = scbKGCreated, + .cbDisconnected = NULL, + .cbDatagramRead = NULL, + .cbDestroy = NULL, +}; + +//////////////////////////////////////////////////////////////////////////////// +static void scbError(Sam3ASession *ses) { + fprintf(stderr, + "\n===============================\nSESION_ERROR: " + "[%s]\n===============================\n", + ses->error); +} + +static void scbCreated(Sam3ASession *ses) { + fprintf(stderr, "\n===============================\nSESION_CREATED\n"); + fprintf(stderr, "\rPRIV: %s\n", ses->privkey); + fprintf(stderr, "\nPUB: %s\n===============================\n", ses->pubkey); + sam3aCancelSession(ses); // it's safe here +} + +static void scbDisconnected(Sam3ASession *ses) { + fprintf(stderr, "\n===============================\nSESION_DISCONNECTED\n====" + "===========================\n"); +} + +static void scbDGramRead(Sam3ASession *ses, const void *buf, int bufsize) { + fprintf(stderr, "\n===============================\nSESION_DATAGRAM_READ\n===" + "============================\n"); +} + +static void scbDestroy(Sam3ASession *ses) { + fprintf(stderr, "\n===============================\nSESION_DESTROYED\n=======" + "========================\n"); +} + +/** callbacks for our SAM session */ +static const Sam3ASessionCallbacks scb = { + .cbError = scbError, + .cbCreated = scbCreated, + .cbDisconnected = scbDisconnected, + .cbDatagramRead = scbDGramRead, + .cbDestroy = scbDestroy, +}; + +//////////////////////////////////////////////////////////////////////////////// +#define HOST SAM3A_HOST_DEFAULT +//#define HOST "google.com" + +int main(int argc, char *argv[]) { + Sam3ASession ses, snr, skg; + // + // libsam3a_debug = 1; + // + if (sam3aCreateSession(&ses, &scb, HOST, SAM3A_PORT_DEFAULT, + SAM3A_DESTINATION_TRANSIENT, + SAM3A_SESSION_STREAM) < 0) { + fprintf(stderr, "FATAL: can't create main session!\n"); + return 1; + } + // generate keys + if (sam3aGenerateKeys(&skg, &scbKG, HOST, SAM3A_PORT_DEFAULT) < 0) { + sam3aCloseSession(&ses); + fprintf(stderr, "FATAL: can't create keygen session!\n"); + return 1; + } + // do a name lookup for zzz.i2p + if (sam3aNameLookup(&snr, &scbNR, HOST, SAM3A_PORT_DEFAULT, "zzz.i2p") < 0) { + sam3aCloseSession(&skg); + sam3aCloseSession(&ses); + fprintf(stderr, "FATAL: can't create name resolving session!\n"); + return 1; + } + // while we have sessions ... + while (sam3aIsActiveSession(&ses) || sam3aIsActiveSession(&snr) || + sam3aIsActiveSession(&skg)) { + fd_set rds, wrs; + int res, maxfd = 0; + struct timeval to; + // set up file descriptors for select() + FD_ZERO(&rds); + FD_ZERO(&wrs); + // obtain the maximum fd for select() + if (sam3aIsActiveSession(&ses) && + (maxfd = sam3aAddSessionToFDS(&ses, -1, &rds, &wrs)) < 0) + break; + if (sam3aIsActiveSession(&snr) && + (maxfd = sam3aAddSessionToFDS(&snr, -1, &rds, &wrs)) < 0) + break; + if (sam3aIsActiveSession(&skg) && + (maxfd = sam3aAddSessionToFDS(&skg, -1, &rds, &wrs)) < 0) + break; + // set timeout to 1 second + sam3ams2timeval(&to, 1000); + // call select() + res = select(maxfd + 1, &rds, &wrs, NULL, &to); + if (res < 0) { + if (errno == EINTR) + continue; + fprintf(stderr, "FATAL: select() error!\n"); + break; + } + if (res == 0) { + // idle, no activity + fprintf(stdout, "."); + fflush(stdout); + } else { + // we have activity, process io + if (sam3aIsActiveSession(&ses)) + sam3aProcessSessionIO(&ses, &rds, &wrs); + if (sam3aIsActiveSession(&snr)) + sam3aProcessSessionIO(&snr, &rds, &wrs); + if (sam3aIsActiveSession(&skg)) + sam3aProcessSessionIO(&skg, &rds, &wrs); + } + } + // close seessions + sam3aCloseSession(&ses); + sam3aCloseSession(&skg); + sam3aCloseSession(&snr); + // exit + return 0; +} diff --git a/supportlibs/libsam3/examples/sam3a/test_sc.c b/supportlibs/libsam3/examples/sam3a/test_sc.c new file mode 100644 index 000000000..8feeb5d52 --- /dev/null +++ b/supportlibs/libsam3/examples/sam3a/test_sc.c @@ -0,0 +1,205 @@ +/* This program is free software. It comes without any warranty, to + * the extent permitted by applicable law. You can redistribute it + * and/or modify it under the terms of the Do What The Fuck You Want + * To Public License, Version 2, as published by Sam Hocevar. See + * http://sam.zoy.org/wtfpl/COPYING for more details. + * + * I2P-Bote: + * 5m77dFKGEq6~7jgtrfw56q3t~SmfwZubmGdyOLQOPoPp8MYwsZ~pfUCwud6LB1EmFxkm4C3CGlzq-hVs9WnhUV + * we are the Borg. */ +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../libsam3a/libsam3a.h" + +//////////////////////////////////////////////////////////////////////////////// +#define KEYFILE "streams.key" + +//////////////////////////////////////////////////////////////////////////////// +static void ccbError(Sam3AConnection *ct) { + fprintf(stderr, + "\n===============================\nCONNECTION_ERROR: " + "[%s]\n===============================\n", + ct->error); +} + +static void ccbDisconnected(Sam3AConnection *ct) { + fprintf(stderr, "\n===============================\nCONNECTION_" + "DISCONNECTED\n===============================\n"); +} + +static void ccbConnected(Sam3AConnection *ct) { + fprintf(stderr, "\n===============================\nCONNECTION_CONNECTED\n===" + "============================\n"); + // sam3aCancelConnection(ct); // cbSent() will not be called +} + +static void ccbAccepted(Sam3AConnection *ct) { + fprintf(stderr, "\n===============================\nCONNECTION_ACCEPTED\n====" + "===========================\n"); +} + +static void ccbSent(Sam3AConnection *ct) { + fprintf(stderr, "\n===============================\nCONNECTION_WANTBYTES\n===" + "============================\n"); + // sam3aCancelConnection(ct); + // sam3aCancelSession(ct->ses); // hehe + fprintf(stderr, "(%p)\n", ct->udata); + // + switch ((intptr_t)ct->udata) { + case 0: + if (sam3aSend(ct, "test\n", -1) < 0) { + fprintf(stderr, "SEND ERROR!\n"); + sam3aCancelSession(ct->ses); // hehe + } + break; + case 1: + if (sam3aSend(ct, "quit\n", -1) < 0) { + fprintf(stderr, "SEND ERROR!\n"); + sam3aCancelSession(ct->ses); // hehe + } + break; + default: + return; + } + ct->udata = (void *)(((intptr_t)ct->udata) + 1); +} + +static void ccbRead(Sam3AConnection *ct, const void *buf, int bufsize) { + fprintf(stderr, + "\n===============================\nCONNECTION_GOTBYTES " + "(%d)\n===============================\n", + bufsize); +} + +static void ccbDestroy(Sam3AConnection *ct) { + fprintf(stderr, "\n===============================\nCONNECTION_DESTROY\n=====" + "==========================\n"); +} + +static const Sam3AConnectionCallbacks ccb = { + .cbError = ccbError, + .cbDisconnected = ccbDisconnected, + .cbConnected = ccbConnected, + .cbAccepted = ccbAccepted, + .cbSent = ccbSent, + .cbRead = ccbRead, + .cbDestroy = ccbDestroy, +}; + +//////////////////////////////////////////////////////////////////////////////// +static void scbError(Sam3ASession *ses) { + fprintf(stderr, + "\n===============================\nSESION_ERROR: " + "[%s]\n===============================\n", + ses->error); +} + +static void scbCreated(Sam3ASession *ses) { + char destkey[517]; + FILE *fl; + // + fprintf(stderr, "\n===============================\nSESION_CREATED\n"); + fprintf(stderr, "\rPRIV: %s\n", ses->privkey); + fprintf(stderr, "\nPUB: %s\n===============================\n", ses->pubkey); + // + fl = fopen(KEYFILE, "rb"); + // + if (fl == NULL) { + fprintf(stderr, "ERROR: NO KEY FILE!\n"); + sam3aCancelSession(ses); + return; + } + if (fread(destkey, 516, 1, fl) != 1) { + fprintf(stderr, "ERROR: INVALID KEY FILE!\n"); + fclose(fl); + sam3aCancelSession(ses); + return; + } + fclose(fl); + destkey[516] = 0; + if (sam3aStreamConnect(ses, &ccb, destkey) == NULL) { + fprintf(stderr, "ERROR: CAN'T CREATE CONNECTION!\n"); + sam3aCancelSession(ses); + return; + } + fprintf(stderr, "GOON: creating connection...\n"); +} + +static void scbDisconnected(Sam3ASession *ses) { + fprintf(stderr, "\n===============================\nSESION_DISCONNECTED\n====" + "===========================\n"); +} + +static void scbDGramRead(Sam3ASession *ses, const void *buf, int bufsize) { + fprintf(stderr, "\n===============================\nSESION_DATAGRAM_READ\n===" + "============================\n"); +} + +static void scbDestroy(Sam3ASession *ses) { + fprintf(stderr, "\n===============================\nSESION_DESTROYED\n=======" + "========================\n"); +} + +static const Sam3ASessionCallbacks scb = { + .cbError = scbError, + .cbCreated = scbCreated, + .cbDisconnected = scbDisconnected, + .cbDatagramRead = scbDGramRead, + .cbDestroy = scbDestroy, +}; + +//////////////////////////////////////////////////////////////////////////////// +#define HOST SAM3A_HOST_DEFAULT +//#define HOST "google.com" + +int main(int argc, char *argv[]) { + Sam3ASession ses; + // + libsam3a_debug = 0; + // + if (sam3aCreateSession(&ses, &scb, HOST, SAM3A_PORT_DEFAULT, + SAM3A_DESTINATION_TRANSIENT, + SAM3A_SESSION_STREAM) < 0) { + fprintf(stderr, "FATAL: can't create main session!\n"); + return 1; + } + // + while (sam3aIsActiveSession(&ses)) { + fd_set rds, wrs; + int res, maxfd = 0; + struct timeval to; + // + FD_ZERO(&rds); + FD_ZERO(&wrs); + if (sam3aIsActiveSession(&ses) && + (maxfd = sam3aAddSessionToFDS(&ses, -1, &rds, &wrs)) < 0) + break; + sam3ams2timeval(&to, 1000); + res = select(maxfd + 1, &rds, &wrs, NULL, &to); + if (res < 0) { + if (errno == EINTR) + continue; + fprintf(stderr, "FATAL: select() error!\n"); + break; + } + if (res == 0) { + fprintf(stdout, "."); + fflush(stdout); + } else { + if (sam3aIsActiveSession(&ses)) + sam3aProcessSessionIO(&ses, &rds, &wrs); + } + } + // + sam3aCloseSession(&ses); + // + return 0; +} diff --git a/supportlibs/libsam3/examples/sam3a/test_ss.c b/supportlibs/libsam3/examples/sam3a/test_ss.c new file mode 100644 index 000000000..c2ff3399c --- /dev/null +++ b/supportlibs/libsam3/examples/sam3a/test_ss.c @@ -0,0 +1,236 @@ +/* This program is free software. It comes without any warranty, to + * the extent permitted by applicable law. You can redistribute it + * and/or modify it under the terms of the Do What The Fuck You Want + * To Public License, Version 2, as published by Sam Hocevar. See + * http://sam.zoy.org/wtfpl/COPYING for more details. + * + * I2P-Bote: + * 5m77dFKGEq6~7jgtrfw56q3t~SmfwZubmGdyOLQOPoPp8MYwsZ~pfUCwud6LB1EmFxkm4C3CGlzq-hVs9WnhUV + * we are the Borg. */ +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../libsam3a/libsam3a.h" + +//////////////////////////////////////////////////////////////////////////////// +#define KEYFILE "streams.key" + +//////////////////////////////////////////////////////////////////////////////// +typedef struct { + char *str; + int strsize; + int strused; + int doQuit; +} ConnData; + +static void cdAppendChar(ConnData *d, char ch) { + if (d->strused + 1 >= d->strsize) { + // fuck errors + d->strsize = d->strused + 1024; + d->str = realloc(d->str, d->strsize + 1); + } + d->str[d->strused++] = ch; + d->str[d->strused] = 0; +} + +//////////////////////////////////////////////////////////////////////////////// +static void ccbError(Sam3AConnection *ct) { + fprintf(stderr, + "\n===============================\nCONNECTION_ERROR: " + "[%s]\n===============================\n", + ct->error); +} + +static void ccbDisconnected(Sam3AConnection *ct) { + fprintf(stderr, "\n===============================\nCONNECTION_" + "DISCONNECTED\n===============================\n"); +} + +static void ccbConnected(Sam3AConnection *ct) { + fprintf(stderr, "\n===============================\nCONNECTION_CONNECTED\n===" + "============================\n"); + // sam3aCancelConnection(ct); // cbSent() will not be called +} + +static void ccbAccepted(Sam3AConnection *ct) { + fprintf(stderr, "\n===============================\nCONNECTION_ACCEPTED\n====" + "===========================\n"); + fprintf(stderr, "FROM: %s\n===============================\n", ct->destkey); +} + +static void ccbSent(Sam3AConnection *ct) { + ConnData *d = (ConnData *)ct->udata; + // + fprintf(stderr, "\n===============================\nCONNECTION_WANTBYTES\n===" + "============================\n"); + if (d->doQuit) { + sam3aCancelSession(ct->ses); // hehe + } +} + +static void ccbRead(Sam3AConnection *ct, const void *buf, int bufsize) { + const char *b = (const char *)buf; + ConnData *d = (ConnData *)ct->udata; + // + fprintf(stderr, + "\n===============================\nCONNECTION_GOTBYTES " + "(%d)\n===============================\n", + bufsize); + while (bufsize > 0) { + cdAppendChar(ct->udata, *b); + if (*b == '\n') { + fprintf(stderr, "cmd: %s", d->str); + if (strcasecmp(d->str, "quit\n") == 0) + d->doQuit = 1; + if (sam3aSend(ct, d->str, -1) < 0) { + // sam3aCancelConnection(ct); // hehe + sam3aCancelSession(ct->ses); // hehe + return; + } + d->str[0] = 0; + d->strused = 0; + } + ++b; + --bufsize; + } +} + +static void ccbDestroy(Sam3AConnection *ct) { + fprintf(stderr, "\n===============================\nCONNECTION_DESTROY\n=====" + "==========================\n"); + if (ct->udata != NULL) { + ConnData *d = (ConnData *)ct->udata; + // + if (d->str != NULL) + free(d->str); + free(d); + } +} + +static const Sam3AConnectionCallbacks ccb = { + .cbError = ccbError, + .cbDisconnected = ccbDisconnected, + .cbConnected = ccbConnected, + .cbAccepted = ccbAccepted, + .cbSent = ccbSent, + .cbRead = ccbRead, + .cbDestroy = ccbDestroy, +}; + +//////////////////////////////////////////////////////////////////////////////// +static void scbError(Sam3ASession *ses) { + fprintf(stderr, + "\n===============================\nSESION_ERROR: " + "[%s]\n===============================\n", + ses->error); +} + +static void scbCreated(Sam3ASession *ses) { + FILE *fl; + Sam3AConnection *conn; + // + fprintf(stderr, "\n===============================\nSESION_CREATED\n"); + fprintf(stderr, "\rPRIV: %s\n", ses->privkey); + fprintf(stderr, "\nPUB: %s\n===============================\n", ses->pubkey); + // + fl = fopen(KEYFILE, "wb"); + // + if (fl == NULL) { + fprintf(stderr, "ERROR: CAN'T CREATE KEY FILE!\n"); + sam3aCancelSession(ses); + return; + } + if (fwrite(ses->pubkey, 516, 1, fl) != 1) { + fprintf(stderr, "ERROR: CAN'T WRITE KEY FILE!\n"); + fclose(fl); + sam3aCancelSession(ses); + return; + } + fclose(fl); + if ((conn = sam3aStreamAccept(ses, &ccb)) == NULL) { + fprintf(stderr, "ERROR: CAN'T CREATE CONNECTION!\n"); + sam3aCancelSession(ses); + return; + } + // + conn->udata = calloc(1, sizeof(ConnData)); + fprintf(stderr, "GOON: accepting connection...\n"); +} + +static void scbDisconnected(Sam3ASession *ses) { + fprintf(stderr, "\n===============================\nSESION_DISCONNECTED\n====" + "===========================\n"); +} + +static void scbDGramRead(Sam3ASession *ses, const void *buf, int bufsize) { + fprintf(stderr, "\n===============================\nSESION_DATAGRAM_READ\n===" + "============================\n"); +} + +static void scbDestroy(Sam3ASession *ses) { + fprintf(stderr, "\n===============================\nSESION_DESTROYED\n=======" + "========================\n"); +} + +static const Sam3ASessionCallbacks scb = { + .cbError = scbError, + .cbCreated = scbCreated, + .cbDisconnected = scbDisconnected, + .cbDatagramRead = scbDGramRead, + .cbDestroy = scbDestroy, +}; + +//////////////////////////////////////////////////////////////////////////////// +#define HOST SAM3A_HOST_DEFAULT +//#define HOST "google.com" + +int main(int argc, char *argv[]) { + Sam3ASession ses; + // + libsam3a_debug = 0; + // + if (sam3aCreateSession(&ses, &scb, HOST, SAM3A_PORT_DEFAULT, + SAM3A_DESTINATION_TRANSIENT, + SAM3A_SESSION_STREAM) < 0) { + fprintf(stderr, "FATAL: can't create main session!\n"); + return 1; + } + // + while (sam3aIsActiveSession(&ses)) { + fd_set rds, wrs; + int res, maxfd = 0; + struct timeval to; + // + FD_ZERO(&rds); + FD_ZERO(&wrs); + if (sam3aIsActiveSession(&ses) && + (maxfd = sam3aAddSessionToFDS(&ses, -1, &rds, &wrs)) < 0) + break; + sam3ams2timeval(&to, 1000); + res = select(maxfd + 1, &rds, &wrs, NULL, &to); + if (res < 0) { + if (errno == EINTR) + continue; + fprintf(stderr, "FATAL: select() error!\n"); + break; + } + if (res == 0) { + fprintf(stdout, "."); + fflush(stdout); + } else { + if (sam3aIsActiveSession(&ses)) + sam3aProcessSessionIO(&ses, &rds, &wrs); + } + } + // + sam3aCloseSession(&ses); + // + return 0; +} diff --git a/supportlibs/libsam3/src/ext/tinytest.c b/supportlibs/libsam3/src/ext/tinytest.c new file mode 100644 index 000000000..252f03051 --- /dev/null +++ b/supportlibs/libsam3/src/ext/tinytest.c @@ -0,0 +1,466 @@ +/* tinytest.c -- Copyright 2009-2012 Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifdef TINYTEST_LOCAL +#include "tinytest_local.h" +#endif + +#include +#include +#include +#include + +#ifndef NO_FORKING + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif + +#if defined(__APPLE__) && defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) +#if (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1060 && \ + __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 1070) +/* Workaround for a stupid bug in OSX 10.6 */ +#define FORK_BREAKS_GCOV +#include +#endif +#endif + +#endif /* !NO_FORKING */ + +#ifndef __GNUC__ +#define __attribute__(x) +#endif + +#include "tinytest.h" +#include "tinytest_macros.h" + +#define LONGEST_TEST_NAME 16384 + +static int in_tinytest_main = 0; /**< true if we're in tinytest_main().*/ +static int n_ok = 0; /**< Number of tests that have passed */ +static int n_bad = 0; /**< Number of tests that have failed. */ +static int n_skipped = 0; /**< Number of tests that have been skipped. */ + +static int opt_forked = 0; /**< True iff we're called from inside a win32 fork*/ +static int opt_nofork = 0; /**< Suppress calls to fork() for debugging. */ +static int opt_verbosity = 1; /**< -==quiet,0==terse,1==normal,2==verbose */ +const char *verbosity_flag = ""; + +const struct testlist_alias_t *cfg_aliases = NULL; + +enum outcome { SKIP = 2, OK = 1, FAIL = 0 }; +static enum outcome cur_test_outcome = FAIL; +const char *cur_test_prefix = NULL; /**< prefix of the current test group */ +/** Name of the current test, if we haven't logged is yet. Used for --quiet */ +const char *cur_test_name = NULL; + +#ifdef _WIN32 +/* Copy of argv[0] for win32. */ +static char commandname[MAX_PATH + 1]; +#endif + +static void usage(struct testgroup_t *groups, int list_groups) + __attribute__((noreturn)); +static int process_test_option(struct testgroup_t *groups, const char *test); + +static enum outcome testcase_run_bare_(const struct testcase_t *testcase) { + void *env = NULL; + enum outcome outcome; + if (testcase->setup) { + env = testcase->setup->setup_fn(testcase); + if (!env) + return FAIL; + else if (env == (void *)TT_SKIP) + return SKIP; + } + + cur_test_outcome = OK; + testcase->fn(env); + outcome = cur_test_outcome; + + if (testcase->setup) { + if (testcase->setup->cleanup_fn(testcase, env) == 0) + outcome = FAIL; + } + + return outcome; +} + +#define MAGIC_EXITCODE 42 + +#ifndef NO_FORKING + +static enum outcome testcase_run_forked_(const struct testgroup_t *group, + const struct testcase_t *testcase) { +#ifdef _WIN32 + /* Fork? On Win32? How primitive! We'll do what the smart kids do: + we'll invoke our own exe (whose name we recall from the command + line) with a command line that tells it to run just the test we + want, and this time without forking. + + (No, threads aren't an option. The whole point of forking is to + share no state between tests.) + */ + int ok; + char buffer[LONGEST_TEST_NAME + 256]; + STARTUPINFOA si; + PROCESS_INFORMATION info; + DWORD exitcode; + + if (!in_tinytest_main) { + printf("\nERROR. On Windows, testcase_run_forked_ must be" + " called from within tinytest_main.\n"); + abort(); + } + if (opt_verbosity > 0) + printf("[forking] "); + + snprintf(buffer, sizeof(buffer), "%s --RUNNING-FORKED %s %s%s", commandname, + verbosity_flag, group->prefix, testcase->name); + + memset(&si, 0, sizeof(si)); + memset(&info, 0, sizeof(info)); + si.cb = sizeof(si); + + ok = CreateProcessA(commandname, buffer, NULL, NULL, 0, 0, NULL, NULL, &si, + &info); + if (!ok) { + printf("CreateProcess failed!\n"); + return 0; + } + WaitForSingleObject(info.hProcess, INFINITE); + GetExitCodeProcess(info.hProcess, &exitcode); + CloseHandle(info.hProcess); + CloseHandle(info.hThread); + if (exitcode == 0) + return OK; + else if (exitcode == MAGIC_EXITCODE) + return SKIP; + else + return FAIL; +#else + int outcome_pipe[2]; + pid_t pid; + (void)group; + + if (pipe(outcome_pipe)) + perror("opening pipe"); + + if (opt_verbosity > 0) + printf("[forking] "); + pid = fork(); +#ifdef FORK_BREAKS_GCOV + vproc_transaction_begin(0); +#endif + if (!pid) { + /* child. */ + int test_r, write_r; + char b[1]; + close(outcome_pipe[0]); + test_r = testcase_run_bare_(testcase); + assert(0 <= (int)test_r && (int)test_r <= 2); + b[0] = "NYS"[test_r]; + write_r = (int)write(outcome_pipe[1], b, 1); + if (write_r != 1) { + perror("write outcome to pipe"); + exit(1); + } + exit(0); + return FAIL; /* unreachable */ + } else { + /* parent */ + int status, r; + char b[1]; + /* Close this now, so that if the other side closes it, + * our read fails. */ + close(outcome_pipe[1]); + r = (int)read(outcome_pipe[0], b, 1); + if (r == 0) { + printf("[Lost connection!] "); + return FAIL; + } else if (r != 1) { + perror("read outcome from pipe"); + } + waitpid(pid, &status, 0); + close(outcome_pipe[0]); + return b[0] == 'Y' ? OK : (b[0] == 'S' ? SKIP : FAIL); + } +#endif +} + +#endif /* !NO_FORKING */ + +int testcase_run_one(const struct testgroup_t *group, + const struct testcase_t *testcase) { + enum outcome outcome; + + if (testcase->flags & (TT_SKIP | TT_OFF_BY_DEFAULT)) { + if (opt_verbosity > 0) + printf("%s%s: %s\n", group->prefix, testcase->name, + (testcase->flags & TT_SKIP) ? "SKIPPED" : "DISABLED"); + ++n_skipped; + return SKIP; + } + + if (opt_verbosity > 0 && !opt_forked) { + printf("%s%s: ", group->prefix, testcase->name); + } else { + if (opt_verbosity == 0) + printf("."); + cur_test_prefix = group->prefix; + cur_test_name = testcase->name; + } + +#ifndef NO_FORKING + if ((testcase->flags & TT_FORK) && !(opt_forked || opt_nofork)) { + outcome = testcase_run_forked_(group, testcase); + } else { +#else + { +#endif + outcome = testcase_run_bare_(testcase); + } + + if (outcome == OK) { + ++n_ok; + if (opt_verbosity > 0 && !opt_forked) + puts(opt_verbosity == 1 ? "OK" : ""); + } else if (outcome == SKIP) { + ++n_skipped; + if (opt_verbosity > 0 && !opt_forked) + puts("SKIPPED"); + } else { + ++n_bad; + if (!opt_forked) + printf("\n [%s FAILED]\n", testcase->name); + } + + if (opt_forked) { + exit(outcome == OK ? 0 : (outcome == SKIP ? MAGIC_EXITCODE : 1)); + return 1; /* unreachable */ + } else { + return (int)outcome; + } +} + +int tinytest_set_flag_(struct testgroup_t *groups, const char *arg, int set, + unsigned long flag) { + int i, j; + size_t length = LONGEST_TEST_NAME; + char fullname[LONGEST_TEST_NAME]; + int found = 0; + if (strstr(arg, "..")) + length = strstr(arg, "..") - arg; + for (i = 0; groups[i].prefix; ++i) { + for (j = 0; groups[i].cases[j].name; ++j) { + struct testcase_t *testcase = &groups[i].cases[j]; + snprintf(fullname, sizeof(fullname), "%s%s", groups[i].prefix, + testcase->name); + if (!flag) { /* Hack! */ + printf(" %s", fullname); + if (testcase->flags & TT_OFF_BY_DEFAULT) + puts(" (Off by default)"); + else if (testcase->flags & TT_SKIP) + puts(" (DISABLED)"); + else + puts(""); + } + if (!strncmp(fullname, arg, length)) { + if (set) + testcase->flags |= flag; + else + testcase->flags &= ~flag; + ++found; + } + } + } + return found; +} + +static void usage(struct testgroup_t *groups, int list_groups) { + puts("Options are: [--verbose|--quiet|--terse] [--no-fork]"); + puts(" Specify tests by name, or using a prefix ending with '..'"); + puts(" To skip a test, prefix its name with a colon."); + puts(" To enable a disabled test, prefix its name with a plus."); + puts(" Use --list-tests for a list of tests."); + if (list_groups) { + puts("Known tests are:"); + tinytest_set_flag_(groups, "..", 1, 0); + } + exit(0); +} + +static int process_test_alias(struct testgroup_t *groups, const char *test) { + int i, j, n, r; + for (i = 0; cfg_aliases && cfg_aliases[i].name; ++i) { + if (!strcmp(cfg_aliases[i].name, test)) { + n = 0; + for (j = 0; cfg_aliases[i].tests[j]; ++j) { + r = process_test_option(groups, cfg_aliases[i].tests[j]); + if (r < 0) + return -1; + n += r; + } + return n; + } + } + printf("No such test alias as @%s!", test); + return -1; +} + +static int process_test_option(struct testgroup_t *groups, const char *test) { + int flag = TT_ENABLED_; + int n = 0; + if (test[0] == '@') { + return process_test_alias(groups, test + 1); + } else if (test[0] == ':') { + ++test; + flag = TT_SKIP; + } else if (test[0] == '+') { + ++test; + ++n; + if (!tinytest_set_flag_(groups, test, 0, TT_OFF_BY_DEFAULT)) { + printf("No such test as %s!\n", test); + return -1; + } + } else { + ++n; + } + if (!tinytest_set_flag_(groups, test, 1, flag)) { + printf("No such test as %s!\n", test); + return -1; + } + return n; +} + +void tinytest_set_aliases(const struct testlist_alias_t *aliases) { + cfg_aliases = aliases; +} + +int tinytest_main(int c, const char **v, struct testgroup_t *groups) { + int i, j, n = 0; + +#ifdef _WIN32 + const char *sp = strrchr(v[0], '.'); + const char *extension = ""; + if (!sp || stricmp(sp, ".exe")) + extension = ".exe"; /* Add an exe so CreateProcess will work */ + snprintf(commandname, sizeof(commandname), "%s%s", v[0], extension); + commandname[MAX_PATH] = '\0'; +#endif + for (i = 1; i < c; ++i) { + if (v[i][0] == '-') { + if (!strcmp(v[i], "--RUNNING-FORKED")) { + opt_forked = 1; + } else if (!strcmp(v[i], "--no-fork")) { + opt_nofork = 1; + } else if (!strcmp(v[i], "--quiet")) { + opt_verbosity = -1; + verbosity_flag = "--quiet"; + } else if (!strcmp(v[i], "--verbose")) { + opt_verbosity = 2; + verbosity_flag = "--verbose"; + } else if (!strcmp(v[i], "--terse")) { + opt_verbosity = 0; + verbosity_flag = "--terse"; + } else if (!strcmp(v[i], "--help")) { + usage(groups, 0); + } else if (!strcmp(v[i], "--list-tests")) { + usage(groups, 1); + } else { + printf("Unknown option %s. Try --help\n", v[i]); + return -1; + } + } else { + int r = process_test_option(groups, v[i]); + if (r < 0) + return -1; + n += r; + } + } + if (!n) + tinytest_set_flag_(groups, "..", 1, TT_ENABLED_); + +#ifdef _IONBF + setvbuf(stdout, NULL, _IONBF, 0); +#endif + + ++in_tinytest_main; + for (i = 0; groups[i].prefix; ++i) + for (j = 0; groups[i].cases[j].name; ++j) + if (groups[i].cases[j].flags & TT_ENABLED_) + testcase_run_one(&groups[i], &groups[i].cases[j]); + + --in_tinytest_main; + + if (opt_verbosity == 0) + puts(""); + + if (n_bad) + printf("%d/%d TESTS FAILED. (%d skipped)\n", n_bad, n_bad + n_ok, + n_skipped); + else if (opt_verbosity >= 1) + printf("%d tests ok. (%d skipped)\n", n_ok, n_skipped); + + return (n_bad == 0) ? 0 : 1; +} + +int tinytest_get_verbosity_(void) { return opt_verbosity; } + +void tinytest_set_test_failed_(void) { + if (opt_verbosity <= 0 && cur_test_name) { + if (opt_verbosity == 0) + puts(""); + printf("%s%s: ", cur_test_prefix, cur_test_name); + cur_test_name = NULL; + } + cur_test_outcome = FAIL; +} + +void tinytest_set_test_skipped_(void) { + if (cur_test_outcome == OK) + cur_test_outcome = SKIP; +} + +char *tinytest_format_hex_(const void *val_, unsigned long len) { + const unsigned char *val = (unsigned char *)val_; + char *result, *cp; + size_t i; + + if (!val) + return strdup("null"); + if (!(result = (char *)malloc(len * 2 + 1))) + return strdup(""); + cp = result; + for (i = 0; i < len; ++i) { + *cp++ = "0123456789ABCDEF"[val[i] >> 4]; + *cp++ = "0123456789ABCDEF"[val[i] & 0x0f]; + } + *cp = 0; + return result; +} diff --git a/supportlibs/libsam3/src/ext/tinytest.h b/supportlibs/libsam3/src/ext/tinytest.h new file mode 100644 index 000000000..bdddcbb54 --- /dev/null +++ b/supportlibs/libsam3/src/ext/tinytest.h @@ -0,0 +1,104 @@ +/* tinytest.h -- Copyright 2009-2012 Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TINYTEST_H_INCLUDED_ +#define TINYTEST_H_INCLUDED_ + +/** Flag for a test that needs to run in a subprocess. */ +#define TT_FORK (1 << 0) +/** Runtime flag for a test we've decided to skip. */ +#define TT_SKIP (1 << 1) +/** Internal runtime flag for a test we've decided to run. */ +#define TT_ENABLED_ (1 << 2) +/** Flag for a test that's off by default. */ +#define TT_OFF_BY_DEFAULT (1 << 3) +/** If you add your own flags, make them start at this point. */ +#define TT_FIRST_USER_FLAG (1 << 4) + +typedef void (*testcase_fn)(void *); + +struct testcase_t; + +/** Functions to initialize/teardown a structure for a testcase. */ +struct testcase_setup_t { + /** Return a new structure for use by a given testcase. */ + void *(*setup_fn)(const struct testcase_t *); + /** Clean/free a structure from setup_fn. Return 1 if ok, 0 on err. */ + int (*cleanup_fn)(const struct testcase_t *, void *); +}; + +/** A single test-case that you can run. */ +struct testcase_t { + const char *name; /**< An identifier for this case. */ + testcase_fn fn; /**< The function to run to implement this case. */ + unsigned long flags; /**< Bitfield of TT_* flags. */ + const struct testcase_setup_t *setup; /**< Optional setup/cleanup fns*/ + void *setup_data; /**< Extra data usable by setup function */ +}; +#define END_OF_TESTCASES \ + { NULL, NULL, 0, NULL, NULL } + +/** A group of tests that are selectable together. */ +struct testgroup_t { + const char *prefix; /**< Prefix to prepend to testnames. */ + struct testcase_t *cases; /** Array, ending with END_OF_TESTCASES */ +}; +#define END_OF_GROUPS \ + { NULL, NULL } + +struct testlist_alias_t { + const char *name; + const char **tests; +}; +#define END_OF_ALIASES \ + { NULL, NULL } + +/** Implementation: called from a test to indicate failure, before logging. */ +void tinytest_set_test_failed_(void); +/** Implementation: called from a test to indicate that we're skipping. */ +void tinytest_set_test_skipped_(void); +/** Implementation: return 0 for quiet, 1 for normal, 2 for loud. */ +int tinytest_get_verbosity_(void); +/** Implementation: Set a flag on tests matching a name; returns number + * of tests that matched. */ +int tinytest_set_flag_(struct testgroup_t *, const char *, int set, + unsigned long); +/** Implementation: Put a chunk of memory into hex. */ +char *tinytest_format_hex_(const void *, unsigned long); + +/** Set all tests in 'groups' matching the name 'named' to be skipped. */ +#define tinytest_skip(groups, named) \ + tinytest_set_flag_(groups, named, 1, TT_SKIP) + +/** Run a single testcase in a single group. */ +int testcase_run_one(const struct testgroup_t *, const struct testcase_t *); + +void tinytest_set_aliases(const struct testlist_alias_t *aliases); + +/** Run a set of testcases from an END_OF_GROUPS-terminated array of groups, + as selected from the command line. */ +int tinytest_main(int argc, const char **argv, struct testgroup_t *groups); + +#endif diff --git a/supportlibs/libsam3/src/ext/tinytest_macros.h b/supportlibs/libsam3/src/ext/tinytest_macros.h new file mode 100644 index 000000000..817734020 --- /dev/null +++ b/supportlibs/libsam3/src/ext/tinytest_macros.h @@ -0,0 +1,219 @@ +/* tinytest_macros.h -- Copyright 2009-2012 Nick Mathewson + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TINYTEST_MACROS_H_INCLUDED_ +#define TINYTEST_MACROS_H_INCLUDED_ + +/* Helpers for defining statement-like macros */ +#define TT_STMT_BEGIN do { +#define TT_STMT_END \ + } \ + while (0) + +/* Redefine this if your test functions want to abort with something besides + * "goto end;" */ +#ifndef TT_EXIT_TEST_FUNCTION +#define TT_EXIT_TEST_FUNCTION \ + TT_STMT_BEGIN goto end; \ + TT_STMT_END +#endif + +/* Redefine this if you want to note success/failure in some different way. */ +#ifndef TT_DECLARE +#define TT_DECLARE(prefix, args) \ + TT_STMT_BEGIN \ + printf("\n %s %s:%d: ", prefix, __FILE__, __LINE__); \ + printf args; \ + TT_STMT_END +#endif + +/* Announce a failure. Args are parenthesized printf args. */ +#define TT_GRIPE(args) TT_DECLARE("FAIL", args) + +/* Announce a non-failure if we're verbose. */ +#define TT_BLATHER(args) \ + TT_STMT_BEGIN \ + if (tinytest_get_verbosity_() > 1) \ + TT_DECLARE(" OK", args); \ + TT_STMT_END + +#define TT_DIE(args) \ + TT_STMT_BEGIN \ + tinytest_set_test_failed_(); \ + TT_GRIPE(args); \ + TT_EXIT_TEST_FUNCTION; \ + TT_STMT_END + +#define TT_FAIL(args) \ + TT_STMT_BEGIN \ + tinytest_set_test_failed_(); \ + TT_GRIPE(args); \ + TT_STMT_END + +/* Fail and abort the current test for the reason in msg */ +#define tt_abort_printf(msg) TT_DIE(msg) +#define tt_abort_perror(op) \ + TT_DIE(("%s: %s [%d]", (op), strerror(errno), errno)) +#define tt_abort_msg(msg) TT_DIE(("%s", msg)) +#define tt_abort() TT_DIE(("%s", "(Failed.)")) + +/* Fail but do not abort the current test for the reason in msg. */ +#define tt_fail_printf(msg) TT_FAIL(msg) +#define tt_fail_perror(op) \ + TT_FAIL(("%s: %s [%d]", (op), strerror(errno), errno)) +#define tt_fail_msg(msg) TT_FAIL(("%s", msg)) +#define tt_fail() TT_FAIL(("%s", "(Failed.)")) + +/* End the current test, and indicate we are skipping it. */ +#define tt_skip() \ + TT_STMT_BEGIN \ + tinytest_set_test_skipped_(); \ + TT_EXIT_TEST_FUNCTION; \ + TT_STMT_END + +#define tt_want_(b, msg, fail) \ + TT_STMT_BEGIN \ + if (!(b)) { \ + tinytest_set_test_failed_(); \ + TT_GRIPE(("%s", msg)); \ + fail; \ + } else { \ + TT_BLATHER(("%s", msg)); \ + } \ + TT_STMT_END + +/* Assert b, but do not stop the test if b fails. Log msg on failure. */ +#define tt_want_msg(b, msg) tt_want_(b, msg, ); + +/* Assert b and stop the test if b fails. Log msg on failure. */ +#define tt_assert_msg(b, msg) tt_want_(b, msg, TT_EXIT_TEST_FUNCTION); + +/* Assert b, but do not stop the test if b fails. */ +#define tt_want(b) tt_want_msg((b), "want(" #b ")") +/* Assert b, and stop the test if b fails. */ +#define tt_assert(b) tt_assert_msg((b), "assert(" #b ")") + +#define tt_assert_test_fmt_type(a, b, str_test, type, test, printf_type, \ + printf_fmt, setup_block, cleanup_block, \ + die_on_fail) \ + TT_STMT_BEGIN \ + type val1_ = (a); \ + type val2_ = (b); \ + int tt_status_ = (test); \ + if (!tt_status_ || tinytest_get_verbosity_() > 1) { \ + printf_type print_; \ + printf_type print1_; \ + printf_type print2_; \ + type value_ = val1_; \ + setup_block; \ + print1_ = print_; \ + value_ = val2_; \ + setup_block; \ + print2_ = print_; \ + TT_DECLARE(tt_status_ ? " OK" : "FAIL", \ + ("assert(%s): " printf_fmt " vs " printf_fmt, str_test, \ + print1_, print2_)); \ + print_ = print1_; \ + cleanup_block; \ + print_ = print2_; \ + cleanup_block; \ + if (!tt_status_) { \ + tinytest_set_test_failed_(); \ + die_on_fail; \ + } \ + } \ + TT_STMT_END + +#define tt_assert_test_type(a, b, str_test, type, test, fmt, die_on_fail) \ + tt_assert_test_fmt_type(a, b, str_test, type, test, type, fmt, \ + { print_ = value_; }, {}, die_on_fail) + +#define tt_assert_test_type_opt(a, b, str_test, type, test, fmt, die_on_fail) \ + tt_assert_test_fmt_type(a, b, str_test, type, test, type, fmt, \ + { print_ = value_ ? value_ : ""; }, {}, \ + die_on_fail) + +/* Helper: assert that a op b, when cast to type. Format the values with + * printf format fmt on failure. */ +#define tt_assert_op_type(a, op, b, type, fmt) \ + tt_assert_test_type(a, b, #a " " #op " " #b, type, (val1_ op val2_), fmt, \ + TT_EXIT_TEST_FUNCTION) + +#define tt_int_op(a, op, b) \ + tt_assert_test_type(a, b, #a " " #op " " #b, long, (val1_ op val2_), "%ld", \ + TT_EXIT_TEST_FUNCTION) + +#define tt_uint_op(a, op, b) \ + tt_assert_test_type(a, b, #a " " #op " " #b, unsigned long, \ + (val1_ op val2_), "%lu", TT_EXIT_TEST_FUNCTION) + +#define tt_ptr_op(a, op, b) \ + tt_assert_test_type(a, b, #a " " #op " " #b, const void *, (val1_ op val2_), \ + "%p", TT_EXIT_TEST_FUNCTION) + +#define tt_str_op(a, op, b) \ + tt_assert_test_type_opt(a, b, #a " " #op " " #b, const char *, \ + (val1_ && val2_ && strcmp(val1_, val2_) op 0), \ + "<%s>", TT_EXIT_TEST_FUNCTION) + +#define tt_mem_op(expr1, op, expr2, len) \ + tt_assert_test_fmt_type( \ + expr1, expr2, #expr1 " " #op " " #expr2, const void *, \ + (val1_ && val2_ && memcmp(val1_, val2_, len) op 0), char *, "%s", \ + { print_ = tinytest_format_hex_(value_, (len)); }, \ + { \ + if (print_) \ + free(print_); \ + }, \ + TT_EXIT_TEST_FUNCTION); + +#define tt_want_int_op(a, op, b) \ + tt_assert_test_type(a, b, #a " " #op " " #b, long, (val1_ op val2_), "%ld", \ + (void)0) + +#define tt_want_uint_op(a, op, b) \ + tt_assert_test_type(a, b, #a " " #op " " #b, unsigned long, \ + (val1_ op val2_), "%lu", (void)0) + +#define tt_want_ptr_op(a, op, b) \ + tt_assert_test_type(a, b, #a " " #op " " #b, const void *, (val1_ op val2_), \ + "%p", (void)0) + +#define tt_want_str_op(a, op, b) \ + tt_assert_test_type(a, b, #a " " #op " " #b, const char *, \ + (strcmp(val1_, val2_) op 0), "<%s>", (void)0) + +#define tt_want_mem_op(expr1, op, expr2, len) \ + tt_assert_test_fmt_type( \ + expr1, expr2, #expr1 " " #op " " #expr2, const void *, \ + (val1_ && val2_ && memcmp(val1_, val2_, len) op 0), char *, "%s", \ + { print_ = tinytest_format_hex_(value_, (len)); }, \ + { \ + if (print_) \ + free(print_); \ + }, \ + (void)0); + +#endif diff --git a/supportlibs/libsam3/src/libsam3/libsam3.c b/supportlibs/libsam3/src/libsam3/libsam3.c new file mode 100644 index 000000000..367949a39 --- /dev/null +++ b/supportlibs/libsam3/src/libsam3/libsam3.c @@ -0,0 +1,1304 @@ +/* This program is free software. It comes without any warranty, to + * the extent permitted by applicable law. You can redistribute it + * and/or modify it under the terms of the Do What The Fuck You Want + * To Public License, Version 2, as published by Sam Hocevar. See + * http://sam.zoy.org/wtfpl/COPYING for more details. + * + * I2P-Bote: + * 5m77dFKGEq6~7jgtrfw56q3t~SmfwZubmGdyOLQOPoPp8MYwsZ~pfUCwud6LB1EmFxkm4C3CGlzq-hVs9WnhUV + * we are the Borg. */ +#include "libsam3.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +//////////////////////////////////////////////////////////////////////////////// +int libsam3_debug = 0; + +//////////////////////////////////////////////////////////////////////////////// +/* convert struct timeval to milliseconds */ +/* +static inline uint64_t timeval2ms (const struct timeval *tv) { + return ((uint64_t)tv->tv_sec)*1000+((uint64_t)tv->tv_usec)/1000; +} +*/ + +/* convert milliseconds to timeval struct */ +static inline void ms2timeval(struct timeval *tv, uint64_t ms) { + tv->tv_sec = ms / 1000; + tv->tv_usec = (ms % 1000) * 1000; +} + +int sam3tcpSetTimeoutSend(int fd, int timeoutms) { + if (fd >= 0 && timeoutms >= 0) { + struct timeval tv; + // + ms2timeval(&tv, timeoutms); + return (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0 ? -1 + : 0); + } + return -1; +} + +int sam3tcpSetTimeoutReceive(int fd, int timeoutms) { + if (fd >= 0 && timeoutms >= 0) { + struct timeval tv; + // + ms2timeval(&tv, timeoutms); + return (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0 ? -1 + : 0); + } + return -1; +} + +int sam3CheckValidKeyLength(const char *pubkey) { + if (strlen(pubkey) >= SAM3_PUBKEY_SIZE && + strlen(pubkey) <= SAM3_PUBKEY_SIZE + SAM3_CERT_SIZE) { + return 1; + } + return 0; +} + +int sam3tcpConnectIP(uint32_t ip, int port) { + struct sockaddr_in addr; + int fd, val = 1; + char ipstr[18]; + // + if (ip == 0 || ip == 0xffffffffUL || port < 1 || port > 65535) + return -1; + // + if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + if (libsam3_debug) + fprintf(stderr, "ERROR: can't create socket\n"); + return -1; + } + // + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = ip; + // + ipstr[0] = 0; + if (libsam3_debug) { + if (getnameinfo((struct sockaddr *)&addr, sizeof(struct sockaddr_in), ipstr, + sizeof(ipstr), NULL, 0, NI_NUMERICHOST) == 0) { + fprintf(stderr, "connecting to [%s:%d]...\n", ipstr, port); + } + } + // + setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)); + // + if (connect(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) < 0) { + if (libsam3_debug) + fprintf(stderr, "ERROR: can't connect\n"); + close(fd); + return -1; + } + // + if (libsam3_debug && ipstr[0]) + fprintf(stderr, "connected to [%s:%d]\n", ipstr, port); + // + return fd; +} + +/* returns fd or -1 */ +int sam3tcpConnect(const char *hostname, int port, uint32_t *ip) { + struct hostent *host = NULL; + // + if (hostname == NULL || !hostname[0] || port < 1 || port > 65535) + return -1; + // + host = gethostbyname(hostname); + if (host == NULL || host->h_name == NULL || !host->h_name[0]) { + if (libsam3_debug) + fprintf(stderr, "ERROR: can't resolve '%s'\n", hostname); + return -1; + } + // + if (libsam3_debug) { + char ipstr[18]; + // + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr = *((struct in_addr *)host->h_addr); + // + if (getnameinfo((struct sockaddr *)&addr, sizeof(struct sockaddr_in), ipstr, + sizeof(ipstr), NULL, 0, NI_NUMERICHOST) == 0) { + fprintf(stderr, "resolving: %s is [%s]...\n", hostname, ipstr); + } + } + // + if (ip != NULL) + *ip = ((struct in_addr *)host->h_addr)->s_addr; + return sam3tcpConnectIP(((struct in_addr *)host->h_addr)->s_addr, port); +} + +// <0: error; 0: ok +int sam3tcpDisconnect(int fd) { + if (fd >= 0) { + shutdown(fd, SHUT_RDWR); + return close(fd); + } + // + return -1; +} + +//////////////////////////////////////////////////////////////////////////////// +// <0: error; 0: ok +int sam3tcpSend(int fd, const void *buf, size_t bufSize) { + const char *c = (const char *)buf; + // + if (fd < 0 || (buf == NULL && bufSize > 0)) + return -1; + // + while (bufSize > 0) { + int wr = send(fd, c, bufSize, MSG_NOSIGNAL); + // + if (wr < 0 && errno == EINTR) + continue; // interrupted by signal + if (wr <= 0) + return -1; // either error or + c += wr; + bufSize -= wr; + } + // + return 0; +} + +/* <0: received (-res) bytes; read error */ +/* can return less that requesten bytes even if `allowPartial` is 0 when + * connection is closed */ +ssize_t sam3tcpReceiveEx(int fd, void *buf, size_t bufSize, int allowPartial) { + char *c = (char *)buf; + ssize_t total = 0; + // + if (fd < 0 || (buf == NULL && bufSize > 0)) + return -1; + // + while (bufSize > 0) { + int rd = recv(fd, c, bufSize, 0); + // + if (rd < 0 && errno == EINTR) + continue; // interrupted by signal + if (rd == 0) + return total; + if (rd < 0) + return -total; + c += rd; + total += rd; + bufSize -= rd; + if (allowPartial) + break; + } + // + return total; +} + +ssize_t sam3tcpReceive(int fd, void *buf, size_t bufSize) { + return sam3tcpReceiveEx(fd, buf, bufSize, 0); +} + +//////////////////////////////////////////////////////////////////////////////// +__attribute__((format(printf, 2, 3))) int sam3tcpPrintf(int fd, const char *fmt, + ...) { + int res; + char buf[1024], *p = buf; + int size = sizeof(buf) - 1; + // + for (;;) { + va_list ap; + char *np; + int n; + // + va_start(ap, fmt); + n = vsnprintf(p, size, fmt, ap); + va_end(ap); + // + if (n > -1 && n < size) + break; + if (n > -1) + size = n + 1; + else + size *= 2; + if (p == buf) { + if ((p = malloc(size + 4)) == NULL) + return -1; + } else { + if ((np = realloc(p, size + 4)) == NULL) { + free(p); + return -1; + } + p = np; + } + } + // + if (libsam3_debug) + fprintf(stderr, "SENDING: %s", p); + res = sam3tcpSend(fd, p, strlen(p)); + if (p != buf) + free(p); + return res; +} + +int sam3tcpReceiveStr(int fd, char *dest, size_t maxSize) { + char *d = dest; + // + if (maxSize < 1 || fd < 0 || dest == NULL) + return -1; + memset(dest, 0, maxSize); + while (maxSize > 1) { + char *e; + int rd = recv(fd, d, maxSize - 1, MSG_PEEK); + // + if (rd < 0 && errno == EINTR) + continue; // interrupted by signal + if (rd == 0) { + rd = recv(fd, d, 1, 0); + if (rd < 0 && errno == EINTR) + continue; // interrupted by signal + if (d[0] == '\n') { + d[0] = 0; // remove '\n' + return 0; + } + } else { + if (rd < 0) + return -1; // error or connection closed; alas + } + // check for EOL + d[maxSize - 1] = 0; + if ((e = strchr(d, '\n')) != NULL) { + rd = e - d + 1; // bytes to receive + if (sam3tcpReceive(fd, d, rd) < 0) + return -1; // alas + d[rd - 1] = 0; // remove '\n' + return 0; // done + } else { + // let's receive this part and go on + if (sam3tcpReceive(fd, d, rd) < 0) + return -1; // alas + maxSize -= rd; + d += rd; + } + } + // alas, the string is too big + return -1; +} + +//////////////////////////////////////////////////////////////////////////////// +int sam3udpSendToIP(uint32_t ip, int port, const void *buf, size_t bufSize) { + // TODO: ipv6 + struct sockaddr_in addr; + int fd, res; + // + if (buf == NULL || bufSize < 1) + return -1; + if (port < 1 || port > 65535) + port = 7655; + // + if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { + if (libsam3_debug) + fprintf(stderr, "ERROR: can't create socket\n"); + return -1; + } + // + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = ip; + // + res = sendto(fd, buf, bufSize, 0, (struct sockaddr *)&addr, sizeof(addr)); + // + if (res < 0) { + if (libsam3_debug) { + res = errno; + fprintf(stderr, "UDP ERROR (%d): %s\n", res, strerror(res)); + } + res = -1; + } else { + if (libsam3_debug) + fprintf(stderr, "UDP: %d bytes sent\n", res); + } + // + close(fd); + // + return (res >= 0 ? 0 : -1); +} + +int sam3udpSendTo(const char *hostname, int port, const void *buf, + size_t bufSize, uint32_t *ip) { + struct hostent *host = NULL; + // TODO: ipv6 + if (buf == NULL || bufSize < 1) + return -1; + if (hostname == NULL || !hostname[0]) + hostname = "localhost"; + if (port < 1 || port > 65535) + port = 7655; + // + host = gethostbyname(hostname); + if (host == NULL || host->h_name == NULL || !host->h_name[0]) { + if (libsam3_debug) + fprintf(stderr, "ERROR: can't resolve '%s'\n", hostname); + return -1; + } + // + if (ip != NULL) + *ip = ((struct in_addr *)host->h_addr)->s_addr; + return sam3udpSendToIP(((struct in_addr *)host->h_addr)->s_addr, port, buf, + bufSize); +} + +//////////////////////////////////////////////////////////////////////////////// +void sam3FreeFieldList(SAMFieldList *list) { + while (list != NULL) { + SAMFieldList *c = list; + // + list = list->next; + if (c->name != NULL) + free(c->name); + if (c->value != NULL) + free(c->value); + free(c); + } +} + +void sam3DumpFieldList(const SAMFieldList *list) { + for (; list != NULL; list = list->next) { + fprintf(stderr, "%s=[%s]\n", list->name, list->value); + } +} + +const char *sam3FindField(const SAMFieldList *list, const char *field) { + if (list != NULL && field != NULL) { + for (list = list->next; list != NULL; list = list->next) { + if (list->name != NULL && strcmp(field, list->name) == 0) + return list->value; + } + } + return NULL; +} + +static char *xstrdup(const char *s, int len) { + if (len >= 0) { + char *res = malloc(len + 1); + // + if (res != NULL) { + if (len > 0) + memcpy(res, s, len); + res[len] = 0; + } + // + return res; + } + // + return NULL; +} + +// returns NULL if there are no more tokens +static inline const char *xstrtokend(const char *s) { + while (*s && isspace(*s)) + ++s; + // + if (*s) { + char qch = 0; + // + while (*s) { + if (*s == qch) { + qch = 0; + } else if (*s == '"') { + qch = *s; + } else if (qch) { + if (*s == '\\' && s[1]) + ++s; + } else if (isspace(*s)) { + break; + } + ++s; + } + } else { + s = NULL; + } + // + return s; +} + +SAMFieldList *sam3ParseReply(const char *rep) { + SAMFieldList *first = NULL, *last, *c; + const char *p = rep, *e, *e1; + // + // first 2 words + while (*p && isspace(*p)) + ++p; + if ((e = xstrtokend(p)) == NULL) + return NULL; + if ((e1 = xstrtokend(e)) == NULL) + return NULL; + // + if ((first = last = c = malloc(sizeof(SAMFieldList))) == NULL) + return NULL; + c->next = NULL; + c->name = c->value = NULL; + if ((c->name = xstrdup(p, e - p)) == NULL) + goto error; + while (*e && isspace(*e)) + ++e; + if ((c->value = xstrdup(e, e1 - e)) == NULL) + goto error; + // + p = e1; + while (*p) { + while (*p && isspace(*p)) + ++p; + if ((e = xstrtokend(p)) == NULL) + break; // no more tokens + // + if (libsam3_debug) + fprintf(stderr, "<%s>\n", p); + // + if ((c = malloc(sizeof(SAMFieldList))) == NULL) + return NULL; + c->next = NULL; + c->name = c->value = NULL; + last->next = c; + last = c; + // + if ((e1 = memchr(p, '=', e - p)) != NULL) { + // key=value + if ((c->name = xstrdup(p, e1 - p)) == NULL) + goto error; + if ((c->value = xstrdup(e1 + 1, e - e1 - 1)) == NULL) + goto error; + } else { + // only key (there is no such replies in SAMv3, but... + if ((c->name = xstrdup(p, e - p)) == NULL) + goto error; + if ((c->value = strdup("")) == NULL) + goto error; + } + p = e; + } + // + if (libsam3_debug) + sam3DumpFieldList(first); + // + return first; +error: + sam3FreeFieldList(first); + return NULL; +} + +// NULL: error; else: list of fields +// first item is always 2-word reply, with first word in name and second in +// value +SAMFieldList *sam3ReadReply(int fd) { + char rep[2048]; // should be enough for any reply + // + if (sam3tcpReceiveStr(fd, rep, sizeof(rep)) < 0) + return NULL; + if (libsam3_debug) + fprintf(stderr, "SAM REPLY: [%s]\n", rep); + return sam3ParseReply(rep); +} + +// example: +// r0: 'HELLO' +// r1: 'REPLY' +// field: NULL or 'RESULT' +// VALUE: NULL or 'OK' +// returns bool +int sam3IsGoodReply(const SAMFieldList *list, const char *r0, const char *r1, + const char *field, const char *value) { + if (list != NULL && list->name != NULL && list->value != NULL) { + if (r0 != NULL && strcmp(r0, list->name) != 0) + return 0; + if (r1 != NULL && strcmp(r1, list->value) != 0) + return 0; + if (field != NULL) { + for (list = list->next; list != NULL; list = list->next) { + if (list->name == NULL || list->value == NULL) + return 0; // invalid list, heh + if (strcmp(field, list->name) == 0) { + if (value != NULL && strcmp(value, list->value) != 0) + return 0; + return 1; + } + } + } + return 1; + } + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +// by Bob Jenkins +// public domain +// http://burtleburtle.net/bob/rand/smallprng.html +// +//////////////////////////////////////////////////////////////////////////////// +typedef struct { + uint32_t a, b, c, d; +} BJRandCtx; + +#define BJPRNG_ROT(x, k) (((x) << (k)) | ((x) >> (32 - (k)))) + +static uint32_t bjprngRand(BJRandCtx *x) { + uint32_t e; + /* original: + e = x->a-BJPRNG_ROT(x->b, 27); + x->a = x->b^BJPRNG_ROT(x->c, 17); + x->b = x->c+x->d; + x->c = x->d+e; + x->d = e+x->a; + */ + /* better, but slower at least in idiotic m$vc */ + e = x->a - BJPRNG_ROT(x->b, 23); + x->a = x->b ^ BJPRNG_ROT(x->c, 16); + x->b = x->c + BJPRNG_ROT(x->d, 11); + x->c = x->d + e; + x->d = e + x->a; + // + return x->d; +} + +static void bjprngInit(BJRandCtx *x, uint32_t seed) { + x->a = 0xf1ea5eed; + x->b = x->c = x->d = seed; + for (int i = 0; i < 20; ++i) + bjprngRand(x); +} + +static inline uint32_t hashint(uint32_t a) { + a -= (a << 6); + a ^= (a >> 17); + a -= (a << 9); + a ^= (a << 4); + a -= (a << 3); + a ^= (a << 10); + a ^= (a >> 15); + return a; +} + +static uint32_t genSeed(void) { + uint32_t res; +#ifndef WIN32 + struct sysinfo sy; + pid_t pid = getpid(); + // + sysinfo(&sy); + res = hashint((uint32_t)pid) ^ hashint((uint32_t)time(NULL)) ^ + hashint((uint32_t)sy.sharedram) ^ hashint((uint32_t)sy.bufferram) ^ + hashint((uint32_t)sy.uptime); +#else + res = hashint((uint32_t)GetCurrentProcessId()) ^ + hashint((uint32_t)GetTickCount()); +#endif + return hashint(res); +} + +//////////////////////////////////////////////////////////////////////////////// +size_t sam3GenChannelName(char *dest, size_t minlen, size_t maxlen) { + BJRandCtx rc; + size_t len; + size_t retlen; + // + if (dest == NULL || minlen < 1 || maxlen < minlen || minlen > 65536 || + maxlen > 65536) + return -1; + bjprngInit(&rc, genSeed()); + len = minlen + (bjprngRand(&rc) % (maxlen - minlen + 1)); + retlen = len; + while (len--) { + int ch = bjprngRand(&rc) % 64; + // + if (ch >= 0 && ch < 10) + ch += '0'; + else if (ch >= 10 && ch < 36) + ch += 'A' - 10; + else if (ch >= 36 && ch < 62) + ch += 'a' - 36; + else if (ch == 62) + ch = '-'; + else if (ch == 63) + ch = '_'; + else if (ch > 64) + abort(); + *dest++ = ch; + } + *dest++ = 0; + return retlen; +} + +//////////////////////////////////////////////////////////////////////////////// +static int sam3HandshakeInternal(int fd) { + SAMFieldList *rep = NULL; + // + if (sam3tcpPrintf(fd, "HELLO VERSION MIN=3.0 MAX=3.1\n") < 0) + goto error; + rep = sam3ReadReply(fd); + if (!sam3IsGoodReply(rep, "HELLO", "REPLY", "RESULT", "OK")) + goto error; + sam3FreeFieldList(rep); + return fd; +error: + sam3tcpDisconnect(fd); + if (rep != NULL) + sam3FreeFieldList(rep); + return -1; +} + +int sam3HandshakeIP(uint32_t ip, int port) { + int fd; + // + if ((fd = sam3tcpConnectIP(ip, (port < 1 || port > 65535 ? 7656 : port))) < 0) + return -1; + return sam3HandshakeInternal(fd); +} + +int sam3Handshake(const char *hostname, int port, uint32_t *ip) { + int fd; + // + if ((fd = sam3tcpConnect( + (hostname == NULL || !hostname[0] ? "localhost" : hostname), + (port < 1 || port > 65535 ? 7656 : port), ip)) < 0) + return -1; + return sam3HandshakeInternal(fd); +} + +//////////////////////////////////////////////////////////////////////////////// +static inline void strcpyerr(Sam3Session *ses, const char *errstr) { + memset(ses->error, 0, sizeof(ses->error)); + if (errstr != NULL) + strncpy(ses->error, errstr, sizeof(ses->error) - 1); +} + +int sam3GenerateKeys(Sam3Session *ses, const char *hostname, int port, + int sigType) { + if (ses != NULL) { + SAMFieldList *rep = NULL; + int fd, res = -1; + static const char *sigtypes[5] = { + "SIGNATURE_TYPE=DSA_SHA1", "SIGNATURE_TYPE=ECDSA_SHA256_P256", + "SIGNATURE_TYPE=ECDSA_SHA384_P384", "SIGNATURE_TYPE=ECDSA_SHA512_P521", + "SIGNATURE_TYPE=EdDSA_SHA512_Ed25519"}; + // + if ((fd = sam3Handshake(hostname, port, NULL)) < 0) { + strcpyerr(ses, "I2P_ERROR"); + return -1; + } + // + if (sam3tcpPrintf(fd, "DEST GENERATE %s\n", sigtypes[sigType]) >= 0) { + if ((rep = sam3ReadReply(fd)) != NULL && + sam3IsGoodReply(rep, "DEST", "REPLY", NULL, NULL)) { + const char *pub = sam3FindField(rep, "PUB"), + *priv = sam3FindField(rep, "PRIV"); + // + if (pub != NULL && sam3CheckValidKeyLength(pub) && priv != NULL && + strlen(priv) >= SAM3_PRIVKEY_MIN_SIZE) { + strcpy(ses->pubkey, pub); + strcpy(ses->privkey, priv); + res = 0; + } + } + } + // + sam3FreeFieldList(rep); + sam3tcpDisconnect(fd); + // + return res; + } + return -1; +} + +int sam3NameLookup(Sam3Session *ses, const char *hostname, int port, + const char *name) { + if (ses != NULL && name != NULL && name[0]) { + SAMFieldList *rep = NULL; + int fd, res = -1; + // + if ((fd = sam3Handshake(hostname, port, NULL)) < 0) { + strcpyerr(ses, "I2P_ERROR"); + return -1; + } + // + strcpyerr(ses, "I2P_ERROR"); + if (sam3tcpPrintf(fd, "NAMING LOOKUP NAME=%s\n", name) >= 0) { + if ((rep = sam3ReadReply(fd)) != NULL && + sam3IsGoodReply(rep, "NAMING", "REPLY", "RESULT", NULL)) { + const char *rs = sam3FindField(rep, "RESULT"), + *pub = sam3FindField(rep, "VALUE"); + // + if (strcmp(rs, "OK") == 0) { + if (pub != NULL && sam3CheckValidKeyLength(pub)) { + strcpy(ses->destkey, pub); + strcpyerr(ses, NULL); + res = 0; + } + } else if (rs[0]) { + strcpyerr(ses, rs); + } + } + } + // + sam3FreeFieldList(rep); + sam3tcpDisconnect(fd); + // + return res; + } + return -1; +} + +//////////////////////////////////////////////////////////////////////////////// +static int sam3CloseConnectionInternal(Sam3Connection *conn) { + return (conn->fd >= 0 ? sam3tcpDisconnect(conn->fd) : 0); +} + +int sam3CloseConnection(Sam3Connection *conn) { + if (conn != NULL) { + int res = sam3CloseConnectionInternal(conn); + // + if (conn->ses != NULL) { + for (Sam3Connection *p = NULL, *c = conn->ses->connlist; c != NULL; + p = c, c = c->next) { + if (c == conn) { + if (p == NULL) + conn->ses->connlist = c->next; + else + p->next = c->next; + break; + } + } + } + free(conn); + // + return res; + } + return -1; +} + +int sam3CloseSession(Sam3Session *ses) { + if (ses != NULL) { + for (Sam3Connection *n, *c = ses->connlist; c != NULL; c = n) { + n = c->next; + sam3CloseConnectionInternal(c); + free(c); + } + if (ses->fwd_fd >= 0) + sam3tcpDisconnect(ses->fwd_fd); + if (ses->fd >= 0) + sam3tcpDisconnect(ses->fd); + memset(ses, 0, sizeof(Sam3Session)); + ses->fd = -1; + return 0; + } + return -1; +} + +int sam3CreateSession(Sam3Session *ses, const char *hostname, int port, + const char *privkey, Sam3SessionType type, + Sam3SigType sigType, const char *params) { + if (ses != NULL) { + static const char *typenames[3] = {"RAW", "DATAGRAM", "STREAM"}; + static const char *sigtypes[5] = { + "SIGNATURE_TYPE=DSA_SHA1", "SIGNATURE_TYPE=ECDSA_SHA256_P256", + "SIGNATURE_TYPE=ECDSA_SHA384_P384", "SIGNATURE_TYPE=ECDSA_SHA512_P521", + "SIGNATURE_TYPE=EdDSA_SHA512_Ed25519"}; + + SAMFieldList *rep; + const char *v = NULL; + const char *pdel = (params != NULL ? " " : ""); + // + memset(ses, 0, sizeof(Sam3Session)); + ses->fd = -1; + ses->fwd_fd = -1; + // + if (privkey != NULL && strlen(privkey) < SAM3_PRIVKEY_MIN_SIZE) + goto error; + if ((int)type < 0 || (int)type > 2) + goto error; + if (privkey == NULL) + privkey = "TRANSIENT"; + // + ses->type = type; + ses->sigType = sigType; + ses->port = (type == SAM3_SESSION_STREAM ? (port ? port : 7656) : 7655); + sam3GenChannelName(ses->channel, 32, 64); + if (libsam3_debug) + fprintf(stderr, "sam3CreateSession: channel=[%s]\n", ses->channel); + // + if ((ses->fd = sam3Handshake(hostname, port, &ses->ip)) < 0) + goto error; + // + if (libsam3_debug) + fprintf(stderr, "sam3CreateSession: creating session (%s)...\n", + typenames[(int)type]); + if (sam3tcpPrintf( + ses->fd, "SESSION CREATE STYLE=%s ID=%s DESTINATION=%s %s %s %s\n", + typenames[(int)type], ses->channel, privkey, sigtypes[(int)sigType], + pdel, (params != NULL ? params : "")) < 0) + goto error; + if ((rep = sam3ReadReply(ses->fd)) == NULL) + goto error; + if (!sam3IsGoodReply(rep, "SESSION", "STATUS", "RESULT", "OK") || + (v = sam3FindField(rep, "DESTINATION")) == NULL || + strlen(v) < SAM3_PRIVKEY_MIN_SIZE) { + if (libsam3_debug) + fprintf(stderr, "sam3CreateSession: invalid reply (%ld)...\n", + (v != NULL ? strlen(v) : -1)); + if (libsam3_debug) + sam3DumpFieldList(rep); + sam3FreeFieldList(rep); + goto error; + } + // save our keys + strcpy(ses->privkey, v); + sam3FreeFieldList(rep); + // get public key + if (sam3tcpPrintf(ses->fd, "NAMING LOOKUP NAME=ME\n") < 0) + goto error; + if ((rep = sam3ReadReply(ses->fd)) == NULL) + goto error; + v = NULL; + if (!sam3IsGoodReply(rep, "NAMING", "REPLY", "RESULT", "OK") || + (v = sam3FindField(rep, "VALUE")) == NULL || + !sam3CheckValidKeyLength(v)) { + if (libsam3_debug) + fprintf(stderr, "sam3CreateSession: invalid NAMING reply (%ld)...\n", + (v != NULL ? strlen(v) : -1)); + if (libsam3_debug) + sam3DumpFieldList(rep); + sam3FreeFieldList(rep); + goto error; + } + strcpy(ses->pubkey, v); + sam3FreeFieldList(rep); + // + if (libsam3_debug) + fprintf(stderr, "sam3CreateSession: complete.\n"); + return 0; + } +error: + sam3CloseSession(ses); + return -1; +} + +Sam3Connection *sam3StreamConnect(Sam3Session *ses, const char *destkey) { + if (ses != NULL) { + SAMFieldList *rep; + Sam3Connection *conn; + // + if (ses->type != SAM3_SESSION_STREAM) { + strcpyerr(ses, "INVALID_SESSION_TYPE"); + return NULL; + } + if (ses->fd < 0) { + strcpyerr(ses, "INVALID_SESSION"); + return NULL; + } + if (destkey == NULL || !sam3CheckValidKeyLength(destkey)) { + strcpyerr(ses, "INVALID_KEY"); + return NULL; + } + if ((conn = calloc(1, sizeof(Sam3Connection))) == NULL) { + strcpyerr(ses, "NO_MEMORY"); + return NULL; + } + if ((conn->fd = sam3HandshakeIP(ses->ip, ses->port)) < 0) { + strcpyerr(ses, "IO_ERROR_SK"); + goto error; + } + if (sam3tcpPrintf(conn->fd, "STREAM CONNECT ID=%s DESTINATION=%s\n", + ses->channel, destkey) < 0) { + strcpyerr(ses, "IO_ERROR"); + goto error; + } + if ((rep = sam3ReadReply(conn->fd)) == NULL) { + strcpyerr(ses, "IO_ERROR"); + goto error; + } + if (!sam3IsGoodReply(rep, "STREAM", "STATUS", "RESULT", "OK")) { + const char *v = sam3FindField(rep, "RESULT"); + // + strcpyerr(ses, (v != NULL && v[0] ? v : "I2P_ERROR")); + sam3CloseConnectionInternal(conn); + free(conn); + conn = NULL; + } else { + // no error + strcpyerr(ses, NULL); + } + sam3FreeFieldList(rep); + if (conn != NULL) { + strcpy(conn->destkey, destkey); + conn->ses = ses; + conn->next = ses->connlist; + ses->connlist = conn; + } + return conn; + error: + sam3CloseConnectionInternal(conn); + free(conn); + return NULL; + } + return NULL; +} + +Sam3Connection *sam3StreamAccept(Sam3Session *ses) { + if (ses != NULL) { + SAMFieldList *rep = NULL; + char repstr[1024]; + Sam3Connection *conn; + // + if (ses->type != SAM3_SESSION_STREAM) { + strcpyerr(ses, "INVALID_SESSION_TYPE"); + return NULL; + } + if (ses->fd < 0) { + strcpyerr(ses, "INVALID_SESSION"); + return NULL; + } + if ((conn = calloc(1, sizeof(Sam3Connection))) == NULL) { + strcpyerr(ses, "NO_MEMORY"); + return NULL; + } + if ((conn->fd = sam3HandshakeIP(ses->ip, ses->port)) < 0) { + strcpyerr(ses, "IO_ERROR_SK"); + goto error; + } + if (sam3tcpPrintf(conn->fd, "STREAM ACCEPT ID=%s\n", ses->channel) < 0) { + strcpyerr(ses, "IO_ERROR_PF"); + goto error; + } + if ((rep = sam3ReadReply(conn->fd)) == NULL) { + strcpyerr(ses, "IO_ERROR_RP"); + goto error; + } + if (!sam3IsGoodReply(rep, "STREAM", "STATUS", "RESULT", "OK")) { + const char *v = sam3FindField(rep, "RESULT"); + // + strcpyerr(ses, (v != NULL && v[0] ? v : "I2P_ERROR_RES")); + goto error; + } + if (sam3tcpReceiveStr(conn->fd, repstr, sizeof(repstr)) < 0) { + strcpyerr(ses, "IO_ERROR_RP1"); + goto error; + } + sam3FreeFieldList(rep); + if ((rep = sam3ParseReply(repstr)) != NULL) { + const char *v = sam3FindField(rep, "RESULT"); + // + strcpyerr(ses, (v != NULL && v[0] ? v : "I2P_ERROR_RES1")); + goto error; + } + if (!sam3CheckValidKeyLength(repstr)) { + strcpyerr(ses, "INVALID_KEY"); + goto error; + } + sam3FreeFieldList(rep); + strcpy(conn->destkey, repstr); + conn->ses = ses; + conn->next = ses->connlist; + ses->connlist = conn; + strcpyerr(ses, NULL); + return conn; + error: + if (rep != NULL) + sam3FreeFieldList(rep); + sam3CloseConnectionInternal(conn); + free(conn); + return NULL; + } + return NULL; +} + +int sam3StreamForward(Sam3Session *ses, const char *hostname, int port) { + if (ses != NULL) { + SAMFieldList *rep = NULL; + // + if (ses->type != SAM3_SESSION_STREAM) { + strcpyerr(ses, "INVALID_SESSION_TYPE"); + return -1; + } + if (ses->fd < 0) { + strcpyerr(ses, "INVALID_SESSION"); + return -1; + } + if (ses->fwd_fd >= 0) { + strcpyerr(ses, "DUPLICATE_FORWARD"); + return -1; + } + if ((ses->fwd_fd = sam3HandshakeIP(ses->ip, ses->port)) < 0) { + strcpyerr(ses, "IO_ERROR_SK"); + goto error; + } + if (sam3tcpPrintf(ses->fwd_fd, "STREAM FORWARD ID=%s PORT=%d HOST=%s SILENT=true\n", + ses->channel, port, hostname) < 0) { + strcpyerr(ses, "IO_ERROR_PF"); + goto error; + } + if ((rep = sam3ReadReply(ses->fwd_fd)) == NULL) { + strcpyerr(ses, "IO_ERROR_RP"); + goto error; + } + if (!sam3IsGoodReply(rep, "STREAM", "STATUS", "RESULT", "OK")) { + const char *v = sam3FindField(rep, "RESULT"); + // + strcpyerr(ses, (v != NULL && v[0] ? v : "I2P_ERROR_RES")); + goto error; + } + sam3FreeFieldList(rep); + strcpyerr(ses, NULL); + return 0; + error: + if (rep != NULL) + sam3FreeFieldList(rep); + return -1; + } + return -1; +} + +int sam3DatagramSend(Sam3Session *ses, const char *destkey, const void *buf, + size_t bufsize) { + if (ses != NULL) { + char *dbuf; + int res, dbufsz; + // + if (ses->type == SAM3_SESSION_STREAM) { + strcpyerr(ses, "INVALID_SESSION_TYPE"); + return -1; + } + if (ses->fd < 0) { + strcpyerr(ses, "INVALID_SESSION"); + return -1; + } + if (destkey == NULL || !sam3CheckValidKeyLength(destkey)) { + strcpyerr(ses, "INVALID_KEY"); + return -1; + } + if (buf == NULL || bufsize < 1 || bufsize > 31744) { + strcpyerr(ses, "INVALID_DATA"); + return -1; + } + dbufsz = bufsize + 4 + SAM3_PUBKEY_SIZE + 1 + strlen(ses->channel) + 1; + if ((dbuf = malloc(dbufsz)) == NULL) { + strcpyerr(ses, "OUT_OF_MEMORY"); + return -1; + } + sprintf(dbuf, "3.0 %s %s\n", ses->channel, destkey); + memcpy(dbuf + strlen(dbuf), buf, bufsize); + res = sam3udpSendToIP(ses->ip, ses->port, dbuf, dbufsz); + free(dbuf); + strcpyerr(ses, (res < 0 ? "IO_ERROR" : NULL)); + return (res < 0 ? -1 : 0); + } + return -1; +} + +ssize_t sam3DatagramReceive(Sam3Session *ses, void *buf, size_t bufsize) { + if (ses != NULL) { + SAMFieldList *rep; + const char *v; + ssize_t size = 0; + // + if (ses->type == SAM3_SESSION_STREAM) { + strcpyerr(ses, "INVALID_SESSION_TYPE"); + return -1; + } + if (ses->fd < 0) { + strcpyerr(ses, "INVALID_SESSION"); + return -1; + } + if (buf == NULL || bufsize < 1) { + strcpyerr(ses, "INVALID_BUFFER"); + return -1; + } + if ((rep = sam3ReadReply(ses->fd)) == NULL) { + strcpyerr(ses, "IO_ERROR"); + return -1; + } + if (!sam3IsGoodReply(rep, "DATAGRAM", "RECEIVED", "SIZE", NULL)) { + strcpyerr(ses, "I2P_ERROR"); + sam3FreeFieldList(rep); + return -1; + } + // + if ((v = sam3FindField(rep, "DESTINATION")) != NULL && + sam3CheckValidKeyLength(v)) + strncpy(ses->destkey, v, sizeof(ses->destkey)); + v = sam3FindField(rep, "SIZE"); // we have this field -- for sure + if (!v[0] || !isdigit(*v)) { + strcpyerr(ses, "I2P_ERROR_SIZE"); + sam3FreeFieldList(rep); + return -1; + } + // + while (*v && isdigit(*v)) { + if ((size = size * 10 + v[0] - '0') > bufsize) { + strcpyerr(ses, "I2P_ERROR_BUFFER_TOO_SMALL"); + sam3FreeFieldList(rep); + return -1; + } + ++v; + } + // + if (*v) { + strcpyerr(ses, "I2P_ERROR_SIZE"); + sam3FreeFieldList(rep); + return -1; + } + sam3FreeFieldList(rep); + // + if (sam3tcpReceive(ses->fd, buf, size) != size) { + strcpyerr(ses, "IO_ERROR"); + return -1; + } + strcpyerr(ses, NULL); + return size; + } + return -1; +} + +//////////////////////////////////////////////////////////////////////////////// +// output 8 bytes for every 5 input +// return size or <0 on error +ssize_t sam3Base32Encode(char *dest, size_t destsz, const void *srcbuf, + size_t srcsize) { + if (dest != NULL && srcbuf != NULL && srcsize >= 0) { + static const char *const b32chars = "abcdefghijklmnopqrstuvwxyz234567="; + const unsigned char *src = (const unsigned char *)srcbuf; + ssize_t destsize = 0; + // + while (srcsize > 0) { + int blksize = (srcsize < 5 ? srcsize : 5); + unsigned char n[8]; + // + memset(n, 0, sizeof(n)); + switch (blksize) { + case 5: + n[7] = (src[4] & 0x1f); + n[6] = ((src[4] & 0xe0) >> 5); + case 4: + n[6] |= ((src[3] & 0x03) << 3); + n[5] = ((src[3] & 0x7c) >> 2); + n[4] = ((src[3] & 0x80) >> 7); + case 3: + n[4] |= ((src[2] & 0x0f) << 1); + n[3] = ((src[2] & 0xf0) >> 4); + case 2: + n[3] |= ((src[1] & 0x01) << 4); + n[2] = ((src[1] & 0x3e) >> 1); + n[1] = ((src[1] & 0xc0) >> 6); + case 1: + n[1] |= ((src[0] & 0x07) << 2); + n[0] = ((src[0] & 0xf8) >> 3); + break; + } + src += blksize; + srcsize -= blksize; + // pad + switch (blksize) { + case 1: + n[2] = n[3] = 32; + case 2: + n[4] = 32; + case 3: + n[5] = n[6] = 32; + case 4: + n[7] = 32; + case 5: + break; + } + // output + if (destsize + 8 <= destsz) { + for (int f = 0; f < 8; ++f) + *dest++ = b32chars[n[f]]; + } + destsize += 8; + } + if (destsize <= destsz) { + *dest++ = 0; // make valid asciiz string + } + return destsize; + } + return -1; +} + +//////////////////////////////////////////////////////////////////////////////// +// output 8 bytes for every 5 input +// return size or <0 on error +ssize_t sam3Base32Decode(char *dest, size_t destsz, const void *srcbuf, + size_t srcsize) { + if (dest != NULL && srcbuf != NULL && srcsize >= 0) { + static const char *const b32chars = "abcdefghijklmnopqrstuvwxyz234567="; + const unsigned char *src = (const unsigned char *)srcbuf; + int destsize = 0; + // + while (srcsize > 0) { + int blksize = (srcsize < 5 ? srcsize : 5); + unsigned char n[8]; + // + memset(n, 0, sizeof(n)); + switch (blksize) { + case 5: + n[7] = (src[4] & 0x1f); + n[6] = ((src[4] & 0xe0) >> 5); + case 4: + n[6] |= ((src[3] & 0x03) << 3); + n[5] = ((src[3] & 0x7c) >> 2); + n[4] = ((src[3] & 0x80) >> 7); + case 3: + n[4] |= ((src[2] & 0x0f) << 1); + n[3] = ((src[2] & 0xf0) >> 4); + case 2: + n[3] |= ((src[1] & 0x01) << 4); + n[2] = ((src[1] & 0x3e) >> 1); + n[1] = ((src[1] & 0xc0) >> 6); + case 1: + n[1] |= ((src[0] & 0x07) << 2); + n[0] = ((src[0] & 0xf8) >> 3); + break; + } + src += blksize; + srcsize -= blksize; + // pad + switch (blksize) { + case 1: + n[2] = n[3] = 32; + case 2: + n[4] = 32; + case 3: + n[5] = n[6] = 32; + case 4: + n[7] = 32; + case 5: + break; + } + // output + if (destsize + 8 <= destsz) { + for (int f = 0; f < 8; ++f) + *dest++ = b32chars[n[f]]; + } + destsize += 8; + } + if (destsize <= destsz) { + *dest++ = 0; // make valid asciiz string + } + return destsize; + } + return -1; +} diff --git a/supportlibs/libsam3/src/libsam3/libsam3.h b/supportlibs/libsam3/src/libsam3/libsam3.h new file mode 100644 index 000000000..3eb261d89 --- /dev/null +++ b/supportlibs/libsam3/src/libsam3/libsam3.h @@ -0,0 +1,280 @@ +/* This program is free software. It comes without any warranty, to + * the extent permitted by applicable law. You can redistribute it + * and/or modify it under the terms of the Do What The Fuck You Want + * To Public License, Version 2, as published by Sam Hocevar. See + * http://sam.zoy.org/wtfpl/COPYING for more details. + * + * I2P-Bote: + * 5m77dFKGEq6~7jgtrfw56q3t~SmfwZubmGdyOLQOPoPp8MYwsZ~pfUCwud6LB1EmFxkm4C3CGlzq-hVs9WnhUV + * we are the Borg. */ +#ifndef LIBSAM3_H +#define LIBSAM3_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +//////////////////////////////////////////////////////////////////////////////// +extern int libsam3_debug; + +//////////////////////////////////////////////////////////////////////////////// +#define SAM3_HOST_DEFAULT (NULL) +#define SAM3_PORT_DEFAULT (0) + +#define SAM3_DESTINATION_TRANSIENT (NULL) + +#define SAM3_PUBKEY_SIZE (516) +#define SAM3_CERT_SIZE (100) +#define SAM3_PRIVKEY_MIN_SIZE (884) + +//////////////////////////////////////////////////////////////////////////////// +/* returns fd or -1 */ +/* 'ip': host IP; can be NULL */ +extern int sam3tcpConnect(const char *hostname, int port, uint32_t *ip); +extern int sam3tcpConnectIP(uint32_t ip, int port); + +/* <0: error; 0: ok */ +extern int sam3tcpDisconnect(int fd); + +/* <0: error; 0: ok */ +extern int sam3tcpSetTimeoutSend(int fd, int timeoutms); + +/* <0: error; 0: ok */ +extern int sam3tcpSetTimeoutReceive(int fd, int timeoutms); + +/* <0: error; 0: ok */ +/* sends the whole buffer */ +extern int sam3tcpSend(int fd, const void *buf, size_t bufSize); + +/* <0: received (-res) bytes; read error */ +/* can return less that requesten bytes even if `allowPartial` is 0 when + * connection is closed */ +extern ssize_t sam3tcpReceiveEx(int fd, void *buf, size_t bufSize, + int allowPartial); + +extern ssize_t sam3tcpReceive(int fd, void *buf, size_t bufSize); + +extern int sam3tcpPrintf(int fd, const char *fmt, ...) + __attribute__((format(printf, 2, 3))); + +extern int sam3tcpReceiveStr(int fd, char *dest, size_t maxSize); + +/* pass NULL for 'localhost' and 0 for 7655 */ +/* 'ip': host IP; can be NULL */ +extern int sam3udpSendTo(const char *hostname, int port, const void *buf, + size_t bufSize, uint32_t *ip); +extern int sam3udpSendToIP(uint32_t ip, int port, const void *buf, + size_t bufSize); + +//////////////////////////////////////////////////////////////////////////////// +typedef struct SAMFieldList { + char *name; + char *value; + struct SAMFieldList *next; +} SAMFieldList; + +extern void sam3FreeFieldList(SAMFieldList *list); +extern void sam3DumpFieldList(const SAMFieldList *list); + +/* read and parse SAM reply */ +/* NULL: error; else: list of fields */ +/* first item is always 2-word reply, with first word in name and second in + * value */ +extern SAMFieldList *sam3ReadReply(int fd); + +extern SAMFieldList *sam3ParseReply(const char *rep); + +/* + * example: + * r0: 'HELLO' + * r1: 'REPLY' + * field: NULL or 'RESULT' + * VALUE: NULL or 'OK' + * returns bool + */ +extern int sam3IsGoodReply(const SAMFieldList *list, const char *r0, + const char *r1, const char *field, + const char *value); + +extern const char *sam3FindField(const SAMFieldList *list, const char *field); + +//////////////////////////////////////////////////////////////////////////////// +/* pass NULL for 'localhost' and 0 for 7656 */ +/* returns <0 on error or socket fd on success */ +extern int sam3Handshake(const char *hostname, int port, uint32_t *ip); +extern int sam3HandshakeIP(uint32_t ip, int port); + +//////////////////////////////////////////////////////////////////////////////// +typedef enum { + SAM3_SESSION_RAW, + SAM3_SESSION_DGRAM, + SAM3_SESSION_STREAM +} Sam3SessionType; + +typedef enum { + DSA_SHA1, + ECDSA_SHA256_P256, + ECDSA_SHA384_P384, + ECDSA_SHA512_P521, + EdDSA_SHA512_Ed25519 +} Sam3SigType; + +typedef struct Sam3Session { + Sam3SessionType type; + Sam3SigType sigType; + int fd; + char privkey[SAM3_PRIVKEY_MIN_SIZE + 1]; // destination private key (asciiz) + char pubkey[SAM3_PUBKEY_SIZE + SAM3_CERT_SIZE + + 1]; // destination public key (asciiz) + char channel[66]; // name of this sam session (asciiz) + char destkey[SAM3_PUBKEY_SIZE + SAM3_CERT_SIZE + + 1]; // for DGRAM sessions (asciiz) + // int destsig; + char error[32]; // error message (asciiz) + uint32_t ip; + int port; // this will be changed to UDP port for DRAM/RAW (can be 0) + struct Sam3Connection *connlist; // list of opened connections + int fwd_fd; +} Sam3Session; + +typedef struct Sam3Connection { + Sam3Session *ses; + struct Sam3Connection *next; + int fd; + char destkey[SAM3_PUBKEY_SIZE + SAM3_CERT_SIZE + + 1]; // remote destination public key (asciiz) + int destcert; + char error[32]; // error message (asciiz) +} Sam3Connection; + +//////////////////////////////////////////////////////////////////////////////// +/* + * create SAM session + * pass NULL as hostname for 'localhost' and 0 as port for 7656 + * pass NULL as privkey to create TRANSIENT session + * 'params' can be NULL + * see http://www.i2p2.i2p/i2cp.html#options for common options, + * and http://www.i2p2.i2p/streaming.html#options for STREAM options + * if result<0: error, 'ses' fields are undefined, no need to call + * sam3CloseSession() if result==0: ok, all 'ses' fields are filled + * TODO: don't clear 'error' field on error (and set it to something meaningful) + */ +extern int sam3CreateSession(Sam3Session *ses, const char *hostname, int port, + const char *privkey, Sam3SessionType type, + Sam3SigType sigType, const char *params); + +/* + * close SAM session (and all it's connections) + * returns <0 on error, 0 on ok + * 'ses' must be properly initialized + */ +extern int sam3CloseSession(Sam3Session *ses); + +/* + * Check to make sure that the destination in use is of a valid length, returns + * 1 if true and 0 if false. + */ +int sam3CheckValidKeyLength(const char *pubkey); + +/* + * open stream connection to 'destkey' endpoint + * 'destkey' is 516-byte public key (asciiz) + * returns <0 on error, fd on ok + * you still have to call sam3CloseSession() on failure + * sets ses->error on error + */ +extern Sam3Connection *sam3StreamConnect(Sam3Session *ses, const char *destkey); + +/* + * accepts stream connection and sets 'destkey' + * 'destkey' is 516-byte public key + * returns <0 on error, fd on ok + * you still have to call sam3CloseSession() on failure + * sets ses->error on error + * note that there is no timeouts for now, but you can use sam3tcpSetTimeout*() + */ +extern Sam3Connection *sam3StreamAccept(Sam3Session *ses); + +/* + * sets up forwarding stream connection + * returns <0 on error, 0 on ok + * you still have to call sam3CloseSession() on failure + * sets ses->error on error + * note that there is no timeouts for now, but you can use sam3tcpSetTimeout*() + */ +extern int sam3StreamForward(Sam3Session *ses, const char *hostname, int port); + +/* + * close SAM connection + * returns <0 on error, 0 on ok + * 'conn' must be properly initialized + * 'conn' is invalid after call + */ +extern int sam3CloseConnection(Sam3Connection *conn); + +//////////////////////////////////////////////////////////////////////////////// +/* + * generate new keypair + * fills 'privkey' and 'pubkey' only + * you should not call sam3CloseSession() on 'ses' + * will not set 'error' field + * returns <0 on error, 0 on ok + */ +extern int sam3GenerateKeys(Sam3Session *ses, const char *hostname, int port, + int sigType); + +/* + * do name lookup (something like gethostbyname()) + * fills 'destkey' only + * you should not call sam3CloseSession() on 'ses' + * will set 'error' field + * returns <0 on error, 0 on ok + */ +extern int sam3NameLookup(Sam3Session *ses, const char *hostname, int port, + const char *name); + +//////////////////////////////////////////////////////////////////////////////// +/* + * sends datagram to 'destkey' endpoint + * 'destkey' is 516-byte public key + * returns <0 on error, 0 on ok + * you still have to call sam3CloseSession() on failure + * sets ses->error on error + * don't send datagrams bigger than 31KB! + */ +extern int sam3DatagramSend(Sam3Session *ses, const char *destkey, + const void *buf, size_t bufsize); + +/* + * receives datagram and sets 'destkey' to source pubkey (if not RAW) + * returns <0 on error (buffer too small is error too) or number of bytes + * written to 'buf' you still have to call sam3CloseSession() on failure sets + * ses->error on error will necer receive datagrams bigger than 31KB (32KB for + * RAW) + */ +extern ssize_t sam3DatagramReceive(Sam3Session *ses, void *buf, size_t bufsize); + +/* + * generate random sam channel name + * return the size of the string + */ +extern size_t sam3GenChannelName(char *dest, size_t minlen, size_t maxlen); + +//////////////////////////////////////////////////////////////////////////////// +// NOT including '\0' terminator +static inline size_t sam3Base32EncodedLength(size_t size) { + return (((size + 5 - 1) / 5) * 8); +} + +// output 8 bytes for every 5 input +// return size or <0 on error +extern ssize_t sam3Base32Encode(char *dest, size_t destsz, const void *srcbuf, + size_t srcsize); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/supportlibs/libsam3/src/libsam3a/libsam3a.c b/supportlibs/libsam3/src/libsam3a/libsam3a.c new file mode 100644 index 000000000..40921e6ba --- /dev/null +++ b/supportlibs/libsam3/src/libsam3a/libsam3a.c @@ -0,0 +1,1757 @@ +/* async SAMv3 library + * + * This program is free software. It comes without any warranty, to + * the extent permitted by applicable law. You can redistribute it + * and/or modify it under the terms of the Do What The Fuck You Want + * To Public License, Version 2, as published by Sam Hocevar. See + * http://sam.zoy.org/wtfpl/COPYING for more details. + * + * I2P-Bote: + * 5m77dFKGEq6~7jgtrfw56q3t~SmfwZubmGdyOLQOPoPp8MYwsZ~pfUCwud6LB1EmFxkm4C3CGlzq-hVs9WnhUV + * we are the Borg. */ +#include "libsam3a.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +//////////////////////////////////////////////////////////////////////////////// +int libsam3a_debug = 0; + +#define DEFAULT_TCP_PORT (7656) +#define DEFAULT_UDP_PORT (7655) + +//////////////////////////////////////////////////////////////////////////////// +uint64_t sam3atimeval2ms(const struct timeval *tv) { + return ((uint64_t)tv->tv_sec) * 1000 + ((uint64_t)tv->tv_usec) / 1000; +} + +void sam3ams2timeval(struct timeval *tv, uint64_t ms) { + tv->tv_sec = ms / 1000; + tv->tv_usec = (ms % 1000) * 1000; +} + +//////////////////////////////////////////////////////////////////////////////// +static inline int isValidKeyChar(char ch) { + return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || + (ch >= '0' && ch <= '9') || ch == '-' || ch == '~'; +} + +int sam3aIsValidPubKey(const char *key) { + if (key != NULL && strlen(key) == SAM3A_PUBKEY_SIZE) { + for (int f = 0; f < SAM3A_PUBKEY_SIZE; ++f) + if (!isValidKeyChar(key[f])) + return 0; + return 1; + } + return 0; +} + +int sam3aIsValidPrivKey(const char *key) { + if (key != NULL && strlen(key) == SAM3A_PRIVKEY_SIZE) { + for (int f = 0; f < SAM3A_PRIVKEY_SIZE; ++f) + if (!isValidKeyChar(key[f])) + return 0; + return 1; + } + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +/* +static int sam3aSocketSetTimeoutSend (int fd, int timeoutms) { + if (fd >= 0 && timeoutms >= 0) { + struct timeval tv; + // + ms2timeval(&tv, timeoutms); + return (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0 ? -1 : +0); + } + return -1; +} + + +static int sam3aSocketSetTimeoutReceive (int fd, int timeoutms) { + if (fd >= 0 && timeoutms >= 0) { + struct timeval tv; + // + ms2timeval(&tv, timeoutms); + return (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0 ? -1 : +0); + } + return -1; +} +*/ + +static int sam3aBytesAvail(int fd) { + int av = 0; + // + if (ioctl(fd, FIONREAD, &av) < 0) + return -1; + return av; +} + +static uint32_t sam3aResolveHost(const char *hostname) { + struct hostent *host; + // + if (hostname == NULL || !hostname[0]) + return 0; + if ((host = gethostbyname(hostname)) == NULL || host->h_name == NULL || + !host->h_addr_list[0][0]) { + if (libsam3a_debug) + fprintf(stderr, "ERROR: can't resolve '%s'\n", hostname); + return 0; + } + return ((struct in_addr *)host->h_addr_list[0])->s_addr; +} + +static int sam3aConnect(uint32_t ip, int port, int *complete) { + int fd, val = 1; + // + if (complete != NULL) + *complete = 0; + if (ip == 0 || ip == 0xffffffffUL || port < 1 || port > 65535) + return -1; + // + // yes, this is Linux-specific; you know what? i don't care. + if ((fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)) < 0) + return -1; + // + setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)); + // + for (;;) { + struct sockaddr_in addr; + // + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = ip; + // + if (connect(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) < 0) { + if (errno == EINPROGRESS) + break; // the process is started + if (errno != EINTR) { + close(fd); + return -1; + } + } else { + // connection complete + if (complete != NULL) + *complete = 1; + break; + } + } + // + return fd; +} + +// <0: error; 0: ok +static int sam3aDisconnect(int fd) { + if (fd >= 0) { + shutdown(fd, SHUT_RDWR); + return close(fd); + } + // + return -1; +} + +//////////////////////////////////////////////////////////////////////////////// +// <0: error; >=0: bytes sent +static int sam3aSendBytes(int fd, const void *buf, int bufSize) { + const char *c = (const char *)buf; + int total = 0; + // + if (fd < 0 || (buf == NULL && bufSize > 0)) + return -1; + // + while (bufSize > 0) { + int wr = send(fd, c, bufSize, MSG_NOSIGNAL); + // + if (wr < 0) { + if (errno == EINTR) + continue; // interrupted by signal + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // bufSize is too big + if (bufSize == 1) + break; // can't send anything + // try to send a half of a buffer + if ((wr = sam3aSendBytes(fd, c, bufSize / 2)) < 0) + return wr; // error + } else { + return -1; // alas + } + } + // + if (wr == 0) + break; // can't send anything + c += wr; + bufSize -= wr; + total += wr; + } + // + return total; +} + +/* <0: error; >=0: bytes received */ +/* note that you should call this function when there is some bytes to read, so + * 0 means 'connection closed' */ +/* +static int sam3aReceive (int fd, void *buf, int bufSize) { + char *c = (char *)buf; + int total = 0; + // + if (fd < 0 || (buf == NULL && bufSize > 0)) return -1; + // + while (bufSize > 0) { + int av = sam3aBytesAvail(fd), rd; + // + if (av == 0) break; // no more + if (av > bufSize) av = bufSize; + rd = recv(fd, c, av, 0); + if (rd < 0) { + if (errno == EINTR) continue; // interrupted by signal + if (errno == EAGAIN || errno == EWOULDBLOCK) break; // the thing that +should not be return -1; // error + } + if (rd == 0) break; + c += rd; + bufSize -= rd; + total += rd; + } + // + return total; +} +*/ + +//////////////////////////////////////////////////////////////////////////////// +char *sam3PrintfVA(int *plen, const char *fmt, va_list app) { + char buf[1024], *p = buf; + int size = sizeof(buf) - 1, len = 0; + // + if (plen != NULL) + *plen = 0; + for (;;) { + va_list ap; + char *np; + int n; + // + va_copy(ap, app); + n = vsnprintf(p, size, fmt, ap); + va_end(ap); + // + if (n > -1 && n < size) { + len = n; + break; + } + if (n > -1) + size = n + 1; + else + size *= 2; + if (p == buf) { + if ((p = malloc(size)) == NULL) + return NULL; + } else { + if ((np = realloc(p, size)) == NULL) { + free(p); + return NULL; + } + p = np; + } + } + // + if (p == buf) { + if ((p = malloc(len + 1)) == NULL) + return NULL; + memcpy(p, buf, len + 1); + } + if (plen != NULL) + *plen = len; + return p; +} + +__attribute__((format(printf, 2, 3))) char *sam3Printf(int *plen, + const char *fmt, ...) { + va_list ap; + char *res; + // + va_start(ap, fmt); + res = sam3PrintfVA(plen, fmt, ap); + va_end(ap); + // + return res; +} + +/* + * check if we have EOL in received socket data + * this function should be called when sam3aBytesAvail() result > 0 + * return: <0: error; 0: no EOL in bytes2check, else: # of bytes to EOL + * (including EOL itself) + */ +/* +static int sam3aCheckEOL (int fd, int bytes2check) { + char *d = dest; + // + if (bytes2check < 0 || fd < 0) return -1; + memset(dest, 0, maxSize); + while (maxSize > 1) { + char *e; + int rd = recv(fd, d, maxSize-1, MSG_PEEK); + // + if (rd < 0 && errno == EINTR) continue; // interrupted by signal + if (rd == 0) { + rd = recv(fd, d, 1, 0); + if (rd < 0 && errno == EINTR) continue; // interrupted by signal + if (d[0] == '\n') { + d[0] = 0; // remove '\n' + return 0; + } + } else { + if (rd < 0) return -1; // error or connection closed; alas + } + // check for EOL + d[maxSize-1] = 0; + if ((e = strchr(d, '\n')) != NULL) { + rd = e-d+1; // bytes to receive + if (sam3atcpReceive(fd, d, rd) < 0) return -1; // alas + d[rd-1] = 0; // remove '\n' + return 0; // done + } else { + // let's receive this part and go on + if (sam3atcpReceive(fd, d, rd) < 0) return -1; // alas + maxSize -= rd; + d += rd; + } + } + // alas, the string is too big + return -1; +} +*/ + +//////////////////////////////////////////////////////////////////////////////// +/* +int sam3audpSendToIP (uint32_t ip, int port, const void *buf, int bufSize) { + struct sockaddr_in addr; + int fd, res; + // + if (buf == NULL || bufSize < 1) return -1; + if (port < 1 || port > 65535) port = 7655; + // + if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { + if (libsam3a_debug) fprintf(stderr, "ERROR: can't create socket\n"); + return -1; + } + // + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = ip; + // + res = sendto(fd, buf, bufSize, 0, (struct sockaddr *)&addr, sizeof(addr)); + // + if (res < 0) { + if (libsam3a_debug) { + res = errno; + fprintf(stderr, "UDP ERROR (%d): %s\n", res, strerror(res)); + } + res = -1; + } else { + if (libsam3a_debug) fprintf(stderr, "UDP: %d bytes sent\n", res); + } + // + close(fd); + // + return (res >= 0 ? 0 : -1); +} + + +int sam3audpSendTo (const char *hostname, int port, const void *buf, int +bufSize, uint32_t *ip) { struct hostent *host = NULL; + // + if (buf == NULL || bufSize < 1) return -1; + if (hostname == NULL || !hostname[0]) hostname = "localhost"; + if (port < 1 || port > 65535) port = 7655; + // + host = gethostbyname(hostname); + if (host == NULL || host->h_name == NULL || !host->h_name[0]) { + if (libsam3a_debug) fprintf(stderr, "ERROR: can't resolve '%s'\n", +hostname); return -1; + } + // + if (ip != NULL) *ip = ((struct in_addr *)host->h_addr)->s_addr; + return sam3audpSendToIP(((struct in_addr *)host->h_addr)->s_addr, port, buf, +bufSize); +} +*/ + +//////////////////////////////////////////////////////////////////////////////// +typedef struct SAMFieldList { + char *name; + char *value; + struct SAMFieldList *next; +} SAMFieldList; + +static void sam3aFreeFieldList(SAMFieldList *list) { + while (list != NULL) { + SAMFieldList *c = list; + // + list = list->next; + if (c->name != NULL) + free(c->name); + if (c->value != NULL) + free(c->value); + free(c); + } +} + +static void sam3aDumpFieldList(const SAMFieldList *list) { + for (; list != NULL; list = list->next) { + fprintf(stderr, "%s=[%s]\n", list->name, list->value); + } +} + +static const char *sam3aFindField(const SAMFieldList *list, const char *field) { + if (list != NULL && field != NULL) { + for (list = list->next; list != NULL; list = list->next) { + if (list->name != NULL && strcmp(field, list->name) == 0) + return list->value; + } + } + return NULL; +} + +static char *xstrdup(const char *s, int len) { + if (len >= 0) { + char *res = malloc(len + 1); + // + if (res != NULL) { + if (len > 0) + memcpy(res, s, len); + res[len] = 0; + } + // + return res; + } + // + return NULL; +} + +// returns NULL if there are no more tokens +static inline const char *xstrtokend(const char *s) { + while (*s && isspace(*s)) + ++s; + // + if (*s) { + char qch = 0; + // + while (*s) { + if (*s == qch) { + qch = 0; + } else if (*s == '"') { + qch = *s; + } else if (qch) { + if (*s == '\\' && s[1]) + ++s; + } else if (isspace(*s)) { + break; + } + ++s; + } + } else { + s = NULL; + } + // + return s; +} + +SAMFieldList *sam3aParseReply(const char *rep) { + SAMFieldList *first = NULL, *last, *c; + const char *p = rep, *e, *e1; + // + // first 2 words + while (*p && isspace(*p)) + ++p; + if ((e = xstrtokend(p)) == NULL) + return NULL; + if ((e1 = xstrtokend(e)) == NULL) + return NULL; + // + if ((first = last = c = malloc(sizeof(SAMFieldList))) == NULL) + return NULL; + c->next = NULL; + c->name = c->value = NULL; + if ((c->name = xstrdup(p, e - p)) == NULL) + goto error; + while (*e && isspace(*e)) + ++e; + if ((c->value = xstrdup(e, e1 - e)) == NULL) + goto error; + // + p = e1; + while (*p) { + while (*p && isspace(*p)) + ++p; + if ((e = xstrtokend(p)) == NULL) + break; // no more tokens + // + if (libsam3a_debug) + fprintf(stderr, "<%s>\n", p); + // + if ((c = malloc(sizeof(SAMFieldList))) == NULL) + return NULL; + c->next = NULL; + c->name = c->value = NULL; + last->next = c; + last = c; + // + if ((e1 = memchr(p, '=', e - p)) != NULL) { + // key=value + if ((c->name = xstrdup(p, e1 - p)) == NULL) + goto error; + if ((c->value = xstrdup(e1 + 1, e - e1 - 1)) == NULL) + goto error; + } else { + // only key (there is no such replies in SAMv3, but... + if ((c->name = xstrdup(p, e - p)) == NULL) + goto error; + if ((c->value = strdup("")) == NULL) + goto error; + } + p = e; + } + // + if (libsam3a_debug) + sam3aDumpFieldList(first); + // + return first; +error: + sam3aFreeFieldList(first); + return NULL; +} + +// example: +// r0: 'HELLO' +// r1: 'REPLY' +// field: NULL or 'RESULT' +// VALUE: NULL or 'OK' +// returns bool +int sam3aIsGoodReply(const SAMFieldList *list, const char *r0, const char *r1, + const char *field, const char *value) { + if (list != NULL && list->name != NULL && list->value != NULL) { + if (r0 != NULL && strcmp(r0, list->name) != 0) + return 0; + if (r1 != NULL && strcmp(r1, list->value) != 0) + return 0; + if (field != NULL) { + for (list = list->next; list != NULL; list = list->next) { + if (list->name == NULL || list->value == NULL) + return 0; // invalid list, heh + if (strcmp(field, list->name) == 0) { + if (value != NULL && strcmp(value, list->value) != 0) + return 0; + return 1; + } + } + } + return 1; + } + return 0; +} + +// NULL: error; else: list of fields +// first item is always 2-word reply, with first word in name and second in +// value +/* +SAMFieldList *sam3aReadReply (int fd) { + char rep[2048]; // should be enough for any reply + // + if (sam3atcpReceiveStr(fd, rep, sizeof(rep)) < 0) return NULL; + if (libsam3a_debug) fprintf(stderr, "SAM REPLY: [%s]\n", rep); + return sam3aParseReply(rep); +} +*/ + +//////////////////////////////////////////////////////////////////////////////// +// by Bob Jenkins +// public domain +// http://burtleburtle.net/bob/rand/smallprng.html +// +//////////////////////////////////////////////////////////////////////////////// +typedef struct { + uint32_t a, b, c, d; +} BJRandCtx; + +#define BJPRNG_ROT(x, k) (((x) << (k)) | ((x) >> (32 - (k)))) + +static uint32_t bjprngRand(BJRandCtx *x) { + uint32_t e; + /* original: + e = x->a-BJPRNG_ROT(x->b, 27); + x->a = x->b^BJPRNG_ROT(x->c, 17); + x->b = x->c+x->d; + x->c = x->d+e; + x->d = e+x->a; + */ + /* better, but slower at least in idiotic m$vc */ + e = x->a - BJPRNG_ROT(x->b, 23); + x->a = x->b ^ BJPRNG_ROT(x->c, 16); + x->b = x->c + BJPRNG_ROT(x->d, 11); + x->c = x->d + e; + x->d = e + x->a; + // + return x->d; +} + +static void bjprngInit(BJRandCtx *x, uint32_t seed) { + x->a = 0xf1ea5eed; + x->b = x->c = x->d = seed; + for (int i = 0; i < 20; ++i) + bjprngRand(x); +} + +static inline uint32_t hashint(uint32_t a) { + a -= (a << 6); + a ^= (a >> 17); + a -= (a << 9); + a ^= (a << 4); + a -= (a << 3); + a ^= (a << 10); + a ^= (a >> 15); + return a; +} + +static uint32_t genSeed(void) { + volatile uint32_t seed = 1; + uint32_t res; +#ifndef WIN32 + struct sysinfo sy; + pid_t pid = getpid(); + // + sysinfo(&sy); + res = hashint((uint32_t)pid) ^ hashint((uint32_t)time(NULL)) ^ + hashint((uint32_t)sy.sharedram) ^ hashint((uint32_t)sy.bufferram) ^ + hashint((uint32_t)sy.uptime); +#else + res = hashint((uint32_t)GetCurrentProcessId()) ^ + hashint((uint32_t)GetTickCount()); +#endif + res += __sync_fetch_and_add(&seed, 1); + // + return hashint(res); +} + +//////////////////////////////////////////////////////////////////////////////// +int sam3aGenChannelName(char *dest, int minlen, int maxlen) { + BJRandCtx rc; + int len; + // + if (dest == NULL || minlen < 1 || maxlen < minlen || minlen > 65536 || + maxlen > 65536) + return -1; + bjprngInit(&rc, genSeed()); + len = minlen + (bjprngRand(&rc) % (maxlen - minlen + 1)); + while (len-- > 0) { + int ch = bjprngRand(&rc) % 64; + // + if (ch >= 0 && ch < 10) + ch += '0'; + else if (ch >= 10 && ch < 36) + ch += 'A' - 10; + else if (ch >= 36 && ch < 62) + ch += 'a' - 36; + else if (ch == 62) + ch = '-'; + else if (ch == 63) + ch = '_'; + else if (ch > 64) + abort(); + *dest++ = ch; + } + *dest++ = 0; + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +int sam3aIsActiveSession(const Sam3ASession *ses) { + return (ses != NULL && ses->fd >= 0 && !ses->cancelled); +} + +int sam3aIsActiveConnection(const Sam3AConnection *conn) { + return (conn != NULL && conn->fd >= 0 && !conn->cancelled); +} + +//////////////////////////////////////////////////////////////////////////////// +static inline void strcpyerrs(Sam3ASession *ses, const char *errstr) { + // memset(ses->error, 0, sizeof(ses->error)); + ses->error[sizeof(ses->error) - 1] = 0; + if (errstr != NULL) + strncpy(ses->error, errstr, sizeof(ses->error) - 1); +} + +static inline void strcpyerrc(Sam3AConnection *conn, const char *errstr) { + // memset(conn->error, 0, sizeof(conn->error)); + conn->error[sizeof(conn->error) - 1] = 0; + if (errstr != NULL) + strncpy(conn->error, errstr, sizeof(conn->error) - 1); +} + +static void connDisconnect(Sam3AConnection *conn) { + conn->cbAIOProcessorR = conn->cbAIOProcessorW = NULL; + if (conn->aio.data != NULL) { + free(conn->aio.data); + conn->aio.data = NULL; + } + if (!conn->cancelled && conn->fd >= 0) { + conn->cancelled = 1; + shutdown(conn->fd, SHUT_RDWR); + if (conn->callDisconnectCB && conn->cb.cbDisconnected != NULL) + conn->cb.cbDisconnected(conn); + } +} + +static void sesDisconnect(Sam3ASession *ses) { + ses->cbAIOProcessorR = ses->cbAIOProcessorW = NULL; + if (ses->aio.data != NULL) { + free(ses->aio.data); + ses->aio.data = NULL; + } + if (!ses->cancelled && ses->fd >= 0) { + ses->cancelled = 1; + shutdown(ses->fd, SHUT_RDWR); + for (Sam3AConnection *c = ses->connlist; c != NULL; c = c->next) + connDisconnect(c); + if (ses->callDisconnectCB && ses->cb.cbDisconnected != NULL) + ses->cb.cbDisconnected(ses); + } +} + +static void sesError(Sam3ASession *ses, const char *errstr) { + if (errstr == NULL || !errstr[0]) + errstr = "I2P_ERROR"; + strcpyerrs(ses, errstr); + if (ses->cb.cbError != NULL) + ses->cb.cbError(ses); + sesDisconnect(ses); +} + +static void connError(Sam3AConnection *conn, const char *errstr) { + if (errstr == NULL || !errstr[0]) + errstr = "I2P_ERROR"; + strcpyerrc(conn, errstr); + if (conn->cb.cbError != NULL) + conn->cb.cbError(conn); + connDisconnect(conn); +} + +//////////////////////////////////////////////////////////////////////////////// +static int aioSender(int fd, Sam3AIO *aio) { + int wr = sam3aSendBytes(fd, aio->data + aio->dataPos, + aio->dataUsed - aio->dataPos); + // + if (wr < 0) + return -1; + aio->dataPos += wr; + return 0; +} + +// dataUsed: max line size (with '\n') +// dataSize: must be at least (dataUsed+1) +static int aioLineReader(int fd, Sam3AIO *aio) { + // + for (;;) { + int av = sam3aBytesAvail(fd), rd; + // + if (av < 0) + return -1; + if (av == 0) + return 0; // do nothing + if (aio->dataPos >= aio->dataUsed - 1) + return -1; // line too long + if ((rd = (aio->dataUsed - 1) - aio->dataPos) > av) + rd = av; + if ((rd = recv(fd, aio->data + aio->dataPos, rd, MSG_PEEK)) < 0) { + if (errno == EINTR) + continue; + return -1; + } + if (rd == 0) + return 0; // do nothing + // now look for '\n' + for (int f = aio->dataPos; f < aio->dataPos + rd; ++f) { + if (aio->data[f] == '\n') { + // got it! + if (recv(fd, aio->data + aio->dataPos, f - aio->dataPos + 1, 0) != + f + 1) + return -1; // the thing that should not be + aio->data[f] = 0; // convert to asciiz + aio->dataUsed = aio->dataPos = f; // length + return 1; // '\n' found! + } + if (!aio->data[f]) + return -1; // there should not be zero bytes + } + // no '\n' found + if (recv(fd, aio->data + aio->dataPos, rd, 0) != rd) + return -1; // the thing that should not be + aio->dataPos += rd; + } +} + +//////////////////////////////////////////////////////////////////////////////// +static void aioSesCmdReplyReader(Sam3ASession *ses) { + int res = aioLineReader(ses->fd, &ses->aio); + // + if (res < 0) { + sesError(ses, "IO_ERROR"); + return; + } + if (res > 0) { + // we got full line + if (libsam3a_debug) + fprintf(stderr, "CMDREPLY: %s\n", ses->aio.data); + if (ses->aio.cbReplyCheckSes != NULL) + ses->aio.cbReplyCheckSes(ses); + } +} + +static void aioSesCmdSender(Sam3ASession *ses) { + if (ses->aio.dataPos < ses->aio.dataUsed) { + if (aioSender(ses->fd, &ses->aio) < 0) { + sesError(ses, "IO_ERROR"); + return; + } + } + // + if (ses->aio.dataPos == ses->aio.dataUsed) { + // hello sent, now wait for reply + // 2048 bytes of reply line should be enough + if (ses->aio.dataSize < 2049) { + char *n = realloc(ses->aio.data, 2049); + // + if (n == NULL) { + sesError(ses, "MEMORY_ERROR"); + return; + } + ses->aio.data = n; + ses->aio.dataSize = 2049; + } + ses->aio.dataUsed = 2048; + ses->aio.dataPos = 0; + ses->cbAIOProcessorR = aioSesCmdReplyReader; + ses->cbAIOProcessorW = NULL; + } +} + +static __attribute__((format(printf, 3, 4))) int +aioSesSendCmdWaitReply(Sam3ASession *ses, void (*cbCheck)(Sam3ASession *ses), + const char *fmt, ...) { + va_list ap; + char *str; + int len; + // + va_start(ap, fmt); + str = sam3PrintfVA(&len, fmt, ap); + va_end(ap); + // + if (str == NULL) + return -1; + if (ses->aio.data != NULL) + free(ses->aio.data); + ses->aio.data = str; + ses->aio.dataUsed = len; + ses->aio.dataSize = len + 1; + ses->aio.dataPos = 0; + ses->aio.cbReplyCheckSes = cbCheck; + ses->cbAIOProcessorR = NULL; + ses->cbAIOProcessorW = aioSesCmdSender; + // + if (libsam3a_debug) + fprintf(stderr, "CMD: %s", str); + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +static void aioSesHelloChecker(Sam3ASession *ses) { + SAMFieldList *rep = sam3aParseReply(ses->aio.data); + // + if (rep != NULL && sam3aIsGoodReply(rep, "HELLO", "REPLY", "RESULT", "OK") && + sam3aIsGoodReply(rep, NULL, NULL, "VERSION", "3.0")) { + ses->cbAIOProcessorR = ses->cbAIOProcessorW = NULL; + sam3aFreeFieldList(rep); + if (ses->aio.udata != NULL) { + void (*cbComplete)(Sam3ASession * ses) = ses->aio.udata; + // + cbComplete(ses); + } + } else { + sam3aFreeFieldList(rep); + sesError(ses, NULL); + } +} + +static int sam3aSesStartHandshake(Sam3ASession *ses, + void (*cbComplete)(Sam3ASession *ses)) { + if (cbComplete != NULL) + ses->aio.udata = cbComplete; + if (aioSesSendCmdWaitReply(ses, aioSesHelloChecker, "%s\n", + "HELLO VERSION MIN=3.0 MAX=3.0") < 0) + return -1; + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +static void aioSesNameMeChecker(Sam3ASession *ses) { + SAMFieldList *rep = sam3aParseReply(ses->aio.data); + const char *v = NULL; + // + if (rep == NULL) { + sesError(ses, NULL); + return; + } + if (!sam3aIsGoodReply(rep, "NAMING", "REPLY", "RESULT", "OK") || + (v = sam3aFindField(rep, "VALUE")) == NULL || + strlen(v) != SAM3A_PUBKEY_SIZE) { + // if (libsam3a_debug) fprintf(stderr, "sam3aCreateSession: invalid NAMING + // reply (%d)...\n", (v != NULL ? strlen(v) : -1)); + if ((v = sam3aFindField(rep, "RESULT")) != NULL && strcmp(v, "OK") == 0) + v = NULL; + sesError(ses, v); + sam3aFreeFieldList(rep); + return; + } + strcpy(ses->pubkey, v); + sam3aFreeFieldList(rep); + // + ses->cbAIOProcessorR = ses->cbAIOProcessorW = NULL; + ses->callDisconnectCB = 1; + if (ses->cb.cbCreated != NULL) + ses->cb.cbCreated(ses); +} + +static void aioSesCreateChecker(Sam3ASession *ses) { + SAMFieldList *rep = sam3aParseReply(ses->aio.data); + const char *v; + // + if (rep == NULL) { + sesError(ses, NULL); + return; + } + if (!sam3aIsGoodReply(rep, "SESSION", "STATUS", "RESULT", "OK") || + (v = sam3aFindField(rep, "DESTINATION")) == NULL || + strlen(v) != SAM3A_PRIVKEY_SIZE) { + sam3aFreeFieldList(rep); + if ((v = sam3aFindField(rep, "RESULT")) != NULL && strcmp(v, "OK") == 0) + v = NULL; + sesError(ses, v); + return; + } + // ok + // fprintf(stderr, "\nPK: %s\n", v); + strcpy(ses->privkey, v); + sam3aFreeFieldList(rep); + // get our public key + if (aioSesSendCmdWaitReply(ses, aioSesNameMeChecker, "%s\n", + "NAMING LOOKUP NAME=ME") < 0) { + sesError(ses, "MEMORY_ERROR"); + } +} + +// handshake for SESSION CREATE complete +static void aioSesHandshacked(Sam3ASession *ses) { + static const char *typenames[3] = {"RAW", "DATAGRAM", "STREAM"}; + // + if (aioSesSendCmdWaitReply( + ses, aioSesCreateChecker, + "SESSION CREATE STYLE=%s ID=%s DESTINATION=%s%s%s\n", + typenames[(int)ses->type], ses->channel, ses->privkey, + (ses->params != NULL ? " " : ""), + (ses->params != NULL ? ses->params : "")) < 0) { + sesError(ses, "MEMORY_ERROR"); + } +} + +static void aioSesConnected(Sam3ASession *ses) { + int res; + socklen_t len = sizeof(res); + // + if (getsockopt(ses->fd, SOL_SOCKET, SO_ERROR, &res, &len) == 0 && res == 0) { + // ok, connected + if (sam3aSesStartHandshake(ses, NULL) < 0) + sesError(ses, NULL); + } else { + // connection error + sesError(ses, "CONNECTION_ERROR"); + } +} + +//////////////////////////////////////////////////////////////////////////////// +int sam3aCreateSessionEx(Sam3ASession *ses, const Sam3ASessionCallbacks *cb, + const char *hostname, int port, const char *privkey, + Sam3ASessionType type, const char *params, + int timeoutms) { + if (ses != NULL) { + // int complete = 0; + // + memset(ses, 0, sizeof(Sam3ASession)); + ses->fd = -1; + if (cb != NULL) + ses->cb = *cb; + if (hostname == NULL || !hostname[0]) + hostname = "127.0.0.1"; + if (port < 0 || port > 65535) + goto error; + if (privkey != NULL && strlen(privkey) != SAM3A_PRIVKEY_SIZE) + goto error; + if ((int)type < 0 || (int)type > 2) + goto error; + if (privkey == NULL) + privkey = "TRANSIENT"; + strcpy(ses->privkey, privkey); + if (params != NULL && (ses->params = strdup(params)) == NULL) + goto error; + ses->timeoutms = timeoutms; + // + if (!port) + port = DEFAULT_TCP_PORT; + ses->type = type; + ses->port = (type == SAM3A_SESSION_STREAM ? port : DEFAULT_UDP_PORT); + if ((ses->ip = sam3aResolveHost(hostname)) == 0) + goto error; + sam3aGenChannelName(ses->channel, 32, 64); + if (libsam3a_debug) + fprintf(stderr, "sam3aCreateSession: channel=[%s]\n", ses->channel); + // + ses->aio.udata = aioSesHandshacked; + ses->cbAIOProcessorW = aioSesConnected; + if ((ses->fd = sam3aConnect(ses->ip, port, NULL)) < 0) + goto error; + /* + if (complete) { + ses->cbAIOProcessorW(ses); + if (!sam3aIsActiveSession(ses)) return -1; + } + */ + // + return 0; // ok, connection process initiated + error: + if (ses->fd >= 0) + sam3aDisconnect(ses->fd); + if (ses->params != NULL) + free(ses->params); + memset(ses, 0, sizeof(Sam3ASession)); + ses->fd = -1; + } + return -1; +} + +//////////////////////////////////////////////////////////////////////////////// +int sam3aCancelSession(Sam3ASession *ses) { + if (ses != NULL) { + sesDisconnect(ses); + return 0; + } + return -1; +} + +int sam3aCloseSession(Sam3ASession *ses) { + if (ses != NULL) { + sam3aCancelSession(ses); + while (ses->connlist != NULL) + sam3aCloseConnection(ses->connlist); + if (ses->cb.cbDestroy != NULL) + ses->cb.cbDestroy(ses); + if (ses->params != NULL) { + free(ses->params); + ses->params = NULL; + } + memset(ses, 0, sizeof(Sam3ASession)); + } + return -1; +} + +//////////////////////////////////////////////////////////////////////////////// +static void aioSesKeyGenChecker(Sam3ASession *ses) { + SAMFieldList *rep = sam3aParseReply(ses->aio.data); + // + if (rep == NULL) { + sesError(ses, NULL); + return; + } + if (sam3aIsGoodReply(rep, "DEST", "REPLY", NULL, NULL)) { + const char *pub = sam3aFindField(rep, "PUB"), + *priv = sam3aFindField(rep, "PRIV"); + // + if (pub != NULL && strlen(pub) == SAM3A_PUBKEY_SIZE && priv != NULL && + strlen(priv) == SAM3A_PRIVKEY_SIZE) { + strcpy(ses->pubkey, pub); + strcpy(ses->privkey, priv); + sam3aFreeFieldList(rep); + if (ses->cb.cbCreated != NULL) + ses->cb.cbCreated(ses); + sam3aCancelSession(ses); + return; + } + } + sam3aFreeFieldList(rep); + sesError(ses, NULL); +} + +// handshake for SESSION CREATE complete +static void aioSesKeyGenHandshacked(Sam3ASession *ses) { + if (aioSesSendCmdWaitReply(ses, aioSesKeyGenChecker, "%s\n", + "DEST GENERATE") < 0) { + sesError(ses, "MEMORY_ERROR"); + } +} + +int sam3aGenerateKeysEx(Sam3ASession *ses, const Sam3ASessionCallbacks *cb, + const char *hostname, int port, int timeoutms) { + if (ses != NULL) { + memset(ses, 0, sizeof(Sam3ASession)); + ses->fd = -1; + if (cb != NULL) + ses->cb = *cb; + if (hostname == NULL || !hostname[0]) + hostname = "127.0.0.1"; + if (port < 0 || port > 65535) + goto error; + ses->timeoutms = timeoutms; + // + if (!port) + port = DEFAULT_TCP_PORT; + ses->port = port; + if ((ses->ip = sam3aResolveHost(hostname)) == 0) + goto error; + // + ses->aio.udata = aioSesKeyGenHandshacked; + ses->cbAIOProcessorW = aioSesConnected; + if ((ses->fd = sam3aConnect(ses->ip, port, NULL)) < 0) + goto error; + // + return 0; // ok, connection process initiated + error: + if (ses->fd >= 0) + sam3aDisconnect(ses->fd); + if (ses->params != NULL) + free(ses->params); + memset(ses, 0, sizeof(Sam3ASession)); + ses->fd = -1; + } + return -1; +} + +//////////////////////////////////////////////////////////////////////////////// +static void aioSesNameResChecker(Sam3ASession *ses) { + SAMFieldList *rep = sam3aParseReply(ses->aio.data); + // + if (rep == NULL) { + sesError(ses, NULL); + return; + } + if (sam3aIsGoodReply(rep, "NAMING", "REPLY", "RESULT", NULL)) { + const char *rs = sam3aFindField(rep, "RESULT"), + *pub = sam3aFindField(rep, "VALUE"); + // + if (strcmp(rs, "OK") == 0) { + if (pub != NULL && strlen(pub) == SAM3A_PUBKEY_SIZE) { + strcpy(ses->destkey, pub); + sam3aFreeFieldList(rep); + if (ses->cb.cbCreated != NULL) + ses->cb.cbCreated(ses); + sam3aCancelSession(ses); + return; + } + sam3aFreeFieldList(rep); + sesError(ses, NULL); + } else { + sesError(ses, rs); + sam3aFreeFieldList(rep); + } + } +} + +// handshake for SESSION CREATE complete +static void aioSesNameResHandshacked(Sam3ASession *ses) { + if (aioSesSendCmdWaitReply(ses, aioSesNameResChecker, + "NAMING LOOKUP NAME=%s\n", ses->params) < 0) { + sesError(ses, "MEMORY_ERROR"); + } +} + +int sam3aNameLookupEx(Sam3ASession *ses, const Sam3ASessionCallbacks *cb, + const char *hostname, int port, const char *name, + int timeoutms) { + if (ses != NULL) { + memset(ses, 0, sizeof(Sam3ASession)); + ses->fd = -1; + if (cb != NULL) + ses->cb = *cb; + if (name == NULL || !name[0] || + (name[0] && toupper(name[0]) == 'M' && name[1] && + toupper(name[1]) == 'E' && (!name[2] || isspace(name[2])))) + goto error; + if (hostname == NULL || !hostname[0]) + hostname = "127.0.0.1"; + if (port < 0 || port > 65535) + goto error; + if ((ses->params = strdup(name)) == NULL) + goto error; + ses->timeoutms = timeoutms; + // + if (!port) + port = DEFAULT_TCP_PORT; + ses->port = port; + if ((ses->ip = sam3aResolveHost(hostname)) == 0) + goto error; + // + ses->aio.udata = aioSesNameResHandshacked; + ses->cbAIOProcessorW = aioSesConnected; + if ((ses->fd = sam3aConnect(ses->ip, port, NULL)) < 0) + goto error; + // + return 0; // ok, connection process initiated + error: + if (ses->fd >= 0) + sam3aDisconnect(ses->fd); + if (ses->params != NULL) + free(ses->params); + memset(ses, 0, sizeof(Sam3ASession)); + ses->fd = -1; + } + return -1; +} + +//////////////////////////////////////////////////////////////////////////////// +static void aioConnCmdReplyReader(Sam3AConnection *conn) { + int res = aioLineReader(conn->fd, &conn->aio); + // + if (res < 0) { + connError(conn, "IO_ERROR"); + return; + } + if (res > 0) { + // we got full line + if (libsam3a_debug) + fprintf(stderr, "CMDREPLY: %s\n", conn->aio.data); + if (conn->aio.cbReplyCheckConn != NULL) + conn->aio.cbReplyCheckConn(conn); + } +} + +static void aioConnCmdSender(Sam3AConnection *conn) { + if (conn->aio.dataPos < conn->aio.dataUsed) { + if (aioSender(conn->fd, &conn->aio) < 0) { + connError(conn, "IO_ERROR"); + return; + } + } + // + if (conn->aio.dataPos == conn->aio.dataUsed) { + // hello sent, now wait for reply + // 2048 bytes of reply line should be enough + if (conn->aio.dataSize < 2049) { + char *n = realloc(conn->aio.data, 2049); + // + if (n == NULL) { + connError(conn, "MEMORY_ERROR"); + return; + } + conn->aio.data = n; + conn->aio.dataSize = 2049; + } + conn->aio.dataUsed = 2048; + conn->aio.dataPos = 0; + conn->cbAIOProcessorR = aioConnCmdReplyReader; + conn->cbAIOProcessorW = NULL; + } +} + +static __attribute__((format(printf, 3, 4))) int +aioConnSendCmdWaitReply(Sam3AConnection *conn, + void (*cbCheck)(Sam3AConnection *conn), const char *fmt, + ...) { + va_list ap; + char *str; + int len; + // + va_start(ap, fmt); + str = sam3PrintfVA(&len, fmt, ap); + va_end(ap); + // + if (str == NULL) + return -1; + if (conn->aio.data != NULL) + free(conn->aio.data); + conn->aio.data = str; + conn->aio.dataUsed = len; + conn->aio.dataSize = len + 1; + conn->aio.dataPos = 0; + conn->aio.cbReplyCheckConn = cbCheck; + conn->cbAIOProcessorR = NULL; + conn->cbAIOProcessorW = aioConnCmdSender; + // + if (libsam3a_debug) + fprintf(stderr, "CMD: %s", str); + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +static void aioConnDataReader(Sam3AConnection *conn) { + char *buf = NULL; + int bufsz = 0; + // + while (sam3aIsActiveConnection(conn)) { + int av = sam3aBytesAvail(conn->fd), rd; + // + if (av < 0) { + if (buf != NULL) + free(buf); + connError(conn, "IO_ERROR"); + return; + } + if (av == 0) + av = 1; + if (bufsz < av) { + char *n = realloc(buf, av + 1); + // + if (n == NULL) { + if (buf != NULL) + free(buf); + connError(conn, "IO_ERROR"); + return; + } + buf = n; + bufsz = av; + } + memset(buf, 0, av + 1); + // + rd = recv(conn->fd, buf, av, 0); + // + if (rd < 0) { + if (errno == EINTR) + continue; // interrupted by signal + if (errno == EAGAIN || errno == EWOULDBLOCK) + break; // no more data + free(buf); + connError(conn, "IO_ERROR"); + return; + } + // + if (rd == 0) { + // connection closed + free(buf); + connDisconnect(conn); + return; + } + // + if (conn->cb.cbRead != NULL) + conn->cb.cbRead(conn, buf, rd); + } + free(buf); +} + +static void aioConnDataWriter(Sam3AConnection *conn) { + if (!sam3aIsActiveConnection(conn)) { + conn->aio.dataPos = conn->aio.dataUsed = 0; + return; + } + // + if (conn->aio.dataPos >= conn->aio.dataUsed) { + conn->aio.dataPos = conn->aio.dataUsed = 0; + return; + } + // + while (sam3aIsActiveConnection(conn) && + conn->aio.dataPos < conn->aio.dataUsed) { + int wr = sam3aSendBytes(conn->fd, conn->aio.data + conn->aio.dataPos, + conn->aio.dataUsed - conn->aio.dataPos); + // + if (wr < 0) { + connError(conn, "IO_ERROR"); + return; + } + if (wr == 0) + break; // can't write more bytes + conn->aio.dataPos += wr; + if (conn->aio.dataPos < conn->aio.dataUsed) { + memmove(conn->aio.data, conn->aio.data + conn->aio.dataPos, + conn->aio.dataUsed - conn->aio.dataPos); + conn->aio.dataUsed -= conn->aio.dataPos; + conn->aio.dataPos = 0; + } + } + // + if (conn->aio.dataPos >= conn->aio.dataUsed) { + conn->aio.dataPos = conn->aio.dataUsed = 0; + if (conn->cb.cbSent != NULL) + conn->cb.cbSent(conn); + if (conn->aio.dataSize > 8192) { + // shrink buffer + char *nn = realloc(conn->aio.data, 8192); + // + if (nn != NULL) { + conn->aio.data = nn; + conn->aio.dataSize = 8192; + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +static void aioConnHelloChecker(Sam3AConnection *conn) { + SAMFieldList *rep = sam3aParseReply(conn->aio.data); + // + if (rep != NULL && sam3aIsGoodReply(rep, "HELLO", "REPLY", "RESULT", "OK") && + sam3aIsGoodReply(rep, NULL, NULL, "VERSION", "3.0")) { + conn->cbAIOProcessorR = conn->cbAIOProcessorW = NULL; + sam3aFreeFieldList(rep); + if (conn->aio.udata != NULL) { + void (*cbComplete)(Sam3AConnection * conn) = conn->aio.udata; + // + cbComplete(conn); + } + } else { + sam3aFreeFieldList(rep); + connError(conn, NULL); + } +} + +static int sam3aConnStartHandshake(Sam3AConnection *conn, + void (*cbComplete)(Sam3AConnection *conn)) { + if (cbComplete != NULL) + conn->aio.udata = cbComplete; + if (aioConnSendCmdWaitReply(conn, aioConnHelloChecker, "%s\n", + "HELLO VERSION MIN=3.0 MAX=3.0") < 0) + return -1; + return 0; +} + +static void aioConnConnected(Sam3AConnection *conn) { + int res; + socklen_t len = sizeof(res); + // + if (getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, &res, &len) == 0 && res == 0) { + // ok, connected + if (sam3aConnStartHandshake(conn, NULL) < 0) + connError(conn, NULL); + } else { + // connection error + connError(conn, "CONNECTION_ERROR"); + } +} + +//////////////////////////////////////////////////////////////////////////////// +static void aioConnConnectChecker(Sam3AConnection *conn) { + SAMFieldList *rep = sam3aParseReply(conn->aio.data); + // + if (rep == NULL) { + connError(conn, NULL); + return; + } + if (!sam3aIsGoodReply(rep, "STREAM", "STATUS", "RESULT", "OK")) { + const char *v = sam3aFindField(rep, "RESULT"); + // + connError(conn, v); + sam3aFreeFieldList(rep); + } else { + // no error + sam3aFreeFieldList(rep); + conn->callDisconnectCB = 1; + conn->cbAIOProcessorR = aioConnDataReader; + conn->cbAIOProcessorW = aioConnDataWriter; + conn->aio.dataPos = conn->aio.dataUsed = 0; + if (conn->cb.cbConnected != NULL) + conn->cb.cbConnected(conn); + // indicate that we are ready for new data + if (sam3aIsActiveConnection(conn) && conn->cb.cbSent != NULL) + conn->cb.cbSent(conn); + } +} + +// handshake for SESSION CREATE complete +static void aioConConnectHandshacked(Sam3AConnection *conn) { + if (aioConnSendCmdWaitReply(conn, aioConnConnectChecker, + "STREAM CONNECT ID=%s DESTINATION=%s\n", + conn->ses->channel, conn->destkey) < 0) { + connError(conn, "MEMORY_ERROR"); + } +} + +//////////////////////////////////////////////////////////////////////////////// +Sam3AConnection *sam3aStreamConnectEx(Sam3ASession *ses, + const Sam3AConnectionCallbacks *cb, + const char *destkey, int timeoutms) { + if (sam3aIsActiveSession(ses) && ses->type == SAM3A_SESSION_STREAM && + destkey != NULL && strlen(destkey) == SAM3A_PUBKEY_SIZE) { + Sam3AConnection *conn = calloc(1, sizeof(Sam3AConnection)); + // + if (conn == NULL) + return NULL; + if (cb != NULL) + conn->cb = *cb; + strcpy(conn->destkey, destkey); + conn->timeoutms = timeoutms; + // + conn->aio.udata = aioConConnectHandshacked; + conn->cbAIOProcessorW = aioConnConnected; + if ((conn->fd = sam3aConnect(ses->ip, ses->port, NULL)) < 0) + goto error; + // + conn->ses = ses; + conn->next = ses->connlist; + ses->connlist = conn; + return conn; // ok, connection process initiated + error: + if (conn->fd >= 0) + sam3aDisconnect(conn->fd); + memset(conn, 0, sizeof(Sam3AConnection)); + free(conn); + } + return NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +static void aioConnAcceptCheckerA(Sam3AConnection *conn) { + SAMFieldList *rep = sam3aParseReply(conn->aio.data); + // + if (rep != NULL || strlen(conn->aio.data) != SAM3A_PUBKEY_SIZE || + !sam3aIsValidPubKey(conn->aio.data)) { + sam3aFreeFieldList(rep); + connError(conn, NULL); + return; + } + sam3aFreeFieldList(rep); + strcpy(conn->destkey, conn->aio.data); + conn->callDisconnectCB = 1; + conn->cbAIOProcessorR = aioConnDataReader; + conn->cbAIOProcessorW = aioConnDataWriter; + conn->aio.dataPos = conn->aio.dataUsed = 0; + if (conn->cb.cbAccepted != NULL) + conn->cb.cbAccepted(conn); + // indicate that we are ready for new data + if (sam3aIsActiveConnection(conn) && conn->cb.cbSent != NULL) + conn->cb.cbSent(conn); +} + +static void aioConnAcceptChecker(Sam3AConnection *conn) { + SAMFieldList *rep = sam3aParseReply(conn->aio.data); + // + if (rep == NULL) { + connError(conn, NULL); + return; + } + if (!sam3aIsGoodReply(rep, "STREAM", "STATUS", "RESULT", "OK")) { + const char *v = sam3aFindField(rep, "RESULT"); + // + connError(conn, v); + sam3aFreeFieldList(rep); + } else { + // no error + sam3aFreeFieldList(rep); + // 2048 bytes of reply line should be enough + if (conn->aio.dataSize < 2049) { + char *n = realloc(conn->aio.data, 2049); + // + if (n == NULL) { + connError(conn, "MEMORY_ERROR"); + return; + } + conn->aio.data = n; + conn->aio.dataSize = 2049; + } + conn->aio.dataUsed = 2048; + conn->aio.dataPos = 0; + conn->cbAIOProcessorR = aioConnCmdReplyReader; + conn->cbAIOProcessorW = NULL; + conn->aio.cbReplyCheckConn = aioConnAcceptCheckerA; + } +} + +// handshake for SESSION CREATE complete +static void aioConAcceptHandshacked(Sam3AConnection *conn) { + if (aioConnSendCmdWaitReply(conn, aioConnAcceptChecker, + "STREAM ACCEPT ID=%s\n", + conn->ses->channel) < 0) { + connError(conn, "MEMORY_ERROR"); + } +} + +//////////////////////////////////////////////////////////////////////////////// +Sam3AConnection *sam3aStreamAcceptEx(Sam3ASession *ses, + const Sam3AConnectionCallbacks *cb, + int timeoutms) { + if (sam3aIsActiveSession(ses) && ses->type == SAM3A_SESSION_STREAM) { + Sam3AConnection *conn = calloc(1, sizeof(Sam3AConnection)); + // + if (conn == NULL) + return NULL; + if (cb != NULL) + conn->cb = *cb; + conn->timeoutms = timeoutms; + // + conn->aio.udata = aioConAcceptHandshacked; + conn->cbAIOProcessorW = aioConnConnected; + if ((conn->fd = sam3aConnect(ses->ip, ses->port, NULL)) < 0) + goto error; + // + conn->ses = ses; + conn->next = ses->connlist; + ses->connlist = conn; + return conn; // ok, connection process initiated + error: + if (conn->fd >= 0) + sam3aDisconnect(conn->fd); + memset(conn, 0, sizeof(Sam3AConnection)); + free(conn); + } + return NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +int sam3aSend(Sam3AConnection *conn, const void *data, int datasize) { + if (datasize == -1) + datasize = (data != NULL ? strlen((const char *)data) : 0); + // + if (sam3aIsActiveConnection(conn) && conn->callDisconnectCB && + conn->cbAIOProcessorW != NULL && + ((datasize > 0 && data != NULL) || datasize == 0)) { + // try to add data to send buffer + if (datasize > 0) { + if (conn->aio.dataUsed + datasize > conn->aio.dataSize) { + // we need more pepper! + int newsz = conn->aio.dataUsed + datasize; + char *nb = realloc(conn->aio.data, newsz); + // + if (nb == NULL) + return -1; // alas + conn->aio.data = nb; + conn->aio.dataSize = newsz; + } + // + memcpy(conn->aio.data + conn->aio.dataUsed, data, datasize); + conn->aio.dataUsed += datasize; + } + return 0; + } + // + return -1; +} + +//////////////////////////////////////////////////////////////////////////////// +int sam3aIsHaveActiveConnections(const Sam3ASession *ses) { + if (sam3aIsActiveSession(ses)) { + for (const Sam3AConnection *c = ses->connlist; c != NULL; c = c->next) { + if (sam3aIsActiveConnection(c)) + return 1; + } + } + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +int sam3aCancelConnection(Sam3AConnection *conn) { + if (conn != NULL) { + connDisconnect(conn); + return 0; + } + return -1; +} + +int sam3aCloseConnection(Sam3AConnection *conn) { + if (conn != NULL) { + sam3aCancelConnection(conn); + if (conn->cb.cbDestroy != NULL) + conn->cb.cbDestroy(conn); + for (Sam3AConnection *p = NULL, *c = conn->ses->connlist; c != NULL; + p = c, c = c->next) { + if (c == conn) { + // got it! + if (p == NULL) + c->ses->connlist = c->next; + else + p->next = c->next; + break; + } + } + if (conn->params != NULL) { + free(conn->params); + conn->params = NULL; + } + memset(conn, 0, sizeof(Sam3AConnection)); + free(conn); + } + return -1; +} + +//////////////////////////////////////////////////////////////////////////////// +int sam3aAddSessionToFDS(Sam3ASession *ses, int maxfd, fd_set *rds, + fd_set *wrs) { + if (ses != NULL) { + if (sam3aIsActiveSession(ses)) { + if (rds != NULL && ses->cbAIOProcessorR != NULL) { + if (maxfd < ses->fd) + maxfd = ses->fd; + FD_SET(ses->fd, rds); + } + // + if (wrs != NULL && ses->cbAIOProcessorW != NULL) { + if (maxfd < ses->fd) + maxfd = ses->fd; + FD_SET(ses->fd, wrs); + } + // + for (Sam3AConnection *c = ses->connlist; c != NULL; c = c->next) { + if (sam3aIsActiveConnection(c)) { + if (rds != NULL && c->cbAIOProcessorR != NULL) { + if (maxfd < c->fd) + maxfd = c->fd; + FD_SET(c->fd, rds); + } + // + if (wrs != NULL && c->cbAIOProcessorW != NULL) { + if (!c->callDisconnectCB || (c->aio.dataPos < c->aio.dataUsed)) + if (maxfd < c->fd) + maxfd = c->fd; + FD_SET(c->fd, wrs); + } + } + } + } + return maxfd; + } + // + return -1; +} + +void sam3aProcessSessionIO(Sam3ASession *ses, fd_set *rds, fd_set *wrs) { + if (sam3aIsActiveSession(ses)) { + if (ses->fd >= 0 && !ses->cancelled && ses->cbAIOProcessorR != NULL && + rds != NULL && FD_ISSET(ses->fd, rds)) + ses->cbAIOProcessorR(ses); + if (ses->fd >= 0 && !ses->cancelled && ses->cbAIOProcessorW != NULL && + wrs != NULL && FD_ISSET(ses->fd, wrs)) + ses->cbAIOProcessorW(ses); + // + for (Sam3AConnection *c = ses->connlist; c != NULL; c = c->next) { + if (c->fd >= 0 && !c->cancelled && c->cbAIOProcessorR != NULL && + rds != NULL && FD_ISSET(c->fd, rds)) + c->cbAIOProcessorR(c); + if (c->fd >= 0 && !c->cancelled && c->cbAIOProcessorW != NULL && + wrs != NULL && FD_ISSET(c->fd, wrs)) + c->cbAIOProcessorW(c); + } + } +} diff --git a/supportlibs/libsam3/src/libsam3a/libsam3a.h b/supportlibs/libsam3/src/libsam3a/libsam3a.h new file mode 100644 index 000000000..4ddec7952 --- /dev/null +++ b/supportlibs/libsam3/src/libsam3a/libsam3a.h @@ -0,0 +1,361 @@ +/* async SAMv3 library + * + * This program is free software. It comes without any warranty, to + * the extent permitted by applicable law. You can redistribute it + * and/or modify it under the terms of the Do What The Fuck You Want + * To Public License, Version 2, as published by Sam Hocevar. See + * http://sam.zoy.org/wtfpl/COPYING for more details. + * + * I2P-Bote: + * 5m77dFKGEq6~7jgtrfw56q3t~SmfwZubmGdyOLQOPoPp8MYwsZ~pfUCwud6LB1EmFxkm4C3CGlzq-hVs9WnhUV + * we are the Borg. */ +#ifndef LIBSAM3A_H +#define LIBSAM3A_H + +#include +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +//////////////////////////////////////////////////////////////////////////////// +/* + * TODO: + * [.] block sam3aClose*() in callbacks + */ + +//////////////////////////////////////////////////////////////////////////////// +extern int libsam3a_debug; + +//////////////////////////////////////////////////////////////////////////////// +#define SAM3A_HOST_DEFAULT (NULL) +#define SAM3A_PORT_DEFAULT (0) + +#define SAM3A_DESTINATION_TRANSIENT (NULL) + +#define SAM3A_PUBKEY_SIZE (516) +#define SAM3A_PRIVKEY_SIZE (884) + +//////////////////////////////////////////////////////////////////////////////// +extern uint64_t sam3atimeval2ms(const struct timeval *tv); +extern void sam3ams2timeval(struct timeval *tv, uint64_t ms); + +//////////////////////////////////////////////////////////////////////////////// +extern int sam3aIsValidPubKey(const char *key); +extern int sam3aIsValidPrivKey(const char *key); + +//////////////////////////////////////////////////////////////////////////////// +typedef struct Sam3ASession Sam3ASession; +typedef struct Sam3AConnection Sam3AConnection; + +typedef enum { + SAM3A_SESSION_RAW, + SAM3A_SESSION_DGRAM, + SAM3A_SESSION_STREAM +} Sam3ASessionType; + +typedef struct { + char *data; + int dataSize; + int dataUsed; + int dataPos; + void *udata; + union { + void (*cbReplyCheckSes)(Sam3ASession *ses); + void (*cbReplyCheckConn)(Sam3AConnection *conn); + }; +} Sam3AIO; + +/** session callback functions */ +typedef struct { + void (*cbError)(Sam3ASession *ses); /** called on error */ + void (*cbCreated)( + Sam3ASession *ses); /** called when we created the session */ + void (*cbDisconnected)(Sam3ASession *ses); /* call when closed; will called + only after cbCreated() */ + void (*cbDatagramRead)(Sam3ASession *ses, const void *buf, + int bufsize); /* called when we got a datagram; bufsize + >= 0; destkey set */ + void (*cbDestroy)(Sam3ASession *ses); /* called when fd is already closed, but + keys is not cleared */ +} Sam3ASessionCallbacks; + +struct Sam3ASession { + Sam3ASessionType type; /** session type */ + int fd; /** socket file descriptor */ + int cancelled; /** fd was shutdown()ed, but not closed yet */ + char privkey[SAM3A_PRIVKEY_SIZE + 1]; /** private key (asciiz) */ + char pubkey[SAM3A_PUBKEY_SIZE + 1]; /** public key (asciiz) */ + char channel[66]; /** channel name (asciiz) */ + char destkey[SAM3A_PUBKEY_SIZE + 1]; /** for DGRAM sessions (asciiz) */ + char error[64]; /** error message (asciiz) */ + uint32_t ip; /** ipv4 address of sam api interface */ + int port; /** UDP port for DRAM/RAW (can be 0) */ + Sam3AConnection *connlist; /** list of opened connections */ + + /** begin internal members */ + // for async i/o + Sam3AIO aio; + void (*cbAIOProcessorR)(Sam3ASession *ses); // internal + void (*cbAIOProcessorW)(Sam3ASession *ses); // internal + int callDisconnectCB; + char *params; // will be cleared only by sam3aCloseSession() + int timeoutms; + + /** end internal members */ + + Sam3ASessionCallbacks cb; + void *udata; +}; + +/** connection callbacks for data sockets */ +typedef struct { + /** called on error */ + void (*cbError)(Sam3AConnection *ct); + /** called when closed or only after cbConnected()/cbAccepted(); note that + * force disconnect is ok */ + void (*cbDisconnected)(Sam3AConnection *ct); + /** called when connected */ + void (*cbConnected)(Sam3AConnection *ct); + /** called instead of cbConnected() for sam3aStreamAccept*(), destkey filled + * with remote destination */ + void (*cbAccepted)(Sam3AConnection *ct); + /** send callback, data sent, can add new data; will be called after + * connect/accept */ + void (*cbSent)(Sam3AConnection *ct); + /** read callback, data read from socket (bufsize is always > 0) */ + void (*cbRead)(Sam3AConnection *ct, const void *buf, int bufsize); + /** fd already closed, but keys is not cleared */ + void (*cbDestroy)(Sam3AConnection *ct); +} Sam3AConnectionCallbacks; + +struct Sam3AConnection { + /** parent session */ + Sam3ASession *ses; + Sam3AConnection *next; + /** file descriptor */ + int fd; + int cancelled; // fd was shutdown()ed, but not closed yet + char destkey[SAM3A_PUBKEY_SIZE + 1]; // (asciiz) + char error[32]; // (asciiz) + + /** begin internal members */ + // for async i/o + Sam3AIO aio; + void (*cbAIOProcessorR)(Sam3AConnection *ct); // internal + void (*cbAIOProcessorW)(Sam3AConnection *ct); // internal + int callDisconnectCB; + char *params; // will be cleared only by sam3aCloseConnection() + int timeoutms; + /** end internal members */ + + /** callbacks */ + Sam3AConnectionCallbacks cb; + /** user data */ + void *udata; +}; + +//////////////////////////////////////////////////////////////////////////////// +/* + * check if session is active (i.e. have opened socket) + * returns bool + */ +extern int sam3aIsActiveSession(const Sam3ASession *ses); + +/* + * check if connection is active (i.e. have opened socket) + * returns bool + */ +extern int sam3aIsActiveConnection(const Sam3AConnection *conn); + +//////////////////////////////////////////////////////////////////////////////// +/* + * note, that return error codes indicates invalid structure, pointer or fd + * (i.e. immediate errors); all network errors indicated with cbError() callback + */ + +/* + * create SAM session + * pass NULL as hostname for 'localhost' and 0 as port for 7656 + * pass NULL as privkey to create TRANSIENT session + * 'params' can be NULL + * see http://www.i2p2.i2p/i2cp.html#options for common options, + * and http://www.i2p2.i2p/streaming.html#options for STREAM options + * if result<0: error, 'ses' fields are undefined, no need to call + * sam3aCloseSession() if result==0: ok, all 'ses' fields are filled + * TODO: don't clear 'error' field on error (and set it to something meaningful) + */ +extern int sam3aCreateSessionEx(Sam3ASession *ses, + const Sam3ASessionCallbacks *cb, + const char *hostname, int port, + const char *privkey, Sam3ASessionType type, + const char *params, int timeoutms); + +static inline int sam3aCreateSession(Sam3ASession *ses, + const Sam3ASessionCallbacks *cb, + const char *hostname, int port, + const char *privkey, + Sam3ASessionType type) { + return sam3aCreateSessionEx(ses, cb, hostname, port, privkey, type, NULL, -1); +} + +/* returns <0 on error, 0 if no, >0 if yes */ +extern int sam3aIsHaveActiveConnections(const Sam3ASession *ses); + +/* + * close SAM session (and all it's connections) + * returns <0 on error, 0 on ok + * 'ses' must be properly initialized + */ +extern int sam3aCloseSession(Sam3ASession *ses); + +/* + * cancel SAM session (and all it's connections), but don't free() or clear + * anything except fds returns <0 on error, 0 on ok 'ses' must be properly + * initialized + */ +extern int sam3aCancelSession(Sam3ASession *ses); + +/* + * open stream connection to 'destkey' endpoint + * 'destkey' is 516-byte public key (asciiz) + * returns <0 on error + * sets ses->error on memory or socket creation error + */ +extern Sam3AConnection *sam3aStreamConnectEx(Sam3ASession *ses, + const Sam3AConnectionCallbacks *cb, + const char *destkey, + int timeoutms); + +static inline Sam3AConnection * +sam3aStreamConnect(Sam3ASession *ses, const Sam3AConnectionCallbacks *cb, + const char *destkey) { + return sam3aStreamConnectEx(ses, cb, destkey, -1); +} + +/* + * accepts stream connection and sets 'destkey' + * 'destkey' is 516-byte public key + * returns <0 on error, fd on ok + * you still have to call sam3aCloseSession() on failure + * sets ses->error on error + * note that there is no timeouts for now, but you can use sam3atcpSetTimeout*() + */ +extern Sam3AConnection *sam3aStreamAcceptEx(Sam3ASession *ses, + const Sam3AConnectionCallbacks *cb, + int timeoutms); + +static inline Sam3AConnection * +sam3aStreamAccept(Sam3ASession *ses, const Sam3AConnectionCallbacks *cb) { + return sam3aStreamAcceptEx(ses, cb, -1); +} + +/* + * close SAM connection, remove it from session and free memory + * returns <0 on error, 0 on ok + * 'conn' must be properly initialized + * 'conn' is invalid after call + */ +extern int sam3aCloseConnection(Sam3AConnection *conn); + +/* + * cancel SAM connection, but don't free() or clear anything except fd + * returns <0 on error, 0 on ok + * 'conn' must be properly initialized + * 'conn' is invalid after call + */ +extern int sam3aCancelConnection(Sam3AConnection *conn); + +//////////////////////////////////////////////////////////////////////////////// +/* + * send data + * this function can be used in cbSent() callback + * + * return: <0: error; 0: ok + */ +extern int sam3aSend(Sam3AConnection *conn, const void *data, int datasize); + +/* + * sends datagram to 'destkey' endpoint + * 'destkey' is 516-byte public key + * returns <0 on error, 0 on ok + * you still have to call sam3aCloseSession() on failure + * sets ses->error on error + * don't send datagrams bigger than 31KB! + */ +extern int sam3aDatagramSend(Sam3ASession *ses, const char *destkey, + const void *buf, int bufsize); + +//////////////////////////////////////////////////////////////////////////////// +/* + * generate random channel name + * dest should be at least (maxlen+1) bytes big + */ +extern int sam3aGenChannelName(char *dest, int minlen, int maxlen); + +//////////////////////////////////////////////////////////////////////////////// +/* + * generate new keypair + * fills 'privkey' and 'pubkey' only + * you should call sam3aCloseSession() on 'ses' + * cbCreated callback will be called when keys generated + * returns <0 on error, 0 on ok + */ +extern int sam3aGenerateKeysEx(Sam3ASession *ses, + const Sam3ASessionCallbacks *cb, + const char *hostname, int port, int timeoutms); + +static inline int sam3aGenerateKeys(Sam3ASession *ses, + const Sam3ASessionCallbacks *cb, + const char *hostname, int port) { + return sam3aGenerateKeysEx(ses, cb, hostname, port, -1); +} + +/* + * do name lookup (something like gethostbyname()) + * fills 'destkey' only + * you should call sam3aCloseSession() on 'ses' + * cbCreated callback will be called when keys generated, ses->destkey will be + * set returns <0 on error, 0 on ok + */ +extern int sam3aNameLookupEx(Sam3ASession *ses, const Sam3ASessionCallbacks *cb, + const char *hostname, int port, const char *name, + int timeoutms); + +static inline int sam3aNameLookup(Sam3ASession *ses, + const Sam3ASessionCallbacks *cb, + const char *hostname, int port, + const char *name) { + return sam3aNameLookupEx(ses, cb, hostname, port, name, -1); +} + +//////////////////////////////////////////////////////////////////////////////// +/* + * append session fd to read and write sets if necessary + * adds all alive session connections too + * returns maxfd or -1 + * TODO: should keep fd count so it will not exceed FD_SETSIZE! + */ +extern int sam3aAddSessionToFDS(Sam3ASession *ses, int maxfd, fd_set *rds, + fd_set *wrs); + +/* + * process session i/o (and all session connections i/o) + * should be called after successful select() + */ +extern void sam3aProcessSessionIO(Sam3ASession *ses, fd_set *rds, fd_set *wrs); + +//////////////////////////////////////////////////////////////////////////////// +/* return malloc()ed buffer and len in 'plen' (if plen != NULL) */ +extern char *sam3PrintfVA(int *plen, const char *fmt, va_list app); +extern char *sam3Printf(int *plen, const char *fmt, ...) + __attribute__((format(printf, 2, 3))); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/supportlibs/libsam3/test/libsam3/test_b32.c b/supportlibs/libsam3/test/libsam3/test_b32.c new file mode 100644 index 000000000..9a852853d --- /dev/null +++ b/supportlibs/libsam3/test/libsam3/test_b32.c @@ -0,0 +1,51 @@ +/* This program is free software. It comes without any warranty, to + * the extent permitted by applicable law. You can redistribute it + * and/or modify it under the terms of the Do What The Fuck You Want + * To Public License, Version 2, as published by Sam Hocevar. See + * http://sam.zoy.org/wtfpl/COPYING for more details. + * + * I2P-Bote: + * 5m77dFKGEq6~7jgtrfw56q3t~SmfwZubmGdyOLQOPoPp8MYwsZ~pfUCwud6LB1EmFxkm4C3CGlzq-hVs9WnhUV + * we are the Borg. */ +#include +#include +#include +#include + +#include "../../src/ext/tinytest.h" +#include "../../src/ext/tinytest_macros.h" +#include "../../src/libsam3/libsam3.h" + +static int testb32(const char *src, const char *res) { + size_t dlen = sam3Base32EncodedLength(strlen(src)), len; + char dest[128]; + // + len = sam3Base32Encode(dest, sizeof(dest), src, strlen(src)); + tt_int_op(len, ==, dlen); + tt_int_op(len, ==, strlen(res)); + tt_str_op(res, ==, dest); + return 1; + +end: + return 0; +} + +void test_b32_encode(void *data) { + (void)data; /* This testcase takes no data. */ + + tt_assert(testb32("", "")); + tt_assert(testb32("f", "my======")); + tt_assert(testb32("fo", "mzxq====")); + tt_assert(testb32("foo", "mzxw6===")); + tt_assert(testb32("foob", "mzxw6yq=")); + tt_assert(testb32("fooba", "mzxw6ytb")); + tt_assert(testb32("foobar", "mzxw6ytboi======")); + +end:; +} + +struct testcase_t b32_tests[] = {{ + "encode", + test_b32_encode, + }, + END_OF_TESTCASES}; diff --git a/supportlibs/libsam3/test/test.c b/supportlibs/libsam3/test/test.c new file mode 100644 index 000000000..9e7357962 --- /dev/null +++ b/supportlibs/libsam3/test/test.c @@ -0,0 +1,12 @@ +#include + +#include "../src/ext/tinytest.h" +#include "../src/ext/tinytest_macros.h" + +extern struct testcase_t b32_tests[]; + +struct testgroup_t test_groups[] = {{"b32/", b32_tests}, END_OF_GROUPS}; + +int main(int argc, const char **argv) { + return tinytest_main(argc, argv, test_groups); +}