]> git.meshlink.io Git - meshlink/commitdiff
Add support for opening a MeshLink instance without permanent storage.
authorGuus Sliepen <guus@meshlink.io>
Mon, 11 Mar 2019 21:02:30 +0000 (22:02 +0100)
committerGuus Sliepen <guus@meshlink.io>
Thu, 13 Jun 2019 21:47:37 +0000 (23:47 +0200)
src/conf.c
src/conf.h
src/meshlink.c
src/meshlink.h
src/meshlink.sym
src/net.h
src/net_setup.c
test/.gitignore
test/Makefile.am
test/ephemeral.c [new file with mode: 0644]
test/ephemeral.test [new file with mode: 0755]

index 9e93c9a168a511b886fc2edbbddaaeddcfd5e362..9664e2b776a923a9d33d69451d5867c52986b5b4 100644 (file)
@@ -75,6 +75,10 @@ static void deltree(const char *dirname) {
 
 /// Create a fresh configuration directory
 bool config_init(meshlink_handle_t *mesh) {
+       if(!mesh->confbase) {
+               return true;
+       }
+
        if(mkdir(mesh->confbase, 0700) && errno != EEXIST) {
                logger(mesh, MESHLINK_DEBUG, "Could not create directory %s: %s\n", mesh->confbase, strerror(errno));
                return false;
@@ -131,6 +135,10 @@ bool config_destroy(const char *confbase) {
 
 /// Check the presence of the main configuration file.
 bool main_config_exists(meshlink_handle_t *mesh) {
+       if(!mesh->confbase) {
+               return false;
+       }
+
        char path[PATH_MAX];
        make_main_path(mesh, path, sizeof(path));
 
@@ -139,6 +147,10 @@ bool main_config_exists(meshlink_handle_t *mesh) {
 
 /// Lock the main configuration file.
 bool main_config_lock(meshlink_handle_t *mesh) {
+       if(!mesh->confbase) {
+               return true;
+       }
+
        char path[PATH_MAX];
        make_main_path(mesh, path, sizeof(path));
 
@@ -181,6 +193,10 @@ void main_config_unlock(meshlink_handle_t *mesh) {
 
 /// Read a configuration file from a FILE handle.
 bool config_read_file(meshlink_handle_t *mesh, FILE *f, config_t *config) {
+       if(!mesh->confbase) {
+               return false;
+       }
+
        (void)mesh;
        long len;
 
@@ -228,6 +244,10 @@ bool config_read_file(meshlink_handle_t *mesh, FILE *f, config_t *config) {
 
 /// Write a configuration file to a FILE handle.
 bool config_write_file(meshlink_handle_t *mesh, FILE *f, const config_t *config) {
+       if(!mesh->confbase) {
+               return true;
+       }
+
        if(mesh->config_key) {
                uint8_t buf[config->len + 16];
                size_t len = sizeof(buf);
@@ -266,6 +286,10 @@ void config_free(config_t *config) {
 
 /// Check the presence of a host configuration file.
 bool config_exists(meshlink_handle_t *mesh, const char *name) {
+       if(!mesh->confbase) {
+               return false;
+       }
+
        char path[PATH_MAX];
        make_host_path(mesh, name, path, sizeof(path));
 
@@ -274,6 +298,10 @@ bool config_exists(meshlink_handle_t *mesh, const char *name) {
 
 /// Read a host configuration file.
 bool config_read(meshlink_handle_t *mesh, const char *name, config_t *config) {
+       if(!mesh->confbase) {
+               return false;
+       }
+
        char path[PATH_MAX];
        make_host_path(mesh, name, path, sizeof(path));
 
@@ -294,8 +322,37 @@ bool config_read(meshlink_handle_t *mesh, const char *name, config_t *config) {
        return true;
 }
 
+void config_scan_all(meshlink_handle_t *mesh, config_scan_action_t action) {
+       if(!mesh->confbase) {
+               return;
+       }
+
+       DIR *dir;
+       struct dirent *ent;
+       char dname[PATH_MAX];
+       make_host_path(mesh, NULL, dname, sizeof(dname));
+
+       dir = opendir(dname);
+
+       if(!dir) {
+               logger(mesh, MESHLINK_ERROR, "Could not open %s: %s", dname, strerror(errno));
+               meshlink_errno = MESHLINK_ESTORAGE;
+               return;
+       }
+
+       while((ent = readdir(dir))) {
+               action(mesh, ent->d_name);
+       }
+
+       closedir(dir);
+}
+
 /// Write a host configuration file.
 bool config_write(meshlink_handle_t *mesh, const char *name, const config_t *config) {
+       if(!mesh->confbase) {
+               return true;
+       }
+
        char path[PATH_MAX];
        make_host_path(mesh, name, path, sizeof(path));
 
@@ -318,6 +375,10 @@ bool config_write(meshlink_handle_t *mesh, const char *name, const config_t *con
 
 /// Read the main configuration file.
 bool main_config_read(meshlink_handle_t *mesh, config_t *config) {
+       if(!mesh->confbase) {
+               return false;
+       }
+
        char path[PATH_MAX];
        make_main_path(mesh, path, sizeof(path));
 
@@ -340,6 +401,10 @@ bool main_config_read(meshlink_handle_t *mesh, config_t *config) {
 
 /// Write the main configuration file.
 bool main_config_write(meshlink_handle_t *mesh, const config_t *config) {
+       if(!mesh->confbase) {
+               return true;
+       }
+
        char path[PATH_MAX];
        make_main_path(mesh, path, sizeof(path));
 
@@ -362,6 +427,10 @@ bool main_config_write(meshlink_handle_t *mesh, const config_t *config) {
 
 /// Read an invitation file, and immediately delete it.
 bool invitation_read(meshlink_handle_t *mesh, const char *name, config_t *config) {
+       if(!mesh->confbase) {
+               return false;
+       }
+
        char path[PATH_MAX];
        char used_path[PATH_MAX];
        make_invitation_path(mesh, name, path, sizeof(path));
@@ -416,6 +485,10 @@ bool invitation_read(meshlink_handle_t *mesh, const char *name, config_t *config
 
 /// Write an invitation file.
 bool invitation_write(meshlink_handle_t *mesh, const char *name, const config_t *config) {
+       if(!mesh->confbase) {
+               return true;
+       }
+
        char path[PATH_MAX];
        make_invitation_path(mesh, name, path, sizeof(path));
 
@@ -438,6 +511,10 @@ bool invitation_write(meshlink_handle_t *mesh, const char *name, const config_t
 
 /// Purge old invitation files
 size_t invitation_purge_old(meshlink_handle_t *mesh, time_t deadline) {
+       if(!mesh->confbase) {
+               return true;
+       }
+
        char path[PATH_MAX];
        make_invitation_path(mesh, "", path, sizeof(path));
 
index f2bb076201e7e21cc9089eced03ef908d6143093..63d5fef4aef369143af63faa1f9a2a20853852fe 100644 (file)
@@ -27,6 +27,8 @@ typedef struct config_t {
        size_t len;
 } config_t;
 
+typedef void (*config_scan_action_t)(struct meshlink_handle *mesh, const char *name);
+
 //extern bool config_read_file(struct meshlink_handle *mesh, FILE *f, struct config_t *);
 //extern bool config_write_file(struct meshlink_handle *mesh, FILE *f, const struct config_t *);
 extern void config_free(struct config_t *config);
@@ -43,6 +45,7 @@ extern bool main_config_write(struct meshlink_handle *mesh, const struct config_
 extern bool config_exists(struct meshlink_handle *mesh, const char *name);
 extern bool config_read(struct meshlink_handle *mesh, const char *name, struct config_t *);
 extern bool config_write(struct meshlink_handle *mesh, const char *name, const struct config_t *);
+extern void config_scan_all(struct meshlink_handle *mesh, config_scan_action_t action);
 
 extern bool invitation_read(struct meshlink_handle *mesh, const char *name, struct config_t *);
 extern bool invitation_write(struct meshlink_handle *mesh, const char *name, const struct config_t *);
index 1b4492d63742d3cc87594b3813b0d72f976ed62d..cead9960d42bc62e0e19bea3722a40260b3f2082 100644 (file)
@@ -606,36 +606,28 @@ static bool finalize_join(meshlink_handle_t *mesh, const void *buf, uint16_t len
                        return false;
                }
 
-               packmsg_input_t in2 = {data, len};
-               uint32_t version = packmsg_get_uint32(&in2);
+               config_t config = {data, len};
+               node_t *n = new_node();
 
-               if(version != MESHLINK_CONFIG_VERSION) {
+               if(!node_read_from_config(mesh, n, &config)) {
+                       free_node(n);
                        logger(mesh, MESHLINK_ERROR, "Invalid host config file in invitation file!\n");
+                       meshlink_errno = MESHLINK_EPEER;
                        return false;
                }
 
-               char *host = packmsg_get_str_dup(&in2);
-
-               if(!check_id(host)) {
-                       logger(mesh, MESHLINK_ERROR, "Invalid node name in invitation file!\n");
-                       free(host);
-                       return false;
-               }
-
-               if(!strcmp(host, name)) {
+               if(!strcmp(n->name, name)) {
                        logger(mesh, MESHLINK_DEBUG, "Secondary chunk would overwrite our own host config file.\n");
-                       free(host);
+                       free_node(n);
+                       meshlink_errno = MESHLINK_EPEER;
                        return false;
                }
 
-               config_t config = {data, len};
-               config_write(mesh, host, &config);
-
-               node_t *n = new_node();
-               n->name = host;
-               node_read_full(mesh, n);
-               n->devclass = mesh->devclass;
                node_add(mesh, n);
+
+               if(!config_write(mesh, n->name, &config)) {
+                       return false;
+               }
        }
 
        sptps_send_record(&(mesh->sptps), 1, ecdsa_get_public_key(mesh->private_key), 32);
@@ -647,8 +639,6 @@ static bool finalize_join(meshlink_handle_t *mesh, const void *buf, uint16_t len
 
        logger(mesh, MESHLINK_DEBUG, "Configuration stored in: %s\n", mesh->confbase);
 
-       load_all_nodes(mesh);
-
        return true;
 }
 
@@ -1093,6 +1083,12 @@ void meshlink_open_params_free(meshlink_open_params_t *params) {
 }
 
 meshlink_handle_t *meshlink_open(const char *confbase, const char *name, const char *appname, dev_class_t devclass) {
+       if(!confbase || !*confbase) {
+               logger(NULL, MESHLINK_ERROR, "No confbase given!\n");
+               meshlink_errno = MESHLINK_EINVAL;
+               return NULL;
+       }
+
        /* Create a temporary struct on the stack, to avoid allocating and freeing one. */
        meshlink_open_params_t params;
        memset(&params, 0, sizeof(params));
@@ -1107,6 +1103,12 @@ meshlink_handle_t *meshlink_open(const char *confbase, const char *name, const c
 }
 
 meshlink_handle_t *meshlink_open_encrypted(const char *confbase, const char *name, const char *appname, dev_class_t devclass, const void *key, size_t keylen) {
+       if(!confbase || !*confbase) {
+               logger(NULL, MESHLINK_ERROR, "No confbase given!\n");
+               meshlink_errno = MESHLINK_EINVAL;
+               return NULL;
+       }
+
        /* Create a temporary struct on the stack, to avoid allocating and freeing one. */
        meshlink_open_params_t params = {NULL};
 
@@ -1123,18 +1125,24 @@ meshlink_handle_t *meshlink_open_encrypted(const char *confbase, const char *nam
        return meshlink_open_ex(&params);
 }
 
+meshlink_handle_t *meshlink_open_ephemeral(const char *name, const char *appname, dev_class_t devclass) {
+       /* Create a temporary struct on the stack, to avoid allocating and freeing one. */
+       meshlink_open_params_t params = {NULL};
+
+       params.name = (char *)name;
+       params.appname = (char *)appname;
+       params.devclass = devclass;
+       params.netns = -1;
+
+       return meshlink_open_ex(&params);
+}
+
 meshlink_handle_t *meshlink_open_ex(const meshlink_open_params_t *params) {
        // Validate arguments provided by the application
        bool usingname = false;
 
        logger(NULL, MESHLINK_DEBUG, "meshlink_open called\n");
 
-       if(!params->confbase || !*params->confbase) {
-               logger(NULL, MESHLINK_ERROR, "No confbase given!\n");
-               meshlink_errno = MESHLINK_EINVAL;
-               return NULL;
-       }
-
        if(!params->appname || !*params->appname) {
                logger(NULL, MESHLINK_ERROR, "No appname given!\n");
                meshlink_errno = MESHLINK_EINVAL;
@@ -1174,7 +1182,11 @@ meshlink_handle_t *meshlink_open_ex(const meshlink_open_params_t *params) {
        }
 
        meshlink_handle_t *mesh = xzalloc(sizeof(meshlink_handle_t));
-       mesh->confbase = xstrdup(params->confbase);
+
+       if(params->confbase) {
+               mesh->confbase = xstrdup(params->confbase);
+       }
+
        mesh->appname = xstrdup(params->appname);
        mesh->devclass = params->devclass;
        mesh->discovery = true;
@@ -1450,7 +1462,7 @@ void meshlink_stop(meshlink_handle_t *mesh) {
 }
 
 void meshlink_close(meshlink_handle_t *mesh) {
-       if(!mesh || !mesh->confbase) {
+       if(!mesh) {
                meshlink_errno = MESHLINK_EINVAL;
                return;
        }
@@ -2420,38 +2432,64 @@ char *meshlink_export(meshlink_handle_t *mesh) {
                return NULL;
        }
 
-       config_t config;
+       // Create a config file on the fly.
 
-       // Get our config file
+       uint8_t buf[4096];
+       packmsg_output_t out = {buf, sizeof(buf)};
+       packmsg_add_uint32(&out, MESHLINK_CONFIG_VERSION);
+       packmsg_add_str(&out, mesh->name);
+       packmsg_add_str(&out, CORE_MESH);
 
        pthread_mutex_lock(&(mesh->mesh_mutex));
 
-       if(!config_read(mesh, mesh->self->name, &config)) {
-               meshlink_errno = MESHLINK_ESTORAGE;
-               pthread_mutex_unlock(&mesh->mesh_mutex);
-               return NULL;
+       packmsg_add_int32(&out, mesh->self->devclass);
+       packmsg_add_bool(&out, mesh->self->status.blacklisted);
+       packmsg_add_bin(&out, ecdsa_get_public_key(mesh->private_key), 32);
+       packmsg_add_str(&out, mesh->self->canonical_address ? mesh->self->canonical_address : "");
+
+       uint32_t count = 0;
+
+       for(uint32_t i = 0; i < 5; i++) {
+               if(mesh->self->recent[i].sa.sa_family) {
+                       count++;
+               } else {
+                       break;
+               }
+       }
+
+       packmsg_add_array(&out, count);
+
+       for(uint32_t i = 0; i < count; i++) {
+               packmsg_add_sockaddr(&out, &mesh->self->recent[i]);
        }
 
        pthread_mutex_unlock(&(mesh->mesh_mutex));
 
+       if(!packmsg_output_ok(&out)) {
+               logger(mesh, MESHLINK_DEBUG, "Error creating export data\n");
+               meshlink_errno = MESHLINK_EINTERNAL;
+               return NULL;
+       }
+
        // Prepare a base64-encoded packmsg array containing our config file
 
-       uint8_t *buf = xmalloc(((config.len + 4) * 4) / 3 + 4);
-       packmsg_output_t out = {buf, config.len + 4};
-       packmsg_add_array(&out, 1);
-       packmsg_add_bin(&out, config.buf, config.len);
-       config_free(&config);
+       uint32_t len = packmsg_output_size(&out, buf);
+       uint32_t len2 = ((len + 4) * 4) / 3 + 4;
+       uint8_t *buf2 = xmalloc(len2);
+       packmsg_output_t out2 = {buf2, len2};
+       packmsg_add_array(&out2, 1);
+       packmsg_add_bin(&out2, buf, packmsg_output_size(&out, buf));
 
-       if(!packmsg_output_ok(&out)) {
+       if(!packmsg_output_ok(&out2)) {
                logger(mesh, MESHLINK_DEBUG, "Error creating export data\n");
                meshlink_errno = MESHLINK_EINTERNAL;
-               free(buf);
+               free(buf2);
                return NULL;
        }
 
-       b64encode_urlsafe(buf, (char *)buf, packmsg_output_size(&out, buf));
+       b64encode_urlsafe(buf2, (char *)buf2, packmsg_output_size(&out2, buf2));
 
-       return (char *)buf;
+       return (char *)buf2;
 }
 
 bool meshlink_import(meshlink_handle_t *mesh, const char *data) {
@@ -2551,8 +2589,9 @@ bool meshlink_import(meshlink_handle_t *mesh, const char *data) {
                }
 
                if(!packmsg_done(&in2) || keylen != 32) {
-                       packmsg_input_invalidate(&in2);
+                       packmsg_input_invalidate(&in);
                        free_node(n);
+                       break;
                } else {
                        config_t config = {data, len};
                        config_write(mesh, n->name, &config);
index f7f840a64b5113cfc3489d4fc1f3f332cfe59baf..96f2d183e222e0163a796a38d6056a85d3cf191a 100644 (file)
@@ -266,6 +266,30 @@ extern meshlink_handle_t *meshlink_open(const char *confbase, const char *name,
  */
 extern meshlink_handle_t *meshlink_open_encrypted(const char *confbase, const char *name, const char *appname, dev_class_t devclass, const void *key, size_t keylen);
 
+/// Create an ephemeral MeshLink instance that does not store any state.
+/** This function creates a MeshLink instance.
+ *  No state is ever saved, so once this instance is closed, all its state is gone.
+ *
+ *  The name given should be a unique identifier for this instance.
+ *
+ *  This function returns a pointer to a struct meshlink_handle that will be allocated by MeshLink.
+ *  When the application does no longer need to use this handle, it must call meshlink_close() to
+ *  free its resources.
+ *
+ *  This function does not start any network I/O yet. The application should
+ *  first set callbacks, and then call meshlink_start().
+ *
+ *  @param name     The name which this instance of the application will use in the mesh.
+ *                  After the function returns, the application is free to overwrite or free @a name @a.
+ *  @param appname  The application name which will be used in the mesh.
+ *                  After the function returns, the application is free to overwrite or free @a name @a.
+ *  @param devclass The device class which will be used in the mesh.
+ *
+ *  @return         A pointer to a meshlink_handle_t which represents this instance of MeshLink, or NULL in case of an error.
+ *                  The pointer is valid until meshlink_close() is called.
+ */
+extern meshlink_handle_t *meshlink_open_ephemeral(const char *name, const char *appname, dev_class_t devclass);
+
 /// Create Sub-Mesh.
 /** This function causes MeshLink to open a new Sub-Mesh network
  *  create a new thread, which will handle all network I/O.
index fcfeb5fcb1426626cde1af4c7b3155c67bf8e65f..10802769137e5533f62a9834c896c5288f300a88 100644 (file)
@@ -43,6 +43,7 @@ meshlink_join
 meshlink_main_loop
 meshlink_open
 meshlink_open_encrypted
+meshlink_open_ephemeral
 meshlink_open_ex
 meshlink_open_params_free
 meshlink_open_params_init
index 8401a4025199c6f165067b44013ba22b4607a879..9277e6d9dc9ed64c6ebc464772c0250429b53eac 100644 (file)
--- a/src/net.h
+++ b/src/net.h
@@ -106,7 +106,7 @@ extern void close_network_connections(struct meshlink_handle *mesh);
 extern int main_loop(struct meshlink_handle *mesh);
 extern void terminate_connection(struct meshlink_handle *mesh, struct connection_t *, bool);
 extern bool node_read_public_key(struct meshlink_handle *mesh, struct node_t *);
-extern bool node_read_full(struct meshlink_handle *mesh, struct node_t *);
+extern bool node_read_from_config(struct meshlink_handle *mesh, struct node_t *, const config_t *config);
 extern bool read_ecdsa_public_key(struct meshlink_handle *mesh, struct connection_t *);
 extern bool read_ecdsa_private_key(struct meshlink_handle *mesh);
 extern bool node_write_config(struct meshlink_handle *mesh, struct node_t *);
index 905e442d6aed296a27d74c01dcedd320f23e60e2..4f2022b7e02de08868d336c7a6a6d3f7d3fbd5dc 100644 (file)
@@ -128,19 +128,36 @@ bool node_read_public_key(meshlink_handle_t *mesh, node_t *n) {
        return true;
 }
 
-/// Read the full host config file. Used whenever we need to start an SPTPS session.
-bool node_read_full(meshlink_handle_t *mesh, node_t *n) {
+/// Fill in node details from a config blob.
+bool node_read_from_config(meshlink_handle_t *mesh, node_t *n, const config_t *config) {
        if(n->canonical_address) {
                return true;
        }
 
-       config_t config;
-       packmsg_input_t in;
+       packmsg_input_t in = {config->buf, config->len};
+       uint32_t version = packmsg_get_uint32(&in);
 
-       if(!node_get_config(mesh, n, &config, &in)) {
+       if(version != MESHLINK_CONFIG_VERSION) {
+               return false;
+       }
+
+       char *name = packmsg_get_str_dup(&in);
+
+       if(!name) {
                return false;
        }
 
+       if(n->name) {
+               if(strcmp(n->name, name)) {
+                       free(name);
+                       return false;
+               }
+
+               free(name);
+       } else {
+               n->name = name;
+       }
+
        char *submesh_name = packmsg_get_str_dup(&in);
 
        if(!strcmp(submesh_name, CORE_MESH)) {
@@ -155,10 +172,10 @@ bool node_read_full(meshlink_handle_t *mesh, node_t *n) {
                }
        }
 
-       packmsg_get_int32(&in); /* devclass */
-       packmsg_get_bool(&in); /* blacklisted */
+       n->devclass = packmsg_get_int32(&in);
+       n->status.blacklisted = packmsg_get_bool(&in);
        const void *key;
-       uint32_t len = packmsg_get_bin_raw(&in, &key); /* public key */
+       uint32_t len = packmsg_get_bin_raw(&in, &key);
 
        if(len != 32) {
                return false;
@@ -169,7 +186,6 @@ bool node_read_full(meshlink_handle_t *mesh, node_t *n) {
        }
 
        n->canonical_address = packmsg_get_str_dup(&in);
-
        uint32_t count = packmsg_get_array(&in);
 
        if(count > 5) {
@@ -180,8 +196,6 @@ bool node_read_full(meshlink_handle_t *mesh, node_t *n) {
                n->recent[i] = packmsg_get_sockaddr(&in);
        }
 
-       config_free(&config);
-
        return packmsg_done(&in);
 }
 
@@ -228,37 +242,26 @@ bool node_write_config(meshlink_handle_t *mesh, node_t *n) {
        return config_write(mesh, n->name, &config);
 }
 
-void load_all_nodes(meshlink_handle_t *mesh) {
-       DIR *dir;
-       struct dirent *ent;
-       char dname[PATH_MAX];
-
-       snprintf(dname, PATH_MAX, "%s" SLASH "hosts", mesh->confbase);
-       dir = opendir(dname);
-
-       if(!dir) {
-               logger(mesh, MESHLINK_ERROR, "Could not open %s: %s", dname, strerror(errno));
+static void load_node(meshlink_handle_t *mesh, const char *name) {
+       if(!check_id(name)) {
                return;
        }
 
-       while((ent = readdir(dir))) {
-               if(!check_id(ent->d_name)) {
-                       continue;
-               }
+       node_t *n = lookup_node(mesh, name);
 
-               node_t *n = lookup_node(mesh, ent->d_name);
+       if(n) {
+               return;
+       }
 
-               if(n) {
-                       continue;
-               }
+       n = new_node();
+       n->name = xstrdup(name);
 
-               n = new_node();
-               n->name = xstrdup(ent->d_name);
-               node_read_partial(mesh, n);
-               node_add(mesh, n);
+       if(!node_read_partial(mesh, n)) {
+               free_node(n);
+               return;
        }
 
-       closedir(dir);
+       node_add(mesh, n);
 }
 
 /*
@@ -373,7 +376,7 @@ bool setup_myself(meshlink_handle_t *mesh) {
 
        graph(mesh);
 
-       load_all_nodes(mesh);
+       config_scan_all(mesh, load_node);
 
        /* Open sockets */
 
index cbb1c1eeaa0389137f090ef4c6f8a07f51f02b06..98cd87cd56873ff7e1705d8251cb39f22c23a07c 100644 (file)
@@ -7,6 +7,8 @@
 /channels-fork
 /duplicate
 /echo-fork
+/encrypted
+/ephemeral
 /import-export
 /invite-join
 /sign-verify
index e96213ed7026393d5f3f3564c06041dd0978d5f4..45ad064cd8beaae0c02a0f20207b9a8664f1a4f2 100644 (file)
@@ -6,6 +6,7 @@ TESTS = \
        channels-cornercases.test \
        duplicate.test \
        encrypted.test \
+       ephemeral.test \
        import-export.test \
        invite-join.test \
        sign-verify.test \
@@ -29,6 +30,7 @@ check_PROGRAMS = \
        duplicate \
        echo-fork \
        encrypted \
+       ephemeral \
        import-export \
        invite-join \
        sign-verify \
@@ -62,6 +64,9 @@ echo_fork_LDADD = ../src/libmeshlink.la
 encrypted_SOURCES = encrypted.c
 encrypted_LDADD = ../src/libmeshlink.la
 
+ephemeral_SOURCES = ephemeral.c
+ephemeral_LDADD = ../src/libmeshlink.la
+
 import_export_SOURCES = import-export.c
 import_export_LDADD = ../src/libmeshlink.la
 
diff --git a/test/ephemeral.c b/test/ephemeral.c
new file mode 100644 (file)
index 0000000..10a6e2b
--- /dev/null
@@ -0,0 +1,74 @@
+#include <stdio.h>
+#include <sys/time.h>
+#include <assert.h>
+
+#include "meshlink.h"
+
+void log_cb(meshlink_handle_t *mesh, meshlink_log_level_t level, const char *text) {
+       static struct timeval tv0;
+       struct timeval tv;
+
+       if(tv0.tv_sec == 0) {
+               gettimeofday(&tv0, NULL);
+       }
+
+       gettimeofday(&tv, NULL);
+       fprintf(stderr, "%u.%.03u ", (unsigned int)(tv.tv_sec - tv0.tv_sec), (unsigned int)tv.tv_usec / 1000);
+
+       if(mesh) {
+               fprintf(stderr, "(%s) ", mesh->name);
+       }
+
+       fprintf(stderr, "[%d] %s\n", level, text);
+}
+
+int main() {
+       meshlink_set_log_cb(NULL, MESHLINK_DEBUG, log_cb);
+
+       // Open two ephemeral meshlink instance.
+
+       meshlink_handle_t *mesh1 = meshlink_open_ephemeral("foo", "ephemeral", DEV_CLASS_BACKBONE);
+       meshlink_handle_t *mesh2 = meshlink_open_ephemeral("bar", "ephemeral", DEV_CLASS_BACKBONE);
+
+       assert(mesh1);
+       assert(mesh2);
+
+       meshlink_set_log_cb(mesh1, MESHLINK_DEBUG, log_cb);
+       meshlink_set_log_cb(mesh2, MESHLINK_DEBUG, log_cb);
+
+       // Exchange data
+
+       assert(meshlink_import(mesh1, meshlink_export(mesh2)));
+       assert(meshlink_import(mesh2, meshlink_export(mesh1)));
+
+       // Check that they know each other
+
+       assert(meshlink_get_node(mesh1, "bar"));
+       assert(meshlink_get_node(mesh2, "foo"));
+
+       // Close the ephemeral instances and reopen them.
+
+       meshlink_close(mesh1);
+       meshlink_close(mesh2);
+
+       mesh1 = meshlink_open_ephemeral("foo", "ephemeral", DEV_CLASS_BACKBONE);
+       mesh2 = meshlink_open_ephemeral("bar", "ephemeral", DEV_CLASS_BACKBONE);
+
+       assert(mesh1);
+       assert(mesh2);
+
+       meshlink_set_log_cb(mesh1, MESHLINK_DEBUG, log_cb);
+       meshlink_set_log_cb(mesh2, MESHLINK_DEBUG, log_cb);
+
+       // Check that the nodes no longer know each other
+
+       assert(!meshlink_get_node(mesh1, "bar"));
+       assert(!meshlink_get_node(mesh2, "foo"));
+
+       // That's it.
+
+       meshlink_close(mesh1);
+       meshlink_close(mesh2);
+
+       return 0;
+}
diff --git a/test/ephemeral.test b/test/ephemeral.test
new file mode 100755 (executable)
index 0000000..9c49dbe
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+./ephemeral