+ if(pthread_mutex_lock(&mesh->mutex) != 0) {
+ abort();
+ }
+
+ if(!whitelist(mesh, (node_t *)node)) {
+ pthread_mutex_unlock(&mesh->mutex);
+ return false;
+ }
+
+ pthread_mutex_unlock(&mesh->mutex);
+
+ logger(mesh, MESHLINK_DEBUG, "Whitelisted %s.\n", node->name);
+ return true;
+}
+
+bool meshlink_whitelist_by_name(meshlink_handle_t *mesh, const char *name) {
+ if(!mesh || !name) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ if(pthread_mutex_lock(&mesh->mutex) != 0) {
+ abort();
+ }
+
+ node_t *n = lookup_node(mesh, (char *)name);
+
+ if(!n) {
+ n = new_node();
+ n->name = xstrdup(name);
+ node_add(mesh, n);
+ }
+
+ if(!whitelist(mesh, (node_t *)n)) {
+ pthread_mutex_unlock(&mesh->mutex);
+ return false;
+ }
+
+ pthread_mutex_unlock(&mesh->mutex);
+
+ logger(mesh, MESHLINK_DEBUG, "Whitelisted %s.\n", name);
+ return true;
+}
+
+void meshlink_set_default_blacklist(meshlink_handle_t *mesh, bool blacklist) {
+ mesh->default_blacklist = blacklist;
+}
+
+bool meshlink_forget_node(meshlink_handle_t *mesh, meshlink_node_t *node) {
+ if(!mesh || !node) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return false;
+ }
+
+ node_t *n = (node_t *)node;
+
+ if(pthread_mutex_lock(&mesh->mutex) != 0) {
+ abort();
+ }
+
+ /* Check that the node is not reachable */
+ if(n->status.reachable || n->connection) {
+ pthread_mutex_unlock(&mesh->mutex);
+ logger(mesh, MESHLINK_WARNING, "Could not forget %s: still reachable", n->name);
+ return false;
+ }
+
+ /* Check that we don't have any active UTCP connections */
+ if(n->utcp && utcp_is_active(n->utcp)) {
+ pthread_mutex_unlock(&mesh->mutex);
+ logger(mesh, MESHLINK_WARNING, "Could not forget %s: active UTCP connections", n->name);
+ return false;
+ }
+
+ /* Check that we have no active connections to this node */
+ for list_each(connection_t, c, mesh->connections) {
+ if(c->node == n) {
+ pthread_mutex_unlock(&mesh->mutex);
+ logger(mesh, MESHLINK_WARNING, "Could not forget %s: active connection", n->name);
+ return false;
+ }
+ }
+
+ /* Remove any pending outgoings to this node */
+ if(mesh->outgoings) {
+ for list_each(outgoing_t, outgoing, mesh->outgoings) {
+ if(outgoing->node == n) {
+ list_delete_node(mesh->outgoings, list_node);
+ }
+ }
+ }
+
+ /* Delete the config file for this node */
+ if(!config_delete(mesh, "current", n->name)) {
+ pthread_mutex_unlock(&mesh->mutex);
+ return false;
+ }
+
+ /* Delete any pending invitations */
+ invitation_purge_node(mesh, n->name);
+
+ /* Delete the node struct and any remaining edges referencing this node */
+ node_del(mesh, n);
+
+ pthread_mutex_unlock(&mesh->mutex);
+
+ return config_sync(mesh, "current");
+}
+
+/* Hint that a hostname may be found at an address
+ * See header file for detailed comment.
+ */
+void meshlink_hint_address(meshlink_handle_t *mesh, meshlink_node_t *node, const struct sockaddr *addr) {
+ if(!mesh || !node || !addr) {
+ meshlink_errno = EINVAL;
+ return;
+ }
+
+ if(pthread_mutex_lock(&mesh->mutex) != 0) {
+ abort();
+ }
+
+ node_t *n = (node_t *)node;
+
+ if(node_add_recent_address(mesh, n, (sockaddr_t *)addr)) {
+ if(!node_write_config(mesh, n)) {
+ logger(mesh, MESHLINK_DEBUG, "Could not update %s\n", n->name);
+ }
+ }
+
+ pthread_mutex_unlock(&mesh->mutex);
+ // @TODO do we want to fire off a connection attempt right away?
+}
+
+static bool channel_pre_accept(struct utcp *utcp, uint16_t port) {
+ (void)port;
+ node_t *n = utcp->priv;
+ meshlink_handle_t *mesh = n->mesh;
+
+ if(mesh->channel_accept_cb && mesh->channel_listen_cb) {
+ return mesh->channel_listen_cb(mesh, (meshlink_node_t *)n, port);
+ } else {
+ return mesh->channel_accept_cb;
+ }
+}
+
+/* Finish one AIO buffer, return true if the channel is still open. */
+static bool aio_finish_one(meshlink_handle_t *mesh, meshlink_channel_t *channel, meshlink_aio_buffer_t **head) {
+ meshlink_aio_buffer_t *aio = *head;
+ *head = aio->next;
+
+ if(channel->c) {
+ channel->in_callback = true;
+
+ if(aio->data) {
+ if(aio->cb.buffer) {
+ aio->cb.buffer(mesh, channel, aio->data, aio->done, aio->priv);
+ }
+ } else {
+ if(aio->cb.fd) {
+ aio->cb.fd(mesh, channel, aio->fd, aio->done, aio->priv);
+ }
+ }
+
+ channel->in_callback = false;
+
+ if(!channel->c) {
+ free(aio);
+ free(channel);
+ return false;
+ }
+ }
+
+ free(aio);
+ return true;
+}
+
+/* Finish all AIO buffers, return true if the channel is still open. */
+static bool aio_abort(meshlink_handle_t *mesh, meshlink_channel_t *channel, meshlink_aio_buffer_t **head) {
+ while(*head) {
+ if(!aio_finish_one(mesh, channel, head)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static ssize_t channel_recv(struct utcp_connection *connection, const void *data, size_t len) {
+ meshlink_channel_t *channel = connection->priv;
+
+ if(!channel) {