+
+void meshlink_channel_close(meshlink_handle_t *mesh, meshlink_channel_t *channel) {
+ if(!mesh || !channel) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return;
+ }
+
+ utcp_close(channel->c);
+ free(channel);
+}
+
+ssize_t meshlink_channel_send(meshlink_handle_t *mesh, meshlink_channel_t *channel, const void *data, size_t len) {
+ if(!mesh || !channel) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return -1;
+ }
+
+ if(!len) {
+ return 0;
+ }
+
+ if(!data) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return -1;
+ }
+
+ // TODO: more finegrained locking.
+ // Ideally we want to put the data into the UTCP connection's send buffer.
+ // Then, preferrably only if there is room in the receiver window,
+ // kick the meshlink thread to go send packets.
+
+ pthread_mutex_lock(&mesh->mesh_mutex);
+ ssize_t retval = utcp_send(channel->c, data, len);
+ pthread_mutex_unlock(&mesh->mesh_mutex);
+
+ if(retval < 0) {
+ meshlink_errno = MESHLINK_ENETWORK;
+ }
+
+ return retval;
+}
+
+uint32_t meshlink_channel_get_flags(meshlink_handle_t *mesh, meshlink_channel_t *channel) {
+ if(!mesh || !channel) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return -1;
+ }
+
+ return channel->c->flags;
+}
+
+void update_node_status(meshlink_handle_t *mesh, node_t *n) {
+ if(n->status.reachable && mesh->channel_accept_cb && !n->utcp) {
+ n->utcp = utcp_init(channel_accept, channel_pre_accept, channel_send, n);
+ }
+
+ if(mesh->node_status_cb) {
+ mesh->node_status_cb(mesh, (meshlink_node_t *)n, n->status.reachable);
+ }
+}
+
+void handle_duplicate_node(meshlink_handle_t *mesh, node_t *n) {
+ if(!mesh->node_duplicate_cb || n->status.duplicate) {
+ return;
+ }
+
+ n->status.duplicate = true;
+ mesh->node_duplicate_cb(mesh, (meshlink_node_t *)n);
+}
+
+void meshlink_enable_discovery(meshlink_handle_t *mesh, bool enable) {
+#if HAVE_CATTA
+
+ if(!mesh) {
+ meshlink_errno = MESHLINK_EINVAL;
+ return;
+ }
+
+ pthread_mutex_lock(&mesh->mesh_mutex);
+
+ if(mesh->discovery == enable) {
+ goto end;
+ }
+
+ if(mesh->threadstarted) {
+ if(enable) {
+ discovery_start(mesh);
+ } else {
+ discovery_stop(mesh);
+ }
+ }
+
+ mesh->discovery = enable;
+
+end:
+ pthread_mutex_unlock(&mesh->mesh_mutex);
+#else
+ (void)mesh;
+ (void)enable;
+ meshlink_errno = MESHLINK_ENOTSUP;
+#endif
+}
+
+static void __attribute__((constructor)) meshlink_init(void) {
+ crypto_init();
+ unsigned int seed;
+ randomize(&seed, sizeof(seed));
+ srand(seed);
+}
+
+static void __attribute__((destructor)) meshlink_exit(void) {
+ crypto_exit();
+}
+
+/// Device class traits
+dev_class_traits_t dev_class_traits[_DEV_CLASS_MAX + 1] = {
+ { .min_connects = 3, .max_connects = 10000, .edge_weight = 1 }, // DEV_CLASS_BACKBONE
+ { .min_connects = 3, .max_connects = 100, .edge_weight = 3 }, // DEV_CLASS_STATIONARY
+ { .min_connects = 3, .max_connects = 3, .edge_weight = 6 }, // DEV_CLASS_PORTABLE
+ { .min_connects = 1, .max_connects = 1, .edge_weight = 9 }, // DEV_CLASS_UNKNOWN
+};