nanonext/ 0000755 0001762 0000144 00000000000 15176120662 012112 5 ustar ligges users nanonext/tests/ 0000755 0001762 0000144 00000000000 15176112256 013254 5 ustar ligges users nanonext/tests/tests.R 0000644 0001762 0000144 00000204625 15176112256 014552 0 ustar ligges users # minitest - a minimal testing framework v0.0.5 --------------------------------
test_library <- function(package) library(package = package, character.only = TRUE)
test_true <- function(x) invisible(isTRUE(x) || {print(x); stop("the above was returned instead of TRUE")})
test_false <- function(x) invisible(isFALSE(x) || {print(x); stop("the above was returned instead of FALSE")})
test_null <- function(x) invisible(is.null(x) || {print(x); stop("the above was returned instead of NULL")})
test_notnull <- function(x) invisible(!is.null(x) || stop("returns NULL when expected to be not NULL"))
test_zero <- function(x) invisible(x == 0L || {print(x); stop("the above was returned instead of 0L")})
test_type <- function(type, x) invisible(typeof(x) == type || {stop("object of type '", typeof(x), "' was returned instead of '", type, "'")})
test_class <- function(class, x) invisible(inherits(x, class) || {stop("object of class '", paste(class(x), collapse = ", "), "' was returned instead of '", class, "'")})
test_equal <- function(a, b) invisible(a == b || {print(a); print(b); stop("the above expressions were not equal")})
test_identical <- function(a, b) invisible(identical(a, b) || {print(a); print(b); stop("the above expressions were not identical")})
test_print <- function(x) invisible(is.character(capture.output(print(x))) || stop("print output of expression cannot be captured as a character value"))
test_error <- function(x, containing = "") invisible(inherits(x <- tryCatch(x, error = identity), "error") && grepl(containing, x[["message"]], fixed = TRUE) || stop("Expected error message containing: ", containing, "\nActual error message: ", x[["message"]]))
NOT_CRAN <- Sys.getenv("NOT_CRAN") == "true"
# ------------------------------------------------------------------------------
test_library("nanonext")
nng_version()
later <- requireNamespace("later", quietly = TRUE)
promises <- requireNamespace("promises", quietly = TRUE)
test_class("nanoObject", n <- nano("req", listen = "inproc://nanonext", autostart = FALSE))
test_class("nanoObject", n1 <- nano("rep", dial = "inproc://nanonext", autostart = FALSE))
test_true(is_nano(n))
test_class("nanoSocket", n$socket)
test_class("nano", n$socket)
n$newmethod <- "doesnotwork"
test_null(n$newmethod)
test_type("integer", attr(n$socket, "id"))
test_equal(n$socket$state, "opened")
test_equal(n$socket$protocol, "req")
test_equal(n$send("not ready", mode = "serial"), 8L)
test_equal(n$recv(), 11L)
test_class("nano", n$opt("recv-size-max", 8192))
test_equal(n$opt("recv-size-max"), 8192L)
test_class("nano", n$opt("recv-buffer", 8L))
test_error(opt(n$socket, "recv-buffer") <- -1, "Incorrect type")
test_class("nano", n$opt("req:resend-time", 0L))
test_class("nano", n$opt("socket-name", "nano"))
test_equal(n$opt("socket-name"), "nano")
test_error(n$opt("socket-name", NULL), "argument")
test_print(n$listener[[1]])
test_class("nanoListener", n$listener[[1]])
test_equal(n$listener[[1]]$url, "inproc://nanonext")
test_equal(n$listener[[1]]$state, "not started")
test_class("nano", n$listener_opt("recv-size-max", 1024)[[1L]])
test_equal(n$listener_opt("recv-size-max")[[1L]], 1024L)
test_error(n$listener_opt("false", 100), "supported")
test_error(n$listener_opt("false"), "supported")
test_error(n$listener_opt("false", "false"), "supported")
test_error(n$listener_opt("false", NULL), "supported")
test_error(n$listener_opt("false", TRUE), "supported")
test_error(n$listener_opt("false", list()), "type")
test_zero(n$listener_start())
test_equal(n$listener[[1]]$state, "started")
test_print(n1$dialer[[1]])
test_class("nanoDialer", n1$dialer[[1]])
test_equal(n1$dialer[[1]]$url, "inproc://nanonext")
test_equal(n1$dialer[[1]]$state, "not started")
test_class("nano", n1$dialer_opt("reconnect-time-min", 1000)[[1L]])
test_equal(n1$dialer_opt("reconnect-time-min")[[1L]], 1000L)
test_class("nano", n1$dialer_opt("recv-size-max", 8192)[[1L]])
test_equal(n1$dialer_opt("recv-size-max")[[1L]], 8192L)
test_error(n1$dialer_opt("false", 100), "supported")
test_error(n1$dialer_opt("false"), "supported")
test_error(n1$dialer_opt("false", "false"), "supported")
test_error(n1$dialer_opt("false", NULL), "supported")
test_error(n1$dialer_opt("false", TRUE), "supported")
test_error(n1$dialer_opt("false", list()), "type")
test_zero(n1$dialer_start())
test_equal(n1$dialer[[1]]$state, "started")
test_error(n$send(list(), mode = "raw"), "atomic vector type")
test_error(n$recv(mode = "none"), "mode")
test_error(n$recv(mode = "int"), "mode")
test_error(n$recv(mode = "logica"), "mode")
test_error(n$recv(mode = "charact"), "mode")
test_error(n$recv(mode = "positrons"), "mode")
test_true(is_aio(raio <- recv_aio(n$socket, timeout = 1L, cv = substitute())))
test_print(raio)
test_class("errorValue", call_aio(raio)$data)
test_class("errorValue", raio$data)
test_zero(pipe_id(raio))
r <- n$send(data.frame(), block = FALSE)
if (r == 8L) r <- n$send(data.frame(), block = 500L)
test_zero(r)
r <- n1$recv(block = FALSE)
if (is_error_value(r)) r <- n1$recv(block = 500)
test_class("data.frame", r)
test_zero(n1$send(c("test", "", "spec"), mode = "raw", block = 500))
test_identical(n$recv("character", block = 500), c("test", "", "spec"))
test_zero(n$send(1:5, mode = "raw"))
test_equal(length(n1$recv("integer", block = 500)), 5L)
test_true(is_aio(saio <- n1$send_aio(paste(replicate(5, random(1e3L)), collapse = ""), mode = 1L, timeout = 900)))
test_print(saio)
if (later) test_null(.keep(saio, new.env()))
test_class("sendAio", call_aio(saio))
test_zero(saio$result)
test_error(n$send("wrong mode", mode = "none"), "mode")
test_error(n$send("wrong mode", mode = "ser"), "mode")
test_error(n$send("wrong mode", mode = "rawial"), "mode")
test_class("recvAio", raio <- n$recv_aio(timeout = 500))
test_print(raio)
test_equal(nchar(call_aio(raio)[["value"]]), 10000L)
test_type("integer", pipe_id(raio))
raio$newfield <- "doesnotwork"
test_null(raio$newfield)
test_class("sendAio", saio <- n$send_aio(c(1.1, 2.2), mode = "raw", timeout = 500))
saio$newfield <- "doesnotwork"
test_null(saio$newfield)
test_type("logical", unresolved(saio))
test_type("logical", .unresolved(saio))
test_class("recvAio", msg <- n1$recv_aio(mode = "numeric", timeout = 500))
test_identical(call_aio(msg), msg)
test_class("recvAio", msg <- n1$recv_aio(mode = "complex", timeout = 500))
test_null(stop_aio(msg))
test_null(stop_aio(n))
test_false(stop_request(n))
test_identical(call_aio(msg), msg)
test_class("errorValue", msg$data)
test_identical(call_aio(n), n)
test_class("sendAio", sraio <- n$send_aio(as.raw(0L), mode = "raw", timeout = 500))
test_class("recvAio", rraio <- n1$recv_aio(mode = 1L, timeout = 500))
test_true(is_nul_byte(suppressWarnings(call_aio_(rraio)$data)))
test_class("sendAio", sraio <- n$send_aio(as.raw(1L), mode = 2L, timeout = 500))
test_class("recvAio", rraio <- n1$recv_aio(mode = "raw", timeout = 500))
test_type("raw", call_aio(rraio)$data)
test_class("sendAio", sraio <- n$send_aio(c(1+2i, 4+3i), mode = "raw", timeout = 500))
test_class("recvAio", rraio <- n1$recv_aio(mode = "complex", timeout = 500))
test_type("complex", call_aio(rraio)$data)
test_class("sendAio", sraio <- n$send_aio(5, mode = "raw", timeout = 500))
test_class("recvAio", rraio <- n1$recv_aio(mode = "double", timeout = 500))
test_type("double", call_aio(rraio)$data)
test_class("sendAio", sraio <- n$send_aio(c(1, 2), mode = "raw", timeout = 500))
test_class("recvAio", rraio <- n1$recv_aio(mode = "numeric", timeout = 500))
test_true(is.numeric(call_aio(rraio)$data))
test_class("sendAio", sraio <- n$send_aio(c(1L, 2L, 3L), mode = "raw", timeout = 500))
test_class("recvAio", rraio <- n1$recv_aio(mode = "integer", timeout = 500))
test_type("integer", call_aio(rraio)$data)
test_class("sendAio", sraio <- n$send_aio(as.raw(0L), mode = "raw", timeout = 500))
test_class("recvAio", rraio <- n1$recv_aio(mode = "double", timeout = 500))
test_type("raw", suppressWarnings(call_aio(rraio)$data))
test_class("sendAio", sraio <- n$send_aio(as.raw(0L), mode = "raw", timeout = 500))
test_class("recvAio", rraio <- n1$recv_aio(mode = "integer", timeout = 500))
test_type("raw", suppressWarnings(call_aio(rraio)$data))
test_class("sendAio", sraio <- n$send_aio(as.raw(0L), mode = "raw", timeout = 500))
test_class("recvAio", rraio <- n1$recv_aio(mode = "logical", timeout = 500))
test_type("raw", suppressWarnings(collect_aio(rraio)))
test_class("sendAio", sraio <- n$send_aio(as.raw(0L), mode = "raw", timeout = 500))
test_class("recvAio", rraio <- n1$recv_aio(mode = "numeric", timeout = 500))
test_type("raw", suppressWarnings(rraio[]))
test_class("sendAio", sraio <- n$send_aio(as.raw(0L), mode = "raw", timeout = 500))
test_class("recvAio", rraio <- n1$recv_aio(mode = "complex", timeout = 500))
test_type("raw", suppressWarnings(collect_aio_(rraio)))
rcv <- cv()
test_equal(race_aio(list(rraio), rcv), 1L)
test_equal(race_aio(list(sraio, rraio), rcv), 1L)
test_equal(race_aio(list(rraio), NULL), 1L)
test_equal(race_aio(list(rraio), "invalid"), 1L)
rtest <- recv_aio(n1$socket, timeout = 100, cv = rcv)
test_equal(race_aio(list(rtest), rcv), 1L)
test_error(opt(rraio[["aio"]], "false") <- 0L, "valid")
test_error(subscribe(rraio[["aio"]], "false"), "valid")
test_error(opt(rraio[["aio"]], "false"), "valid")
test_error(stat(rraio[["aio"]], "pipes"), "valid")
test_zero(n$dial(url = "inproc://two", autostart = FALSE))
test_zero(n$dialer_start())
test_class("nanoDialer", n$dialer[[1L]])
test_type("double", stat(n$dialer[[1L]], "id"))
test_zero(n$listen(url = "inproc://three", autostart = FALSE))
test_zero(n$listener_start())
test_class("nanoListener", n$listener[[2L]])
test_type("double", stat(n$listener[[2L]], "id"))
test_zero(n$dial(url = "inproc://four"))
test_zero(close(n$listener[[1]]))
test_equal(suppressWarnings(start(n$listener[[1]])), 12L)
test_equal(suppressWarnings(close(n$listener[[1]])), 12L)
test_zero(close(n1$dialer[[1]]))
test_equal(suppressWarnings(start(n1$dialer[[1]])), 12L)
test_equal(suppressWarnings(close(n1$dialer[[1]])), 12L)
test_zero(reap(n$listener[[2]]))
test_zero(reap(n$dialer[[2]]))
test_zero(n$close())
test_zero(n1$close())
test_equal(suppressWarnings(n1$close()), 7L)
test_equal(n$socket[["state"]], "closed")
test_equal(n1$socket[state], "closed")
test_class("conditionVariable", cv <- cv())
test_print(cv)
test_false(until(cv, 10L))
test_false(until(cv, 10))
test_false(until_(cv, 10L))
test_false(until_(cv, 10))
test_false(until_(cv, "test"))
test_zero(cv_reset(cv))
test_zero(cv_value(cv))
test_class("nanoObject", req <- nano("req", listen = "inproc://testing"))
test_class("nanoSocket", rep <- socket("rep", dial = "inproc://testing", listen = "inproc://testing2"))
test_print(rep)
test_equal(stat(rep, "dialers"), 1)
test_equal(stat(rep, "protocol"), "rep")
test_null(stat(rep, "nonexistentstat"))
test_class("nano", req$opt("req:resend-time", 1000))
test_equal(req$opt("req:resend-time"), 1000L)
test_error(req$opt("none"), "supported")
test_type("externalptr", req$context_open())
test_class("nanoContext", req$context)
test_class("nano", req$context)
test_type("integer", req$context$id)
test_equal(req$context$state, "opened")
test_equal(req$context$protocol, "req")
test_class("nano", req$opt("send-timeout", 1000))
test_equal(req$opt("send-timeout"), 1000L)
test_error(req$opt("false", 100), "supported")
test_error(req$opt("false"), "supported")
test_error(req$opt("false", "false"), "supported")
test_error(req$opt("false", NULL), "supported")
test_error(req$opt("false", TRUE), "supported")
test_error(req$opt("false", list()), "type")
test_class("nanoContext", ctx <- context(rep))
test_print(ctx)
test_true(.mark())
test_class("sendAio", csaio <- req$send_aio(data.frame(), mode = 1L, timeout = 500))
test_zero(call_aio_(csaio)$result)
test_class("recvAio", craio <- recv_aio(ctx, mode = 8L, timeout = 500))
test_type("raw", res <- collect_aio(craio))
test_type("list", unserialize(res[9:length(res)]))
test_false(.mark(FALSE))
test_zero(req$send("context test", mode ="raw", block = 500))
test_equal(recv(ctx, mode = "string", block = 500), "context test")
test_type("integer", req$send(data.frame(), mode = "serial", block = 500))
test_class("recvAio", msg <- recv_aio(ctx, mode = "serial", timeout = 500))
test_type("logical", .unresolved(msg))
test_type("logical", unresolved(msg))
test_class("data.frame", call_aio(msg)$data)
test_false(unresolved(msg))
test_zero(req$send(c(TRUE, FALSE, TRUE), mode = 2L, block = 500))
test_class("recvAio", msg <- recv_aio(ctx, mode = 6L, timeout = 500))
test_type("logical", msg[])
test_identical(collect_aio(msg), collect_aio_(msg))
test_class("sendAio", err <- send_aio(ctx, msg[["data"]], mode = "serial"))
test_null(stop_aio(err))
test_class("sendAio", err <- send_aio(ctx, "test"))
test_class("errorValue", call_aio(err)$result)
test_class("errorValue", call_aio(list(err))[[1L]][["result"]])
test_class("errorValue", call_aio_(err)$result)
test_class("errorValue", call_aio_(list(item = err))[["item"]][["result"]])
test_class("errorValue", collect_aio(err))
test_class("errorValue", collect_aio(list(item = err))[["item"]])
test_class("errorValue", collect_aio_(list(err))[[1L]])
test_zero(req$send(serialize(NULL, NULL, ascii = TRUE), mode = 2L, block = 500))
test_null(call_aio(recv_aio(ctx, mode = 1L, timeout = 500))[["value"]])
test_class("sendAio", saio <- send_aio(ctx, as.raw(1L), mode = 2L, timeout = 500))
test_identical(req$recv(mode = 8L, block = 500), as.raw(1L))
test_class("recvAio", rek <- request(req$context, c(1+3i, 4+2i), send_mode = 2L, recv_mode = "complex", timeout = 500))
test_zero(reply(ctx, execute = identity, recv_mode = 3L, send_mode = "raw", timeout = 500))
test_type("complex", call_aio(rek)[["data"]])
test_class("recvAio", rek <- request(req$context, c(1+3i, 4+2i), send_mode = "serial", recv_mode = "serial", timeout = 500))
test_zero(reply(ctx, execute = identity, recv_mode = 1L, send_mode = 1L, timeout = 500))
test_type("complex", call_aio(rek)[["data"]])
test_type("integer", rek[["aio"]])
test_type("list", cfg <- serial_config(class = c("invalid", "custom"), sfunc = list(identity, function(x) raw(1L)), ufunc = list(identity, as.integer)))
opt(req$socket, "serial") <- cfg
opt(rep, "serial") <- cfg
custom <- list(`class<-`(new.env(), "custom"), new.env())
test_zero(send(req$socket, custom, mode = "serial", block = 500))
test_type("integer", recv(rep, block = 500)[[1L]])
custom <- list(`class<-`(new.env(), "unused"), new.env())
test_zero(send(req$socket, custom, mode = "serial", block = 500))
test_type("list", recv(rep, block = 500))
cfg <- serial_config("custom", function(x) as.raw(length(x)), function(x) lapply(seq_len(as.integer(x)), new.env))
test_type("list", cfg)
opt(req$socket, "serial") <- cfg
opt(rep, "serial") <- cfg
test_zero(send(rep, custom, block = 500))
test_type("list", recv(req$socket, mode = 1L, block = 500))
cfg <- serial_config(c("error_env", "string_class"), list(function(x) stop("serialization failue"), function(x) "serialized string"), list(function(x) stop(), function(x) stop()))
opt(req$socket, "serial") <- cfg
test_error(send(req$socket, `class<-`(new.env(), "error_env"), block = 500), "serialization failue")
test_error(send(req$socket, `class<-`(new.env(), "string_class"), block = 500), "string_class")
opt(req$socket, "serial") <- cfg
opt(req$socket, "serial") <- list()
opt(rep, "serial") <- list()
test_error(serial_config(1L, identity, identity), "must be a character vector")
test_error(serial_config(c("custom", "custom2"), list(identity), list(identity)), "must all be the same length")
test_error(serial_config("custom", "func1", "func2"), "must be a function or list of functions")
test_error(serial_config("custom", identity, "func2"), "must be a function or list of functions")
test_error(opt(rep, "wrong") <- cfg, "not supported")
test_error(opt(rep, "serial") <- pairlist(a = 1L), "not supported")
test_class("recvAio", cs <- request(req$context, "test", send_mode = "serial", cv = cv, timeout = 500, id = TRUE))
test_notnull(cs$data)
test_type("externalptr", ctxn <- .context(rep))
test_class("recvAio", cr <- recv_aio(ctxn, mode = 8L, cv = cv, timeout = 500))
test_type("raw", call_aio(cr)$data)
test_type("integer", cr$aio)
test_type("integer", send(ctxn, TRUE, mode = 0L, block = FALSE))
test_type("externalptr", ctxn <- .context(rep))
test_class("recvAio", cs <- request(.context(req$socket), data = TRUE, cv = NA, id = TRUE))
test_notnull(cs$data)
test_true(recv(ctxn, block = 500))
test_zero(send(ctxn, TRUE, mode = 1L, block = 500))
test_class("recvAio", cs <- request(.context(req$socket), data = TRUE, timeout = 5, id = TRUE))
test_false(stop_request(cs))
test_zero(reap(ctxn))
test_equal(reap(ctxn), 7L)
test_zero(pipe_notify(rep, cv, add = TRUE, flag = TRUE))
test_zero(pipe_notify(rep, cv, remove = TRUE, flag = tools::SIGCONT))
test_zero(pipe_notify(req$socket, cv = cv, add = TRUE))
test_zero(pipe_notify(rep, cv = NULL, add = TRUE, remove = TRUE))
test_error(request(err, "test", cv = cv), "valid")
test_error(recv_aio(err, cv = cv, timeout = 500))
test_error(wait(err), "valid")
test_error(wait_(err), "valid")
test_error(until(err, 10), "valid")
test_error(until_(err, 10), "valid")
test_error(cv_value(err), "valid")
test_error(cv_reset(err), "valid")
test_error(cv_signal(err), "valid")
test_error(pipe_notify(err, cv), "valid Socket")
test_error(pipe_notify(rep, err), "valid Condition Variable")
test_zero(req$context_close())
test_null(req$context_close)
test_zero(req$close())
test_null(req$context)
rep$dialer <- NULL
test_type("externalptr", rep$dialer[[1L]])
test_zero(close(ctx))
test_equal(suppressWarnings(close(ctx)), 7L)
test_zero(close(rep))
test_class("nanoObject", pub <- nano("pub", listen = "inproc://ps"))
test_class("nanoObject", sub <- nano("sub", dial = "inproc://ps", autostart = NA))
test_zero(cv_reset(cv))
test_zero(pipe_notify(pub$socket, cv, add = TRUE, remove = TRUE))
test_class("nano", sub$opt(name = "sub:prefnew", value = FALSE))
test_false(sub$opt(name = "sub:prefnew"))
test_error(sub$opt(name = "false", value = 100), "supported")
test_error(sub$opt(name = "false"), "supported")
test_error(sub$opt(name = "false", value = list()), "type")
test_class("nano", sub$subscribe("test"))
test_class("nano", subscribe(sub$socket, NULL))
test_class("nano", sub$unsubscribe("test"))
test_type("externalptr", sub$context_open())
test_class("nanoContext", sub$context)
test_class("nano", sub$subscribe(12))
test_class("nano", sub$unsubscribe(12))
test_class("nano", sub$subscribe(NULL))
test_zero(sub$context_close())
test_null(sub$context)
test_zero(sub$close())
test_zero(pub$close())
test_true(wait(cv))
test_type("externalptr", cv2 <- cv())
test_type("externalptr", cv3 <- cv())
test_type("externalptr", cv %~>% cv2 %~>% cv3)
test_zero(cv_signal(cv))
test_equal(cv_value(cv), 1L)
test_true(wait_(cv3))
test_type("externalptr", cv %~>% cv3)
test_error("a" %~>% cv3, "valid Condition Variable")
test_error(cv3 %~>% "a", "valid Condition Variable")
test_class("nanoObject", surv <- nano(protocol = "surveyor", listen = "inproc://sock1", dial = "inproc://sock2"))
test_print(surv)
test_class("nanoObject", resp <- nano(protocol = "respondent", listen = "inproc://sock2", dial = "inproc://sock1"))
test_zero(pipe_notify(surv$socket, cv, add = TRUE, remove = TRUE, flag = TRUE))
surv$dialer <- NULL
test_type("externalptr", surv$dialer[[1L]])
test_type("externalptr", surv$listener[[1L]])
test_class("nano", surv$survey_time(50))
test_zero(surv$send("survey", block = 500))
test_class("errorValue", surv$recv(block = 200))
test_type("externalptr", surv$context_open())
test_type("externalptr", resp$context_open())
test_class("nano", surv$survey_time(value = 2000))
test_zero(surv$context_close())
test_zero(resp$context_close())
test_zero(surv$close())
test_zero(resp$close())
test_false(wait(cv))
test_class("errorValue", resp$recv())
test_class("nanoObject", poly <- nano(protocol = "poly", listen = "inproc://polytest"))
test_equal(formals(poly$send)$pipe, 0L)
test_equal(formals(poly$send_aio)$pipe, 0L)
test_zero(poly$close())
test_zero(cv_reset(cv))
test_class("nanoSocket", poly <- socket(protocol = "poly"))
test_class("nanoSocket", poly1 <- socket(protocol = "poly"))
test_class("nanoSocket", poly2 <- socket(protocol = "poly"))
test_class("nanoMonitor", m <- monitor(poly, cv))
test_print(m)
test_zero(listen(poly))
test_null(read_monitor(m))
test_class("sendAio", send_aio(poly, "one", timeout = 50))
invisible(gc())
test_null(msleep(55))
test_zero(dial(poly1))
test_zero(dial(poly2))
test_true(wait(cv))
test_true(wait(cv))
test_equal(length(pipes <- read_monitor(m)), 2L)
test_zero(send_aio(poly, "one", timeout = 500, pipe = pipes[1L])[])
test_zero(send(poly, "two", block = 500, pipe = pipes[2L]))
test_type("integer", send(poly, "two", block = FALSE, pipe = pipes[2L]))
test_type("character", recv(poly1, block = 500))
test_type("character", recv(poly2, block = 500))
test_zero(reap(poly2))
test_zero(reap(poly1))
test_true(wait(cv))
test_type("integer", read_monitor(m))
test_error(read_monitor(poly), "valid Monitor")
test_error(monitor("socket", "cv"), "valid Socket")
test_error(monitor(poly, "cv"), "valid Condition Variable")
test_zero(reap(poly))
test_class("nanoSocket", bus <- socket(protocol = "bus"))
test_class("nanoSocket", push <- socket(protocol = "push"))
test_class("nanoSocket", pull <- socket(protocol = "pull"))
test_class("nanoSocket", pair <- socket(protocol = "pair"))
test_class("nano", bus)
test_error(context(bus), "Not supported")
test_error(context(push), "Not supported")
test_error(context(pull), "Not supported")
test_error(context(pair), "Not supported")
test_equal(suppressWarnings(listen(bus, url = "test", fail = "warn")), 3L)
test_error(listen(bus, url = "test", fail = "error"), "argument")
test_equal(listen(bus, url = "test", fail = "none"), 3L)
test_equal(suppressWarnings(dial(bus, url = "test", fail = 1L)), 3L)
test_error(dial(bus, url = "test", fail = 2L), "argument")
test_equal(dial(bus, url = "test", fail = 3L), 3L)
test_error(listen(bus, url = "tls+tcp://localhost/:0", tls = "wrong"), "valid TLS")
test_error(dial(bus, url = "tls+tcp://localhost/:0", tls = "wrong"), "valid TLS")
test_zero(close(bus))
test_equal(suppressWarnings(close(bus)), 7L)
test_null(stat(bus, "pipes"))
test_zero(close(push))
test_zero(close(pull))
test_zero(reap(pair))
test_error(socket(protocol = "newprotocol"), "protocol")
test_error(socket(dial = "test"), "argument")
test_error(socket(listen = "test"), "argument")
test_type("list", ncurl("http://www.cam.ac.uk/"))
test_type("list", res <- ncurl("http://www.cam.ac.uk/", follow = FALSE, response = TRUE))
if (res$status == 301L) test_true(length(res$headers) > 1L)
test_type("list", ncurl("http://www.cam.ac.uk/", follow = TRUE))
test_type("list", ncurl("https://postman-echo.com/post", convert = FALSE, method = "POST", headers = c(`Content-Type` = "application/octet-stream"), data = as.raw(1L), response = c("Date", "Server"), timeout = 3000))
test_class("errorValue", ncurl("http")$data)
test_class("recvAio", haio <- ncurl_aio("http://www.cam.ac.uk/", timeout = 3000L))
test_true(is_aio(haio))
test_type("integer", call_aio(haio)$status)
test_class("ncurlAio", haio <- ncurl_aio("https://www.cam.ac.uk/", convert = FALSE, response = "server", timeout = 3000L))
test_notnull(haio$status)
if (call_aio(haio)$status == 200L) test_notnull(haio$headers)
test_class("ncurlAio", put1 <- ncurl_aio("https://postman-echo.com/put", method = "PUT", headers = c(`Content-Type` = "text/plain", Authorization = "Bearer token"), data = "test", response = TRUE, timeout = 3000L))
test_print(put1)
test_type("integer", call_aio_(put1)$status)
if (put1$status == 200L) test_notnull(put1$headers)
if (put1$status == 200L) test_notnull(put1$data)
test_null(stop_aio(put1))
test_false(stop_request(put1))
test_class("ncurlAio", haio <- ncurl_aio("https://i.i"))
test_class("errorValue", call_aio(haio)$data)
test_print(haio$data)
test_class("ncurlAio", ncaio <- ncurl_aio("https://nanonext.r-lib.org/reference/figures/logo.png"))
if (suppressWarnings(call_aio(ncaio)$status == 200L)) test_type("raw", ncaio$data)
test_class("errorValue", ncurl_aio("http")$data)
sess <- ncurl_session("https://postman-echo.com/post", method = "POST", headers = c(`Content-Type` = "text/plain"), data = "test", response = c("date", "Server"), timeout = 3000L)
test_true(is_ncurl_session(sess) || is_error_value(sess))
if (is_ncurl_session(sess)) test_equal(length(transact(sess)), 3L)
if (is_ncurl_session(sess)) test_zero(close(sess))
if (is_ncurl_session(sess)) test_equal(suppressWarnings(close(sess)), 7L)
sess <- ncurl_session("https://postman-echo.com/post", convert = FALSE, method = "POST", headers = c(`Content-Type` = "text/plain"), timeout = 3000)
test_true(is_ncurl_session(sess) || is_error_value(sess))
if (is_ncurl_session(sess)) test_equal(length(transact(sess)), 3L)
if (is_ncurl_session(sess)) test_zero(close(sess))
if (is_ncurl_session(sess)) test_equal(transact(sess)$data, 7L)
sess_all <- ncurl_session("https://postman-echo.com/get", response = TRUE, timeout = 3000L)
test_true(is_ncurl_session(sess_all) || is_error_value(sess_all))
if (is_ncurl_session(sess_all)) {
trans_all <- transact(sess_all)
test_true(length(trans_all$headers) > 0L)
test_zero(close(sess_all))
}
test_class("errorValue", suppressWarnings(ncurl_session("https://i")))
test_error(ncurl_aio("https://", tls = "wrong"), "valid TLS")
test_error(ncurl("https://www.cam.ac.uk/", tls = "wrong"), "valid TLS")
test_type("externalptr", etls <- tls_config())
test_error(stream(dial = "wss://127.0.0.1:5555", textframes = TRUE, tls = etls))
test_error(stream(dial = "wss://127.0.0.1:5555"))
test_error(stream(dial = "errorValue3"), "argument")
test_error(stream(dial = "inproc://notsup"), "Not supported")
test_error(stream(dial = "wss://127.0.0.1:5555", tls = "wrong"), "valid TLS")
test_error(stream(listen = "errorValue3"), "argument")
test_error(stream(listen = "inproc://notsup"), "Not supported")
test_error(stream(listen = "errorValue3", tls = "wrong"), "valid TLS")
test_error(stream(), "specify a URL")
test_type("character", ver <- nng_version())
test_equal(length(ver), 2L)
test_equal(nng_error(5L), "5 | Timed out")
test_equal(nng_error(8), "8 | Try again")
test_true(is_nul_byte(as.raw(0L)))
test_false(is_nul_byte(NULL))
test_false(is_error_value(1L))
test_type("double", mclock())
test_null(msleep(1L))
test_null(msleep(1))
test_null(msleep("a"))
test_null(msleep(-1L))
test_type("character", urlp <- parse_url("://"))
test_equal(length(urlp), 7L)
test_true(all(nzchar(parse_url("wss://use:r@[::1]/path?q=1#name"))))
test_type("character", random())
test_equal(nchar(random(1)), 2L)
test_equal(nchar(random(1024)), 2048L)
test_equal(length(random(4L, convert = FALSE)), 4L)
test_error(random(1025), "between 1 and 1024")
test_error(random(0), "between 1 and 1024")
test_error(random(-1), "between 1 and 1024")
test_error(random("test"), "integer")
test_error(parse_url("tcp:/"), "argument")
test_equal(parse_url("://missing/scheme")["scheme"], "")
test_equal(parse_url("tcp://")["port"], "")
for (i in c(100:103, 200:208, 226, 300:308, 400:426, 428:431, 451, 500:511, 600))
test_type("character", status_code(i))
s <- tryCatch(stream(dial = "wss://echo.websocket.org/", textframes = TRUE), error = function(e) NULL)
if (is_nano(s)) test_notnull(recv(s, block = 500L))
if (is_nano(s)) test_type("character", opt(s, "ws:response-headers"))
if (is_nano(s)) test_error(opt(s, "ws:request-headers") <- "test\n", 24)
if (is_nano(s)) test_type("integer", send(s, c("message1", "test"), block = 500L))
if (is_nano(s)) test_notnull(recv(s, block = FALSE))
if (is_nano(s)) test_type("integer", send(s, "message2", block = FALSE))
if (is_nano(s)) test_notnull(suppressWarnings(recv(s, mode = 9L, block = 100)))
if (is_nano(s)) test_type("integer", send(s, 2L, block = 500))
if (is_nano(s)) test_class("recvAio", sr <- recv_aio(s, mode = "integer", timeout = 500L))
if (is_nano(s)) test_notnull(suppressWarnings(call_aio(sr)[["data"]]))
if (is_nano(s)) test_null(stop_aio(sr))
if (is_nano(s)) test_class("sendAio", ss <- send_aio(s, "async", timeout = 500L))
if (is_nano(s)) test_type("integer", ss[])
if (is_nano(s)) test_null(stop_aio(ss))
if (is_nano(s)) test_type("integer", send(s, 12.56, mode = "raw", block = 500L))
if (is_nano(s)) test_class("recvAio", sr <- recv_aio(s, mode = "double", timeout = 500L, cv = cv))
if (is_nano(s)) test_notnull(suppressWarnings(call_aio_(sr)[["data"]]))
if (is_nano(s)) test_true(cv_value(cv) > 0L)
if (is_nano(s)) test_type("character", opt(s, "ws:request-headers"))
if (is_nano(s)) test_notnull(opt(s, "tcp-nodelay") <- FALSE)
if (is_nano(s)) test_error(opt(s, "none"), "supported")
if (is_nano(s)) test_error(`opt<-`(s, "none", list()), "supported")
if (is_nano(s)) test_print(s)
if (is_nano(s)) test_type("integer", close(s))
test_equal(nanonext:::.DollarNames.ncurlAio(NULL, "sta"), "status")
test_equal(nanonext:::.DollarNames.recvAio(NULL, "dat"), "data")
test_equal(nanonext:::.DollarNames.sendAio(NULL, "r"), "result")
test_zero(length(nanonext:::.DollarNames.nano(NULL)))
fakesock <- `class<-`(new.env(), "nanoSocket")
test_error(dial(fakesock), "valid Socket")
test_error(dial(fakesock, autostart = FALSE), "valid Socket")
test_error(listen(fakesock), "valid Socket")
test_error(listen(fakesock, autostart = FALSE), "valid Socket")
test_error(context(fakesock), "valid Socket")
test_error(.context(fakesock), "valid Socket")
test_error(stat(fakesock, "pipes"), "valid Socket")
test_error(close(fakesock), "valid Socket")
test_false(.unresolved(fakesock))
fakectx <- `class<-`("test", "nanoContext")
test_false(unresolved(fakectx))
test_false(.unresolved(fakectx))
test_error(request(fakectx, data = "test"), "valid Context")
test_error(subscribe(fakectx, NULL), "valid")
test_error(close(fakectx), "valid Context")
test_equal(reap(fakectx), 3L)
fakestream <- `class<-`("test", "nanoStream")
test_print(fakestream)
fakesession <- `class<-`("test", "ncurlSession")
test_print(fakesession)
test_error(transact(fakesession), "valid")
test_error(close(fakesession), "valid")
test_error(send(fakestream, "test"), "valid")
test_error(send_aio(fakestream, "test"), "valid")
test_error(recv(fakestream), "valid")
test_error(recv_aio(fakestream), "valid")
test_error(opt(fakestream, name = "test") <- "test", "valid")
test_error(opt(fakestream, name = "test"), "valid")
test_equal(close(fakestream), 7L)
fakedial <- `class<-`("test", "nanoDialer")
test_error(start(fakedial), "valid Dialer")
test_error(close(fakedial), "valid Dialer")
fakelist <- `class<-`("test", "nanoListener")
test_error(start(fakelist), "valid Listener")
test_error(close(fakelist), "valid Listener")
unres <- `class<-`(NA, "unresolvedValue")
test_false(unresolved(unres))
test_print(unres)
test_type("logical", unres <- unresolved(list("a", "b")))
test_equal(length(unres), 1L)
test_type("integer", unres <- .unresolved(list("a", "b")))
test_equal(length(unres), 1L)
test_zero(race_aio(list(), cv()))
test_zero(race_aio("not a list", cv()))
test_zero(race_aio(list("a", "b"), cv()))
test_identical(call_aio("a"), "a")
test_identical(call_aio_("a"), "a")
test_error(collect_aio_("a"), "object is not an Aio or list of Aios")
test_error(collect_aio_(list("a")), "object is not an Aio or list of Aios")
test_error(collect_aio(list(fakesock)), "object is not an Aio or list of Aios")
test_null(stop_aio("a"))
test_false(stop_request("a"))
test_null(stop_aio(list("a")))
test_false(stop_request(list("a")))
test_null(.keep(NULL, new.env()))
test_null(.keep(new.env(), new.env()))
pem <- "-----BEGIN CERTIFICATE----- -----END CERTIFICATE-----"
test_tls <- function(pem) {
file <- tempfile()
on.exit(unlink(file))
cat(pem, file = file)
test_error(tls_config(client = file), "Cryptographic error")
test_error(tls_config(server = file), "Cryptographic error")
}
test_true(test_tls(pem = pem))
test_error(tls_config(client = c(pem, pem)), "Cryptographic error")
test_error(tls_config(server = c(pem, pem)), "Cryptographic error")
test_type("list", cert <- write_cert(cn = "127.0.0.1"))
test_equal(length(cert), 2L)
test_type("character", cert[[1L]])
test_identical(names(cert), c("server", "client"))
test_type("externalptr", tls <- tls_config(client = cert$client))
test_class("tlsConfig", tls)
test_print(tls)
test_class("errorValue", ncurl("https://www.cam.ac.uk/", tls = tls)$status)
test_class("errorValue", call_aio(ncurl_aio("https://www.cam.ac.uk/", tls = tls))$data)
test_error(ncurl_session("https://www.cam.ac.uk/", tls = cert$client), "not a valid TLS")
sess <- ncurl_session("https://www.cam.ac.uk/", tls = tls)
test_true(is_ncurl_session(sess) || is_error_value(sess))
if (is_ncurl_session(sess)) test_class("errorValue", transact(sess)[["headers"]])
test_type("externalptr", s <- socket(listen = "tls+tcp://127.0.0.1:0", tls = tls_config(server = cert$server)))
test_true(parse_url(s$listener[[1]]$url)[["port"]] > 0L)
test_type("externalptr", s1 <- socket(dial = s$listener[[1]]$url, tls = tls))
test_true(suppressWarnings(dial(s, url = "tls+tcp://.", tls = tls)) > 0)
test_true(suppressWarnings(listen(s, url = "tls+tcp://.", tls = tls)) > 0)
test_zero(close(s1))
test_zero(close(s))
test_type("externalptr", s <- socket(listen = "tcp://127.0.0.1:0", autostart = FALSE))
test_zero(parse_url(s$listener[[1]]$url)[["port"]])
test_zero(start(s$listener[[1]]))
test_true(parse_url(s$listener[[1]]$url)[["port"]] > 0L)
test_zero(close(s))
if (promises) test_class("nano", s <- socket(listen = "inproc://nanonext"))
if (promises) test_class("nano", s1 <- socket(dial = "inproc://nanonext"))
if (promises) test_class("recvAio", r <- recv_aio(s, timeout = 500L))
if (promises) test_true(promises::is.promise(promises::then(r, identity, identity)))
if (promises) test_type("integer", send(s1, "promises test\n", block = 500L))
if (promises) test_class("recvAio", r2 <- recv_aio(s, timeout = 1L))
if (promises) test_true(promises::is.promising(call_aio(r2)))
if (promises) test_true(promises::is.promise(promises::then(r2, identity, identity)))
if (promises) test_true(promises::is.promising(call_aio(r)))
if (promises) test_type("integer", send(s1, "promises test2\n", block = 500L))
if (promises) test_true(promises::is.promise(promises::then(call_aio(recv_aio(s, timeout = 500L)), identity, identity)))
if (promises) test_true(is_aio(n <- ncurl_aio("https://www.cam.ac.uk/", timeout = 3000L)))
if (promises) test_true(promises::is.promise(promises::then(n, identity, identity)))
if (promises) test_true(promises::is.promising(call_aio(n)))
if (promises) test_true(promises::is.promise(promises::as.promise(call_aio(ncurl_aio("https://www.cam.ac.uk/", timeout = 3000L)))))
if (promises) { later::run_now(1L); later::run_now() }
if (promises) test_zero(close(s1))
if (promises) test_zero(close(s))
if (promises) { later::run_now(1L); later::run_now() }
test_type("character", ip_addr())
test_type("character", names(ip_addr()))
test_null(write_stdout(""))
test_false(identical(get0(".Random.seed"), {.advance(); .Random.seed}))
test_type("integer", .Call(nanonext:::rnng_traverse_precious))
test_error(.dispatcher("invalid", NULL, NULL, NULL, NULL, NULL, NULL), "valid Socket")
test_class("nanoSocket", dsock <- socket("rep"))
test_error(.dispatcher(dsock, "invalid", NULL, NULL, NULL, NULL, NULL), "valid Socket")
test_class("nanoSocket", dpsock <- socket("poly"))
test_error(.dispatcher(dsock, dpsock, "invalid", NULL, NULL, NULL, NULL), "valid Monitor")
test_zero(close(dpsock))
test_zero(close(dsock))
if (NOT_CRAN) {
if (.Platform$OS.type == "windows") {
url_rep <- sprintf("ipc://nanonext-rep-%d", Sys.getpid())
url_poly <- sprintf("ipc://nanonext-poly-%d", Sys.getpid())
} else {
url_rep <- sprintf("ipc://%s", tempfile())
url_poly <- sprintf("ipc://%s", tempfile())
}
dispatcher_code <- sprintf('
library(nanonext)
cv <- cv()
rep_sock <- socket("rep", listen = "%s")
poly_sock <- socket("poly", listen = "%s")
mon <- monitor(poly_sock, cv)
pipe_notify(rep_sock, cv, remove = TRUE, flag = tools::SIGTERM)
.dispatcher(rep_sock, poly_sock, mon, raw(10), NULL, new.env(), function(e) integer(7))
close(poly_sock)
', url_rep, url_poly)
script <- tempfile(fileext = ".R")
writeLines(dispatcher_code, script)
Rscript <- file.path(R.home("bin"), if (.Platform$OS.type == "windows") "Rscript.exe" else "Rscript")
system2(Rscript, script, wait = FALSE, stdout = FALSE, stderr = FALSE)
Sys.sleep(0.5)
daemon <- socket("poly", dial = url_poly)
Sys.sleep(0.3)
init_data <- recv(daemon, mode = "raw", block = 2000)
test_type("raw", init_data)
client <- socket("req", dial = url_rep)
Sys.sleep(0.1)
send(client, raw(8), mode = "raw", block = 2000)
status <- recv(client, mode = "integer", block = 2000)
test_equal(length(status), 5L)
test_true(status[1L] >= 1L)
test_true(status[2L] >= 1L)
cancel_msg <- raw(8)
cancel_msg[5] <- as.raw(99L)
send(client, cancel_msg, mode = "raw", block = 2000)
cancel_result <- recv(client, mode = "integer", block = 2000)
test_equal(cancel_result, 0L)
task_exec <- raw(13)
task_exec[1] <- as.raw(0x07)
task_exec[5] <- as.raw(60L)
send(client, task_exec, mode = "raw", block = 2000)
task_exec_recv <- recv(daemon, mode = "raw", block = 2000)
test_type("raw", task_exec_recv)
cancel_exec <- raw(8)
cancel_exec[5] <- as.raw(60L)
send(client, cancel_exec, mode = "raw", block = 2000)
cancel_exec_res <- recv(client, mode = "integer", block = 2000)
test_equal(cancel_exec_res, 1L)
cancel_sig <- recv(daemon, mode = "raw", block = 2000)
test_equal(length(cancel_sig), 0L)
send(daemon, raw(13), mode = "raw", block = 2000)
recv(client, mode = "raw", block = 2000)
task_busy <- raw(13)
task_busy[1] <- as.raw(0x07)
task_busy[5] <- as.raw(70L)
send(client, task_busy, mode = "raw", block = 2000)
task_queued <- raw(13)
task_queued[1] <- as.raw(0x07)
task_queued[5] <- as.raw(71L)
send(client, task_queued, mode = "raw", block = 2000)
cancel_inq <- raw(8)
cancel_inq[5] <- as.raw(71L)
send(client, cancel_inq, mode = "raw", block = 2000)
cancel_inq_res <- recv(client, mode = "integer", block = 2000)
test_equal(cancel_inq_res, 1L)
recv(daemon, mode = "raw", block = 2000)
send(daemon, raw(13), mode = "raw", block = 2000)
recv(client, mode = "raw", block = 2000)
task_q1 <- raw(13)
task_q1[1] <- as.raw(0x07)
task_q1[5] <- as.raw(72L)
send(client, task_q1, mode = "raw", block = 2000)
task_q2 <- raw(13)
task_q2[1] <- as.raw(0x07)
task_q2[5] <- as.raw(73L)
send(client, task_q2, mode = "raw", block = 2000)
recv(daemon, mode = "raw", block = 2000)
send(daemon, raw(13), mode = "raw", block = 2000)
recv(client, mode = "raw", block = 2000)
q2_recv <- recv(daemon, mode = "raw", block = 2000)
test_type("raw", q2_recv)
send(daemon, raw(13), mode = "raw", block = 2000)
recv(client, mode = "raw", block = 2000)
task_msg <- raw(13)
task_msg[1] <- as.raw(0x07)
task_msg[5] <- as.raw(1L)
send(client, task_msg, mode = "raw", block = 2000)
task_recv <- recv(daemon, mode = "raw", block = 2000)
test_type("raw", task_recv)
send(daemon, raw(13), mode = "raw", block = 2000)
result <- recv(client, mode = "raw", block = 2000)
test_type("raw", result)
sync_task <- raw(13)
sync_task[1] <- as.raw(0x07)
sync_task[4] <- as.raw(0x01)
sync_task[5] <- as.raw(80L)
send(client, sync_task, mode = "raw", block = 2000)
sync_recv <- recv(daemon, mode = "raw", block = 2000)
test_type("raw", sync_recv)
send(daemon, raw(13), mode = "raw", block = 2000)
recv(client, mode = "raw", block = 2000)
daemon2 <- socket("poly", dial = url_poly)
Sys.sleep(0.3)
init_data2 <- recv(daemon2, mode = "raw", block = 2000)
test_type("raw", init_data2)
next_task <- raw(13)
next_task[1] <- as.raw(0x07)
next_task[5] <- as.raw(81L)
send(client, next_task, mode = "raw", block = 2000)
next_recv <- recv(daemon2, mode = "raw", block = 2000)
test_type("raw", next_recv)
send(daemon2, raw(13), mode = "raw", block = 2000)
recv(client, mode = "raw", block = 2000)
close(daemon2)
Sys.sleep(0.1)
post_sync <- raw(13)
post_sync[1] <- as.raw(0x07)
post_sync[5] <- as.raw(82L)
send(client, post_sync, mode = "raw", block = 2000)
post_recv <- recv(daemon, mode = "raw", block = 2000)
test_type("raw", post_recv)
mark_resp <- raw(13)
mark_resp[1] <- as.raw(0x07)
mark_resp[4] <- as.raw(0x01)
send(daemon, mark_resp, mode = "raw", block = 2000)
mark_res <- recv(client, mode = "raw", block = 2000)
test_type("raw", mark_res)
disc_sig <- recv(daemon, mode = "raw", block = 2000)
test_equal(length(disc_sig), 0L)
close(daemon)
daemon3 <- socket("poly", dial = url_poly)
Sys.sleep(0.3)
recv(daemon3, mode = "raw", block = 2000)
reset_task <- raw(13)
reset_task[1] <- as.raw(0x07)
reset_task[5] <- as.raw(90L)
send(client, reset_task, mode = "raw", block = 2000)
recv(daemon3, mode = "raw", block = 2000)
close(daemon3)
Sys.sleep(0.2)
reset_result <- recv(client, mode = "raw", block = 2000)
test_equal(length(reset_result), 10L)
send(client, raw(8), mode = "raw", block = 2000)
status_final <- recv(client, mode = "integer", block = 2000)
test_equal(status_final[1L], 0L)
close(client)
Sys.sleep(0.5)
unlink(script)
}
if (NOT_CRAN) {
cert <- write_cert(cn = "127.0.0.1")
certfile <- tempfile()
cat(cert$server, file = certfile, sep = "\n")
certfile <- gsub("\\", "/", certfile, fixed = TRUE)
stream_code <- sprintf('
library(nanonext)
s <- stream(listen = "tcp://127.0.0.1:25555")
msg <- recv(s, mode = "character", block = 2000)
send(s, paste0("reply:", msg), block = 2000)
msg2 <- recv(s, mode = "character", block = 2000)
send(s, paste0("async:", msg2), block = 2000)
Sys.sleep(0.1)
close(s)
tls <- tls_config(server = "%s")
s <- stream(listen = "wss://127.0.0.1:25555/secure", tls = tls, textframes = TRUE)
Sys.sleep(0.1)
close(s)
', certfile)
script <- tempfile(fileext = ".R")
writeLines(stream_code, script)
Rscript <- file.path(R.home("bin"), if (.Platform$OS.type == "windows") "Rscript.exe" else "Rscript")
system2(Rscript, script, wait = FALSE, stdout = FALSE, stderr = FALSE)
Sys.sleep(0.5)
s <- tryCatch(stream(dial = "tcp://127.0.0.1:25555", buffer = 1024L), error = identity)
if (is_nano(s)) {
test_class("nanoStream", s)
test_zero(send(s, "test", block = 2000))
test_equal(recv(s, mode = "character"), "reply:test")
test_class("sendAio", sa <- send_aio(s, "async_test", timeout = 2000))
test_zero(call_aio(sa)$result)
test_class("recvAio", ra <- recv_aio(s, mode = "character", timeout = 2000))
test_equal(call_aio(ra)$data, "async:async_test")
Sys.sleep(0.1)
test_zero(close(s))
test_equal(close(s), 7L)
Sys.sleep(0.3)
tls <- tls_config(client = cert$client)
s <- tryCatch(stream(dial = "wss://127.0.0.1:25555/secure", tls = tls, textframes = TRUE), error = identity)
if (is_nano(s)) {
test_class("nanoStream", s)
Sys.sleep(0.1)
test_zero(close(s))
test_equal(close(s), 7L)
}
}
unlink(script)
unlink(certfile)
}
test_error(http_server("http://127.0.0.1:29995", tls = "invalid"), "valid TLS")
fakeserver <- `class<-`("test", "nanoServer")
test_error(close(fakeserver), "valid HTTP Server")
test_class("list", suppressWarnings(handler_file("/bad", "/nonexistent/file.txt")))
test_error(handler_redirect("/bad", "/good", status = 999L), "301, 302, 303, 307, or 308")
fakestream_conn <- `class<-`("test", "nanoStreamConn")
test_print(fakestream_conn)
test_equal(format_sse("Hello"), "data: Hello\n\n")
test_equal(format_sse("Hello", event = "msg"), "event: msg\ndata: Hello\n\n")
test_equal(format_sse("Hello", id = "1"), "id: 1\ndata: Hello\n\n")
test_equal(format_sse("Hello", retry = 1000), "retry: 1000\ndata: Hello\n\n")
test_equal(format_sse("Line1\nLine2"), "data: Line1\ndata: Line2\n\n")
test_equal(format_sse("test", event = "e", id = "2", retry = 500), "event: e\nid: 2\nretry: 500\ndata: test\n\n")
if (later && NOT_CRAN) {
test_error(http_server("http://127.0.0.1:0", handlers = list(list(type = 99L, path = "/"))), "Invalid argument")
received_headers <- NULL
test_class("nanoServer", srv <- http_server(
url = "http://127.0.0.1:0",
handlers = list(
handler("/test", function(req) list(status = 200L, body = "OK")),
handler("/api/data", function(req) {
list(status = 200L, headers = c("Content-Type" = "application/json"), body = '{"value":42}')
}),
handler("/headers", function(req) {
received_headers <<- req$headers
list(status = 200L, body = paste(names(req$headers), collapse = ","))
}),
handler("/echo-body", function(req) {
list(status = 200L, body = req$body)
}, method = "POST"),
handler("/patch", function(req) {
list(status = 200L, body = paste0("patched:", rawToChar(req$body)))
}, method = "PATCH"),
handler("/error", function(req) stop(simpleError(""))),
handler("/api", function(req) list(status = 200L, body = paste("path:", req$uri)), prefix = TRUE),
handler("/any", function(req) list(status = 200L, body = req$method), method = "*"),
handler("/put", function(req) list(status = 200L, body = "PUT OK"), method = "PUT"),
handler("/delete", function(req) list(status = 200L, body = "DELETE OK"), method = "DELETE"),
handler("/raw", function(req) list(status = 200L, body = charToRaw("raw response"))),
handler("/default-status", function(req) list(body = "no status")),
handler("/no-body", function(req) list(status = 204L))
)
))
test_print(srv)
test_equal(srv$state, "not started")
test_zero(parse_url(srv$url)[["port"]])
test_zero(srv$start())
test_equal(srv$state, "started")
test_true(parse_url(srv$url)[["port"]] > 0L)
base_url <- srv$url
Sys.sleep(0.1)
aio <- ncurl_aio(paste0(base_url, "/test"), timeout = 2000)
while (unresolved(aio)) later::run_now(1)
test_equal(aio$status, 200L)
test_equal(aio$data, "OK")
aio <- ncurl_aio(paste0(base_url, "/api/data"), timeout = 2000)
while (unresolved(aio)) later::run_now(1)
test_equal(aio$status, 200L)
test_equal(aio$data, '{"value":42}')
aio <- ncurl_aio(
paste0(base_url, "/headers"),
headers = c("X-Custom-Header" = "test123", "X-Another-Header" = "value456"),
timeout = 2000
)
while (unresolved(aio)) later::run_now(1)
test_equal(aio$status, 200L)
test_true("X-Custom-Header" %in% names(received_headers))
test_true("X-Another-Header" %in% names(received_headers))
test_equal(received_headers[["X-Custom-Header"]], "test123")
test_equal(received_headers[["X-Another-Header"]], "value456")
aio <- ncurl_aio(paste0(base_url, "/error"), timeout = 2000)
while (unresolved(aio)) later::run_now(1)
test_equal(aio$status, 500L)
aio <- ncurl_aio(paste0(base_url, "/api/users/123"), timeout = 2000)
while (unresolved(aio)) later::run_now(1)
test_equal(aio$status, 200L)
test_equal(aio$data, "path: /api/users/123")
aio <- ncurl_aio(paste0(base_url, "/any"), timeout = 2000)
while (unresolved(aio)) later::run_now(1)
test_equal(aio$status, 200L)
aio <- ncurl_aio(paste0(base_url, "/any"), method = "POST", timeout = 2000)
while (unresolved(aio)) later::run_now(1)
test_equal(aio$status, 200L)
aio <- ncurl_aio(paste0(base_url, "/put"), method = "PUT", timeout = 2000)
while (unresolved(aio)) later::run_now(1)
test_equal(aio$status, 200L)
test_equal(aio$data, "PUT OK")
aio <- ncurl_aio(paste0(base_url, "/delete"), method = "DELETE", timeout = 2000)
while (unresolved(aio)) later::run_now(1)
test_equal(aio$status, 200L)
test_equal(aio$data, "DELETE OK")
aio <- ncurl_aio(paste0(base_url, "/raw"), timeout = 2000)
while (unresolved(aio)) later::run_now(1)
test_equal(aio$status, 200L)
test_equal(aio$data, "raw response")
aio <- ncurl_aio(paste0(base_url, "/default-status"), timeout = 2000)
while (unresolved(aio)) later::run_now(1)
test_equal(aio$status, 200L)
test_equal(aio$data, "no status")
aio <- ncurl_aio(paste0(base_url, "/no-body"), timeout = 2000)
while (unresolved(aio)) later::run_now(1)
test_equal(aio$status, 204L)
aio <- ncurl_aio(paste0(base_url, "/echo-body"), method = "POST",
headers = c("Content-Type" = "text/plain"),
data = "post data", timeout = 2000)
while (unresolved(aio)) later::run_now(1)
test_equal(aio$status, 200L)
aio <- ncurl_aio(paste0(base_url, "/patch"), method = "PATCH",
headers = c("Content-Type" = "text/plain"),
data = "hello", timeout = 2000)
while (unresolved(aio)) later::run_now(1)
test_equal(aio$status, 200L)
aio <- ncurl_aio(paste0(base_url, "/nonexistent"), timeout = 2000)
while (unresolved(aio)) later::run_now(1)
test_equal(aio$status, 404L)
aio <- ncurl_aio(paste0(base_url, "/put"), timeout = 2000)
while (unresolved(aio)) later::run_now(1)
test_equal(aio$status, 405L)
test_zero(srv$close())
}
if (later && NOT_CRAN) {
test_class("nanoServer", srv <- http_server(
url = "http://127.0.0.1:0",
handlers = handler("/single", function(req) list(status = 200L, body = "single handler"))
))
test_zero(srv$start())
Sys.sleep(0.1)
aio <- ncurl_aio(paste0(srv$url, "/single"), timeout = 2000)
while (unresolved(aio)) later::run_now(1)
test_equal(aio$status, 200L)
test_equal(aio$data, "single handler")
test_zero(srv$close())
}
if (later && NOT_CRAN) {
msgs <- list()
ws_conn <- NULL
ws_req <- NULL
test_class("nanoServer", srv <- http_server(
url = "http://127.0.0.1:0",
handlers = list(
handler("/", function(req) list(status = 200L, body = "index")),
handler_ws(
"/ws",
on_message = function(ws, data) {
msgs <<- c(msgs, list(data))
ws$send(data)
},
on_open = function(ws, req) {
ws_conn <<- ws
ws_req <<- req
msgs <<- c(msgs, list("open"))
},
on_close = function(ws) { msgs <<- c(msgs, list("close")) },
textframes = TRUE
),
handler_ws("/ws-reject", function(ws, data) ws$send(data), on_open = function(ws, req) ws$close())
)
))
test_zero(srv$start())
base_url <- srv$url
ws_url <- sub("^http", "ws", base_url)
Sys.sleep(0.1)
aio <- ncurl_aio(paste0(base_url, "/"), timeout = 2000)
while (unresolved(aio)) later::run_now(1)
test_equal(aio$status, 200L)
test_equal(aio$data, "index")
ws <- tryCatch(stream(dial = paste0(ws_url, "/ws"), textframes = TRUE), error = identity)
if (is_nano(ws)) {
while (is.null(ws_conn)) later::run_now(1)
test_class("nanoWsConn", ws_conn)
test_print(ws_conn)
test_type("integer", ws_conn$id)
test_type("closure", ws_conn$send)
test_type("closure", ws_conn$close)
test_null(ws_conn$nonexistent)
test_type("list", ws_req)
test_equal(ws_req$uri, "/ws")
test_type("character", ws_req$headers)
test_false(is.null(names(ws_req$headers)))
test_zero(send(ws, "hello", block = 500))
while (length(msgs) < 2L) later::run_now(1)
reply <- recv(ws, block = 500, mode = "character")
test_equal(reply, "hello")
test_zero(ws_conn$send(charToRaw("raw-from-server")))
raw_reply <- recv(ws, block = 500, mode = "raw")
test_type("raw", raw_reply)
test_error(ws_conn$send(123L), "`data` must be raw or character")
test_error(ws_conn$send(list(a = 1)), "`data` must be raw or character")
test_zero(close(ws))
while (length(msgs) < 3L) later::run_now(1)
test_equal(msgs[[1]], "open")
test_equal(msgs[[2]], "hello")
test_equal(ws_conn$close(), 7L)
test_equal(ws_conn$send("after close"), 7L)
test_equal(ws_conn$send(charToRaw("raw after close")), 7L)
}
ws_reject <- tryCatch(stream(dial = paste0(ws_url, "/ws-reject")), error = identity)
if (is_nano(ws_reject)) {
later::run_now(1)
close(ws_reject)
}
ws_req <- NULL
ws_h <- tryCatch(stream(dial = paste0(ws_url, "/ws"), textframes = TRUE,
headers = c(Authorization = "Bearer testtoken", "X-Custom" = "value1")),
error = identity)
if (is_nano(ws_h)) {
while (is.null(ws_req)) later::run_now(1)
test_equal(ws_req$headers[["Authorization"]], "Bearer testtoken")
test_equal(ws_req$headers[["X-Custom"]], "value1")
n_msgs <- length(msgs)
close(ws_h)
while (length(msgs) == n_msgs) later::run_now(1)
}
test_zero(srv$close())
test_error(http_server(url = "http://127.0.0.1:0", handlers = list(
handler_ws(paste0("/", strrep("x", 8180)), function(ws, data) ws$send(data))
)), "Invalid argument")
}
if (later && NOT_CRAN) {
gc_srv <- http_server(
url = "http://127.0.0.1:0",
handlers = list(
handler("/gc", function(req) list(status = 200L, body = "gc test"))
)
)
test_zero(gc_srv$start())
Sys.sleep(0.1)
rm(gc_srv)
Sys.sleep(0.1)
invisible(gc())
}
if (later && NOT_CRAN) {
wss_cert <- write_cert(cn = "127.0.0.1")
wss_tls_server <- tls_config(server = wss_cert$server)
wss_tls_client <- tls_config(client = wss_cert$client)
wss_msgs <- list()
test_class("nanoServer", wss_srv <- http_server(
url = "https://127.0.0.1:0",
handlers = list(
handler("/secure", function(req) list(status = 200L, body = "secure")),
handler_ws(
"/wss",
on_message = function(ws, data) {
wss_msgs <<- c(wss_msgs, list(data))
ws$send(paste0("wss:", data))
},
on_open = function(ws, req) { wss_msgs <<- c(wss_msgs, list("wss_open")) },
on_close = function(ws) { wss_msgs <<- c(wss_msgs, list("wss_close")) },
textframes = TRUE
)
),
tls = wss_tls_server
))
test_zero(wss_srv$start())
wss_base_url <- wss_srv$url
wss_ws_url <- sub("^https", "wss", wss_base_url)
Sys.sleep(1L)
wss_aio <- ncurl_aio(paste0(wss_base_url, "/secure"), tls = wss_tls_client, timeout = 2000)
while (unresolved(wss_aio)) later::run_now(1)
if (wss_aio$status == 200L) test_equal(wss_aio$data, "secure")
wss_client <- tryCatch(stream(dial = paste0(wss_ws_url, "/wss"), tls = wss_tls_client, textframes = TRUE), error = identity)
if (is_nano(wss_client)) {
while (length(wss_msgs) < 1L) later::run_now(1)
test_zero(send(wss_client, "secure_hello", block = 500))
while (length(wss_msgs) < 2L) later::run_now(1)
wss_reply <- recv(wss_client, block = 500, mode = "character")
test_equal(wss_reply, "wss:secure_hello")
test_zero(close(wss_client))
later::run_now(1)
test_equal(wss_msgs[[1]], "wss_open")
test_equal(wss_msgs[[2]], "secure_hello")
}
test_zero(wss_srv$close())
}
# Test multiple WebSocket endpoints and concurrent connections
if (later && NOT_CRAN) {
echo_msgs <- list()
upper_msgs <- list()
conn_ids <- c()
ws_shutdown_closed <- 0L
test_class("nanoServer", multi_ws_srv <- http_server(
url = "http://127.0.0.1:0",
handlers = list(
handler_ws("/echo", function(ws, data) {
echo_msgs <<- c(echo_msgs, list(data))
ws$send(data)
}),
handler_ws("/upper", function(ws, data) {
upper_msgs <<- c(upper_msgs, list(data))
ws$send(toupper(data))
}, on_open = function(ws, req) {
conn_ids <<- c(conn_ids, ws$id)
}, on_close = function(ws) {
ws_shutdown_closed <<- ws_shutdown_closed + 1L
}, textframes = TRUE)
)
))
test_zero(multi_ws_srv$start())
test_zero(multi_ws_srv$start())
base_url <- multi_ws_srv$url
ws_url <- sub("^http", "ws", base_url)
Sys.sleep(0.1)
ws_echo <- tryCatch(stream(dial = paste0(ws_url, "/echo")), error = identity)
if (is_nano(ws_echo)) {
later::run_now(1)
test_zero(send(ws_echo, charToRaw("binary_test"), block = 500))
while (length(echo_msgs) < 1L) later::run_now(1)
echo_reply <- recv(ws_echo, block = 500, mode = "raw")
test_equal(rawToChar(echo_reply), "binary_test")
test_zero(close(ws_echo))
later::run_now(1)
}
ws_upper1 <- tryCatch(stream(dial = paste0(ws_url, "/upper"), textframes = TRUE), error = identity)
ws_upper2 <- tryCatch(stream(dial = paste0(ws_url, "/upper"), textframes = TRUE), error = identity)
if (is_nano(ws_upper1) && is_nano(ws_upper2)) {
while (length(conn_ids) < 2L) later::run_now(1)
test_equal(length(conn_ids), 2L)
test_true(conn_ids[1] != conn_ids[2])
test_zero(send(ws_upper1, "hello", block = 500))
test_zero(send(ws_upper2, "world", block = 500))
while (length(upper_msgs) < 2L) later::run_now(1)
upper_reply1 <- recv(ws_upper1, block = 500, mode = "character")
upper_reply2 <- recv(ws_upper2, block = 500, mode = "character")
test_equal(upper_reply1, "HELLO")
test_equal(upper_reply2, "WORLD")
test_equal(ws_shutdown_closed, 0L)
test_zero(multi_ws_srv$close())
while (ws_shutdown_closed < 2L) later::run_now(1)
test_equal(ws_shutdown_closed, 2L)
close(ws_upper1)
close(ws_upper2)
} else {
multi_ws_srv$close()
}
test_error(multi_ws_srv$start(), "valid HTTP Server")
}
# Test static handlers, redirects, and prefix parameter
if (later && NOT_CRAN) {
test_class("list", suppressWarnings(handler_directory("/bad", "/nonexistent/directory")))
static_test_dir <- tempfile()
dir.create(static_test_dir)
writeLines("Hello from file", file.path(static_test_dir, "test.txt"))
writeLines("
Index", file.path(static_test_dir, "index.html"))
test_class("nanoServer", static_srv <- http_server(
url = "http://127.0.0.1:0",
handlers = list(
handler_file("/single.txt", file.path(static_test_dir, "test.txt")),
handler_file("/tree-file", file.path(static_test_dir, "test.txt"), prefix = TRUE),
handler_directory("/files", static_test_dir),
handler_inline("/inline", "Inline content", content_type = "text/plain"),
handler_inline("/tree-inline", "tree inline", content_type = "text/plain", prefix = TRUE),
handler_inline("/binary", as.raw(c(0x89, 0x50, 0x4e, 0x47))),
handler_redirect("/r301", "/single.txt", status = 301L),
handler_redirect("/r302", "/single.txt", status = 302L),
handler_redirect("/r303", "/single.txt", status = 303L),
handler_redirect("/r307", "/single.txt", status = 307L),
handler_redirect("/r308", "/single.txt", status = 308L),
handler_redirect("/tree-redir", "/single.txt", status = 302L, prefix = TRUE)
)
))
test_zero(static_srv$start())
base_url <- static_srv$url
Sys.sleep(0.1)
aio <- ncurl_aio(paste0(base_url, "/single.txt"), timeout = 2000)
while (unresolved(aio)) later::run_now(1)
test_equal(aio$status, 200L)
test_equal(trimws(aio$data), "Hello from file")
aio <- ncurl_aio(paste0(base_url, "/tree-file/extra"), timeout = 2000)
while (unresolved(aio)) later::run_now(1)
test_equal(aio$status, 200L)
aio <- ncurl_aio(paste0(base_url, "/files/test.txt"), timeout = 2000)
while (unresolved(aio)) later::run_now(1)
test_equal(aio$status, 200L)
aio <- ncurl_aio(paste0(base_url, "/inline"), timeout = 2000)
while (unresolved(aio)) later::run_now(1)
test_equal(aio$status, 200L)
test_equal(aio$data, "Inline content")
aio <- ncurl_aio(paste0(base_url, "/tree-inline/extra"), timeout = 2000)
while (unresolved(aio)) later::run_now(1)
test_equal(aio$status, 200L)
test_equal(aio$data, "tree inline")
aio <- ncurl_aio(paste0(base_url, "/binary"), timeout = 2000)
while (unresolved(aio)) later::run_now(1)
test_equal(aio$status, 200L)
for (code in c(301L, 302L, 303L, 307L, 308L)) {
aio <- ncurl_aio(paste0(base_url, "/r", code), timeout = 2000)
while (unresolved(aio)) later::run_now(1)
test_equal(aio$status, code)
}
aio <- ncurl_aio(paste0(base_url, "/tree-redir/extra"), timeout = 2000)
while (unresolved(aio)) later::run_now(1)
test_equal(aio$status, 302L)
test_zero(static_srv$close())
unlink(static_test_dir, recursive = TRUE)
}
if (later && NOT_CRAN) {
stream_conn <- NULL
stream_closed <- FALSE
received_methods <- character()
test_class("nanoServer", stream_srv <- http_server(
url = "http://127.0.0.1:0",
handlers = list(
handler_stream(
"/events",
on_request = function(conn, req) {
stream_conn <<- conn
conn$set_status(200L)
conn$set_header("Content-Type", "text/event-stream")
conn$set_header("Cache-Control", "no-cache")
conn$send(format_sse(data = "connected"))
},
on_close = function(conn) {
stream_closed <<- TRUE
}
),
handler_stream(
"/stream",
on_request = function(conn, req) {
received_methods <<- c(received_methods, req$method)
conn$set_header("Content-Type", "text/plain")
conn$send(paste0("method:", req$method))
conn$close()
}
),
handler_stream(
"/prefix-stream",
on_request = function(conn, req) {
conn$set_header("Content-Type", "text/plain")
conn$send(paste0("prefix:", req$uri))
conn$close()
},
prefix = TRUE
)
)
))
test_zero(stream_srv$start())
base_url <- stream_srv$url
Sys.sleep(0.1)
sse_client <- tryCatch(stream(dial = paste0("tcp://", sub("^http://", "", base_url), "/events")), error = identity)
if (is_nano(sse_client)) {
http_req <- "GET /events HTTP/1.1\r\nHost: 127.0.0.1\r\nAccept: text/event-stream\r\n\r\n"
test_zero(send(sse_client, http_req, block = 500))
while (is.null(stream_conn)) later::run_now(1)
test_class("nanoStreamConn", stream_conn)
test_print(stream_conn)
test_type("integer", stream_conn$id)
test_type("closure", stream_conn$send)
test_type("closure", stream_conn$close)
test_type("closure", stream_conn$set_status)
test_type("closure", stream_conn$set_header)
test_null(stream_conn$nonexistent)
test_error(stream_conn$set_status(201L), "after headers have been sent")
test_error(stream_conn$set_header("X-Test", "value"), "after headers have been sent")
test_zero(stream_conn$send(format_sse(data = "update")))
test_zero(stream_conn$send(charToRaw("raw-chunk")))
test_error(stream_conn$send(123L), "`data` must be raw or character")
test_error(stream_conn$send(list()), "`data` must be raw or character")
test_zero(stream_conn$close())
while (!stream_closed) later::run_now(1)
test_true(stream_closed)
test_error(stream_conn$send("after close"), "valid connection")
test_class("errorValue", stream_conn$close())
close(sse_client)
}
get_aio <- ncurl_aio(paste0(base_url, "/stream"))
while (unresolved(get_aio)) later::run_now(1)
test_equal(call_aio(get_aio)$status, 200L)
post_aio <- ncurl_aio(paste0(base_url, "/stream"), method = "POST", data = "test")
while (unresolved(post_aio)) later::run_now(1)
test_equal(call_aio(post_aio)$status, 200L)
test_true("GET" %in% received_methods)
test_true("POST" %in% received_methods)
prefix_aio <- ncurl_aio(paste0(base_url, "/prefix-stream/sub/path"), timeout = 2000)
while (unresolved(prefix_aio)) later::run_now(1)
test_equal(call_aio(prefix_aio)$status, 200L)
test_zero(stream_srv$close())
}
test_null(.dispatcher_stop("invalid"))
test_null(.dispatcher_wait("invalid", 1L))
test_equal(length(.dispatcher_info("invalid")), 5L)
test_null(.dispatcher_gate("invalid"))
test_null(.dispatcher_try_gate("invalid"))
test_identical(.dispatcher_capacity("invalid"), c(used = NA_real_, peak = NA_real_, capacity = NA_real_))
if (NOT_CRAN) {
if (.Platform$OS.type == "windows") {
disp_poly_url <- sprintf("ipc://nanonext-dpoly-%d", Sys.getpid())
} else {
disp_poly_url <- sprintf("ipc://%s", tempfile())
}
disp_inproc_url <- sprintf("inproc://disp-test-%d", Sys.getpid())
RNGkind("L'Ecuyer-CMRG")
.advance()
stream <- .Random.seed
cvar <- cv()
client <- socket("req", listen = disp_inproc_url)
disp <- .dispatcher_start(disp_poly_url, disp_inproc_url, NULL, NULL, stream, NULL, cvar)
test_type("externalptr", disp)
daemon <- socket("poly", dial = disp_poly_url)
.dispatcher_wait(disp, 1L)
init_data <- recv(daemon, mode = "raw", block = 2000)
test_type("raw", init_data)
info <- .dispatcher_info(disp)
test_equal(length(info), 5L)
test_true(info[1L] >= 1L)
test_true(info[2L] >= 1L)
cap <- .dispatcher_capacity(disp)
test_type("double", cap)
test_identical(names(cap), c("used", "peak", "capacity"))
test_true(is.na(cap[["capacity"]]))
test_true(cap[["used"]] >= 0)
test_true(cap[["peak"]] >= cap[["used"]])
send(client, raw(8), mode = "raw", block = 2000)
status <- recv(client, mode = "integer", block = 2000)
test_equal(length(status), 5L)
task_msg <- raw(13)
task_msg[1] <- as.raw(0x07)
task_msg[5] <- as.raw(1L)
send(client, task_msg, mode = "raw", block = 2000)
task_recv <- recv(daemon, mode = "raw", block = 2000)
test_type("raw", task_recv)
send(daemon, raw(13), mode = "raw", block = 2000)
result <- recv(client, mode = "raw", block = 2000)
test_type("raw", result)
info2 <- .dispatcher_info(disp)
test_true(info2[5L] >= 1L)
ctx <- context(client)
aio <- request(ctx, data = "task", id = disp)
task_recv2 <- recv(daemon, mode = "raw", block = 2000)
test_type("raw", task_recv2)
test_true(stop_request(aio))
cancel_sig <- recv(daemon, mode = "raw", block = 2000)
test_equal(length(cancel_sig), 0L)
close(ctx)
ctx2 <- context(client)
aio2 <- request(ctx2, data = "queued_task", id = disp)
while (.dispatcher_info(disp)[3L] == 0L) msleep(1)
send(daemon, raw(13), mode = "raw", block = 2000)
test_true(stop_request(aio2))
close(ctx2)
test_null(.dispatcher_stop(disp))
test_equal(length(.dispatcher_info(disp)), 5L)
test_null(.dispatcher_stop(disp))
close(daemon)
close(client)
cvar2 <- cv()
client2 <- socket("req", listen = disp_inproc_url)
disp2 <- .dispatcher_start(disp_poly_url, disp_inproc_url, NULL, NULL, stream, 2L, cvar2)
test_type("externalptr", disp2)
test_true(.dispatcher_gate(disp2))
test_true(.dispatcher_try_gate(disp2))
test_equal(.dispatcher_capacity(disp2)[["capacity"]], 2)
test_null(.dispatcher_stop(disp2))
close(client2)
cvar2u <- cv()
client2u <- socket("req", listen = disp_inproc_url)
disp2u <- .dispatcher_start(disp_poly_url, disp_inproc_url, NULL, NULL, stream, NULL, cvar2u)
test_true(.dispatcher_try_gate(disp2u))
test_null(.dispatcher_stop(disp2u))
close(client2u)
disp_tls_cert <- write_cert(cn = "127.0.0.1")
disp_tls <- tls_config(server = disp_tls_cert$server)
disp_inproc_url3 <- sprintf("inproc://disp-test3-%d", Sys.getpid())
cvar3 <- cv()
client3 <- socket("req", listen = disp_inproc_url3)
disp3 <- .dispatcher_start("tls+tcp://127.0.0.1:0", disp_inproc_url3, disp_tls, NULL, stream, NULL, cvar3)
test_type("externalptr", disp3)
disp3_url <- attr(disp3, "url")
test_type("character", disp3_url)
disp_tls_client <- tls_config(client = disp_tls_cert$client)
daemon3 <- socket("poly", dial = disp3_url, tls = disp_tls_client)
.dispatcher_wait(disp3, 1L)
init_data3 <- recv(daemon3, mode = "raw", block = 2000)
test_type("raw", init_data3)
test_true(.dispatcher_info(disp3)[1L] >= 1L)
test_null(.dispatcher_stop(disp3))
close(daemon3)
close(client3)
}
if (!interactive() && NOT_CRAN) {
test_class("conditionVariable", cv <- cv())
f <- file("stdin", open = "r")
test_true(is_nano(reader <- read_stdin()))
test_zero(pipe_notify(reader, cv, remove = TRUE, flag = TRUE))
test_true(is_aio(r <- recv_aio(reader, mode = "raw", cv = cv)))
close(f)
test_false(wait(cv))
test_zero(close(reader))
test_equal(collect_aio(r), 7L)
}
if (Sys.info()[["sysname"]] == "Linux") {
rm(list = ls())
invisible(gc())
.Call(nanonext:::rnng_fini_priors)
Sys.sleep(1L)
.Call(nanonext:::rnng_fini)
invisible()
}
nanonext/MD5 0000644 0001762 0000144 00000101421 15176120662 012421 0 ustar ligges users 7bcccc9f7599b3aaf721b49c27ae05b1 *DESCRIPTION
ac718869ba024194348697eb8f88de17 *LICENSE
c1e0bc5184e9171960feaa0bf798acbb *LICENSE.note
ccd8343758017ba32cf9f61c7687d017 *NAMESPACE
3b7fa6265ed504fcd7c833bf1797899a *NEWS.md
13a70d4dfbe02d1cbb9d6e0f05b08b83 *R/aio.R
4361aeb1f10ac8b59dfd99fa0e360057 *R/context.R
340bcca989df0328e90eb4f706f3f3eb *R/dispatcher.R
adf2c1fcd07b14fdb869bf68751b486d *R/docs.R
f1f78ba29e03eda6dd008eb872ecba6f *R/listdial.R
47ab13627d4629bdb301d1e64fd36cce *R/nano.R
d4776d82b8291e1124135704a0354df8 *R/nanonext-package.R
95bed7271299769689e1edcc2039fb35 *R/ncurl.R
ad78b959a731da2e4715ab9ed78f9103 *R/opts.R
cc5e536c28e9ff329d7244de99bb691b *R/sendrecv.R
d3e300c775f0480c0dbf65415b8b09da *R/server.R
cf2da3fdbcbb8714845cc44f90e13f2a *R/socket.R
8538eb921c5e16cf8ea711edcb27660b *R/stats.R
527a7d57f1ac4e930e89efd2e7f41099 *R/stream.R
db77a18e14b5a7b155fc55b52409e6e3 *R/sync.R
2882dfcb2f753b10ea509e5f88f7a16c *R/tls.R
2ece4d0f68f29276c9a5dbfac7d26c9e *R/utils.R
a9bffe0242b1c6b3772a71fb1874518e *README.md
03d4434d79fd538b1fb5f084f85115d2 *build/vignette.rds
e47b9827fc41464cbb86919b5215a2e4 *cleanup
b1fd2dfc7718ee88d94152678f324a55 *cleanup.win
9155a8a3f11e0d15d4018900c5a09c9e *configure
e1dece6099cc32bdad634f230dbe7c1b *configure.ucrt
1812150b576585e00f98a25ad18e30d1 *configure.win
73116821dd0a8a32f807dd6ae97ea6f9 *inst/doc/nanonext.Rmd
c279f7b1f7044af274b83a02cef271e1 *inst/doc/nanonext.html
1e229ac2eab79e3bbb4a4d53a6a44be2 *inst/doc/v01-messaging.Rmd
4764dffe53d58d111cd742831e02a6bd *inst/doc/v01-messaging.html
c8ea0fd34f2fa552eba2aba3af2eb0b4 *inst/doc/v02-protocols.Rmd
2e17782c3d2f8dd7004194e6aaad42a2 *inst/doc/v02-protocols.html
2069758b3429d409ccc1e85102d78459 *inst/doc/v03-configuration.Rmd
22c960b15285236d289dae858844ad7f *inst/doc/v03-configuration.html
62b33d163137ea05621c4d0494d64d11 *inst/doc/v04-web.Rmd
2de005021648fc5da95a1209cb5e8512 *inst/doc/v04-web.html
1ed1f7f8dee477f8ca79143cc02f8722 *man/as.promise.ncurlAio.Rd
d47ac3271151cc5b85deb72b8850c3cc *man/as.promise.recvAio.Rd
1eb2dce9451ef3b547d84a126584d0c2 *man/call_aio.Rd
bfcecd5d3afe8a64a5a5e8921d9af154 *man/close.Rd
e35fffa95d71e3c27cb43b6e8458d311 *man/collect_aio.Rd
27fcf10cf3f353fd1a72fa5acee8699d *man/context.Rd
d4062cd82dde01c5d73a942e5526c024 *man/cv.Rd
8cce11fac9d97ba05d785cbe6110cbf9 *man/dial.Rd
d76315ad812fb61f926564ce7dfa9485 *man/dot-advance.Rd
522a2aa99f2a572c382b8d50b58cc036 *man/dot-context.Rd
4f5758a10ebde1c49e26cb5d7d7000f3 *man/dot-dispatcher.Rd
80da12737b06979c0476d2fc729aef27 *man/dot-dispatcher_capacity.Rd
6ece4387106336e4241af564cc8be09c *man/dot-dispatcher_gate.Rd
e791e1165cd7243e427156e95967404c *man/dot-dispatcher_info.Rd
c6537197ced32f4c60a9d674141bf8da *man/dot-dispatcher_start.Rd
c4abebc77e30e2afb4fb09112ce0059a *man/dot-dispatcher_stop.Rd
8da5a41ddee89f5b195675087b1fec4c *man/dot-dispatcher_try_gate.Rd
d45422fb2d899afe4f384b7ccce70234 *man/dot-dispatcher_wait.Rd
a80735c4b992793b48c367b0c9a4fe06 *man/dot-keep.Rd
24b57042de49862b85103482f1e5c83a *man/dot-mark.Rd
d628d953895b6cf26f1a5908d3ae3bce *man/dot-unresolved.Rd
79f04906b54de637e48b5b680ed9080b *man/figures/logo.png
eb68e31695d3fa11ee0c4870571fc025 *man/format_sse.Rd
24137eeb8a7c119abf28d559b54a358f *man/grapes-twiddle-greater-than-grapes.Rd
4a307fbdd28f58948cf7ee2cc241b153 *man/handler.Rd
ba4b7ec4b2a7d5c4e601d0c5799dc389 *man/handler_directory.Rd
e63e491a57aa93db96faf2a2377d0b8e *man/handler_file.Rd
7cd130b1ee5964efad0fcfdc80407bef *man/handler_inline.Rd
edfe7ddabc7e3e3085856e48048cf008 *man/handler_redirect.Rd
699326c2a6b34d0edaa4326cdc8cf974 *man/handler_stream.Rd
fec266fc2ca5786ef0fb30403214f72f *man/handler_ws.Rd
95d674026987c0ef047904bdeb47f53f *man/http_server.Rd
3d85bed5b15059adc1345658d38aee1a *man/ip_addr.Rd
4eb413d94f46f585e1563c5efa9f2de5 *man/is_aio.Rd
ad7fb7ae49641b1379100c8193366428 *man/is_error_value.Rd
99873f8e0abf541434c1ee395ef187fb *man/listen.Rd
fbc7439af3c5dea03c2486e6cc76c010 *man/mclock.Rd
05f83767a5ad1a4f5f2dbaa69fa7de52 *man/monitor.Rd
9ba6531fb5b677f656c5f62a9f5f0cb4 *man/msleep.Rd
41651ad47393d7294ca56cabcde5cc11 *man/nano.Rd
928abc635ae82911d3f74fa59d2c9441 *man/nanonext-package.Rd
313a4da456c9f039851d027e00756212 *man/ncurl.Rd
ee64c26f5a7639d76b20578401606529 *man/ncurl_aio.Rd
7bd6f1748256b58ef64420de9ce7d0cc *man/ncurl_session.Rd
6bcf314b3fdc44766d9923ec5af7694e *man/nng_error.Rd
7b7a77cad5245ffca36f45ea24035a28 *man/nng_version.Rd
90692e4668d54a104fc787b3818d32cf *man/opt.Rd
aae3cdae4756028d4915a305ee34f99c *man/parse_url.Rd
4f80040e3a575ba755c56e77688b10cc *man/pipe_id.Rd
3f679550c2284368708243c01c416be2 *man/pipe_notify.Rd
fec38fe0ad909541371d84fa93f42153 *man/protocols.Rd
c58e4f7f4313b014c4e861f28142417d *man/race_aio.Rd
9ff86f3fc9e3d295dc72ffc7d890a900 *man/random.Rd
8a15b352a3d49b1444b1add3e718fddd *man/read_stdin.Rd
47dc9ea4028e0961827f0df836e1f813 *man/reap.Rd
84e08409094479123e799531b6484154 *man/recv.Rd
b23ad4bb05d80c2b19945d87d8fa1173 *man/recv_aio.Rd
c2a31b2e1c8ef1cc56b792390efbdb3d *man/reply.Rd
9ccd20c84f052f23074ca4676bdb31d0 *man/request.Rd
cce343f3fe6a2317ccd2d34d7fca551f *man/send.Rd
04f525f7d78485c4f8f6279c697587bc *man/send_aio.Rd
17285f4436de35fbc7ee4384fdbde79f *man/serial_config.Rd
8d99d72f6da943c0df93eac44312d590 *man/socket.Rd
fe470b184deb7e123673a32252c6ec83 *man/start.Rd
f01a421e3ada2c7c110bf5d749d3d690 *man/stat.Rd
5f5dc210b72668ffa8ba8d18b5a25780 *man/status_code.Rd
bf5942689788f59928913b9e2ea390f2 *man/stop_aio.Rd
034a84e0380674a51a35fe564e51f9a1 *man/stop_request.Rd
a84466a3cd7c2201a439897847356b84 *man/stream.Rd
b62e038d79458f76d8deff3280beb77c *man/subscribe.Rd
98e313d07f6eff42b3d199d9e644e4f3 *man/survey_time.Rd
9f06965b00e7385b1e5ff9150e5b0a27 *man/tls_config.Rd
35a046f69e6be6e1493590e329019f57 *man/transports.Rd
cdbdaad300eea3fe00439a7934b6b87b *man/unresolved.Rd
45af0974f4b492e93f3d90de340708de *man/write_cert.Rd
066ad9832f14796c4f0be13c11e5a6a4 *man/write_stdout.Rd
5382ef82f489686a843c9ef582ca1ef8 *man/zzz.Rd
09600c345f0e365678e6cf3d97baf3b8 *src/Makevars.in
83504d2ed55899f095829e68d0564e4f *src/Makevars.ucrt
5b34374041d36e1049a64e46be764727 *src/Makevars.win
c2b3ce251319bbb059937e7571e04313 *src/aio.c
6200db49a92bf4225df8db49dbd91dda *src/comms.c
9fe56b65bc8f744b698541a74cb9921e *src/core.c
782a5078d1b1b58e57be287c174a8931 *src/dispatcher.c
327f5adde7a27e5aa50f4cc620f731e4 *src/init.c
dccf85e155f72419a7a40924a8b22495 *src/mbedtls/CMakeLists.txt
379d5819937a6c2f1ef1630d341e026d *src/mbedtls/LICENSE
ac0fcae42219d3ee47aa94959fc594df *src/mbedtls/cmake/MbedTLSConfig.cmake.in
01154513e9fcb8d6aa9c4b225fabf5bb *src/mbedtls/include/CMakeLists.txt
d9918ded2f81758c11562b3b7859d0fb *src/mbedtls/include/mbedtls/aes.h
d1aa154e07bb966eda9931d51de4bbfe *src/mbedtls/include/mbedtls/asn1.h
8758f633f0aea5981370b284c00010aa *src/mbedtls/include/mbedtls/asn1write.h
ddd6475f5f4f3ef2e5df3899066f1189 *src/mbedtls/include/mbedtls/base64.h
3a1a92f76c21f1306b8b8c4690f058e7 *src/mbedtls/include/mbedtls/bignum.h
0e19eeeb56f460dc4f1043c60e6f7375 *src/mbedtls/include/mbedtls/block_cipher.h
79a10ef4f21b6bbdd3eba6d7ae723f21 *src/mbedtls/include/mbedtls/build_info.h
29f03221455cfb5d555a18b3021f677f *src/mbedtls/include/mbedtls/ccm.h
0f14ffb18b9319bf4a5055239f387603 *src/mbedtls/include/mbedtls/chacha20.h
56f20bae64841a68a4f58500e35e7e56 *src/mbedtls/include/mbedtls/chachapoly.h
f82ec087d3014fa147fde097bee6b4e1 *src/mbedtls/include/mbedtls/check_config.h
ac75e19a3bc1b2b2b622ca922134eaa1 *src/mbedtls/include/mbedtls/cipher.h
614d25f5a10eb2cd73d8fec1106a3918 *src/mbedtls/include/mbedtls/cmac.h
62c291539e4eb29abe6dbcf27b852018 *src/mbedtls/include/mbedtls/config_adjust_legacy_crypto.h
3bb6ca5bfbe23a42945e6158e0458e9c *src/mbedtls/include/mbedtls/config_adjust_ssl.h
55e32fb295f98ee6e3955040fa761ce6 *src/mbedtls/include/mbedtls/config_adjust_x509.h
836cb52f9acea2dd197803591e513aa4 *src/mbedtls/include/mbedtls/constant_time.h
d7f9b624d72be1d79aa97f11ac59fbd3 *src/mbedtls/include/mbedtls/ctr_drbg.h
9fb951d2e2eacae5c0a4ee24723202fb *src/mbedtls/include/mbedtls/debug.h
89bbdbb757f537fa40f00ca129e06484 *src/mbedtls/include/mbedtls/des.h
89f05dce4cecda2a8bb0d47cff37682f *src/mbedtls/include/mbedtls/ecdh.h
cf771033d7c88efcba500548065a4c5d *src/mbedtls/include/mbedtls/ecdsa.h
7d1fe1c5e46c2fbe025ae43433f40c79 *src/mbedtls/include/mbedtls/ecp.h
5b6fe50ab01add9b2358d8e56f09151f *src/mbedtls/include/mbedtls/entropy.h
e36c6b0c849eb13db6e177763275263c *src/mbedtls/include/mbedtls/error.h
1249ee13cbafd49cdcb6923586927ad4 *src/mbedtls/include/mbedtls/gcm.h
98438bdf21a35169676c83b5e070cb52 *src/mbedtls/include/mbedtls/hkdf.h
594dc3244353ae6bfe26b9316cc9a4f7 *src/mbedtls/include/mbedtls/mbedtls_config.h
c263cae0c706e56e223dde4de34b24f7 *src/mbedtls/include/mbedtls/md.h
ed10766e9e24774563d4c2e6dc2d436a *src/mbedtls/include/mbedtls/md5.h
fca91564de6bcd09f1e59eb9d4d5f3f5 *src/mbedtls/include/mbedtls/net_sockets.h
b5495a961fb7eedac06730665e030a6c *src/mbedtls/include/mbedtls/oid.h
6d7dfae8654f8156fdc6bf7e7850c1f0 *src/mbedtls/include/mbedtls/pem.h
1ff285aedc374e9990a6d4d0528f70b9 *src/mbedtls/include/mbedtls/pk.h
586d942de982106205e8a6ed48e7b4a3 *src/mbedtls/include/mbedtls/pkcs12.h
929af962208edb58bd27b9e1f4900da5 *src/mbedtls/include/mbedtls/pkcs5.h
f7da75a159af9b0389228adf9c2dfb34 *src/mbedtls/include/mbedtls/platform.h
b90d74c5948e54365d894db7d82362b8 *src/mbedtls/include/mbedtls/platform_time.h
25bfdf4fb39fdb26000d64938f01a492 *src/mbedtls/include/mbedtls/platform_util.h
307d5183d7e6559c386d908c5d24c9bd *src/mbedtls/include/mbedtls/poly1305.h
03a0bf380f64a1d87f94c800a0e10302 *src/mbedtls/include/mbedtls/private_access.h
7edaf4c928f1e039376c8584b7238e80 *src/mbedtls/include/mbedtls/rsa.h
85108f35fd0bd4bc69462bbc57c5ebd5 *src/mbedtls/include/mbedtls/sha1.h
a3fa40f1ec95c90ce78decb485816bfd *src/mbedtls/include/mbedtls/sha256.h
6049e8230e2b9aaf466113c343bb94cf *src/mbedtls/include/mbedtls/sha512.h
8ab25623286991ffa23c0c56567e37ce *src/mbedtls/include/mbedtls/ssl.h
c7bf68384a46e67ced15753bcb791c18 *src/mbedtls/include/mbedtls/ssl_cache.h
726efb3dbe81b8497693ca6a81e8e76d *src/mbedtls/include/mbedtls/ssl_ciphersuites.h
51c5848279965b292e41fc66cdfac34c *src/mbedtls/include/mbedtls/ssl_cookie.h
a3f0e5af10084d6e5d614ebea3855ab0 *src/mbedtls/include/mbedtls/ssl_ticket.h
6fede07fe114af0e9cefdbc0fdbfadc7 *src/mbedtls/include/mbedtls/threading.h
3c3379ae448a8c0432b60fec034bd421 *src/mbedtls/include/mbedtls/timing.h
006bb0f39f006f4b582487a6e8e01694 *src/mbedtls/include/mbedtls/version.h
6070faa90b3dd335e22ddbb03c5a1217 *src/mbedtls/include/mbedtls/x509.h
3d7d77b1487eb8573ed1f0b7059691a4 *src/mbedtls/include/mbedtls/x509_crl.h
0574a13cbf16fa1f1706f2e6a2460572 *src/mbedtls/include/mbedtls/x509_crt.h
98129f33caa5f5a8c89e9f155bb16220 *src/mbedtls/include/mbedtls/x509_csr.h
57e5d9ec97c56a7300f20949cac9d7af *src/mbedtls/library/CMakeLists.txt
d3ace2b35ca23f65d3665003fe051e44 *src/mbedtls/library/aes.c
f3254e2e0436640dc4bf675941154ceb *src/mbedtls/library/aesce.c
5bd66d92915c9de25775a4d402e72ded *src/mbedtls/library/aesce.h
2539e25b9f6b048f22495500ff555750 *src/mbedtls/library/aesni.c
e180f45b03bd279b13190219094be2a8 *src/mbedtls/library/aesni.h
65571df3b37c278fba88ff3a3c31b5ab *src/mbedtls/library/alignment.h
37aed9469d2dfddbbb98ac49329d7bd6 *src/mbedtls/library/asn1parse.c
00ced1b73b985b41798d72f3ce11f38c *src/mbedtls/library/asn1write.c
9f9bf9b14abfac01424e05fb2e3af2c2 *src/mbedtls/library/base64.c
22f01de042d916759e928f8882bbc2ff *src/mbedtls/library/base64_internal.h
1a9e04c1bbdcee008d648831878f5179 *src/mbedtls/library/bignum.c
016cd4339abab0de9d69a9a22109e122 *src/mbedtls/library/bignum_core.c
470d7c9db2d6c609bef89108beed3e67 *src/mbedtls/library/bignum_core.h
e7c6c148647318e06d4104075dfd4a25 *src/mbedtls/library/bignum_core_invasive.h
8715e887ff9cd55b9978c28db9e6ae69 *src/mbedtls/library/bignum_internal.h
c92e5889df8e2bdbd86c555a0fdb5939 *src/mbedtls/library/block_cipher.c
b8e95c3efb99b4acb1f5bdbdcda724a3 *src/mbedtls/library/block_cipher_internal.h
cd678f5fb94be5cf6eb8f647210ab048 *src/mbedtls/library/bn_mul.h
a78e901ff3dd29c528a936e7a07513cd *src/mbedtls/library/ccm.c
f91512083706a729f157ace66090588e *src/mbedtls/library/chacha20.c
62707985235c61452dd4b928b41590ce *src/mbedtls/library/chachapoly.c
2efe48bd24317fc9f031984df2190f7c *src/mbedtls/library/check_crypto_config.h
b6e91d3f17ce346abf46bd27f5600976 *src/mbedtls/library/cipher.c
327c4bd3b718efbfce3245e93f7e2e29 *src/mbedtls/library/cipher_invasive.h
fb80d5ee943e76e38f25a61c98df3bdd *src/mbedtls/library/cipher_wrap.c
2a053437b7ddf14abf31eb21b9b800eb *src/mbedtls/library/cipher_wrap.h
44bd56e6b85910a0c3f3a7e057f22f51 *src/mbedtls/library/cmac.c
7eab4237b9ddf08d85c4db7892605b53 *src/mbedtls/library/common.h
c43c2381e36eb6888bcc7b53da593dc1 *src/mbedtls/library/constant_time.c
9db82bcb5d4db75e1dd2610b522c8e00 *src/mbedtls/library/constant_time_impl.h
64a0e479d4fb53a982aa8799a5aa1cbd *src/mbedtls/library/constant_time_internal.h
23a6d678603b53a860320376fa453265 *src/mbedtls/library/ctr.h
c465cab0eef90546a6f2d39eff695ddd *src/mbedtls/library/ctr_drbg.c
d5a16d5b962243055d14c9f4e5a5fe5e *src/mbedtls/library/debug_internal.h
704eec4cef871c8f5fb7eee59f735716 *src/mbedtls/library/des.c
27acb8d8f7c93a7b068cf300a02ac84c *src/mbedtls/library/ecdh.c
656f52fe3d7a30eb025c4ec49c49dc78 *src/mbedtls/library/ecdsa.c
64c461fa68a587e0ec9b9df8e50c10fc *src/mbedtls/library/ecp.c
c399f2220dfd2773e97983e90e53f08e *src/mbedtls/library/ecp_curves.c
88328f85b479d2471cf700871594c967 *src/mbedtls/library/ecp_internal_alt.h
6575fe58395339e6ab64333665c47863 *src/mbedtls/library/ecp_invasive.h
a2b1f3baf0f8bf8fe74b141bf86274a5 *src/mbedtls/library/entropy.c
5e9b3a666141a9a7377b82139fb126f2 *src/mbedtls/library/entropy_poll.c
619d9a1db117e9348a5719f6666e4088 *src/mbedtls/library/entropy_poll.h
ce0c4b84c728cb6690dbffb163b58e38 *src/mbedtls/library/error.c
37e48442df18d2b50c1baef3e7cc4919 *src/mbedtls/library/gcm.c
2777d136d6ae8f88d2bbc375a8d047dd *src/mbedtls/library/hkdf.c
c92564521dae604c65c68dabd2f76d8c *src/mbedtls/library/md.c
ac20b0a9ba4062ed5f7440d4955945ab *src/mbedtls/library/md5.c
fdc87424be549dc8c4728744aa65eaca *src/mbedtls/library/md_wrap.h
496944bf19fb83f83e9ccba80c192aab *src/mbedtls/library/net_sockets.c
c7f732d9feac665e198853353f2fc16d *src/mbedtls/library/oid.c
046261153c40798888088b6ba45b16c3 *src/mbedtls/library/pem.c
d692f109eb7120b63312523296e9084c *src/mbedtls/library/pk.c
f1606eb3c9888bc35cdd7b6ebfbf9b06 *src/mbedtls/library/pk_ecc.c
ed9d478ff85fca84ff7029bec4dc6125 *src/mbedtls/library/pk_internal.h
9ea66471eb9a3ef00a34a081db745d6c *src/mbedtls/library/pk_wrap.c
598427bb1a11c0cbede23a8bfd8da19c *src/mbedtls/library/pk_wrap.h
bb21cfb9721c7a8b5893a77e5a7a1e6e *src/mbedtls/library/pkcs12.c
18db7fa1f1708d933078f6468c34ca2a *src/mbedtls/library/pkcs5.c
9f6432940e3eff1cf027fbbd7d43ac3c *src/mbedtls/library/pkparse.c
fc5fe264fb9785e4d7c1ea003a04ee63 *src/mbedtls/library/pkwrite.c
e2235ac6c9adb60abb888af01fe1a7d4 *src/mbedtls/library/pkwrite.h
4e633811b30c2c2b22bcf294953853e9 *src/mbedtls/library/platform.c
8fff5b6895d1a72555486023131c6a58 *src/mbedtls/library/platform_util.c
06c1396567b717ff15a60ae41eccfe67 *src/mbedtls/library/poly1305.c
c287c430c2b792eb8e51f6a794d6f219 *src/mbedtls/library/rsa.c
e00c96d4ca4205acd7ddce2524b2317d *src/mbedtls/library/rsa_alt_helpers.c
7d5d9806a129d153dbb861e35a484a16 *src/mbedtls/library/rsa_alt_helpers.h
0afaa859e9d9cb51b01332f5d75a96a7 *src/mbedtls/library/rsa_internal.h
964b8d0ab8d60cefd86b1a90aa8d8d46 *src/mbedtls/library/sha1.c
41384c5cc71855344cca062abd4a0e7c *src/mbedtls/library/sha256.c
e0eeaf5d3d17e0d385ebc5a96948a572 *src/mbedtls/library/sha512.c
6fbd220d21c2cdad04729c3f94ddc2dc *src/mbedtls/library/ssl_cache.c
e41008b9946b09647db34513932c55fe *src/mbedtls/library/ssl_ciphersuites.c
45a9b58891d8554352f2bb6689f20388 *src/mbedtls/library/ssl_ciphersuites_internal.h
1c3ef403b351b1683a1fea196d7ec35e *src/mbedtls/library/ssl_client.c
c5f4168d27845d6f6dbc8ecd0c948878 *src/mbedtls/library/ssl_client.h
52c39eb976aeff061cc9d2724cad224d *src/mbedtls/library/ssl_cookie.c
a2ecf93294b0481af0c772ec16f1abc9 *src/mbedtls/library/ssl_debug_helpers.h
aad65f795b808de11e08e986f742190d *src/mbedtls/library/ssl_debug_helpers_generated.c
b4eb6062a4a342c8701603c4034df9ba *src/mbedtls/library/ssl_misc.h
a2a6ad7fad583071efeee5db5bac4b71 *src/mbedtls/library/ssl_msg.c
c5caa6eba8104be11a54ecf2838eb521 *src/mbedtls/library/ssl_ticket.c
c19c2910c80ad5e3bfee51d0066c6729 *src/mbedtls/library/ssl_tls.c
b33ae80de5ef17928adbb6c008dd7bcd *src/mbedtls/library/ssl_tls12_client.c
fe2a43906e1e8e44d7580737f77fdcad *src/mbedtls/library/ssl_tls12_server.c
14d5a5f7db0b87b4f08f13a4a5ed8c75 *src/mbedtls/library/ssl_tls13_invasive.h
1da5e10543583469489a4f99efd43092 *src/mbedtls/library/ssl_tls13_keys.h
7e392af0f76def405e0817f01ae306cd *src/mbedtls/library/threading.c
984dd02eb9f76a7e05d54724c4adab2d *src/mbedtls/library/threading_internal.h
0afa99f0b287a25e0c08fb1703d37d0a *src/mbedtls/library/timing.c
28dec6c444b9f9a27bf68def085962ca *src/mbedtls/library/version.c
31bbd43b1e906bd18b9a809b1f1ecd23 *src/mbedtls/library/x509.c
668344bd7dbd2de31a52eb4778e0e7e8 *src/mbedtls/library/x509_create.c
fabe550495318689a675f2eedbac3572 *src/mbedtls/library/x509_crl.c
132f86a30d1a206fabf8f5290c076547 *src/mbedtls/library/x509_crt.c
64a9e3549aa535d94e16c900f4ad6d02 *src/mbedtls/library/x509_csr.c
d60d8d1c34811d87d414c21035b48bfa *src/mbedtls/library/x509_internal.h
bb9f548b44a7de5b7fdbf346a428b5f9 *src/mbedtls/library/x509write.c
ea13bedd9ca6ebc0de362b2636752f49 *src/mbedtls/library/x509write_crt.c
96db50c9501db5a0f66c7e1c658f1ddc *src/mbedtls/library/x509write_csr.c
39fc728b2a78a9942d5d4ef146345e1c *src/nanonext.h
d502b508b15d7d4af6d92effd8b31b0a *src/ncurl.c
85c079baf13866f30fe8b17039c37301 *src/net.c
f30e4744ce6b2c86c6fbeeb14db88f7d *src/nng/CMakeLists.txt
a41e579bb4326c21c774f8e51e41d8a3 *src/nng/LICENSE.txt
10857b10077b330442698d93ad7ec51d *src/nng/cmake/FindmbedTLS.cmake
8db2f023daddcd18bb77f586f4940603 *src/nng/cmake/NNGHelpers.cmake
e931eed10117c35be2800e2d020b6983 *src/nng/cmake/NNGOptions.cmake
a3979ada5d2c25bb0846a49c9492bf7c *src/nng/cmake/nng-config.cmake.in
b31f9749af19c92da28f209fb2240a0b *src/nng/include/nng/nng.h
f4afcb884b6a719b06064b499cde8e47 *src/nng/include/nng/protocol/bus0/bus.h
0522e1462140d2c251ee40efe1e2e504 *src/nng/include/nng/protocol/pair0/pair.h
9dc50dba11df15e9b78b13d2dd199072 *src/nng/include/nng/protocol/pair1/pair.h
1a33ccc21d34b32292a1b3746565f159 *src/nng/include/nng/protocol/pipeline0/pull.h
e8e60a30c24868e70f9b824383136114 *src/nng/include/nng/protocol/pipeline0/push.h
63b47d416a9bccea59477ae16489d800 *src/nng/include/nng/protocol/pubsub0/pub.h
159c9c8a9519c3998bafa1da2f2d9b49 *src/nng/include/nng/protocol/pubsub0/sub.h
f9de6075875e2da40fc13c5ec6d94cdc *src/nng/include/nng/protocol/reqrep0/rep.h
13bca32947634728532aab504faaafe1 *src/nng/include/nng/protocol/reqrep0/req.h
5ae1f285a1b6b2b9dfc5152d4ac7e121 *src/nng/include/nng/protocol/survey0/respond.h
72d49e9a04799b10a2d2a9f43328be38 *src/nng/include/nng/protocol/survey0/survey.h
180546c77b4c9651b4be48880424661e *src/nng/include/nng/supplemental/http/http.h
668dd789a7ef5537bbc31320cc5b8491 *src/nng/include/nng/supplemental/tls/engine.h
e280b7fa8ea671857063877deef30a3c *src/nng/include/nng/supplemental/tls/tls.h
3c9f828f8fbb87350cb805ab54a5a807 *src/nng/include/nng/supplemental/util/options.h
b320e89da5f1d18f0abf7136f4be6ba3 *src/nng/include/nng/supplemental/util/platform.h
b30b35bdd399fb10621d1e482a547c55 *src/nng/include/nng/transport/inproc/inproc.h
2685b537a7c21e4d617ce749cc4a13c5 *src/nng/include/nng/transport/ipc/ipc.h
42d28035b5d676a92590c687ebaa1c9b *src/nng/include/nng/transport/tcp/tcp.h
34878651be2d41aeaf9bd59d267d3993 *src/nng/include/nng/transport/tls/tls.h
2d9f6fbffe4c668189063ce79e21348e *src/nng/include/nng/transport/ws/websocket.h
55e34af62e1f829793e3fc286e6df9c8 *src/nng/src/CMakeLists.txt
05ec3a8ffc4352fbe350ffc73a18408e *src/nng/src/core/CMakeLists.txt
998cba77d4a87d83d2a6ce91f20d8c86 *src/nng/src/core/aio.c
229fc884d4ab1ccd36f833a6b6111129 *src/nng/src/core/aio.h
cbc7966de92b158025265e59260c8b92 *src/nng/src/core/defs.h
3f568b9d980a7eb5764f0d2025e0fc14 *src/nng/src/core/device.c
e026af225102ff9f5ba3a42703534704 *src/nng/src/core/device.h
acfebd0875a1373d0c18b0784725f8d9 *src/nng/src/core/dialer.c
0af96ed562e0c147a55784c4ff3367a9 *src/nng/src/core/dialer.h
469527f4ffb60115275473e5b468a60a *src/nng/src/core/file.c
555129fb59b58d309a031de02b4a690c *src/nng/src/core/file.h
577ea63b8b948e1070dcb0820727bb65 *src/nng/src/core/idhash.c
5764ae2f7595b17352a885b9782832d1 *src/nng/src/core/idhash.h
fbdb7ca9439237350407b47298fdb315 *src/nng/src/core/init.c
5c4b8d03c606eb67f0205ca97a0c4033 *src/nng/src/core/init.h
7911c236bdf070be4dcac991a7c319a8 *src/nng/src/core/list.c
ad2ca9a26502de21ae721a9289d3047a *src/nng/src/core/list.h
a41eb4fcabd9929fef9a4ff0dbf4f5d1 *src/nng/src/core/listener.c
819d6484cb0dae92c9e7645db24e918f *src/nng/src/core/listener.h
70d4027bf5fd091b26a423d8a3978ab5 *src/nng/src/core/lmq.c
0b5efebf29fd351d4e31cdd988131693 *src/nng/src/core/lmq.h
355ff1019edc5b11a9e542fe34dc820b *src/nng/src/core/message.c
1dc79ff0ba3286e6065a4601e54d3bbd *src/nng/src/core/message.h
0e34f655b9c0847b0525c8fb62985e5d *src/nng/src/core/msgqueue.c
a6e374b2cda1f54ac24adb6c4bc10c67 *src/nng/src/core/msgqueue.h
731c6321e78e393f226d489123f8aa8b *src/nng/src/core/nng_impl.h
83df8a8e6878622a3314a2a28bca6d7d *src/nng/src/core/options.c
68f5ae658641a676c6999b020cf1f300 *src/nng/src/core/options.h
94f60f3d9daaba189968f403492ff05a *src/nng/src/core/panic.c
9babf56d231640bf8ca52ab1bf446388 *src/nng/src/core/panic.h
46da0e91371df41e0a5d7300dea05d52 *src/nng/src/core/pipe.c
325d208fe458eab1d466706cbead0c82 *src/nng/src/core/pipe.h
30e45ffb8a6626011b26e6372eba0b3d *src/nng/src/core/platform.h
d94592d544c89538896eb54adfef5428 *src/nng/src/core/pollable.c
b56559f4985f1483ed2539a4949eeca2 *src/nng/src/core/pollable.h
04aad56b9827b6d7075c02966dbe8e5c *src/nng/src/core/protocol.h
3b7b876ec9adeef6d4e0875b28ac0051 *src/nng/src/core/reap.c
426f79527d0d4146189a74f684865c12 *src/nng/src/core/reap.h
b109b90ad6cd37fd5af9458ba8f120a1 *src/nng/src/core/socket.c
def4e3f2a01c71a90e0af3ddb075a825 *src/nng/src/core/socket.h
1875539dffa9b59e2290c60e3719b8d8 *src/nng/src/core/sockfd.c
1bb114329d3df76ec4b190d6934a2f6a *src/nng/src/core/sockfd.h
4462e6e7cb73d7bb885f6f2567c2f268 *src/nng/src/core/sockimpl.h
96bedae0bb5a492e294afde92041b70f *src/nng/src/core/stats.c
3c05617b1f5d2906a508aee45658ef5c *src/nng/src/core/stats.h
240ad5ba465c222e137c600139da1a06 *src/nng/src/core/stream.c
7d16c2447336e405ed8319fe4286ec8c *src/nng/src/core/stream.h
7e548b25ff2b15ea1eb32e67d431a35d *src/nng/src/core/strs.c
86fb93836b71487c0670150348bc7dee *src/nng/src/core/strs.h
f068bd9cfed4324409b038ffc8d641d8 *src/nng/src/core/taskq.c
fe76c675582264026f9d064cc72c1402 *src/nng/src/core/taskq.h
a556f31a6d697b79f7f864febd2f2603 *src/nng/src/core/tcp.c
9c8c491eb91c00524380a08cc1fa2d17 *src/nng/src/core/tcp.h
8dd7eae7c67fc1b9423f1a7e9845538c *src/nng/src/core/thread.c
1e3aecfc7ae0cd543411b8b35f783b59 *src/nng/src/core/thread.h
003ade259495dec46b88434dcaccaa2e *src/nng/src/core/url.c
bcfb89f5e8ac10704aa60946457658e7 *src/nng/src/core/url.h
e58711aa23f1c5cd2f3ef5578111614a *src/nng/src/nng.c
ed2c29b5db8f7b76ac37c1fd32f715d2 *src/nng/src/platform/CMakeLists.txt
8f4518a78546d3a75af81c0e2f6f5c92 *src/nng/src/platform/posix/CMakeLists.txt
b118058267c3fbda6b7863d23edc17d7 *src/nng/src/platform/posix/posix_aio.h
d421afc0fc7efa61b69282b3341de42f *src/nng/src/platform/posix/posix_alloc.c
aa36e3dc45e2d35b08d58ebe72fa4a98 *src/nng/src/platform/posix/posix_atomic.c
f44a7618bf8f78dfd8bcbf479dcbab30 *src/nng/src/platform/posix/posix_clock.c
440fd8e2c486bca8960b809fe973ba0f *src/nng/src/platform/posix/posix_config.h
4f869c6214f2b3dcd411edf9aee55ea7 *src/nng/src/platform/posix/posix_debug.c
9c9f2456f8a4dc88b540337057edfb93 *src/nng/src/platform/posix/posix_file.c
8393a39284218daffbb366b1053ebbda *src/nng/src/platform/posix/posix_impl.h
45cf8325497bc5a2478ffc923949e4c1 *src/nng/src/platform/posix/posix_ipc.h
fea4a0e7ee261c091b3be801e15b9cca *src/nng/src/platform/posix/posix_ipcconn.c
ba61412686406faa45c84eba98ef0b22 *src/nng/src/platform/posix/posix_ipcdial.c
5df0403e6f5f8f8b29346c21af00a41a *src/nng/src/platform/posix/posix_ipclisten.c
076c372b77708efa00a1ee8f5597c7ec *src/nng/src/platform/posix/posix_peerid.c
e53796cb61d956f7d9dfabaac3f753bd *src/nng/src/platform/posix/posix_peerid.h
c96808ff7bf1d5c88ceaa23d9e60b198 *src/nng/src/platform/posix/posix_pipe.c
26dd67af9c36106559db5a7912d7ff16 *src/nng/src/platform/posix/posix_pollq.h
553c1d3557598d5c67349c2ad9cb84a6 *src/nng/src/platform/posix/posix_pollq_epoll.c
2f6c29fde3303013fa271495e75c4f91 *src/nng/src/platform/posix/posix_pollq_kqueue.c
4b585ae0508645d0fb3b7d24c85583b5 *src/nng/src/platform/posix/posix_pollq_poll.c
5f442abcc0e17139b6546e33339a6240 *src/nng/src/platform/posix/posix_pollq_port.c
666a0bb037a97a2685350568126e70f9 *src/nng/src/platform/posix/posix_rand_arc4random.c
64d718fb9e12f0b45a3c5bac97a684a9 *src/nng/src/platform/posix/posix_rand_getrandom.c
25803d23706dc4e6714f181a7119dbe1 *src/nng/src/platform/posix/posix_rand_urandom.c
ce06d96bf13f8f9adcb7ccb82a5d33c2 *src/nng/src/platform/posix/posix_resolv_gai.c
615da78fd7c711fbc155193f51ab4826 *src/nng/src/platform/posix/posix_sockaddr.c
5967809b4d7d71820b0735df78d739f8 *src/nng/src/platform/posix/posix_socketpair.c
ead52cc9d14a96c40f58b58c056a4b76 *src/nng/src/platform/posix/posix_sockfd.c
b6211c20ec92fdd759d7ef0dd883d0d9 *src/nng/src/platform/posix/posix_tcp.h
ff9e64b901df2adff3c8503cd91bb824 *src/nng/src/platform/posix/posix_tcpconn.c
c24b37c41862b2c17a7263ddd4524d68 *src/nng/src/platform/posix/posix_tcpdial.c
63cd75d4127c0ac8fb60959de976c05d *src/nng/src/platform/posix/posix_tcplisten.c
0e9a473ae18b902c53ea6876d9c23c5b *src/nng/src/platform/posix/posix_thread.c
e123e2693858d8dfed264729dbc442dd *src/nng/src/platform/posix/posix_udp.c
428d1e50fe4ac80f83f3a7a727d04ac3 *src/nng/src/platform/windows/CMakeLists.txt
73ddf5aa66a03c87d4556033fa10f396 *src/nng/src/platform/windows/win_clock.c
44a8927fbf748291722082e54ecb0cb0 *src/nng/src/platform/windows/win_debug.c
73b928a4226f830c9a420022a096c58c *src/nng/src/platform/windows/win_file.c
f240339278fd75d0dc5d7e31654308ab *src/nng/src/platform/windows/win_impl.h
cded393d588b642676e6798f51ff6bd9 *src/nng/src/platform/windows/win_io.c
42ed4b4e091f04d7e137fcb3aafe1b0a *src/nng/src/platform/windows/win_ipc.h
0ca66bc471e0415e2fc22e6acd25a62d *src/nng/src/platform/windows/win_ipcconn.c
56d86cdf3974d2b8cb7e5630804654f0 *src/nng/src/platform/windows/win_ipcdial.c
372c32bda323040473095f4c8f773b42 *src/nng/src/platform/windows/win_ipclisten.c
587ea78fb608be86473d7ba2b192b88e *src/nng/src/platform/windows/win_pipe.c
a09b8de274c2889d6bf26f7e053ec008 *src/nng/src/platform/windows/win_rand.c
be3960db00aa36c20e5064ae81335eea *src/nng/src/platform/windows/win_resolv.c
f26c6a5193114ffe78619f9e61f78ae8 *src/nng/src/platform/windows/win_sockaddr.c
c244f937a600a6e6d46eb06fd2b86fa1 *src/nng/src/platform/windows/win_socketpair.c
1b4903d024908a3a1127eb731350d15b *src/nng/src/platform/windows/win_tcp.c
aa61f8d1ed5c320836f598fafb81abe1 *src/nng/src/platform/windows/win_tcp.h
8fc6acd146b8438147c76b97afc60c90 *src/nng/src/platform/windows/win_tcpconn.c
7eac1a47d3e8e0ce13de07aecf2fe659 *src/nng/src/platform/windows/win_tcpdial.c
5915f7828d488508f1d8f6fb10936656 *src/nng/src/platform/windows/win_tcplisten.c
8c80d1e40fbc90af7c185d18d7b6dce2 *src/nng/src/platform/windows/win_thread.c
96f173c01a6086be92c2ab8ba0dcfa4c *src/nng/src/platform/windows/win_udp.c
3f08d645f2ac60c46c84bfdc935c5c06 *src/nng/src/sp/CMakeLists.txt
3a09d307ff9fea5d50f206db27bf1a30 *src/nng/src/sp/protocol.c
8afbfc464327d999dba25b3f3bfe857f *src/nng/src/sp/protocol/CMakeLists.txt
be9ec140e19dad11a407f52689854f7c *src/nng/src/sp/protocol/bus0/CMakeLists.txt
415cfc15866e7169c6343ea49d7127e0 *src/nng/src/sp/protocol/bus0/bus.c
60f4a8257e7b863b741e47705cba02cc *src/nng/src/sp/protocol/pair0/CMakeLists.txt
c6579159eab52be8e1cfaf8c6943794d *src/nng/src/sp/protocol/pair0/pair.c
2dba2ccc75bcd71fb300b6d72c5aeb6a *src/nng/src/sp/protocol/pair1/CMakeLists.txt
094ccaaaf2c7c688e4d1d42b42dfbb39 *src/nng/src/sp/protocol/pair1/pair.c
a422bb0f023f48f393b9ee08123c8c66 *src/nng/src/sp/protocol/pair1/pair1_poly.c
2eccc05ed27b0aa624b77997cf158c1b *src/nng/src/sp/protocol/pipeline0/CMakeLists.txt
9ede35689cc7327b66b5433fa516849c *src/nng/src/sp/protocol/pipeline0/pull.c
8ef42331dea53bf4a77bbe46e186e162 *src/nng/src/sp/protocol/pipeline0/push.c
3de5d730266f9946d7584e8e22072099 *src/nng/src/sp/protocol/pubsub0/CMakeLists.txt
9b5af98f6b5bb05c22a7d925fc030df4 *src/nng/src/sp/protocol/pubsub0/pub.c
0c8628a5926cc643bbd6f03c29cf5d47 *src/nng/src/sp/protocol/pubsub0/sub.c
0db315be80dd4a72122490059bb2fda0 *src/nng/src/sp/protocol/pubsub0/xsub.c
eed8ab14d131939714ce23871e3f6e6b *src/nng/src/sp/protocol/reqrep0/CMakeLists.txt
c2390e104447247e0dcbbdd010312fd4 *src/nng/src/sp/protocol/reqrep0/rep.c
f38a0e1afc7424cb38254e07ffbdcaf7 *src/nng/src/sp/protocol/reqrep0/req.c
1dc225f4a1f959efa482458b0cacf391 *src/nng/src/sp/protocol/reqrep0/xrep.c
50edc566abca0c9cd767d2b5fd2ff2de *src/nng/src/sp/protocol/reqrep0/xreq.c
111725a2311c145d039f9174b4e3d17e *src/nng/src/sp/protocol/survey0/CMakeLists.txt
22e4be84d03c6b78bd0a27d719fa45db *src/nng/src/sp/protocol/survey0/respond.c
6afd7d2ad4db9cc1f347a9bd70215d66 *src/nng/src/sp/protocol/survey0/survey.c
fa9c2c6340ed95d617ea0b2628b08ebd *src/nng/src/sp/protocol/survey0/xrespond.c
3c1ec0329252b5b50bb68e9a68e5cbd8 *src/nng/src/sp/protocol/survey0/xsurvey.c
3cda4f8a48c583f05cdaa9fbe96c4464 *src/nng/src/sp/transport.c
2890ef8bd0976ab5998ecbd783a57bbe *src/nng/src/sp/transport.h
40093b4f9402530e3f67c07bc6e94709 *src/nng/src/sp/transport/CMakeLists.txt
e35400ac5f9323b71e05ed5c19bbdd0a *src/nng/src/sp/transport/inproc/CMakeLists.txt
b8f514c964251f48a2b25185dbbe2e6b *src/nng/src/sp/transport/inproc/inproc.c
0ebef58f231e60e3640f6d6f468301ae *src/nng/src/sp/transport/ipc/CMakeLists.txt
3f09fb0b7c0349c15cc425726800573b *src/nng/src/sp/transport/ipc/ipc.c
c0f9fbc73d693df1fb1138f970df489c *src/nng/src/sp/transport/socket/CMakeLists.txt
4f7c48a4390bbef56465beb37174cab8 *src/nng/src/sp/transport/socket/sockfd.c
f37cedd8c4a622086a3e09f7738d26d6 *src/nng/src/sp/transport/tcp/CMakeLists.txt
c06c6c4c8b22229a41ec258ff0ca6554 *src/nng/src/sp/transport/tcp/tcp.c
e0477cc4bf2c1c6fbaf5a297fd301ffe *src/nng/src/sp/transport/tls/CMakeLists.txt
31884cccb6e43e93f11a60827580e650 *src/nng/src/sp/transport/tls/tls.c
ec54db440a00f31a46d8b5aa33b95735 *src/nng/src/sp/transport/ws/CMakeLists.txt
be98cfc00a95248d03f20cd50c775f10 *src/nng/src/sp/transport/ws/websocket.c
a85eb61fd7a0efb5f62c5df191fdeb1f *src/nng/src/supplemental/CMakeLists.txt
dcd623b6445cf3cbd280c4d752e4f863 *src/nng/src/supplemental/base64/CMakeLists.txt
3492cfe4ea5d9767e5b9e0067e479fae *src/nng/src/supplemental/base64/base64.c
a6d270fa15004f590d572807a2a6f465 *src/nng/src/supplemental/base64/base64.h
9d30491f297804da59fe8216ab768ac8 *src/nng/src/supplemental/http/CMakeLists.txt
6b95775ead03be16e30f4fb0365b90db *src/nng/src/supplemental/http/http_api.h
dc89eb0d48904505155eae8d33297735 *src/nng/src/supplemental/http/http_chunk.c
786ad4dc08cc4d09c33168d0cbdedcf4 *src/nng/src/supplemental/http/http_client.c
b88ced0952f137c7822001ccebff8eb9 *src/nng/src/supplemental/http/http_conn.c
c17573c9be1a14e10d67d5f17d031ea2 *src/nng/src/supplemental/http/http_msg.c
958c5c7489916f10a9ceed47b86fdb49 *src/nng/src/supplemental/http/http_public.c
0400ef9906871f2e2465eda9eb9bcf46 *src/nng/src/supplemental/http/http_schemes.c
2d80276eec98b0f356d1f4289e8605e7 *src/nng/src/supplemental/http/http_server.c
6058dcf45259fe61b6872968f64b53bd *src/nng/src/supplemental/sha1/CMakeLists.txt
f005544ec4a3b8fc485f3dc525283f2c *src/nng/src/supplemental/sha1/sha1.c
fa9fe241b74f82a850aefe8421169853 *src/nng/src/supplemental/sha1/sha1.h
894d833e40a32762527ce9395fa79910 *src/nng/src/supplemental/tls/CMakeLists.txt
3cda364ea0ba7c06370a2afbfbcacd45 *src/nng/src/supplemental/tls/mbedtls/CMakeLists.txt
e228e057d53d05a0b1f6de8a9d60595c *src/nng/src/supplemental/tls/mbedtls/tls.c
8fa96bf45f83e83773d4eee0fc6576ea *src/nng/src/supplemental/tls/tls_api.h
fb3d1d4ae49dade77a678206d03709c4 *src/nng/src/supplemental/tls/tls_common.c
955e326cafdd8330d5bb155f67b0c270 *src/nng/src/supplemental/util/CMakeLists.txt
274d04d79610da268c873981b86d4d1c *src/nng/src/supplemental/util/options.c
ba61cc76ad610dc83a19cb665d55f526 *src/nng/src/supplemental/util/platform.c
5e98fc4713aeb2c0d3f8b4a4bc7b6bcc *src/nng/src/supplemental/websocket/CMakeLists.txt
5ae4571d6101ade75be7ff0dedc1018b *src/nng/src/supplemental/websocket/stub.c
141c6b669a38ade13f715410edff1bd9 *src/nng/src/supplemental/websocket/websocket.c
ba2f08d7d3c743ca35ae10de44e4c57a *src/nng/src/supplemental/websocket/websocket.h
fc81cc8cdb68bfa5b3ea0c3cf254293b *src/nng_structs.h
0cceae2cf027036bc1561c9f420342d4 *src/proto.c
8737d33001fcf964a827cff9c05204d4 *src/server.c
197734b6c5e7138bdc97a9f8be6aeb7a *src/sync.c
a3bbaaffc6c251ed58a1ba6a123543d9 *src/thread.c
a6fac3e04aee8ba1637b1730b67425eb *src/tls.c
322c144aa3107b006cdafc2f3bae7cf1 *src/utils.c
a3f21cd23f9d52507ab7a2ddbd74d4fe *tests/tests.R
73116821dd0a8a32f807dd6ae97ea6f9 *vignettes/nanonext.Rmd
1e229ac2eab79e3bbb4a4d53a6a44be2 *vignettes/v01-messaging.Rmd
c8ea0fd34f2fa552eba2aba3af2eb0b4 *vignettes/v02-protocols.Rmd
2069758b3429d409ccc1e85102d78459 *vignettes/v03-configuration.Rmd
62b33d163137ea05621c4d0494d64d11 *vignettes/v04-web.Rmd
nanonext/configure.win 0000755 0001762 0000144 00000001454 15142221674 014617 0 ustar ligges users for ARCH in x64 i386; do
if [ -e "${R_HOME}/bin/${ARCH}/R" ]; then
CC=`"${R_HOME}/bin/${ARCH}/R" CMD config CC`
CFLAGS=`"${R_HOME}/bin/${ARCH}/R" CMD config CFLAGS`
LDFLAGS=`"${R_HOME}/bin/${ARCH}/R" CMD config LDFLAGS`
export CC CFLAGS LDFLAGS
echo "Compiling 'libmbedtls' from source for ${ARCH} ..."
cmake -S src/mbedtls -B nano-build -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=nano-install/${ARCH}
cmake --build nano-build --target install
rm -rf nano-build
echo "Compiling 'libnng' from source for ${ARCH} ..."
cmake -S src/nng -B nano-build -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=nano-install/${ARCH}
cmake --build nano-build --target install
rm -rf nano-build
else
echo "Note: ${ARCH} not installed, skipping ..."
fi
done
# Success
exit 0
nanonext/R/ 0000755 0001762 0000144 00000000000 15174743044 012316 5 ustar ligges users nanonext/R/sendrecv.R 0000644 0001762 0000144 00000013321 15166504164 014251 0 ustar ligges users # nanonext - Core Functions - send/recv ----------------------------------------
#' Send
#'
#' Send data over a connection (Socket, Context or Stream).
#'
#' @param con a Socket, Context or Stream.
#' @param data an object (a vector, if `mode = "raw"`).
#' @param mode \[default 'serial'\] character value or integer equivalent -
#' either `"serial"` (1L) to send serialised R objects, or `"raw"` (2L) to
#' send atomic vectors of any type as a raw byte vector. For Streams, `"raw"`
#' is the only option and this argument is ignored.
#' @param block \[default NULL\] which applies the connection default (see
#' section 'Blocking' below). Specify logical `TRUE` to block until successful
#' or `FALSE` to return immediately even if unsuccessful (e.g. if no
#' connection is available), or else an integer value specifying the maximum
#' time to block in milliseconds, after which the operation will time out.
#' @param pipe \[default 0L\] only applicable to Sockets using the 'poly'
#' protocol, an integer pipe ID if directing the send via a specific pipe.
#'
#' @return An integer exit code (zero on success).
#'
#' @section Blocking:
#'
#' For Sockets and Contexts: the default behaviour is non-blocking with
#' `block = FALSE`. This will return immediately with an error if the message
#' could not be queued for sending. Certain protocol / transport combinations
#' may limit the number of messages that can be queued if they have yet to be
#' received.
#'
#' For Streams: the default behaviour is blocking with `block = TRUE`. This will
#' wait until the send has completed. Set a timeout to ensure that the function
#' returns under all scenarios. As the underlying implementation uses an
#' asynchronous send with a wait, it is recommended to set a small positive
#' value for `block` rather than `FALSE`.
#'
#' @section Send Modes:
#'
#' The default mode `"serial"` sends serialised R objects to ensure perfect
#' reproducibility within R. When receiving, the corresponding mode `"serial"`
#' should be used. Custom serialization and unserialization functions for
#' reference objects may be enabled by the function [serial_config()].
#'
#' Mode `"raw"` sends atomic vectors of any type as a raw byte vector, and must
#' be used when interfacing with external applications or raw system sockets,
#' where R serialization is not in use. When receiving, the mode corresponding
#' to the vector sent should be used.
#'
#' @seealso [send_aio()] for asynchronous send.
#'
#' @examples
#' pub <- socket("pub", dial = "inproc://nanonext")
#'
#' send(pub, data.frame(a = 1, b = 2))
#' send(pub, c(10.1, 20.2, 30.3), mode = "raw", block = 100)
#'
#' close(pub)
#'
#' req <- socket("req", listen = "inproc://nanonext")
#' rep <- socket("rep", dial = "inproc://nanonext")
#'
#' ctx <- context(req)
#' send(ctx, data.frame(a = 1, b = 2), block = 100)
#'
#' msg <- recv_aio(rep, timeout = 100)
#' send(ctx, c(1.1, 2.2, 3.3), mode = "raw", block = 100)
#'
#' close(req)
#' close(rep)
#'
#' @export
#'
send <- function(con, data, mode = c("serial", "raw"), block = NULL, pipe = 0L)
.Call(rnng_send, con, data, mode, block, pipe)
#' Receive
#'
#' Receive data over a connection (Socket, Context or Stream).
#'
#' @inheritParams send
#' @param mode \[default 'serial'\] character value or integer equivalent - one
#' of `"serial"` (1L), `"character"` (2L), `"complex"` (3L), `"double"` (4L),
#' `"integer"` (5L), `"logical"` (6L), `"numeric"` (7L), `"raw"` (8L), or
#' `"string"` (9L). The default `"serial"` means a serialised R object; for
#' the other modes, received bytes are converted into the respective mode.
#' `"string"` is a faster option for length one character vectors. For
#' Streams, `"serial"` will default to `"character"`.
#' @return The received data in the `mode` specified.
#'
#' @section Errors:
#'
#' In case of an error, an integer 'errorValue' is returned (to be
#' distiguishable from an integer message value). This can be verified using
#' [is_error_value()].
#'
#' If an error occurred in unserialization or conversion of the message data to
#' the specified mode, a raw vector will be returned instead to allow recovery
#' (accompanied by a warning).
#'
#' @section Blocking:
#'
#' For Sockets and Contexts: the default behaviour is non-blocking with
#' `block = FALSE`. This will return immediately with an error if no messages
#' are available.
#'
#' For Streams: the default behaviour is blocking with `block = TRUE`. This will
#' wait until a message is received. Set a timeout to ensure that the function
#' returns under all scenarios. As the underlying implementation uses an
#' asynchronous receive with a wait, it is recommended to set a small positive
#' value for `block` rather than `FALSE`.
#'
#' @seealso [recv_aio()] for asynchronous receive.
#'
#' @examples
#' s1 <- socket("pair", listen = "inproc://nanonext")
#' s2 <- socket("pair", dial = "inproc://nanonext")
#'
#' send(s1, data.frame(a = 1, b = 2))
#' res <- recv(s2)
#' res
#' send(s1, data.frame(a = 1, b = 2))
#' recv(s2)
#'
#' send(s1, c(1.1, 2.2, 3.3), mode = "raw")
#' res <- recv(s2, mode = "double", block = 100)
#' res
#' send(s1, "example message", mode = "raw")
#' recv(s2, mode = "character")
#'
#' close(s1)
#' close(s2)
#'
#' req <- socket("req", listen = "inproc://nanonext")
#' rep <- socket("rep", dial = "inproc://nanonext")
#'
#' ctxq <- context(req)
#' ctxp <- context(rep)
#' send(ctxq, data.frame(a = 1, b = 2), block = 100)
#' recv(ctxp, block = 100)
#'
#' send(ctxq, c(1.1, 2.2, 3.3), mode = "raw", block = 100)
#' recv(ctxp, mode = "double", block = 100)
#'
#' close(req)
#' close(rep)
#'
#' @export
#'
recv <- function(
con,
mode = c("serial", "character", "complex", "double", "integer", "logical", "numeric", "raw", "string"),
block = NULL
)
.Call(rnng_recv, con, mode, block)
nanonext/R/nanonext-package.R 0000644 0001762 0000144 00000006236 15077362607 015677 0 ustar ligges users # nanonext - Package -----------------------------------------------------------
#' nanonext: NNG (Nanomsg Next Gen) Lightweight Messaging Library
#'
#' R binding for NNG (Nanomsg Next Gen), a successor to ZeroMQ. NNG is a socket
#' library for reliable, high-performance messaging over in-process, IPC, TCP,
#' WebSocket and secure TLS transports. Implements 'Scalability Protocols', a
#' standard for common communications patterns including publish/subscribe,
#' request/reply and service discovery. As its own threaded concurrency
#' framework, provides a toolkit for asynchronous programming and distributed
#' computing. Intuitive 'aio' objects resolve automatically when asynchronous
#' operations complete, and synchronisation primitives allow R to wait upon
#' events signalled by concurrent threads.
#'
#' @section Usage notes:
#'
#' \pkg{nanonext} offers 2 equivalent interfaces: a functional interface, and an
#' object-oriented interface.
#'
#' The primary object in the functional interface is the Socket. Use [socket()]
#' to create a socket and dial or listen at an address. The socket is then
#' passed as the first argument of subsequent actions such as `send()` or
#' `recv()`.
#'
#' The primary object in the object-oriented interface is the nano object. Use
#' [nano()] to create a nano object which encapsulates a Socket and
#' Dialer/Listener. Methods such as `$send()` or `$recv()` can then be accessed
#' directly from the object.
#'
#' @section Documentation:
#'
#' Guide to the implemented protocols for sockets: [protocols]
#'
#' Guide to the supported transports for dialers and listeners:
#' [transports]
#'
#' Guide to the options that can be inspected and set using: [opt] /
#' [opt<-]
#'
#' @section Reference Manual:
#'
#' `vignette("nanonext", package = "nanonext")`
#'
#' @section Conceptual overview:
#'
#' NNG presents a socket view of networking. A socket implements precisely one
#' protocol, such as 'bus', etc.
#'
#' Each socket can be used to send and receive messages (if the protocol
#' supports it, and implements the appropriate protocol semantics). For example,
#' the 'sub' protocol automatically filters incoming messages to discard topics
#' that have not been subscribed.
#'
#' NNG sockets are message-oriented, and messages are either delivered wholly,
#' or not at all. Partial delivery is not possible. Furthermore, NNG does not
#' provide any other delivery or ordering guarantees: messages may be dropped or
#' reordered (some protocols, such as 'req' may offer stronger guarantees by
#' performing their own retry and validation schemes).
#'
#' Each socket can have zero, one, or many endpoints, which are either listeners
#' or dialers (a given socket may use listeners, dialers, or both). These
#' endpoints provide access to underlying transports, such as TCP, etc.
#'
#' Each endpoint is associated with a URL, which is a service address. For
#' dialers, this is the service address that is contacted, whereas for listeners
#' this is where new connections will be accepted.
#'
#' @section Links:
#'
#' NNG: \cr
#' Mbed TLS:
#'
#' @useDynLib nanonext, .registration = TRUE
#'
"_PACKAGE"
nanonext/R/context.R 0000644 0001762 0000144 00000020304 15166504164 014123 0 ustar ligges users # nanonext - Contexts and RPC --------------------------------------------------
#' Open Context
#'
#' Open a new Context to be used with a Socket. The purpose of a Context is to
#' permit applications to share a single socket, with its underlying dialers and
#' listeners, while still benefiting from separate state tracking.
#'
#' Contexts allow the independent and concurrent use of stateful operations
#' using the same socket. For example, two different contexts created on a rep
#' socket can each receive requests, and send replies to them, without any
#' regard to or interference with each other.
#'
#' Only the following protocols support creation of contexts: req, rep, sub
#' (in a pub/sub pattern), surveyor, respondent.
#'
#' To send and receive over a context use [send()] and [recv()] or their async
#' counterparts [send_aio()] and [recv_aio()].
#'
#' For nano objects, use the `$context_open()` method, which will attach a new
#' context at `$context`. See [nano()].
#'
#' @param socket a Socket.
#'
#' @return A Context (object of class 'nanoContext' and 'nano').
#'
#' @seealso [request()] and [reply()] for use with contexts.
#' @examples
#' s <- socket("req", listen = "inproc://nanonext")
#' ctx <- context(s)
#' ctx
#' close(ctx)
#' close(s)
#'
#' n <- nano("req", listen = "inproc://nanonext")
#' n$context_open()
#' n$context
#' n$context_open()
#' n$context
#' n$context_close()
#' n$close()
#'
#' @export
#'
context <- function(socket) .Call(rnng_ctx_open, socket)
#' Technical Utility: Open Context
#'
#' Open a new Context to be used with a Socket. This function is a performance
#' variant of [context()], designed to wrap a socket in a function argument when
#' calling [request()] or [reply()].
#'
#' External pointers created by this function are unclassed, hence methods for
#' contexts such as [close()] will not work (use [reap()] instead). Otherwise
#' they function identically to a Context when passed to all messaging
#' functions.
#'
#' @param socket a Socket.
#'
#' @return An external pointer.
#'
#' @keywords internal
#' @export
#'
.context <- function(socket) .Call(rnng_ctx_create, socket)
#' @rdname close
#' @method close nanoContext
#' @export
#'
close.nanoContext <- function(con, ...) invisible(.Call(rnng_ctx_close, con))
#' Reply over Context (RPC Server for Req/Rep Protocol)
#'
#' Implements an executor/server for the rep node of the req/rep protocol.
#' Awaits data, applies an arbitrary specified function, and returns the result
#' to the caller/client.
#'
#' Receive will block while awaiting a message to arrive and is usually the
#' desired behaviour. Set a timeout to allow the function to return if no data
#' is forthcoming.
#'
#' In the event of an error in either processing the messages or in evaluation
#' of the function with respect to the data, a nul byte `00` (or serialized
#' nul byte) will be sent in reply to the client to signal an error. This is to
#' be distinguishable from a possible return value. [is_nul_byte()] can be used
#' to test for a nul byte.
#'
#' @param context a Context.
#' @param execute a function which takes the received (converted) data as its
#' first argument. Can be an anonymous function of the form
#' `function(x) do(x)`. Additional arguments can also be passed in through
#' `...`.
#' @param send_mode \[default 'serial'\] character value or integer equivalent -
#' either `"serial"` (1L) to send serialised R objects, or `"raw"` (2L) to
#' send atomic vectors of any type as a raw byte vector.
#' @param recv_mode \[default 'serial'\] character value or integer equivalent -
#' one of `"serial"` (1L), `"character"` (2L), `"complex"` (3L), `"double"`
#' (4L), `"integer"` (5L), `"logical"` (6L), `"numeric"` (7L), `"raw"` (8L),
#' or `"string"` (9L). The default `"serial"` means a serialised R object; for
#' the other modes, received bytes are converted into the respective mode.
#' `"string"` is a faster option for length one character vectors.
#' @param timeout \[default NULL\] integer value in milliseconds or NULL, which
#' applies a socket-specific default, usually the same as no timeout. Note
#' that this applies to receiving the request. The total elapsed time would
#' also include performing 'execute' on the received data. The timeout then
#' also applies to sending the result (in the event that the requestor has
#' become unavailable since sending the request).
#' @param ... additional arguments passed to the function specified by
#' 'execute'.
#'
#' @return Integer exit code (zero on success).
#'
#' @inheritSection send Send Modes
#'
#' @examples
#' req <- socket("req", listen = "inproc://req-example")
#' rep <- socket("rep", dial = "inproc://req-example")
#'
#' ctxq <- context(req)
#' ctxp <- context(rep)
#'
#' send(ctxq, 2022, block = 100)
#' reply(ctxp, execute = function(x) x + 1, send_mode = "raw", timeout = 100)
#' recv(ctxq, mode = "double", block = 100)
#'
#' send(ctxq, 100, mode = "raw", block = 100)
#' reply(ctxp, recv_mode = "double", execute = log, base = 10, timeout = 100)
#' recv(ctxq, block = 100)
#'
#' close(req)
#' close(rep)
#'
#' @export
#'
reply <- function(
context,
execute,
recv_mode = c("serial", "character", "complex", "double", "integer", "logical", "numeric", "raw", "string"),
send_mode = c("serial", "raw"),
timeout = NULL,
...
) {
block <- if (is.null(timeout)) TRUE else timeout
res <- recv(context, mode = recv_mode, block = block)
is_error_value(res) && return(res)
data <- .Call(rnng_eval_safe, as.call(list(execute, res, ...)))
send(context, data = data, mode = send_mode, block = block)
}
#' Request over Context (RPC Client for Req/Rep Protocol)
#'
#' Implements a caller/client for the req node of the req/rep protocol. Sends
#' data to the rep node (executor/server) and returns an Aio, which can be
#' called for the value when required.
#'
#' Sending the request and receiving the result are both performed async, hence
#' the function will return immediately with a 'recvAio' object. Access the
#' return value at `$data`.
#'
#' This is designed so that the process on the server can run concurrently
#' without blocking the client.
#'
#' Optionally use [call_aio()] on the 'recvAio' to call (and wait for) the
#' result.
#'
#' If an error occured in the server process, a nul byte `00` will be received.
#' This allows an error to be easily distinguished from a NULL return value.
#' [is_nul_byte()] can be used to test for a nul byte.
#'
#' It is recommended to use a new context for each request to ensure consistent
#' state tracking. The integer context ID is appended as the attribute `id` to
#' the returned 'recvAio'.
#'
#' @inheritParams reply
#' @inheritParams recv
#' @param data an object (if `send_mode = "raw"`, a vector).
#' @param timeout \[default NULL\] integer value in milliseconds or NULL, which
#' applies a socket-specific default, usually the same as no timeout.
#' @param cv (optional) a 'conditionVariable' to signal when the async receive
#' is complete, or NULL.
#' @param id NULL. For package internal use only.
#'
#' @return A 'recvAio' (object of class 'mirai' and 'recvAio') (invisibly).
#'
#' @inheritSection send Send Modes
#' @inheritSection recv_aio Signalling
#'
#' @examples
#' \dontrun{
#'
#' # works if req and rep are running in parallel in different processes
#'
#' req <- socket("req", listen = "tcp://127.0.0.1:6546")
#' rep <- socket("rep", dial = "tcp://127.0.0.1:6546")
#'
#' reply(.context(rep), execute = function(x) x + 1, timeout = 50)
#' aio <- request(.context(req), data = 2022)
#' aio$data
#'
#' close(req)
#' close(rep)
#'
#' # Signalling a condition variable
#'
#' req <- socket("req", listen = "tcp://127.0.0.1:6546")
#' ctxq <- context(req)
#' cv <- cv()
#' aio <- request(ctxq, data = 2022, cv = cv)
#' until(cv, 10L)
#' close(req)
#'
#' # The following should be run in another process
#' rep <- socket("rep", dial = "tcp://127.0.0.1:6546")
#' ctxp <- context(rep)
#' reply(ctxp, execute = function(x) x + 1)
#' close(rep)
#'
#' }
#'
#' @export
#'
request <- function(
context,
data,
send_mode = c("serial", "raw"),
recv_mode = c("serial", "character", "complex", "double", "integer", "logical", "numeric", "raw", "string"),
timeout = NULL,
cv = NULL,
id = NULL
)
data <- .Call(rnng_request, context, data, send_mode, recv_mode, timeout, cv, id, environment())
nanonext/R/tls.R 0000644 0001762 0000144 00000010632 15142221674 013240 0 ustar ligges users # nanonext - TLS Configuration -------------------------------------------------
#' Create TLS Configuration
#'
#' Create a TLS configuration object to be used for secure connections. Specify
#' `client` to create a client configuration or `server` to create a server
#' configuration.
#'
#' Specify one of `client` or `server` only, or neither (in which case an empty
#' client configuration is created), as a configuration can only be of one type.
#'
#' @section Public Internet HTTPS:
#'
#' When making HTTPS requests over the public internet, you should supply a TLS
#' configuration to validate server certificates.
#'
#' Root CA certificates in PEM format may be found at:
#'
#' - Linux: \file{/etc/ssl/certs/ca-certificates.crt} or
#' \file{/etc/pki/tls/certs/ca-bundle.crt}
#' - macOS: \file{/etc/ssl/cert.pem}
#' - Windows: download from the Common CA Database site run by Mozilla:
#' (select the Server Authentication SSL/TLS
#' certificates text file). *This link is not endorsed; use at your own risk.*
#'
#' @param client **either** the character path to a file containing X.509
#' certificate(s) in PEM format, comprising the certificate authority
#' certificate chain (and revocation list if present), used to validate
#' certificates presented by peers,\cr
#' **or** a length 2 character vector comprising \[i\] the certificate
#' authority certificate chain and \[ii\] the certificate revocation list, or
#' empty string `""` if not applicable.
#' @param server **either** the character path to a file containing the
#' PEM-encoded TLS certificate and associated private key (may contain
#' additional certificates leading to a validation chain, with the leaf
#' certificate first),\cr
#' **or** a length 2 character vector comprising \[i\] the TLS certificate
#' (optionally certificate chain) and \[ii\] the associated private key.
#' @param pass (optional) required only if the secret key supplied to `server`
#' is encrypted with a password. For security, consider providing through a
#' function that returns this value, rather than directly.
#' @param auth logical value whether to require authentication - by default TRUE
#' for client and FALSE for server configurations. If TRUE, the session is
#' only allowed to proceed if the peer has presented a certificate and it has
#' been validated. If FALSE, authentication is optional, whereby a certificate
#' is validated if presented by the peer, but the session allowed to proceed
#' otherwise. If neither `client` nor `server` are supplied, then no
#' authentication is performed and this argument has no effect.
#'
#' @return A 'tlsConfig' object.
#'
#' @examples
#' tls <- tls_config()
#' tls
#' ncurl("https://postman-echo.com/get", timeout = 1000L, tls = tls)
#'
#' # client TLS configuration for public internet HTTPS on Linux
#' # tls <- tls_config(client = "/etc/ssl/certs/ca-certificates.crt")
#'
#' @export
#'
tls_config <- function(client = NULL, server = NULL, pass = NULL, auth = is.null(server))
.Call(rnng_tls_config, client, server, pass, auth)
# nanonext - Key Gen and Certificates ------------------------------------------
#' Generate Self-Signed Certificate and Key
#'
#' Generate self-signed x509 certificate and 4096 bit RSA private/public key
#' pair for use with authenticated, encrypted TLS communications.
#'
#' Note that it can take a second or two for the key and certificate to be
#' generated.
#'
#' @param cn \[default '127.0.0.1'\] character issuer common name (CN) for the
#' certificate. This can be either a hostname or an IP address, but must match
#' the actual server URL as client authentication will depend on it.
#' @param valid \[default '20301231235959'\] character 'not after' date-time in
#' 'yyyymmddhhmmss' format. The certificate is not valid after this time.
#'
#' @return A list of length 2, comprising `$server` and `$client`. These may be
#' passed directly to the relevant argument of [tls_config()].
#'
#' @examplesIf interactive()
#' cert <- write_cert(cn = "127.0.0.1")
#' ser <- tls_config(server = cert$server)
#' cli <- tls_config(client = cert$client)
#'
#' s <- socket(listen = "tls+tcp://127.0.0.1:5555", tls = ser)
#' s1 <- socket(dial = "tls+tcp://127.0.0.1:5555", tls = cli)
#'
#' # secure TLS connection established
#'
#' close(s1)
#' close(s)
#'
#' cert
#'
#' @export
#'
write_cert <- function(cn = "127.0.0.1", valid = "20301231235959")
.Call(rnng_write_cert, cn, valid)
nanonext/R/sync.R 0000644 0001762 0000144 00000015746 15130506167 013425 0 ustar ligges users # nanonext - Synchronisation Primitives ----------------------------------------
#' Condition Variables
#'
#' `cv` creates a new condition variable (protected by a mutex internal to the
#' object).
#'
#' Pass the 'conditionVariable' to the asynchronous receive functions
#' [recv_aio()] or [request()]. Alternatively, to be notified of a pipe event,
#' pass it to [pipe_notify()].
#'
#' Completion of the receive or pipe event, which happens asynchronously and
#' independently of the main R thread, will signal the condition variable by
#' incrementing it by 1.
#'
#' This will cause the R execution thread waiting on the condition variable
#' using `wait()` or `until()` to wake and continue.
#'
#' For argument `msec`, non-integer values will be coerced to integer.
#' Non-numeric input will be ignored and return immediately.
#'
#' @return For **cv**: a 'conditionVariable' object.
#'
#' For **wait**: (invisibly) logical TRUE, or else FALSE if a flag has been
#' set.
#'
#' For **until**: (invisibly) logical TRUE if signalled, or else FALSE if the
#' timeout was reached.
#'
#' For **cv_value**: integer value of the condition variable.
#'
#' For **cv_reset** and **cv_signal**: zero (invisibly).
#'
#' @section Condition:
#'
#' The condition internal to this 'conditionVariable' maintains a state (value).
#' Each signal increments the value by 1. Each time `wait()` or `until()`
#' returns (apart from due to timeout), the value is decremented by 1.
#'
#' The internal condition may be inspected at any time using `cv_value()` and
#' reset using `cv_reset()`. This affords a high degree of flexibility in
#' designing complex concurrent applications.
#'
#' @section Flag:
#'
#' The condition variable also contains a flag that certain signalling functions
#' such as [pipe_notify()] can set. When this flag has been set, all subsequent
#' `wait()` calls will return logical FALSE instead of TRUE.
#'
#' Note that the flag is not automatically reset, but may be reset manually
#' using `cv_reset()`.
#'
#' @examples
#' cv <- cv()
#'
#' @export
#'
cv <- function() .Call(rnng_cv_alloc)
#' Condition Variables - Wait
#'
#' `wait` waits on a condition being signalled by completion of an
#' asynchronous receive or pipe event. \cr `wait_` is a variant that allows
#' user interrupts, suitable for interactive use.
#'
#' @param cv a 'conditionVariable' object.
#'
#' @examples
#' \dontrun{
#' wait(cv) # would block until the cv is signalled
#' wait_(cv) # would block until the cv is signalled or interrupted
#' }
#'
#' @rdname cv
#' @export
#'
wait <- function(cv) invisible(.Call(rnng_cv_wait, cv))
#' @rdname cv
#' @export
#'
wait_ <- function(cv) invisible(.Call(rnng_cv_wait_safe, cv))
#' Condition Variables - Until
#'
#' `until` waits until a future time on a condition being signalled by
#' completion of an asynchronous receive or pipe event. \cr `until_` is a
#' variant that allows user interrupts, suitable for interactive use.
#'
#' @param msec maximum time in milliseconds to wait for the condition variable
#' to be signalled.
#'
#' @examples
#' until(cv, 10L)
#' until_(cv, 10L)
#'
#' @rdname cv
#' @export
#'
until <- function(cv, msec) invisible(.Call(rnng_cv_until, cv, msec))
#' @rdname cv
#' @export
#'
until_ <- function(cv, msec) invisible(.Call(rnng_cv_until_safe, cv, msec))
#' Condition Variables - Value
#'
#' `cv_value` inspects the internal value of a condition variable.
#'
#' @examples
#' cv_value(cv)
#'
#' @rdname cv
#' @export
#'
cv_value <- function(cv) .Call(rnng_cv_value, cv)
#' Condition Variables - Reset
#'
#' `cv_reset` resets the internal value and flag of a condition variable.
#'
#' @examples
#' cv_reset(cv)
#'
#' @rdname cv
#' @export
#'
cv_reset <- function(cv) invisible(.Call(rnng_cv_reset, cv))
#' Condition Variables - Signal
#'
#' `cv_signal` signals a condition variable.
#'
#' @examples
#' cv_value(cv)
#' cv_signal(cv)
#' cv_value(cv)
#'
#' @rdname cv
#' @export
#'
cv_signal <- function(cv) invisible(.Call(rnng_cv_signal, cv))
#' Pipe Notify
#'
#' Signals a 'conditionVariable' whenever pipes (individual connections) are
#' added or removed at a socket.
#'
#' For add: this event occurs after the pipe is fully added to the socket. Prior
#' to this time, it is not possible to communicate over the pipe with the
#' socket.
#'
#' For remove: this event occurs after the pipe has been removed from the
#' socket. The underlying transport may be closed at this point, and it is not
#' possible to communicate using this pipe.
#'
#' @param socket a Socket.
#' @param cv a 'conditionVariable' to signal, or NULL to cancel a previously set
#' signal.
#' @param add \[default FALSE\] logical value whether to signal (or cancel
#' signal) when a pipe is added.
#' @param remove \[default FALSE\] logical value whether to signal (or cancel
#' signal) when a pipe is removed.
#' @param flag \[default FALSE\] logical value whether to also set a flag in the
#' 'conditionVariable'. This can help distinguish between different types of
#' signal, and causes any subsequent [wait()] to return FALSE instead of TRUE.
#' If a signal from the \pkg{tools} package, e.g. `tools::SIGINT`, or an
#' equivalent integer value is supplied, this sets a flag and additionally
#' raises this signal upon the flag being set. For `tools::SIGTERM`, the
#' signal is raised with a 200ms grace period to allow a process to exit
#' normally.
#'
#' @return Invisibly, zero on success (will otherwise error).
#'
#' @examples
#' s <- socket(listen = "inproc://nanopipe")
#' cv <- cv()
#'
#' pipe_notify(s, cv, add = TRUE, remove = TRUE, flag = TRUE)
#' cv_value(cv)
#'
#' s1 <- socket(dial = "inproc://nanopipe")
#' cv_value(cv)
#' reap(s1)
#' cv_value(cv)
#'
#' pipe_notify(s, NULL, add = TRUE, remove = TRUE)
#' s1 <- socket(dial = "inproc://nanopipe")
#' cv_value(cv)
#' reap(s1)
#'
#' (wait(cv))
#'
#' close(s)
#'
#' @export
#'
pipe_notify <- function(socket, cv, add = FALSE, remove = FALSE, flag = FALSE)
invisible(.Call(rnng_pipe_notify, socket, cv, add, remove, flag))
#' Signal Forwarder
#'
#' Forwards signals from one 'conditionVariable' to another.
#'
#' The condition value of `cv` is initially reset to zero when this operator
#' returns. Only one forwarder can be active on a `cv` at any given time, and
#' assigning a new forwarding target cancels any currently existing forwarding.
#'
#' Changes in the condition value of `cv` are forwarded to `cv2`, but only on
#' each occassion `cv` is signalled. This means that waiting on `cv` will cause
#' a temporary divergence between the actual condition value of `cv` and that
#' recorded at `cv2`, until the next time `cv` is signalled.
#'
#' @param cv a 'conditionVariable' object, from which to forward the signal.
#' @param cv2 a 'conditionVariable' object, to which the signal is forwarded.
#'
#' @return Invisibly, `cv2`.
#'
#' @examples
#' cva <- cv(); cvb <- cv(); cv1 <- cv(); cv2 <- cv()
#'
#' cva %~>% cv1 %~>% cv2
#' cvb %~>% cv2
#'
#' cv_signal(cva)
#' cv_signal(cvb)
#' cv_value(cv1)
#' cv_value(cv2)
#'
#' @export
#'
`%~>%` <- function(cv, cv2) invisible(.Call(rnng_signal_thread_create, cv, cv2))
nanonext/R/server.R 0000644 0001762 0000144 00000046556 15163546346 013773 0 ustar ligges users # nanonext - HTTP/WebSocket Server Interface ------------------------------------
#' Create HTTP/WebSocket Server
#'
#' Creates a server that can handle HTTP requests and WebSocket connections.
#'
#' @param url URL to listen on (e.g., "http://127.0.0.1:8080").
#' @param handlers A handler or list of handlers created with [handler()], [handler_ws()], etc.
#' @param tls TLS configuration for HTTPS/WSS, created via [tls_config()].
#'
#' @return A nanoServer object with methods:
#'
#' - `$start()` - Start accepting connections
#' - `$close()` - Stop and release all resources
#' - `$serve()` - Start and block, processing requests via the \pkg{later}
#' event loop until interrupted (e.g., Ctrl+C). The server is automatically
#' closed on exit
#' - `$url` - The server URL
#'
#' @details
#' This function leverages NNG's shared HTTP server architecture. When both
#' HTTP handlers and WebSocket handlers are provided, they share the same
#' underlying server and port. WebSocket handlers automatically handle
#' the HTTP upgrade handshake and all WebSocket framing (RFC 6455).
#'
#' WebSocket and streaming callbacks are executed on R's main thread via the
#' \pkg{later} package. To process callbacks, you must run the event loop
#' (e.g., using `later::run_now()` in a loop), or use `$serve()` which
#' handles this automatically.
#'
#' Requires the \pkg{later} package.
#'
#' @examplesIf interactive() && requireNamespace("later", quietly = TRUE)
#' # Simple HTTP server
#' server <- http_server(
#' url = "http://127.0.0.1:8080",
#' handlers = list(
#' handler("/", function(req) {
#' list(status = 200L, body = "Hello, World!")
#' }),
#' handler("/api/data", function(req) {
#' list(
#' status = 200L,
#' headers = c("Content-Type" = "application/json"),
#' body = '{"value": 42}'
#' )
#' })
#' )
#' )
#' server$start()
#' # Run event loop: repeat later::run_now(Inf)
#' server$close()
#'
#' # HTTP + WebSocket server
#' server <- http_server(
#' url = "http://127.0.0.1:8080",
#' handlers = list(
#' handler("/", function(req) {
#' list(status = 200L, body = "...")
#' }),
#' handler_ws("/ws", function(ws, data) {
#' ws$send(data) # Echo
#' }, textframes = TRUE)
#' )
#' )
#'
#' # Multiple WebSocket endpoints
#' server <- http_server(
#' url = "http://127.0.0.1:8080",
#' handlers = list(
#' handler_ws("/echo", function(ws, data) ws$send(data)),
#' handler_ws("/upper", function(ws, data) ws$send(toupper(data)), textframes = TRUE)
#' )
#' )
#'
#' # HTTPS server with self-signed certificate
#' cert <- write_cert(cn = "127.0.0.1")
#' cfg <- tls_config(server = cert$server)
#' server <- http_server(
#' url = "https://127.0.0.1:8443",
#' handlers = list(
#' handler("/", function(req) list(status = 200L, body = "Secure!"))
#' ),
#' tls = cfg
#' )
#' server$start()
#'
#' # Send async request and run event loop
#' aio <- ncurl_aio(
#' "https://127.0.0.1:8443/",
#' tls = tls_config(client = cert$client),
#' timeout = 2000
#' )
#' while (unresolved(aio)) later::run_now(0.1)
#'
#' aio$status
#' aio$data
#'
#' server$close()
#'
#' @export
#'
http_server <- function(url, handlers = list(), tls = NULL) {
if (is.integer(handlers$type))
handlers <- list(handlers)
srv <- .Call(rnng_http_server_create, url, handlers, tls)
attr(srv, "start") <- function() {
invisible(.Call(rnng_http_server_start, srv))
}
attr(srv, "close") <- function() {
invisible(.Call(rnng_http_server_close, srv))
}
attr(srv, "serve") <- function() {
on.exit(.Call(rnng_http_server_close, srv))
.Call(rnng_http_server_start, srv)
cat(sprintf("Serving at %s - press Ctrl+C to stop\n", attr(srv, "url")))
repeat later::run_now(Inf)
}
srv
}
#' Create HTTP Handler
#'
#' Creates an HTTP route handler for use with [http_server()].
#'
#' @param path URI path to match (e.g., "/api/data", "/users").
#' @param callback Function to handle requests. Receives a list with:
#'
#' - `method` - HTTP method (character)
#' - `uri` - Request URI (character)
#' - `headers` - Named character vector of headers
#' - `body` - Request body (raw vector)
#'
#' Should return a list with:
#'
#' - `status` - HTTP status code (integer, default 200)
#' - `headers` - Response headers as a named character vector, e.g.
#' `c("Content-Type" = "application/json")` (optional)
#' - `body` - Response body (character or raw)
#' @param method \[default "GET"\] HTTP method to match (e.g., "GET", "POST",
#' "PUT", "DELETE"). Use `"*"` to match any method.
#' @param prefix \[default FALSE\] Logical, if TRUE matches path as a prefix
#' (e.g., "/api" will match "/api/users", "/api/items", etc.).
#'
#' @return A handler object for use with [http_server()].
#'
#' @details If the callback throws an error, a 500 Internal Server Error
#' response is returned to the client.
#'
#' @seealso [handler_ws()] for WebSocket handlers. Static handlers:
#' [handler_file()], [handler_directory()], [handler_inline()],
#' [handler_redirect()].
#'
#' @examples
#' # Simple GET handler
#' h1 <- handler("/hello", function(req) {
#' list(status = 200L, body = "Hello!")
#' })
#'
#' # POST handler that echoes the request body
#' h2 <- handler("/echo", function(req) {
#' list(status = 200L, body = req$body)
#' }, method = "POST")
#'
#' # Catch-all handler for a path prefix
#' h3 <- handler("/static", function(req) {
#' # Serve static files under /static/*
#' }, method = "*", prefix = TRUE)
#'
#' @export
#'
handler <- function(path, callback, method = "GET", prefix = FALSE) {
list(type = 1L, path = path, callback = callback, method = method, prefix = prefix)
}
#' Create WebSocket Handler
#'
#' Creates a WebSocket handler for use with [http_server()].
#'
#' @param path URI path for WebSocket connections (e.g., "/ws").
#' @param on_message Function called when a message is received.
#' Signature: `function(ws, data)` where `ws` is the connection
#' object and `data` is the message. Use `ws$send()` to send
#' responses; the return value is ignored.
#' @param on_open \[default NULL\] Function called when a connection opens.
#' Signature: `function(ws, req)` where `req` is a list with `uri`
#' (character) and `headers` (named character vector) from the HTTP upgrade
#' request.
#' @param on_close \[default NULL\] Function called when a connection closes.
#' Signature: `function(ws)`
#' @param textframes \[default FALSE\] Logical, use text frames instead of binary.
#' When TRUE: incoming `data` is character, outgoing data should be character.
#' When FALSE: incoming `data` is raw vector, outgoing data should be raw vector.
#'
#' @return A handler object for use with [http_server()].
#'
#' @section Connection Object:
#' The `ws` object passed to callbacks has the following fields and methods:
#'
#' - `ws$send(data)`: Send a message to the client. `data` can be
#' a raw vector or character string. Returns 0 on success, or an error code
#' on failure (e.g., if the connection is closed).
#' - `ws$close()`: Close the connection.
#' - `ws$id`: Unique integer identifier for this connection. No two
#' connections on the same server will share an ID, even across different
#' handlers, making IDs safe to use as keys in a shared data structure.
#'
#' @examples
#' # Simple echo server
#' h <- handler_ws("/ws", function(ws, data) ws$send(data))
#'
#' # With connection tracking
#' clients <- list()
#' h <- handler_ws(
#' "/chat",
#' on_message = function(ws, data) {
#' # Broadcast to all
#' for (client in clients) client$send(data)
#' },
#' on_open = function(ws, req) {
#' clients[[as.character(ws$id)]] <<- ws
#' },
#' on_close = function(ws) {
#' clients[[as.character(ws$id)]] <<- NULL
#' },
#' textframes = TRUE
#' )
#'
#' @export
#'
handler_ws <- function(path, on_message, on_open = NULL, on_close = NULL,
textframes = FALSE) {
list(type = 2L, path = path, on_message = on_message, on_open = on_open,
on_close = on_close, textframes = textframes)
}
#' Create Static File Handler
#'
#' Creates an HTTP handler that serves a single file. NNG handles MIME type
#' detection automatically.
#'
#' @param path URI path to match (e.g., "/favicon.ico").
#' @param file Path to the file to serve.
#' @param prefix \[default FALSE\] Logical, if TRUE matches path as a prefix.
#'
#' @return A handler object for use with [http_server()].
#'
#' @examplesIf interactive()
#' h <- handler_file("/favicon.ico", "~/favicon.ico")
#'
#' @export
#'
handler_file <- function(path, file, prefix = FALSE) {
if (!file.exists(file))
warning("file does not exist: ", file)
list(type = 3L, path = path, file = normalizePath(file, mustWork = FALSE),
prefix = prefix)
}
#' Create Static Directory Handler
#'
#' Creates an HTTP handler that serves files from a directory tree. NNG handles
#' MIME type detection automatically.
#'
#' @param path URI path prefix (e.g., "/static"). Requests to "/static/foo.js"
#' will serve "directory/foo.js".
#' @param directory Path to the directory to serve.
#'
#' @return A handler object for use with [http_server()].
#'
#' @details
#' Directory handlers automatically match all paths under the prefix (prefix
#' matching is implicit). The URI path is mapped to the filesystem:
#'
#' - Request to "/static/css/style.css" serves "directory/css/style.css"
#' - Request to "/static/" serves "directory/index.html" if it exists
#'
#' Note: The trailing slash behavior depends on how clients make requests.
#' A request to "/static" (no trailing slash) will not automatically redirect
#' to "/static/". Consider using [handler_redirect()] if you need this behavior.
#'
#' @examplesIf interactive()
#' h <- handler_directory("/static", "www/assets")
#'
#' @export
#'
handler_directory <- function(path, directory) {
if (!dir.exists(directory))
warning("directory does not exist: ", directory)
list(type = 4L, path = path,
directory = normalizePath(directory, mustWork = FALSE))
}
#' Create Inline Static Content Handler
#'
#' Creates an HTTP handler that serves in-memory static content. Useful for
#' small files like robots.txt or inline JSON/HTML.
#'
#' @param path URI path to match (e.g., "/robots.txt").
#' @param data Content to serve. Character data is converted to raw bytes.
#' @param content_type MIME type (e.g., "text/plain", "application/json").
#' Defaults to "application/octet-stream" if NULL.
#' @param prefix \[default FALSE\] Logical, if TRUE matches path as a prefix.
#'
#' @return A handler object for use with [http_server()].
#'
#' @examples
#' h1 <- handler_inline("/robots.txt", "User-agent: *\nDisallow:",
#' content_type = "text/plain")
#' h2 <- handler_inline("/health", '{"status":"ok"}',
#' content_type = "application/json")
#'
#' @export
#'
handler_inline <- function(path, data, content_type = NULL, prefix = FALSE) {
list(type = 5L, path = path, data = data, content_type = content_type,
prefix = prefix)
}
#' Create HTTP Redirect Handler
#'
#' Creates an HTTP handler that returns a redirect response.
#'
#' @param path URI path to match (e.g., "/old-page").
#' @param location URL to redirect to. Can be relative (e.g., "/new-page") or
#' absolute (e.g., "https://example.com/page").
#' @param status HTTP redirect status code. Must be one of:
#'
#' - 301 - Moved Permanently
#' - 302 - Found (default)
#' - 303 - See Other
#' - 307 - Temporary Redirect
#' - 308 - Permanent Redirect
#' @param prefix \[default FALSE\] Logical, if TRUE matches path as a prefix.
#'
#' @return A handler object for use with [http_server()].
#'
#' @examples
#' # Permanent redirect
#' h1 <- handler_redirect("/old", "/new", status = 301L)
#'
#' # Redirect bare path to trailing slash
#' h2 <- handler_redirect("/app", "/app/")
#'
#' @export
#'
handler_redirect <- function(path, location, status = 302L, prefix = FALSE) {
status <- as.integer(status)
if (!status %in% c(301L, 302L, 303L, 307L, 308L))
stop("redirect status must be 301, 302, 303, 307, or 308")
list(type = 6L, path = path, location = location, status = status, prefix = prefix)
}
#' @rdname close
#' @method close nanoServer
#' @export
#'
close.nanoServer <- function(con, ...) invisible(.Call(rnng_http_server_close, con))
#' @export
#'
print.nanoServer <- function(x, ...) {
cat("< nanoServer >\n")
cat(" - url:", attr(x, "url"), "\n")
cat(" - state:", attr(x, "state"), "\n")
invisible(x)
}
#' @export
#'
print.nanoWsConn <- function(x, ...) {
cat("< nanoWsConn >\n")
cat(" - id:", attr(x, "id"), "\n")
invisible(x)
}
#' @export
#'
`$.nanoWsConn` <- function(x, name) {
switch(name,
send = function(data) .Call(rnng_ws_send, x, data),
close = function() .Call(rnng_ws_close, x),
id = attr(x, "id"),
attr(x, name, exact = TRUE))
}
#' Create HTTP Streaming Handler
#'
#' Creates an HTTP streaming handler using chunked transfer encoding. Supports
#' any streaming HTTP protocol including Server-Sent Events (SSE), newline-
#' delimited JSON (NDJSON), and custom streaming formats.
#'
#' @param path URI path to match (e.g., "/stream").
#' @param on_request Function called when a request arrives. Signature:
#' `function(conn, req)` where `conn` is the connection object and `req`
#' is a list with `method`, `uri`, `headers`, `body`.
#' @param on_close \[default NULL\] Function called when the connection closes.
#' Signature: `function(conn)`
#' @param method \[default "*"\] HTTP method to match (e.g., "GET", "POST").
#' Use `"*"` to match any method.
#' @param prefix \[default FALSE\] Logical, if TRUE matches path as a prefix.
#'
#' @return A handler object for use with [http_server()].
#'
#' @section Connection Object:
#' The `conn` object passed to callbacks has these methods:
#'
#' - `conn$send(data)`: Send data chunk to client.
#' - `conn$close()`: Close the connection (sends terminal chunk).
#' - `conn$set_status(code)`: Set HTTP status code (before first send).
#' - `conn$set_header(name, value)`: Set response header (before first send).
#' - `conn$id`: Unique connection identifier.
#'
#' @details
#' HTTP streaming uses chunked transfer encoding (RFC 9112). The first
#' `$send()` triggers writing of HTTP headers with `Transfer-Encoding: chunked`.
#' Headers cannot be modified after the first send.
#'
#' Set an appropriate `Content-Type` header for your streaming format:
#' - NDJSON: `application/x-ndjson`
#' - JSON stream: `application/stream+json`
#' - SSE: `text/event-stream` (see [format_sse()])
#' - Plain text: `text/plain`
#'
#' **SSE Reconnection:** When an SSE client reconnects after a disconnect, it
#' sends a `Last-Event-ID` header containing the last event ID it received.
#' Access this via `req$headers["Last-Event-ID"]` in `on_request` to resume
#' the event stream from the correct position.
#'
#' To broadcast to multiple clients, store connection objects in a list and
#' iterate over them (e.g., `lapply(conns, function(c) c$send(data))`).
#'
#' @seealso [format_sse()] for formatting Server-Sent Events.
#'
#' @examples
#' # NDJSON streaming endpoint
#' h <- handler_stream("/stream", function(conn, req) {
#' conn$set_header("Content-Type", "application/x-ndjson")
#' conn$send('{"status":"connected"}\n')
#' })
#'
#' # SSE endpoint with reconnection support
#' h <- handler_stream("/events", function(conn, req) {
#' conn$set_header("Content-Type", "text/event-stream")
#' conn$set_header("Cache-Control", "no-cache")
#' last_id <- req$headers["Last-Event-ID"]
#' # Resume from last_id if client is reconnecting
#' conn$send(format_sse(data = "connected", id = "1"))
#' })
#'
#' # Long-lived streaming with broadcast triggered by POST
#' conns <- list()
#' handlers <- list(
#' handler_stream("/stream",
#' on_request = function(conn, req) {
#' conn$set_header("Content-Type", "application/x-ndjson")
#' conns[[as.character(conn$id)]] <<- conn
#' conn$send('{"status":"connected"}\n')
#' },
#' on_close = function(conn) {
#' conns[[as.character(conn$id)]] <<- NULL
#' }
#' ),
#' # POST endpoint triggers broadcast to all streaming clients
#' handler("/broadcast", function(req) {
#' msg <- paste0('{"msg":"', rawToChar(req$body), '"}\n')
#' lapply(conns, function(c) c$send(msg))
#' list(status = 200L, body = "sent")
#' }, method = "POST")
#' )
#'
#' @export
#'
handler_stream <- function(path, on_request, on_close = NULL,
method = "*", prefix = FALSE) {
list(type = 7L, path = path, on_request = on_request, on_close = on_close,
method = method, prefix = prefix)
}
#' Format Server-Sent Event
#'
#' Helper function to format messages according to the Server-Sent Events (SSE)
#' specification. Use with [handler_stream()] to create SSE endpoints.
#'
#' @param data The data payload. Will be prefixed with "data: " on each line.
#' @param event \[default NULL\] Optional event type (e.g., "message", "error").
#' @param id \[default NULL\] Optional event ID for client reconnection.
#' @param retry \[default NULL\] Optional retry interval in milliseconds.
#'
#' @return A character string formatted as an SSE message, ready to pass to
#' `conn$send()`.
#'
#' @details
#' Server-Sent Events is a W3C standard for server-to-client streaming over
#' HTTP, supported natively by browsers via the `EventSource` API. SSE is
#' commonly used for real-time updates, notifications, and LLM token streaming.
#'
#' SSE messages have this format:
#'
#' ```
#' event:
#' id:
#' retry:
#' data:
#' data:
#'
#' ```
#'
#' Each message ends with two newlines. Multi-line data is split and each
#' line prefixed with "data: ".
#'
#' When using SSE with [handler_stream()], set the appropriate headers:
#' - `Content-Type: text/event-stream`
#' - `Cache-Control: no-cache`
#' - `X-Accel-Buffering: no` (prevents proxy buffering)
#'
#' @seealso [handler_stream()] for creating streaming HTTP endpoints.
#'
#' @examples
#' format_sse(data = "Hello")
#' #> "data: Hello\n\n"
#'
#' format_sse(data = "Hello", event = "greeting")
#' #> "event: greeting\ndata: Hello\n\n"
#'
#' format_sse(data = "Line 1\nLine 2")
#' #> "data: Line 1\ndata: Line 2\n\n"
#'
#' # Typical SSE endpoint setup
#' h <- handler_stream("/events", function(conn, req) {
#' conn$set_header("Content-Type", "text/event-stream")
#' conn$set_header("Cache-Control", "no-cache")
#' conn$set_header("X-Accel-Buffering", "no")
#' conn$send(format_sse(data = "connected", id = "1"))
#' })
#'
#' @export
#'
format_sse <- function(data, event = NULL, id = NULL, retry = NULL) {
parts <- character()
if (!is.null(event)) parts <- c(parts, paste0("event: ", event))
if (!is.null(id)) parts <- c(parts, paste0("id: ", id))
if (!is.null(retry)) parts <- c(parts, paste0("retry: ", as.integer(retry)))
lines <- strsplit(as.character(data), "\n", fixed = TRUE)[[1L]]
parts <- c(parts, paste0("data: ", lines))
paste0(paste(parts, collapse = "\n"), "\n\n")
}
#' @export
#'
print.nanoStreamConn <- function(x, ...) {
cat("< nanoStreamConn >\n")
cat(" - id:", attr(x, "id"), "\n")
invisible(x)
}
#' @export
#'
`$.nanoStreamConn` <- function(x, name) {
switch(name,
send = function(data) .Call(rnng_stream_conn_send, x, data),
close = function() .Call(rnng_conn_close, x),
set_status = function(code) .Call(rnng_stream_conn_set_status, x, as.integer(code)),
set_header = function(name, value) .Call(rnng_stream_conn_set_header, x, name, value),
id = attr(x, "id"),
attr(x, name, exact = TRUE))
}
nanonext/R/opts.R 0000644 0001762 0000144 00000037361 15077362607 013444 0 ustar ligges users # nanonext - Options Configuration and Helper Functions ------------------------
#' Get and Set Options for a Socket, Context, Stream, Listener or Dialer
#'
#' Get and set the value of options for a Socket, Context, Stream, Listener or
#' Dialer.
#'
#' Note: once a dialer or listener has started, it is not generally possible to
#' change its configuration. Hence create the dialer or listener with
#' `autostart = FALSE` if configuration needs to be set.
#'
#' To get or set options on a Listener or Dialer attached to a Socket or nano
#' object, pass in the objects directly via for example `$listener[[1]]` for the
#' first Listener.
#'
#' Some options are only meaningful or supported in certain contexts; for
#' example there is no single meaningful address for a socket, since sockets can
#' have multiple dialers and endpoints associated with them.
#'
#' For an authoritative guide please refer to the online documentation for the
#' NNG library at .
#'
#' @param object a Socket, Context, Stream, Listener or Dialer.
#' @param name name of option, e.g. 'recv-buffer', as a character string. See
#' below options details.
#' @param value value of option. Supply character type for 'string' options,
#' integer or double for 'int', 'duration', 'size' and 'uint64', and logical
#' for 'bool'.
#'
#' @return The value of the option (logical for type 'bool', integer for 'int',
#' 'duration' and 'size', character for 'string', and double for 'uint64').
#'
#' @section Serialization:
#'
#' Apart from the NNG options documented below, there is the following special
#' option:
#'
#' \itemize{
#' \item 'serial' (type list)
#'
#' For Sockets only. This accepts a configuration created by
#' [serial_config()]. Setting a new configuration replaces any already set. To
#' remove entirely, supply an empty list. Note: this option is write-only and
#' can be set but not retrieved.
#' }
#'
#' @section Global Options:
#'
#' \itemize{
#' \item 'reconnect-time-min' (type 'ms')
#'
#' This is the minimum amount of time (milliseconds) to wait before attempting
#' to establish a connection after a previous attempt has failed. This can be
#' set on a socket, but it can also be overridden on an individual dialer. The
#' option is irrelevant for listeners.
#'
#' \item 'reconnect-time-max' (type 'ms')
#'
#' This is the maximum amount of time (milliseconds) to wait before attempting
#' to establish a connection after a previous attempt has failed. If this is
#' non-zero, then the time between successive connection attempts will start
#' at the value of 'reconnect-time-min', and grow exponentially, until it
#' reaches this value. If this value is zero, then no exponential back-off
#' between connection attempts is done, and each attempt will wait the time
#' specified by 'reconnect-time-min'. This can be set on a socket, but it can
#' also be overridden on an individual dialer. The option is irrelevant for
#' listeners.
#'
#' \item 'recv-size-max' (type 'size')
#'
#' This is the maximum message size that the will be accepted from a remote
#' peer. If a peer attempts to send a message larger than this, then the
#' message will be discarded. If the value of this is zero, then no limit on
#' message sizes is enforced. This option exists to prevent certain kinds of
#' denial-of-service attacks, where a malicious agent can claim to want to
#' send an extraordinarily large message, without sending any data. This
#' option can be set for the socket, but may be overridden for on a per-dialer
#' or per-listener basis. NOTE: Applications on hostile networks should set
#' this to a non-zero value to prevent denial-of-service attacks. NOTE: Some
#' transports may have further message size restrictions.
#'
#' \item 'recv-buffer' (type 'int')
#'
#' This is the depth of the socket’s receive buffer as a number of messages.
#' Messages received by a transport may be buffered until the application has
#' accepted them for delivery. This value must be an integer between 0 and
#' 8192, inclusive. NOTE: Not all protocols support buffering received
#' messages. For example req can only deal with a single reply at a time.
#'
#' \item 'recv-timeout' (type 'ms')
#'
#' This is the socket receive timeout in milliseconds. When no message is
#' available for receiving at the socket for this period of time, receive
#' operations will fail with a return value of 5L ('timed out').
#'
#' \item 'send-buffer' (type 'int')
#'
#' This is the depth of the socket send buffer as a number of messages.
#' Messages sent by an application may be buffered by the socket until a
#' transport is ready to accept them for delivery. This value must be an
#' integer between 0 and 8192, inclusive. NOTE: Not all protocols support
#' buffering sent messages; generally multicast protocols like pub will simply
#' discard messages when they cannot be delivered immediately.
#'
#' \item 'send-timeout' (type 'ms')
#'
#' This is the socket send timeout in milliseconds. When a message cannot be
#' queued for delivery by the socket for this period of time (such as if send
#' buffers are full), the operation will fail with a return value of 5L
#' ('timed out').
#'
#' \item 'recv-fd' (type 'int')
#'
#' This is the socket receive file descriptor. For supported protocols, this
#' will become readable when a message is available for receiving on the
#' socket. Attempts should not be made to read or write to the returned file
#' descriptor, but it is suitable for use with poll(), select(), or WSAPoll()
#' on Windows, and similar functions.
#'
#' \item 'send-fd' (type 'int')
#'
#' This is the socket send file descriptor. Attempts should not be made to
#' read or write to the returned file descriptor, but it is suitable for use
#' with poll(), select(), or WSAPoll() on Windows, and similar functions.
#'
#' \item 'socket-name' (type 'string')
#'
#' This is the socket name. By default this is a string corresponding to the
#' value of the socket. The string must fit within 64-bytes, including the
#' terminating NUL byte. The value is intended for application use, and is not
#' used for anything in the library itself.
#'
#' \item 'url' (type 'string')
#'
#' This read-only option is used on a listener or dialer to obtain the URL
#' with which it was configured.
#'
#' }
#'
#' @section Protocol-specific Options:
#'
#' \itemize{
#' \item 'req:resend-time' (type 'ms')
#'
#' (Request protocol) When a new request is started, a timer of this duration
#' is also started. If no reply is received before this timer expires, then
#' the request will be resent. (Requests are also automatically resent if the
#' peer to whom the original request was sent disconnects, or if a peer
#' becomes available while the requester is waiting for an available peer.)
#'
#' \item 'sub:subscribe' (type 'string')
#'
#' (Subscribe protocol) This option registers a topic that the subscriber is
#' interested in. Each incoming message is checked against the list of
#' subscribed topics. If the body begins with the entire set of bytes in the
#' topic, then the message is accepted. If no topic matches, then the message
#' is discarded. To receive all messages, set the topic to NULL.
#'
#' \item 'sub:unsubscribe' (type 'string')
#'
#' (Subscribe protocol) This option removes a topic from the subscription
#' list. Note that if the topic was not previously subscribed to with
#' 'sub:subscribe' then an 'entry not found' error will result.
#'
#' \item 'sub:prefnew' (type 'bool')
#'
#' (Subscribe protocol) This option specifies the behavior of the subscriber
#' when the queue is full. When TRUE (the default), the subscriber will make
#' room in the queue by removing the oldest message. When FALSE, the
#' subscriber will reject messages if the message queue does not have room.
#'
#' \item 'surveyor:survey-time' (type 'ms')
#'
#' (Surveyor protocol) Duration of surveys. When a new survey is started, a
#' timer of this duration is also started. Any responses arriving after this
#' time will be discarded. Attempts to receive after the timer expires with no
#' other surveys started will result in an 'incorrect state' error. Attempts
#' to receive when this timer expires will result in a 'timed out' error.
#'
#' }
#'
#' @section Transport-specific Options:
#'
#' \itemize{
#' \item 'ipc:permissions' (type 'int')
#'
#' (IPC transport) This option may be applied to a listener to configure the
#' permissions that are used on the UNIX domain socket created by that
#' listener. This property is only supported on POSIX systems. The value is of
#' type int, representing the normal permission bits on a file, such as 0600
#' (typically meaning read-write to the owner, and no permissions for anyone
#' else.) The default is system-specific, most often 0644.
#'
#' \item 'tcp-nodelay' (type 'bool')
#'
#' (TCP transport) This option is used to disable (or enable) the use of
#' Nagle's algorithm for TCP connections. When TRUE (the default), messages
#' are sent immediately by the underlying TCP stream without waiting to gather
#' more data. When FALSE, Nagle’s algorithm is enabled, and the TCP stream may
#' wait briefly in an attempt to coalesce messages. Nagle’s algorithm is
#' useful on low-bandwidth connections to reduce overhead, but it comes at a
#' cost to latency. When used on a dialer or a listener, the value affects how
#' newly created connections will be configured.
#'
#' \item 'tcp-keepalive' (type 'bool')
#'
#' (TCP transport) This option is used to enable the sending of keep-alive
#' messages on the underlying TCP stream. This option is FALSE by default.
#' When enabled, if no messages are seen for a period of time, then a zero
#' length TCP message is sent with the ACK flag set in an attempt to tickle
#' some traffic from the peer. If none is still seen (after some
#' platform-specific number of retries and timeouts), then the remote peer is
#' presumed dead, and the connection is closed. When used on a dialer or a
#' listener, the value affects how newly created connections will be
#' configured. This option has two purposes. First, it can be used to detect
#' dead peers on an otherwise quiescent network. Second, it can be used to
#' keep connection table entries in NAT and other middleware from expiring due
#' to lack of activity.
#'
#' \item 'tcp-bound-port' (type 'int')
#'
#' (TCP transport) Local TCP port number. This is used on a listener, and is
#' intended to be used after starting the listener in combination with a
#' wildcard (0) local port. This determines the actual ephemeral port that was
#' selected and bound. The value is provided as an integer, but only the low
#' order 16 bits will be set, and is in native byte order for convenience.
#'
#' \item 'ws:request-headers' (type 'string')
#'
#' (WebSocket transport) Concatenation of multiple lines terminated by CRLF
#' sequences, that can be used to add further headers to the HTTP request sent
#' when connecting. This option can be set on dialers, and must be done before
#' the transport is started.
#'
#' \item 'ws:response-headers' (type 'string')
#'
#' (WebSocket transport) Concatenation of multiple lines terminated by CRLF
#' sequences, that can be used to add further headers to the HTTP response
#' sent when connecting. This option can be set on listeners, and must be done
#' before the transport is started.
#'
#' \item 'ws:request-uri' (type 'string')
#'
#' (WebSocket transport) For obtaining the URI sent by the client. This can be
#' useful when a handler supports an entire directory tree.
#'
#' }
#'
#' @examples
#' s <- socket("pair")
#' opt(s, "send-buffer")
#' close(s)
#'
#' s <- socket("req")
#' ctx <- context(s)
#' opt(ctx, "send-timeout")
#' close(ctx)
#' close(s)
#'
#' s <- socket("pair", dial = "inproc://nanonext", autostart = FALSE)
#' opt(s$dialer[[1]], "reconnect-time-min")
#' close(s)
#'
#' s <- socket("pair", listen = "inproc://nanonext", autostart = FALSE)
#' opt(s$listener[[1]], "recv-size-max")
#' close(s)
#'
#' @export
#'
opt <- function(object, name) .Call(rnng_get_opt, object, name)
#' @examples
#' s <- socket("pair")
#' opt(s, "recv-timeout") <- 2000
#' close(s)
#'
#' s <- socket("req")
#' ctx <- context(s)
#' opt(ctx, "send-timeout") <- 2000
#' close(ctx)
#' close(s)
#'
#' s <- socket("pair", dial = "inproc://nanonext", autostart = FALSE)
#' opt(s$dialer[[1]], "reconnect-time-min") <- 2000
#' start(s$dialer[[1]])
#' close(s)
#'
#' s <- socket("pair", listen = "inproc://nanonext", autostart = FALSE)
#' opt(s$listener[[1]], "recv-size-max") <- 1024
#' start(s$listener[[1]])
#' close(s)
#'
#' @rdname opt
#' @export
#'
`opt<-` <- function(object, name, value) .Call(rnng_set_opt, object, name, value)
#' Subscribe / Unsubscribe Topic
#'
#' For a socket or context using the sub protocol in a publisher/subscriber
#' pattern. Set a topic to subscribe to, or remove a topic from the subscription
#' list.
#'
#' To use pub/sub the publisher must:
#' \itemize{
#' \item specify `mode = 'raw'` when sending.
#' \item ensure the sent vector starts with the topic.
#' }
#' The subscriber should then receive specifying the correct mode.
#'
#' @param con a Socket or Context using the 'sub' protocol.
#' @param topic \[default NULL\] an atomic type or `NULL`. The default `NULL`
#' subscribes to all topics / unsubscribes from all topics (if all topics were
#' previously subscribed).
#'
#' @return Invisibly, the passed Socket or Context.
#'
#' @examples
#' pub <- socket("pub", listen = "inproc://nanonext")
#' sub <- socket("sub", dial = "inproc://nanonext")
#'
#' subscribe(sub, "examples")
#'
#' send(pub, c("examples", "this is an example"), mode = "raw")
#' recv(sub, "character")
#' send(pub, "examples will also be received", mode = "raw")
#' recv(sub, "character")
#' send(pub, c("other", "this other topic will not be received"), mode = "raw")
#' recv(sub, "character")
#' unsubscribe(sub, "examples")
#' send(pub, c("examples", "this example is no longer received"), mode = "raw")
#' recv(sub, "character")
#'
#' subscribe(sub, 2)
#' send(pub, c(2, 10, 10, 20), mode = "raw")
#' recv(sub, "double")
#' unsubscribe(sub, 2)
#' send(pub, c(2, 10, 10, 20), mode = "raw")
#' recv(sub, "double")
#'
#' close(pub)
#' close(sub)
#'
#' @export
#'
subscribe <- function(con, topic = NULL)
invisible(.Call(rnng_subscribe, con, topic, TRUE))
#' @rdname subscribe
#' @export
#'
unsubscribe <- function(con, topic = NULL)
invisible(.Call(rnng_subscribe, con, topic, FALSE))
#' Set Survey Time
#'
#' For a socket or context using the surveyor protocol in a surveyor/respondent
#' pattern. Set the survey timeout in milliseconds (remains valid for all
#' subsequent surveys). Messages received by the surveyor after the timer has
#' ended are discarded.
#'
#' After using this function, to start a new survey, the surveyor must:
#' \itemize{
#' \item send a message.
#' \item switch to receiving responses.
#' }
#'
#' To respond to a survey, the respondent must:
#' \itemize{
#' \item receive the survey message.
#' \item send a reply using [send_aio()] before the survey has timed out (a
#' reply can only be sent after receiving a survey).
#' }
#'
#' @param con a Socket or Context using the 'surveyor' protocol.
#' @param value \[default 1000L\] integer survey timeout in milliseconds.
#'
#' @return Invisibly, the passed Socket or Context.
#'
#' @examples
#' sur <- socket("surveyor", listen = "inproc://nanonext")
#' res <- socket("respondent", dial = "inproc://nanonext")
#'
#' survey_time(sur, 1000)
#'
#' send(sur, "reply to this survey")
#' aio <- recv_aio(sur)
#'
#' recv(res)
#' s <- send_aio(res, "replied")
#'
#' call_aio(aio)$data
#'
#' close(sur)
#' close(res)
#'
#' @export
#'
`survey_time` <- function(con, value = 1000L)
invisible(.Call(rnng_set_opt, con, "surveyor:survey-time", value))
nanonext/R/ncurl.R 0000644 0001762 0000144 00000022415 15142221674 013563 0 ustar ligges users # nanonext - ncurl - async http client -----------------------------------------
#' ncurl
#'
#' nano cURL - a minimalist http(s) client.
#'
#' @param url the URL address.
#' @param convert \[default TRUE\] logical value whether to attempt conversion
#' of the received raw bytes to a character vector. Set to `FALSE` if
#' downloading non-text data.
#' @param follow \[default FALSE\] logical value whether to automatically follow
#' redirects (not applicable for async requests). If `FALSE`, the redirect
#' address is returned as response header 'Location'.
#' @param method (optional) the HTTP method as a character string. Defaults to
#' 'GET' if not specified, and could also be 'POST', 'PUT' etc.
#' @param headers (optional) a named character vector specifying the HTTP
#' request headers, for example: \cr
#' `c(Authorization = "Bearer APIKEY", "Content-Type" = "text/plain")` \cr
#' A non-character or non-named vector will be ignored.
#' @param data (optional) request data to be submitted. Must be a character
#' string or raw vector, and other objects are ignored. If a character vector,
#' only the first element is taken. When supplying binary data, the
#' appropriate 'Content-Type' header should be set to specify the binary
#' format.
#' @param response (optional) a character vector specifying the response headers
#' to return e.g. `c("date", "server")`. These are case-insensitive and
#' will return NULL if not present. Specify `TRUE` to return all response
#' headers. A non-character vector will be ignored (other than `TRUE`).
#' @param timeout (optional) integer value in milliseconds after which the
#' transaction times out if not yet complete.
#' @param tls (optional) applicable to secure HTTPS sites only, a client TLS
#' Configuration object created by [tls_config()]. If missing or NULL,
#' certificates are not validated.
#'
#' @return Named list of 3 elements:
#'
#' - `$status` - integer HTTP repsonse status code (200 - OK). Use
#' [status_code()] for a translation of the meaning.
#' - `$headers` - named list of response headers (all headers if
#' `response = TRUE`, or those specified in `response`, or NULL otherwise).
#' If the status code is within the 300 range, i.e. a redirect, the response
#' header 'Location' is automatically appended to return the redirect address.
#' - `$data` - the response body, as a character string if `convert = TRUE` (may
#' be further parsed as html, json, xml etc. as required), or a raw byte
#' vector if FALSE (use [writeBin()] to save as a file).
#'
#' @section Public Internet HTTPS:
#'
#' When making HTTPS requests over the public internet, you should supply a TLS
#' configuration to validate server certificates. See [tls_config()] for
#' details.
#'
#' @seealso [ncurl_aio()] for asynchronous http requests; [ncurl_session()] for
#' persistent connections.
#'
#' @examples
#' ncurl(
#' "https://postman-echo.com/get",
#' convert = FALSE,
#' response = c("date", "content-type"),
#' timeout = 1200L
#' )
#' ncurl(
#' "https://postman-echo.com/get",
#' response = TRUE,
#' timeout = 1200L
#' )
#' ncurl(
#' "https://postman-echo.com/put",
#' method = "PUT",
#' headers = c(Authorization = "Bearer APIKEY"),
#' data = "hello world",
#' timeout = 1500L
#' )
#' ncurl(
#' "https://postman-echo.com/post",
#' method = "POST",
#' headers = c(`Content-Type` = "application/json"),
#' data = '{"key":"value"}',
#' timeout = 1500L
#' )
#'
#' @export
#'
ncurl <- function(
url,
convert = TRUE,
follow = FALSE,
method = NULL,
headers = NULL,
data = NULL,
response = NULL,
timeout = NULL,
tls = NULL
)
.Call(rnng_ncurl, url, convert, follow, method, headers, data, response, timeout, tls)
#' ncurl Async
#'
#' nano cURL - a minimalist http(s) client - async edition.
#'
#' @inheritParams ncurl
#'
#' @return An 'ncurlAio' (object of class 'ncurlAio' and 'recvAio') (invisibly).
#' The following elements may be accessed:
#'
#' - `$status` - integer HTTP repsonse status code (200 - OK). Use
#' [status_code()] for a translation of the meaning.
#' - `$headers` - named list of response headers (all headers if
#' `response = TRUE`, or those specified in `response`, or NULL otherwise).
#' If the status code is within the 300 range, i.e. a redirect, the response
#' header 'Location' is automatically appended to return the redirect address.
#' - `$data` - the response body, as a character string if `convert = TRUE` (may
#' be further parsed as html, json, xml etc. as required), or a raw byte
#' vector if FALSE (use [writeBin()] to save as a file).
#'
#' @inheritSection ncurl Public Internet HTTPS
#'
#' @section Promises:
#'
#' 'ncurlAio' may be used anywhere that accepts a 'promise' from the
#' \CRANpkg{promises} package through the included `as.promise` method.
#'
#' The promises created are completely event-driven and non-polling.
#'
#' If a status code of 200 (OK) is returned then the promise is resolved with
#' the reponse body, otherwise it is rejected with a translation of the status
#' code or 'errorValue' as the case may be.
#'
#' @seealso [ncurl()] for synchronous http requests; [ncurl_session()] for
#' persistent connections.
#'
#' @examples
#' nc <- ncurl_aio(
#' "https://postman-echo.com/get",
#' response = c("date", "server"),
#' timeout = 2000L
#' )
#' call_aio(nc)
#' nc$status
#' nc$headers
#' nc$data
#'
#' @examplesIf interactive() && requireNamespace("promises", quietly = TRUE)
#' library(promises)
#' p <- as.promise(nc)
#' print(p)
#'
#' p2 <- ncurl_aio("https://postman-echo.com/get") %>%
#' then(function(x) cat(x$data))
#' is.promise(p2)
#'
#' @export
#'
ncurl_aio <- function(
url,
convert = TRUE,
method = NULL,
headers = NULL,
data = NULL,
response = NULL,
timeout = NULL,
tls = NULL
)
data <- .Call(rnng_ncurl_aio, url, convert, method, headers, data, response, timeout, tls, environment())
#' ncurl Session
#'
#' nano cURL - a minimalist http(s) client. A session encapsulates a connection,
#' along with all related parameters, and may be used to return data multiple
#' times by repeatedly calling `transact()`, which transacts once over the
#' connection.
#'
#' @inheritParams ncurl
#' @param timeout (optional) integer value in milliseconds after which the
#' connection and subsequent transact attempts time out.
#'
#' @return For `ncurl_session`: an 'ncurlSession' object if successful, or else
#' an 'errorValue'.
#'
#' @inheritSection ncurl Public Internet HTTPS
#'
#' @seealso [ncurl()] for synchronous http requests; [ncurl_aio()] for
#' asynchronous http requests.
#'
#' @examples
#' s <- ncurl_session(
#' "https://postman-echo.com/get",
#' response = "date",
#' timeout = 2000L
#' )
#' s
#' if (is_ncurl_session(s)) transact(s)
#' if (is_ncurl_session(s)) close(s)
#'
#' @export
#'
ncurl_session <- function(
url,
convert = TRUE,
method = NULL,
headers = NULL,
data = NULL,
response = NULL,
timeout = NULL,
tls = NULL
)
.Call(rnng_ncurl_session, url, convert, method, headers, data, response, timeout, tls)
#' @param session an 'ncurlSession' object.
#'
#' @return For `transact`: a named list of 3 elements:
#'
#' - `$status` - integer HTTP repsonse status code (200 - OK). Use
#' [status_code()] for a translation of the meaning.
#' - `$headers` - named list of response headers (all headers if
#' `response = TRUE` was specified for the session, those specified in
#' `response`, or NULL otherwise).
#' - `$data` - the response body as a character string (if `convert = TRUE` was
#' specified for the session), which may be further parsed as html, json, xml
#' etc. as required, or else a raw byte vector, which may be saved as a file
#' using [writeBin()].
#'
#' @rdname ncurl_session
#' @export
#'
transact <- function(session) .Call(rnng_ncurl_transact, session)
#' @rdname close
#' @method close ncurlSession
#' @export
#'
close.ncurlSession <- function(con, ...) invisible(.Call(rnng_ncurl_session_close, con))
#' Make ncurlAio Promise
#'
#' Creates a 'promise' from an 'ncurlAio' object.
#'
#' This function is an S3 method for the generic `as.promise` for class
#' 'ncurlAio'.
#'
#' Requires the \pkg{promises} package.
#'
#' Allows an 'ncurlAio' to be used with functions such as `promises::then()`,
#' which schedules a function to run upon resolution of the Aio.
#'
#' The promise is resolved with a list of 'status', 'headers' and 'data' or
#' rejected with the error translation if an NNG error is returned e.g. for an
#' invalid address.
#'
#' @param x an object of class 'ncurlAio'.
#'
#' @return A 'promise' object.
#'
#' @exportS3Method promises::as.promise
#'
as.promise.ncurlAio <- function(x) {
promise <- .subset2(x, "promise")
if (is.null(promise)) {
promise <- promises::promise(
function(resolve, reject) {
if (unresolved(x)) {
.keep(x, environment())
} else {
resolve(
list(
status = .subset2(x, "result"),
headers = .subset2(x, "protocol"),
data = .subset2(x, "value")
)
)
}
}
)$then(onFulfilled = check_for_nng_error)
`[[<-`(x, "promise", promise)
}
promise
}
#' @exportS3Method promises::is.promising
#'
is.promising.ncurlAio <- function(x) TRUE
check_for_nng_error <- function(value, .visible) {
value[["status"]] >= 100 || stop(nng_error(value[["status"]]))
value
}
nanonext/R/listdial.R 0000644 0001762 0000144 00000014073 15077362607 014257 0 ustar ligges users # nanonext - Listeners / Dialers -----------------------------------------------
#' Dial an Address from a Socket
#'
#' Creates a new Dialer and binds it to a Socket.
#'
#' To view all Dialers bound to a socket use `$dialer` on the socket, which
#' returns a list of Dialer objects. To access any individual Dialer (e.g. to
#' set options on it), index into the list e.g. `$dialer[[1]]` to return the
#' first Dialer.
#'
#' A Dialer is an external pointer to a dialer object, which creates a single
#' outgoing connection at a time. If the connection is broken, or fails, the
#' dialer object will automatically attempt to reconnect, and will keep doing so
#' until the dialer or socket is destroyed.
#'
#' @param socket a Socket.
#' @param url \[default 'inproc://nanonext'\] a URL to dial, specifying the
#' transport and address as a character string e.g. 'inproc://anyvalue' or
#' 'tcp://127.0.0.1:5555' (see [transports]).
#' @param tls \[default NULL\] for secure tls+tcp:// or wss:// connections only,
#' provide a TLS configuration object created by [tls_config()].
#' @param autostart \[default TRUE\] whether to start the dialer (by default
#' asynchronously). Set to NA to start synchronously - this is less resilient
#' if a connection is not immediately possible, but avoids subtle errors from
#' attempting to use the socket before an asynchronous dial has completed. Set
#' to FALSE if setting configuration options on the dialer as it is not
#' generally possible to change these once started.
#' @param fail \[default 'warn'\] failure mode - a character value or integer
#' equivalent, whether to warn (1L), error (2L), or for none (3L) just return
#' an 'errorValue' without any corresponding warning.
#'
#' @return Invisibly, an integer exit code (zero on success). A new Dialer
#' (object of class 'nanoDialer' and 'nano') is created and bound to the
#' Socket if successful.
#'
#' @section Further details:
#'
#' Dialers and Listeners are always associated with a single socket. A given
#' socket may have multiple Listeners and/or multiple Dialers.
#'
#' The client/server relationship described by dialer/listener is completely
#' orthogonal to any similar relationship in the protocols. For example, a rep
#' socket may use a dialer to connect to a listener on an req socket. This
#' orthogonality can lead to innovative solutions to otherwise challenging
#' communications problems.
#'
#' Any configuration options on the dialer/listener should be set by [opt<-()]
#' before starting the dialer/listener with [start()].
#'
#' Dialers/Listeners may be destroyed by [close()]. They are also closed when
#' their associated socket is closed.
#'
#' @examples
#' socket <- socket("rep")
#' dial(socket, url = "inproc://nanodial", autostart = FALSE)
#' socket$dialer
#' start(socket$dialer[[1]])
#' socket$dialer
#' close(socket$dialer[[1]])
#' close(socket)
#'
#' nano <- nano("bus")
#' nano$dial(url = "inproc://nanodial", autostart = FALSE)
#' nano$dialer
#' nano$dialer_start()
#' nano$dialer
#' close(nano$dialer[[1]])
#' nano$close()
#'
#' @export
#'
dial <- function(socket, url = "inproc://nanonext", tls = NULL, autostart = TRUE, fail = c("warn", "error", "none"))
invisible(.Call(rnng_dial, socket, url, tls, autostart, fail))
#' Listen to an Address from a Socket
#'
#' Creates a new Listener and binds it to a Socket.
#'
#' To view all Listeners bound to a socket use `$listener` on the socket,
#' which returns a list of Listener objects. To access any individual Listener
#' (e.g. to set options on it), index into the list e.g. `$listener[[1]]`
#' to return the first Listener.
#'
#' A listener is an external pointer to a listener object, which accepts
#' incoming connections. A given listener object may have many connections at
#' the same time, much like an HTTP server can have many connections to multiple
#' clients simultaneously.
#'
#' @inheritParams dial
#' @param autostart \[default TRUE\] whether to start the listener. Set to FALSE
#' if setting configuration options on the listener as it is not generally
#' possible to change these once started.
#'
#' @return Invisibly, an integer exit code (zero on success). A new Listener
#' (object of class 'nanoListener' and 'nano') is created and bound to the
#' Socket if successful.
#'
#' @inheritSection dial Further details
#'
#' @examples
#' socket <- socket("req")
#' listen(socket, url = "inproc://nanolisten", autostart = FALSE)
#' socket$listener
#' start(socket$listener[[1]])
#' socket$listener
#' close(socket$listener[[1]])
#' close(socket)
#'
#' nano <- nano("bus")
#' nano$listen(url = "inproc://nanolisten", autostart = FALSE)
#' nano$listener
#' nano$listener_start()
#' nano$listener
#' close(nano$listener[[1]])
#' nano$close()
#'
#' @export
#'
listen <- function(socket, url = "inproc://nanonext", tls = NULL, autostart = TRUE, fail = c("warn", "error", "none"))
invisible(.Call(rnng_listen, socket, url, tls, autostart, fail))
#' Start Listener/Dialer
#'
#' Start a Listener/Dialer.
#'
#' @param x a Listener or Dialer.
#' @param async \[default TRUE\] (applicable to Dialers only) logical flag
#' whether the connection attempt, including any name resolution, is to be
#' made asynchronously. This behaviour is more resilient, but also generally
#' makes diagnosing failures somewhat more difficult. If FALSE, failure, such
#' as if the connection is refused, will be returned immediately, and no
#' further action will be taken.
#' @param ... not used.
#'
#' @return Invisibly, an integer exit code (zero on success).
#'
#' @name start
#' @rdname start
#'
NULL
#' @rdname start
#' @exportS3Method stats::start
#'
start.nanoListener <- function(x, ...)
invisible(.Call(rnng_listener_start, x))
#' @rdname start
#' @exportS3Method stats::start
#'
start.nanoDialer <- function(x, async = TRUE, ...)
invisible(.Call(rnng_dialer_start, x, async))
#' @rdname close
#' @method close nanoDialer
#' @export
#'
close.nanoDialer <- function(con, ...)
invisible(.Call(rnng_dialer_close, con))
#' @rdname close
#' @method close nanoListener
#' @export
#'
close.nanoListener <- function(con, ...)
invisible(.Call(rnng_listener_close, con))
nanonext/R/aio.R 0000644 0001762 0000144 00000031152 15166504164 013212 0 ustar ligges users # nanonext - Core - Aio Functions ----------------------------------------------
# send_aio/recv_aio ------------------------------------------------------------
#' Send Async
#'
#' Send data asynchronously over a connection (Socket, Context, Stream or Pipe).
#'
#' Async send is always non-blocking and returns a 'sendAio' immediately.
#'
#' For a 'sendAio', the send result is available at `$result`. An 'unresolved'
#' logical NA is returned if the async operation is yet to complete. The
#' resolved value will be zero on success, or else an integer error code.
#'
#' To wait for and check the result of the send operation, use [call_aio()] on
#' the returned 'sendAio' object.
#'
#' Alternatively, to stop the async operation, use [stop_aio()].
#'
#' @inheritParams send
#' @param con a Socket, Context or Stream.
#' @param timeout \[default NULL\] integer value in milliseconds or NULL, which
#' applies a socket-specific default, usually the same as no timeout.
#'
#' @return A 'sendAio' (object of class 'sendAio') (invisibly).
#'
#' @inheritSection send Send Modes
#'
#' @seealso [send()] for synchronous send.
#'
#' @examples
#' pub <- socket("pub", dial = "inproc://nanonext")
#'
#' res <- send_aio(pub, data.frame(a = 1, b = 2), timeout = 100)
#' res
#' res$result
#'
#' res <- send_aio(pub, "example message", mode = "raw", timeout = 100)
#' call_aio(res)$result
#'
#' close(pub)
#'
#' @export
#'
send_aio <- function(con, data, mode = c("serial", "raw"), timeout = NULL, pipe = 0L)
data <- .Call(rnng_send_aio, con, data, mode, timeout, pipe, environment())
#' Receive Async
#'
#' Receive data asynchronously over a connection (Socket, Context or Stream).
#'
#' Async receive is always non-blocking and returns a 'recvAio' immediately.
#'
#' For a 'recvAio', the received message is available at `$data`. An
#' 'unresolved' logical NA is returned if the async operation is yet to
#' complete.
#'
#' To wait for the async operation to complete and retrieve the received
#' message, use [call_aio()] on the returned 'recvAio' object.
#'
#' Alternatively, to stop the async operation, use [stop_aio()].
#'
#' In case of an error, an integer 'errorValue' is returned (to be
#' distiguishable from an integer message value). This can be checked using
#' [is_error_value()].
#'
#' If an error occurred in unserialization or conversion of the message data to
#' the specified mode, a raw vector will be returned instead to allow recovery
#' (accompanied by a warning).
#'
#' @inheritParams recv
#' @inheritParams send_aio
#' @param cv (optional) a 'conditionVariable' to signal when the async receive
#' is complete.
#'
#' @return A 'recvAio' (object of class 'recvAio') (invisibly).
#'
#' @section Signalling:
#'
#' By supplying a 'conditionVariable', when the receive is complete, the
#' 'conditionVariable' is signalled by incrementing its value by 1. This
#' happens asynchronously and independently of the R execution thread.
#'
#' @seealso [recv()] for synchronous receive.
#'
#' @examples
#' s1 <- socket("pair", listen = "inproc://nanonext")
#' s2 <- socket("pair", dial = "inproc://nanonext")
#'
#' res <- send_aio(s1, data.frame(a = 1, b = 2), timeout = 100)
#' msg <- recv_aio(s2, timeout = 100)
#' msg
#' msg$data
#'
#' res <- send_aio(s1, c(1.1, 2.2, 3.3), mode = "raw", timeout = 100)
#' msg <- recv_aio(s2, mode = "double", timeout = 100)
#' msg
#' msg$data
#'
#' res <- send_aio(s1, "example message", mode = "raw", timeout = 100)
#' msg <- recv_aio(s2, mode = "character", timeout = 100)
#' call_aio(msg)
#' msg$data
#'
#' close(s1)
#' close(s2)
#'
#' # Signalling a condition variable
#'
#' s1 <- socket("pair", listen = "inproc://cv-example")
#' cv <- cv()
#' msg <- recv_aio(s1, timeout = 100, cv = cv)
#' until(cv, 10L)
#' msg$data
#' close(s1)
#'
#' # in another process in parallel
#' s2 <- socket("pair", dial = "inproc://cv-example")
#' res <- send_aio(s2, c(1.1, 2.2, 3.3), mode = "raw", timeout = 100)
#' close(s2)
#'
#' @export
#'
recv_aio <- function(
con,
mode = c("serial", "character", "complex", "double", "integer", "logical", "numeric", "raw", "string"),
timeout = NULL,
cv = NULL
)
data <- .Call(rnng_recv_aio, con, mode, timeout, cv, environment())
# Core aio functions -----------------------------------------------------------
#' Call the Value of an Asynchronous Aio Operation
#'
#' `call_aio` retrieves the value of an asynchronous Aio operation, waiting
#' for the operation to complete if still in progress. For a list of Aios, waits
#' for all asynchronous operations to complete before returning.
#'
#' For a 'recvAio', the received value may be retrieved at `$data`.
#'
#' For a 'sendAio', the send result may be retrieved at `$result`.
#' This will be zero on success, or else an integer error code.
#'
#' To access the values directly, use for example on a 'recvAio' `x`:
#' `call_aio(x)$data`.
#'
#' For a 'recvAio', if an error occurred in unserialization or conversion of the
#' message data to the specified mode, a raw vector will be returned instead to
#' allow recovery (accompanied by a warning).
#'
#' Note: this function operates silently and does not error even if `x` is not
#' an active Aio or list of Aios, always returning invisibly the passed object.
#'
#' @param x an Aio or list of Aios (objects of class 'sendAio', 'recvAio' or
#' 'ncurlAio').
#'
#' @return The passed object (invisibly).
#'
#' @section Alternatively:
#'
#' Aio values may be accessed directly at `$result` for a 'sendAio', and `$data`
#' for a 'recvAio'. If the Aio operation is yet to complete, an 'unresolved'
#' logical NA will be returned. Once complete, the resolved value will be
#' returned instead.
#'
#' [unresolved()] may also be used, which returns `TRUE` only if an Aio or Aio
#' value has yet to resolve and `FALSE` otherwise. This is suitable for use in
#' control flow statements such as `while` or `if`.
#'
#' @examples
#' s1 <- socket("pair", listen = "inproc://nanonext")
#' s2 <- socket("pair", dial = "inproc://nanonext")
#'
#' res <- send_aio(s1, data.frame(a = 1, b = 2), timeout = 100)
#' res
#' call_aio(res)
#' res$result
#'
#' msg <- recv_aio(s2, timeout = 100)
#' msg
#' call_aio_(msg)$data
#'
#' close(s1)
#' close(s2)
#'
#' @export
#'
call_aio <- function(x) invisible(.Call(rnng_aio_call, x))
#' Call the Value of an Asynchronous Aio Operation
#'
#' `call_aio_` is a variant that allows user interrupts, suitable for
#' interactive use.
#'
#' @rdname call_aio
#' @export
#'
call_aio_ <- function(x) invisible(.Call(rnng_wait_thread_create, x))
#' Collect Data of an Aio or List of Aios
#'
#' `collect_aio` collects the data of an Aio or list of Aios, waiting for
#' resolution if still in progress.
#'
#' This function will wait for the asynchronous operation(s) to complete if
#' still in progress (blocking).
#'
#' Using `x[]` on an Aio `x` is equivalent to the user-interruptible
#' `collect_aio_(x)`.
#'
#' @inheritParams call_aio
#'
#' @return Depending on the type of `x` supplied, an object or list of objects
#' (the same length as `x`, preserving names).
#'
#' @examples
#' s1 <- socket("pair", listen = "inproc://nanonext")
#' s2 <- socket("pair", dial = "inproc://nanonext")
#'
#' res <- send_aio(s1, data.frame(a = 1, b = 2), timeout = 100)
#' collect_aio(res)
#'
#' msg <- recv_aio(s2, timeout = 100)
#' collect_aio_(msg)
#'
#' msg[]
#'
#' close(s1)
#' close(s2)
#'
#' @export
#'
collect_aio <- function(x) .Call(rnng_aio_collect, x)
#' Collect Data of an Aio or List of Aios
#'
#' `collect_aio_` is a variant that allows user interrupts, suitable for
#' interactive use.
#'
#' @rdname collect_aio
#' @export
#'
collect_aio_ <- function(x) .Call(rnng_aio_collect_safe, x)
#' Race Aio
#'
#' Returns the index of the first resolved Aio in a list, waiting if necessary.
#'
#' @param x A list of Aio objects.
#' @param cv A condition variable. This must be the same cv supplied to
#' [recv_aio()] or [request()] when creating the Aio objects in `x`.
#'
#' @return Integer index of the first resolved Aio, or 0L if none are resolved,
#' the list is empty, or the cv was terminated.
#'
#' @export
#'
race_aio <- function(x, cv) .Call(rnng_race_aio, x, cv)
#' Stop Asynchronous Aio Operation
#'
#' Stop an asynchronous Aio operation, or a list of Aio operations.
#'
#' Stops the asynchronous I/O operation associated with Aio `x` by aborting, and
#' then waits for it to complete or to be completely aborted, and for the
#' callback associated with the Aio to have completed executing. If successful,
#' the Aio will resolve to an 'errorValue' 20 (Operation canceled).
#'
#' Note this function operates silently and does not error even if `x` is not an
#' active Aio, always returning invisible NULL.
#'
#' @inheritParams call_aio
#'
#' @return Invisible NULL.
#'
#' @export
#'
stop_aio <- function(x) invisible(.Call(rnng_aio_stop, x))
#' Stop Request Operation
#'
#' Stop an asynchronous Aio operation, or a list of Aio operations, created by
#' [request()]. This is an augmented version of [stop_aio()] that additionally
#' requests cancellation by sending an integer zero followed by the context ID
#' over the context, and waiting for the response.
#'
#' @param x an Aio or list of Aios (objects of class 'recvAio' returned by
#' [request()]).
#'
#' @return Invisibly, a logical vector.
#'
#' @keywords internal
#' @export
#'
stop_request <- function(x) invisible(.Call(rnng_request_stop, x))
#' Query if an Aio is Unresolved
#'
#' Query whether an Aio, Aio value or list of Aios remains unresolved. Unlike
#' [call_aio()], this function does not wait for completion.
#'
#' Suitable for use in control flow statements such as `while` or `if`.
#'
#' Note: querying resolution may cause a previously unresolved Aio to resolve.
#'
#' @param x an Aio or list of Aios (objects of class 'sendAio', 'recvAio' or
#' 'ncurlAio'), or Aio value stored at `$result` or `$data` etc.
#'
#' @return Logical `TRUE` if `x` is an unresolved Aio or Aio value or the list
#' of Aios contains at least one unresolved Aio, or `FALSE` otherwise.
#'
#' @examples
#' s1 <- socket("pair", listen = "inproc://nanonext")
#' aio <- send_aio(s1, "test", timeout = 100)
#'
#' while (unresolved(aio)) {
#' # do stuff before checking resolution again
#' cat("unresolved\n")
#' msleep(100)
#' }
#'
#' unresolved(aio)
#'
#' close(s1)
#'
#' @export
#'
unresolved <- function(x) .Call(rnng_unresolved, x)
#' Get the Pipe ID of a recvAio
#'
#' Caution: must only be used on an already-resolved 'recvAio'. This function
#' does not perform validation of these pre-conditions.
#'
#' @param x a resolved 'recvAio'.
#'
#' @return Integer pipe ID.
#'
#' @export
#'
pipe_id <- function(x) .subset2(x, "aio")
#' Technical Utility: Query if an Aio is Unresolved
#'
#' Query whether an Aio or list of Aios remains unresolved. This is an
#' experimental technical utility version of [unresolved()] not intended for
#' ordinary use. Provides a method of querying the busy status of an Aio without
#' altering its state in any way i.e. not attempting to retrieve the result or
#' message.
#'
#' `.unresolved()` is not intended to be used for 'recvAio' returned by a
#' signalling function, in which case [unresolved()] must be used in all cases.
#'
#' @inheritParams call_aio
#'
#' @return Logical `TRUE` if `x` is an unresolved Aio or else `FALSE`, or if `x`
#' is a list, the integer number of unresolved Aios in the list.
#'
#' @keywords internal
#' @export
#'
.unresolved <- function(x) .Call(rnng_unresolved2, x)
#' Make recvAio Promise
#'
#' Creates a 'promise' from an 'recvAio' object.
#'
#' This function is an S3 method for the generic `as.promise` for class
#' 'recvAio'.
#'
#' Requires the \pkg{promises} package.
#'
#' Allows a 'recvAio' to be used with the promise pipe `%...>%`, which schedules
#' a function to run upon resolution of the Aio.
#'
#' @param x an object of class 'recvAio'.
#'
#' @return A 'promise' object.
#'
#' @exportS3Method promises::as.promise
#'
as.promise.recvAio <- function(x) {
promise <- .subset2(x, "promise")
if (is.null(promise)) {
promise <- promises::promise(
function(resolve, reject) {
if (unresolved(x)) .keep(x, environment()) else resolve(.subset2(x, "value"))
}
)$then(
onFulfilled = handle_fulfilled
)
`[[<-`(x, "promise", promise)
}
promise
}
handle_fulfilled <- function(value, .visible) {
is_error_value(value) && stop(nng_error(value))
value
}
#' @exportS3Method promises::is.promising
#'
is.promising.recvAio <- function(x) TRUE
#' Keep Promise
#'
#' Internal package function.
#'
#' If successful, both `x` and `ctx` are preserved and accessible from the
#' promise callback.
#'
#' @param x a 'recvAio' or 'ncurlAio' object.
#' @param ctx the return value of `environment()`.
#'
#' @return NULL.
#'
#' @keywords internal
#' @export
#'
.keep <- function(x, ctx) .Call(rnng_set_promise_context, x, ctx)
nanonext/R/utils.R 0000644 0001762 0000144 00000026141 15163546346 013611 0 ustar ligges users # nanonext - Utilities ---------------------------------------------------------
#' NNG Library Version
#'
#' Returns the versions of the 'libnng' and 'libmbedtls' libraries used by the
#' package.
#'
#' @return A character vector of length 2.
#'
#' @examples
#' nng_version()
#'
#' @export
#'
nng_version <- function() .Call(rnng_version)
#' Translate Error Codes
#'
#' Translate integer exit codes generated by the NNG library. All package
#' functions return an integer exit code on error rather than the expected
#' return value. These are classed 'errorValue' and may be checked by
#' [is_error_value()].
#'
#' @param xc integer exit code to translate.
#'
#' @return A character string comprising the error code and error message
#' separated by `'|'`.
#'
#' @examples
#' nng_error(1L)
#'
#' @export
#'
nng_error <- function(xc) .Call(rnng_strerror, xc)
#' Clock Utility
#'
#' Provides the number of elapsed milliseconds since an arbitrary reference time
#' in the past. The reference time will be the same for a given session, but may
#' differ between sessions.
#'
#' A convenience function for building concurrent applications. The resolution
#' of the clock depends on the underlying system timing facilities and may not
#' be particularly fine-grained. This utility should however be faster than
#' using `Sys.time()`.
#'
#' @return A double.
#'
#' @examples
#' time <- mclock(); msleep(100); mclock() - time
#'
#' @export
#'
mclock <- function() .Call(rnng_clock)
#' Sleep Utility
#'
#' Sleep function. May block for longer than requested, with the actual wait
#' time determined by the capabilities of the underlying system.
#'
#' Non-integer values for `time` are coerced to integer. Negative, logical and
#' other non-numeric values are ignored, causing the function to return
#' immediately.
#'
#' Note that unlike `Sys.sleep()`, this function is not user-interruptible by
#' sending SIGINT e.g. with ctrl + c.
#'
#' @param time integer number of milliseconds to block the caller.
#'
#' @return Invisible NULL.
#'
#' @examples
#' time <- mclock(); msleep(100); mclock() - time
#'
#' @export
#'
msleep <- function(time) invisible(.Call(rnng_sleep, time))
#' Random Data Generation
#'
#' Strictly not for use in statistical analysis. Non-reproducible and with
#' unknown statistical properties. Provides an alternative source of randomness
#' from the Mbed TLS library for purposes such as cryptographic key generation.
#' Mbed TLS uses a block-cipher in counter mode operation, as defined in
#' NIST SP800-90A: *Recommendation for Random Number Generation Using
#' Deterministic Random Bit Generators*. The implementation uses AES-256 as the
#' underlying block cipher, with a derivation function, and an entropy collector
#' combining entropy from multiple sources including at least one strong entropy
#' source.
#'
#' @param n \[default 1L\] integer random bytes to generate (from 0 to 1024),
#' coerced to integer if required. If a vector, the first element is taken.
#' @param convert \[default TRUE\] logical `FALSE` to return a raw vector, or
#' `TRUE` to return the hex representation of the bytes as a character string.
#'
#' @return A length `n` raw vector, or length one vector of `2n` random
#' characters, depending on the value of `convert` supplied.
#'
#' @note Results obtained are independent of and do not alter the state of R's
#' own pseudo-random number generators.
#'
#' @examples
#' random()
#' random(8L)
#' random(n = 8L, convert = FALSE)
#'
#' @export
#'
random <- function(n = 1L, convert = TRUE) .Call(rnng_random, n, convert)
#' Parse URL
#'
#' Parses a character string containing an RFC 3986 compliant URL as per NNG.
#'
#' @param url character string containing a URL.
#'
#' @return A named character vector of length 7, comprising:
#' \itemize{
#' \item `scheme` - the URL scheme, such as "http" or "inproc" (always lower
#' case).
#' \item `userinfo` - the username and password (if supplied in the URL
#' string).
#' \item `hostname` - the name of the host.
#' \item `port` - the port (if not specified, the default port if defined by
#' the scheme).
#' \item `path` - the path, typically used with HTTP or WebSocket.
#' \item `query` - the query info (typically following ? in the URL).
#' \item `fragment` - used for specifying an anchor, the part after # in a
#' URL.
#' }
#' Values that cannot be determined are represented by an empty string `""`.
#'
#' @examples
#' parse_url("https://user:password@w3.org:8080/type/path?q=info#intro")
#' parse_url("tcp://192.168.0.2:5555")
#'
#' @export
#'
parse_url <- function(url) .Call(rnng_url_parse, url)
#' Validators
#'
#' Validator functions for object types created by \pkg{nanonext}.
#'
#' Is the object an Aio (inheriting from class 'sendAio' or 'recvAio').
#'
#' Is the object an object inheriting from class 'nano' i.e. a nanoSocket,
#' nanoContext, nanoStream, nanoListener, nanoDialer, nanoMonitor or nano
#' Object.
#'
#' Is the object an ncurlSession (object of class 'ncurlSession').
#'
#' Is the object a Condition Variable (object of class 'conditionVariable').
#'
#' @param x an object.
#'
#' @return Logical value TRUE or FALSE.
#'
#' @examples
#' nc <- call_aio(ncurl_aio("https://postman-echo.com/get", timeout = 1000L))
#' is_aio(nc)
#'
#' @export
#'
is_aio <- function(x) inherits(x, c("recvAio", "sendAio"))
#' @examples
#' s <- socket()
#' is_nano(s)
#' n <- nano()
#' is_nano(n)
#' close(s)
#' n$close()
#'
#' @rdname is_aio
#' @export
#'
is_nano <- function(x) inherits(x, c("nano", "nanoObject"))
#' @examples
#' s <- ncurl_session("https://postman-echo.com/get", timeout = 1000L)
#' is_ncurl_session(s)
#' if (is_ncurl_session(s)) close(s)
#'
#' @rdname is_aio
#' @export
#'
is_ncurl_session <- function(x) inherits(x, "ncurlSession")
#' Error Validators
#'
#' Validator functions for error value types created by \pkg{nanonext}.
#'
#' Is the object an error value generated by the package. All non-success
#' integer return values are classed 'errorValue' to be distinguishable from
#' integer message values. Includes error values returned after a timeout etc.
#'
#' Is the object a nul byte.
#'
#' @param x an object.
#'
#' @return Logical value TRUE or FALSE.
#'
#' @examples
#' s <- socket()
#' r <- recv_aio(s, timeout = 10)
#' call_aio(r)$data
#' close(s)
#' r$data == 5L
#' is_error_value(r$data)
#' is_error_value(5L)
#'
#' @export
#'
is_error_value <- function(x) .Call(rnng_is_error_value, x)
#' @examples
#' is_nul_byte(as.raw(0L))
#' is_nul_byte(raw(length = 1L))
#' is_nul_byte(writeBin("", con = raw()))
#' is_nul_byte(0L)
#' is_nul_byte(NULL)
#' is_nul_byte(NA)
#'
#' @rdname is_error_value
#' @export
#'
is_nul_byte <- function(x) .Call(rnng_is_nul_byte, x)
#' Translate HTTP Status Codes
#'
#' Provides an explanation for HTTP response status codes (in the range 100 to
#' 599). If the status code is not defined as per RFC 9110,
#' `"Unknown HTTP Status"` is returned - this may be a custom code used by the
#' server.
#'
#' @param x numeric HTTP status code to translate.
#'
#' @return A character vector comprising the status code and explanation
#' separated by `'|'`.
#'
#' @examples
#' status_code(200)
#' status_code(404)
#'
#' @export
#'
status_code <- function(x) .Call(rnng_status_code, x)
#' Create Serialization Configuration
#'
#' Returns a serialization configuration, which may be set on a Socket for
#' custom serialization and unserialization of non-system reference objects,
#' allowing these to be sent and received between different R sessions. Once
#' set, the functions apply to all send and receive operations performed in mode
#' 'serial' over the Socket, or Context created from the Socket.
#'
#' This feature utilises the 'refhook' system of R native serialization.
#'
#' @param class a character string (or vector) of the class of object custom
#' serialization functions are applied to, e.g. `'ArrowTabular'` or
#' `c('torch_tensor', 'ArrowTabular')`.
#' @param sfunc a function (or list of functions) that accepts a reference
#' object inheriting from `class` and returns a raw vector.
#' @param ufunc a function (or list of functions) that accepts a raw vector and
#' returns a reference object.
#'
#' @return A list comprising the configuration. This should be set on a Socket
#' using [opt<-()] with option name `"serial"`.
#'
#' @examples
#' cfg <- serial_config("test_cls", function(x) serialize(x, NULL), unserialize)
#' cfg
#'
#' cfg <- serial_config(
#' c("class_one", "class_two"),
#' list(function(x) serialize(x, NULL), function(x) serialize(x, NULL)),
#' list(unserialize, unserialize)
#' )
#' cfg
#'
#' s <- socket()
#' opt(s, "serial") <- cfg
#'
#' # provide an empty list to remove registered functions
#' opt(s, "serial") <- list()
#'
#' close(s)
#'
#' @export
#'
serial_config <- function(class, sfunc, ufunc)
.Call(rnng_serial_config, class, sfunc, ufunc)
#' Write to Stdout
#'
#' Performs a non-buffered write to `stdout` using the C function `writev()` or
#' equivalent. Avoids interleaved output when writing concurrently from multiple
#' processes.
#'
#' This function writes to the C-level `stdout` of the process and hence cannot
#' be re-directed by [sink()].
#'
#' A newline character is automatically appended to `x`, hence there is no need
#' to include this within the input string.
#'
#' @param x character string.
#'
#' @return Invisible NULL. As a side effect, `x` is output to `stdout`.
#'
#' @examples
#' write_stdout("")
#'
#' @export
#'
write_stdout <- function(x) invisible(.Call(rnng_write_stdout, x))
#' Read stdin
#'
#' Reads `stdin` from a background thread, allowing the stream to be accessed as
#' messages from an NNG 'inproc' socket. As the read is blocking, it can only be
#' used in non-interactive sessions. Closing `stdin` causes the background
#' thread to exit and the socket connection to end.
#'
#' A 'pull' protocol socket is returned, and hence can only be used with receive
#' functions.
#'
#' @return a Socket.
#'
#' @export
#'
read_stdin <- function() .Call(rnng_read_stdin, interactive())
#' IP Address
#'
#' Returns a character string comprising the local network IPv4 address, or
#' vector if there are multiple addresses from multiple network adapters, or
#' an empty character string if unavailable.
#'
#' The IP addresses will be named by interface (adapter friendly name on
#' Windows) e.g. 'eth0' or 'en0'.
#'
#' @return A named character string.
#'
#' @examples
#' ip_addr()
#'
#' @export
#'
ip_addr <- function() .Call(rnng_ip_addr)
#' Advances the RNG State
#'
#' Internal package function.
#'
#' @return NULL.
#'
#' @keywords internal
#' @export
#'
.advance <- function() .Call(rnng_advance_rng_state)
#' Set Serialization Marker
#'
#' Internal package function.
#'
#' @param bool logical value.
#'
#' @return The logical `bool` supplied.
#'
#' @keywords internal
#' @export
#'
.mark <- function(bool = TRUE) .Call(rnng_marker_set, bool)
#' Internal Package Function
#'
#' Only present for cleaning up after running examples and tests. Do not attempt
#' to run the examples.
#'
#' @examples
#' if (Sys.info()[["sysname"]] == "Linux") {
#' rm(list = ls())
#' invisible(gc())
#' .Call(nanonext:::rnng_fini_priors)
#' Sys.sleep(1L)
#' .Call(nanonext:::rnng_fini)
#' }
#'
#' @keywords internal
#'
zzz <- function() {}
nanonext/R/dispatcher.R 0000644 0001762 0000144 00000007705 15174743044 014600 0 ustar ligges users # nanonext - Dispatcher --------------------------------------------------------
#' Dispatcher
#'
#' Run the dispatcher event loop for mirai task distribution.
#'
#' @param sock REP socket for host communication.
#' @param psock POLY socket for daemon communication.
#' @param monitor Monitor object for pipe events (its CV is used for signaling).
#' @param reset Pre-serialized connection reset error (raw vector).
#' @param serial Serialization configuration (list or NULL).
#' @param envir Environment containing RNG stream state.
#' @param next_stream Function to get next RNG stream, called as next_stream(envir).
#'
#' @return Integer status code (0 = normal exit).
#'
#' @keywords internal
#' @export
#'
.dispatcher <- function(sock, psock, monitor, reset, serial, envir, next_stream) {
.Call(rnng_dispatcher_run, sock, psock, monitor, reset, serial, envir, next_stream)
}
#' Start In-Process Dispatcher
#'
#' @param url URL to listen at for daemon connections.
#' @param disp_url inproc:// URL for host REQ socket.
#' @param tls TLS configuration or NULL.
#' @param serial Serialization configuration (list or NULL).
#' @param stream RNG stream integer vector (.Random.seed).
#' @param capacity Memory budget in MB (metric, 1 MB = 1,000,000 bytes) for
#' queued task payloads. `NULL`, 0, non-finite, or negative values are
#' treated as unlimited.
#' @param cvar Shared condition variable for capacity signaling.
#'
#' @return External pointer to dispatcher handle.
#'
#' @keywords internal
#' @export
#'
.dispatcher_start <- function(url, disp_url, tls, serial, stream, capacity, cvar) {
.Call(rnng_dispatcher_start, url, disp_url, tls, serial, stream, capacity, cvar)
}
#' Stop In-Process Dispatcher
#'
#' @param disp External pointer to dispatcher handle.
#'
#' @return Invisible NULL.
#'
#' @keywords internal
#' @export
#'
.dispatcher_stop <- function(disp) invisible(.Call(rnng_dispatcher_stop, disp))
#' Wait for N Daemon Connections
#'
#' @param disp External pointer to dispatcher handle.
#' @param n Number of connections to wait for.
#'
#' @return Invisible NULL.
#'
#' @keywords internal
#' @export
#'
.dispatcher_wait <- function(disp, n) invisible(.Call(rnng_dispatcher_wait, disp, n))
#' Dispatcher Info
#'
#' Read dispatcher statistics directly under lock.
#'
#' @param disp External pointer to dispatcher handle.
#'
#' @return Integer vector of length 5: connections, cumulative, awaiting,
#' executing, completed.
#'
#' @keywords internal
#' @export
#'
.dispatcher_info <- function(disp) .Call(rnng_dispatcher_info, disp)
#' Dispatcher Capacity
#'
#' Read current and peak queued task payload usage at dispatcher, plus the
#' configured capacity, in MB (metric, 1 MB = 1,000,000 bytes).
#'
#' @param disp External pointer to dispatcher handle.
#'
#' @return Named numeric vector of length 3: **used** (current) and **peak**
#' (high-watermark) usage, and **capacity** (the `capacity` set on
#' [.dispatcher_start()], `NA_real_` if unset/unbounded), all in MB.
#' `NA_real_` in each slot if `disp` is invalid.
#'
#' @keywords internal
#' @export
#'
.dispatcher_capacity <- function(disp) .Call(rnng_dispatcher_capacity, disp)
#' Dispatcher Gate
#'
#' Block while queued bytes at dispatcher exceed the memory budget set on
#' `.dispatcher_start()`.
#'
#' @param disp External pointer to dispatcher handle.
#'
#' @return Logical TRUE.
#'
#' @keywords internal
#' @export
#'
.dispatcher_gate <- function(disp) .Call(rnng_dispatcher_gate, disp)
#' Dispatcher Try Gate
#'
#' Snapshot read of dispatcher capacity. Returns immediately, never blocks.
#' The non-blocking counterpart to [.dispatcher_gate()].
#'
#' @param disp External pointer to dispatcher handle.
#'
#' @return Logical `TRUE` if submission would not block (queued bytes below
#' the memory budget set on `.dispatcher_start()`, or unbounded), `FALSE`
#' if at or over the budget. `NULL` if `disp` is invalid.
#'
#' @keywords internal
#' @export
#'
.dispatcher_try_gate <- function(disp) .Call(rnng_dispatcher_try_gate, disp)
nanonext/R/stats.R 0000644 0001762 0000144 00000005330 15077362607 013604 0 ustar ligges users # nanonext - Stats -------------------------------------------------------------
#' Get Statistic for a Socket, Listener or Dialer
#'
#' Obtain value of a statistic for a Socket, Listener or Dialer. This function
#' exposes the stats interface of NNG.
#'
#' Note: the values of individual statistics are guaranteed to be atomic, but
#' due to the way statistics are collected there may be discrepancies between
#' them at times. For example, statistics counting bytes and messages received
#' may not reflect the same number of messages, depending on when the snapshot
#' is taken. This potential inconsistency arises as a result of optimisations to
#' minimise the impact of statistics on actual operations.
#'
#' @param object a Socket, Listener or Dialer.
#' @param name character name of statistic to return.
#'
#' @return The value of the statistic (character or double depending on the type
#' of statistic requested) if available, or else NULL.
#'
#' @section Stats:
#'
#' The following stats may be requested for a Socket:
#' \itemize{
#' \item 'id' - numeric id of the socket.
#' \item 'name' - character socket name.
#' \item 'protocol' - character protocol type e.g. 'bus'.
#' \item 'pipes' - numeric number of pipes (active connections).
#' \item 'dialers' - numeric number of listeners attached to the socket.
#' \item 'listeners' - numeric number of dialers attached to the socket.
#' }
#'
#' The following stats may be requested for a Listener / Dialer:
#' \itemize{
#' \item 'id' - numeric id of the listener / dialer.
#' \item 'socket' - numeric id of the socket of the listener / dialer.
#' \item 'url' - character URL address.
#' \item 'pipes' - numeric number of pipes (active connections).
#' }
#'
#' The following additional stats may be requested for a Listener:
#' \itemize{
#' \item 'accept' - numeric total number of connection attempts, whether
#' successful or not.
#' \item 'reject' - numeric total number of rejected connection attempts e.g.
#' due to incompatible protocols.
#' }
#'
#' The following additional stats may be requested for a Dialer:
#' \itemize{
#' \item 'connect' - numeric total number of connection attempts, whether
#' successful or not.
#' \item 'reject' - numeric total number of rejected connection attempts e.g.
#' due to incompatible protocols.
#' \item 'refused' - numeric total number of refused connections e.g. when
#' starting synchronously with no listener on the other side.
#' }
#'
#' @examples
#' s <- socket("bus", listen = "inproc://stats")
#' stat(s, "pipes")
#'
#' s1 <- socket("bus", dial = "inproc://stats")
#' stat(s, "pipes")
#'
#' close(s1)
#' stat(s, "pipes")
#'
#' close(s)
#'
#' @export
#'
stat <- function(object, name) .Call(rnng_stats_get, object, name)
nanonext/R/socket.R 0000644 0001762 0000144 00000015315 15077362607 013742 0 ustar ligges users # nanonext - Core - Sockets ----------------------------------------------------
#' Open Socket
#'
#' Open a Socket implementing `protocol`, and optionally dial (establish an
#' outgoing connection) or listen (accept an incoming connection) at an address.
#'
#' NNG presents a socket view of networking. The sockets are constructed using
#' protocol-specific functions, as a given socket implements precisely one
#' protocol.
#'
#' Each socket may be used to send and receive messages (if the protocol
#' supports it, and implements the appropriate protocol semantics). For example,
#' sub sockets automatically filter incoming messages to discard those for
#' topics that have not been subscribed.
#'
#' This function (optionally) binds a single Dialer and/or Listener to a Socket.
#' More complex network topologies may be created by binding further Dialers /
#' Listeners to the Socket as required using [dial()] and [listen()].
#'
#' New contexts may also be created using [context()] if the protocol supports
#' it.
#'
#' @param protocol \[default 'bus'\] choose protocol - `"bus"`, `"pair"`,
#' `"poly"`, `"push"`, `"pull"`, `"pub"`, `"sub"`, `"req"`, `"rep"`,
#' `"surveyor"`, or `"respondent"` - see [protocols].
#' @param dial (optional) a URL to dial, specifying the transport and address as
#' a character string e.g. 'inproc://anyvalue' or 'tcp://127.0.0.1:5555' (see
#' [transports]).
#' @param listen (optional) a URL to listen at, specifying the transport and
#' address as a character string e.g. 'inproc://anyvalue' or
#' 'tcp://127.0.0.1:5555' (see [transports]).
#' @param autostart \[default TRUE\] whether to start the dialer/listener. Set
#' to FALSE if setting configuration options on the dialer/listener as it is
#' not generally possible to change these once started. For dialers only: set
#' to NA to start synchronously - this is less resilient if a connection is
#' not immediately possible, but avoids subtle errors from attempting to use
#' the socket before an asynchronous dial has completed.
#' @param raw \[default FALSE\] whether to open raw mode sockets. Note: not for
#' general use - do not enable unless you have a specific need (refer to NNG
#' documentation).
#' @inheritParams dial
#'
#' @return A Socket (object of class 'nanoSocket' and 'nano').
#'
#' @section Protocols:
#'
#' The following Scalability Protocols (communication patterns) are implemented:
#' \itemize{
#' \item Bus (mesh networks) - protocol: 'bus'
#' \item Pair (two-way radio) - protocol: 'pair'
#' \item Poly (one-to-one of many) - protocol: 'poly'
#' \item Pipeline (one-way pipe) - protocol: 'push', 'pull'
#' \item Publisher/Subscriber (topics & broadcast) - protocol: 'pub', 'sub'
#' \item Request/Reply (RPC) - protocol: 'req', 'rep'
#' \item Survey (voting & service discovery) - protocol: 'surveyor',
#' 'respondent'
#' }
#'
#' Please see [protocols] for further documentation.
#'
#' @section Transports:
#'
#' The following communications transports may be used:
#'
#' \itemize{
#' \item Inproc (in-process) - url: 'inproc://'
#' \item IPC (inter-process communications) - url: 'ipc://' (or 'abstract://'
#' on Linux)
#' \item TCP and TLS over TCP - url: 'tcp://' and 'tls+tcp://'
#' \item WebSocket and TLS over WebSocket - url: 'ws://' and 'wss://'
#' }
#'
#' Please see [transports] for further documentation.
#'
#' @examples
#' s <- socket(protocol = "req", listen = "inproc://nanosocket")
#' s
#' s1 <- socket(protocol = "rep", dial = "inproc://nanosocket")
#' s1
#'
#' send(s, "hello world!")
#' recv(s1)
#'
#' close(s1)
#' close(s)
#'
#' @export
#'
socket <- function(
protocol = c("bus", "pair", "poly", "push", "pull", "pub", "sub", "req", "rep", "surveyor", "respondent"),
dial = NULL,
listen = NULL,
tls = NULL,
autostart = TRUE,
raw = FALSE
)
.Call(rnng_protocol_open, protocol, dial, listen, tls, autostart, raw)
#' Close Connection
#'
#' Close Connection on a Socket, Context, Dialer, Listener, Stream, Pipe, or
#' ncurl Session.
#'
#' Closing an object explicitly frees its resources. An object can also be
#' removed directly in which case its resources are freed when the object is
#' garbage collected.
#'
#' Closing a Socket associated with a Context also closes the Context.
#'
#' Dialers and Listeners are implicitly closed when the Socket they are
#' associated with is closed.
#'
#' Closing a Socket or a Context: messages that have been submitted for sending
#' may be flushed or delivered, depending upon the transport. Closing the Socket
#' while data is in transmission will likely lead to loss of that data. There is
#' no automatic linger or flush to ensure that the Socket send buffers have
#' completely transmitted.
#'
#' Closing a Stream: if any send or receive operations are pending, they will be
#' terminated and any new operations will fail after the connection is closed.
#'
#' Closing an 'ncurlSession' closes the http(s) connection.
#'
#' @param con a Socket, Context, Dialer, Listener, Stream, or 'ncurlSession'.
#' @param ... not used.
#'
#' @return Invisibly, an integer exit code (zero on success).
#'
#' @seealso [reap()]
#'
#' @name close
#' @rdname close
#'
NULL
#' @rdname close
#' @method close nanoSocket
#' @export
#'
close.nanoSocket <- function(con, ...) invisible(.Call(rnng_close, con))
#' Reap
#'
#' An alternative to `close` for Sockets, Contexts, Listeners, and Dialers
#' avoiding S3 method dispatch.
#'
#' May be used on unclassed external pointers e.g. those created by
#' [.context()]. Returns silently and does not warn or error, nor does it update
#' the state of object attributes.
#'
#' @param con a Socket, Context, Listener or Dialer.
#'
#' @return An integer exit code (zero on success).
#'
#' @seealso [close()]
#'
#' @examples
#' s <- socket("req")
#' listen(s)
#' dial(s)
#' ctx <- .context(s)
#'
#' reap(ctx)
#' reap(s[["dialer"]][[1]])
#' reap(s[["listener"]][[1]])
#' reap(s)
#' reap(s)
#'
#' @export
#'
reap <- function(con) .Call(rnng_reap, con)
#' Monitor a Socket for Pipe Changes
#'
#' This function monitors pipe additions and removals from a socket.
#'
#' @param sock a Socket.
#' @param cv a 'conditionVariable'.
#'
#' @return For `monitor`: a Monitor (object of class 'nanoMonitor'). \cr
#' For `read_monitor`: an integer vector of pipe IDs (positive if added,
#' negative if removed), or else NULL if there were no changes since the
#' previous read.
#'
#' @examples
#' cv <- cv()
#' s <- socket("poly")
#' s1 <- socket("poly")
#'
#' m <- monitor(s, cv)
#' m
#'
#' listen(s)
#' dial(s1)
#'
#' cv_value(cv)
#' read_monitor(m)
#'
#' close(s)
#' close(s1)
#'
#' read_monitor(m)
#'
#' @export
#'
monitor <- function(sock, cv) .Call(rnng_monitor_create, sock, cv)
#' @param x a Monitor.
#'
#' @rdname monitor
#' @export
#'
read_monitor <- function(x) .Call(rnng_monitor_read, x)
nanonext/R/nano.R 0000644 0001762 0000144 00000035104 15077362607 013403 0 ustar ligges users # nanonext - Core - Nano Object and S3 Methods ---------------------------------
#' Create Nano Object
#'
#' Create a nano object, encapsulating a Socket, Dialers/Listeners and
#' associated methods.
#'
#' This function encapsulates a Socket, Dialer and/or Listener, and its
#' associated methods.
#'
#' The Socket may be accessed by `$socket`, and the Dialer or Listener by
#' `$dialer[[1]]` or `$listener[[1]]` respectively.
#'
#' The object's methods may be accessed by `$` e.g. `$send()` or `$recv()`.
#' These methods mirror their functional equivalents, with the same arguments
#' and defaults, apart from that the first argument of the functional equivalent
#' is mapped to the object's encapsulated socket (or context, if active) and
#' does not need to be supplied.
#'
#' More complex network topologies may be created by binding further dialers or
#' listeners using the object's `$dial()` and `$listen()` methods. The new
#' dialer/listener will be attached to the object e.g. if the object already has
#' a dialer, then at `$dialer[[2]]` etc.
#'
#' Note that `$dialer_opt()` and `$listener_opt()` methods will be available
#' once dialers/listeners are attached to the object. These methods get or apply
#' settings for all dialers or listeners equally. To get or apply settings for
#' individual dialers/listeners, access them directly via `$dialer[[2]]` or
#' `$listener[[2]]` etc.
#'
#' The methods `$opt()`, and also `$dialer_opt()` or `$listener_opt()` as may be
#' applicable, will get the requested option if a single argument `name` is
#' provided, and will set the value for the option if both arguments `name` and
#' `value` are provided.
#'
#' For Dialers or Listeners not automatically started, the `$dialer_start()` or
#' `$listener_start()` methods will be available. These act on the most recently
#' created Dialer or Listener respectively.
#'
#' For applicable protocols, new contexts may be created by using the
#' `$context_open()` method. This will attach a new context at `$context` as
#' well as a `$context_close()` method. While a context is active, all object
#' methods use the context rather than the socket. A new context may be created
#' by calling `$context_open()`, which will replace any existing context. It is
#' only necessary to use `$context_close()` to close the existing context and
#' revert to using the socket.
#'
#' @inheritParams socket
#'
#' @return A nano object of class 'nanoObject'.
#'
#' @examples
#' nano <- nano("bus", listen = "inproc://nanonext")
#' nano
#' nano$socket
#' nano$listener[[1]]
#'
#' nano$opt("send-timeout", 1500)
#' nano$opt("send-timeout")
#'
#' nano$listen(url = "inproc://nanonextgen")
#' nano$listener
#'
#' nano1 <- nano("bus", dial = "inproc://nanonext")
#' nano$send("example test", mode = "raw")
#' nano1$recv("character")
#'
#' nano$close()
#' nano1$close()
#'
#' @export
#'
nano <- function(
protocol = c("bus", "pair", "poly", "push", "pull", "pub", "sub", "req", "rep", "surveyor", "respondent"),
dial = NULL,
listen = NULL,
tls = NULL,
autostart = TRUE
) {
nano <- `class<-`(new.env(hash = FALSE), "nanoObject")
socket <- socket(protocol)
sock2 <- NULL
makeActiveBinding(
"socket",
function(x) if (length(sock2)) sock2 else socket,
nano
)
is_poly <- attr(socket, "protocol") == "poly"
if (length(dial)) {
r <- dial(socket, url = dial, tls = tls, autostart = autostart)
if (r == 0L) {
nano[["dialer"]] <- attr(socket, "dialer")
nano[["dialer_opt"]] <- function(name, value)
if (missing(value))
lapply(.subset2(nano, "dialer"), opt, name = name) else
invisible(lapply(.subset2(nano, "dialer"), `opt<-`, name = name, value = value))
if (isFALSE(autostart)) nano[["dialer_start"]] <- function(async = TRUE) {
s <- start.nanoDialer(.subset2(nano, "dialer")[[1L]], async = async)
if (s == 0L) rm("dialer_start", envir = nano)
invisible(s)
}
}
}
if (length(listen)) {
r <- listen(socket, url = listen, tls = tls, autostart = autostart)
if (r == 0L) {
nano[["listener"]] <- attr(socket, "listener")
nano[["listener_opt"]] <- function(name, value)
if (missing(value))
lapply(.subset2(nano, "listener"), opt, name = name) else
invisible(lapply(.subset2(nano, "listener"), `opt<-`, name = name, value = value))
if (isFALSE(autostart)) nano[["listener_start"]] <- function() {
s <- start.nanoListener(.subset2(nano, "listener")[[1L]])
if (s == 0L) rm("listener_start", envir = nano)
invisible(s)
}
}
}
nano[["close"]] <- function() close(.subset2(nano, "socket"))
nano[["dial"]] <- function(url = "inproc://nanonext", tls = NULL, autostart = TRUE) {
r <- dial(socket, url = url, tls = tls, autostart = autostart)
if (r == 0L) {
nano[["dialer"]] <- attr(socket, "dialer")
nano[["dialer_opt"]] <- function(name, value)
if (missing(value))
lapply(.subset2(nano, "dialer"), opt, name = name) else
invisible(lapply(.subset2(nano, "dialer"), `opt<-`, name = name, value = value))
if (isFALSE(autostart)) nano[["dialer_start"]] <- function(async = TRUE) {
s <- start.nanoDialer((d <- .subset2(nano, "dialer"))[[length(d)]], async = async)
if (s == 0L) rm("dialer_start", envir = nano)
invisible(s)
}
}
invisible(r)
}
nano[["listen"]] <- function(url = "inproc://nanonext", tls = NULL, autostart = TRUE) {
r <- listen(socket, url = url, tls = tls, autostart = autostart)
if (r == 0L) {
nano[["listener"]] <- attr(socket, "listener")
nano[["listener_opt"]] <- function(name, value)
if (missing(value))
lapply(.subset2(nano, "listener"), opt, name = name) else
invisible(lapply(.subset2(nano, "listener"), `opt<-`, name = name, value = value))
if (isFALSE(autostart)) nano[["listener_start"]] <- function() {
s <- start.nanoListener((l <- .subset2(nano, "listener"))[[length(l)]])
if (s == 0L) rm("listener_start", envir = nano)
invisible(s)
}
}
invisible(r)
}
nano[["recv"]] <- function(mode = c("serial", "character", "complex", "double",
"integer", "logical", "numeric", "raw", "string"),
block = NULL)
recv(socket, mode = mode, block = block)
nano[["recv_aio"]] <- function(mode = c("serial", "character", "complex", "double",
"integer", "logical", "numeric", "raw", "string"),
timeout = NULL)
recv_aio(socket, mode = mode, timeout = timeout)
nano[["send"]] <- if (is_poly) {
function(data, mode = c("serial", "raw"), block = NULL, pipe = 0L)
send(socket, data = data, mode = mode, block = block, pipe = pipe)
} else {
function(data, mode = c("serial", "raw"), block = NULL)
send(socket, data = data, mode = mode, block = block)
}
nano[["send_aio"]] <- if (is_poly) {
function(data, mode = c("serial", "raw"), timeout = NULL, pipe = 0L)
send_aio(socket, data = data, mode = mode, timeout = timeout, pipe = pipe)
} else {
function(data, mode = c("serial", "raw"), timeout = NULL)
send_aio(socket, data = data, mode = mode, timeout = timeout)
}
nano[["opt"]] <- function(name, value)
if (missing(value)) opt(socket, name = name) else
invisible(`opt<-`(socket, name = name, value = value))
nano[["stat"]] <- function(name) stat(socket, name = name)
switch(attr(socket, "protocol"),
req = ,
rep = {
nano[["context_open"]] <- function() {
if (is.null(sock2)) sock2 <<- socket
nano[["context_close"]] <- function() if (length(sock2)) {
r <- close(socket)
socket <<- sock2
sock2 <<- NULL
rm(list = c("context", "context_close"), envir = nano)
r
}
socket <<- nano[["context"]] <- context(sock2)
}
},
sub = {
nano[["context_open"]] <- function() {
if (is.null(sock2)) sock2 <<- socket
nano[["context_close"]] <- function() if (length(sock2)) {
r <- close(socket)
socket <<- sock2
sock2 <<- NULL
rm(list = c("context", "context_close"), envir = nano)
r
}
socket <<- nano[["context"]] <- context(sock2)
}
nano[["subscribe"]] <- function(topic = NULL)
subscribe(socket, topic = topic)
nano[["unsubscribe"]] <- function(topic = NULL)
unsubscribe(socket, topic = topic)
},
surveyor = {
nano[["context_open"]] <- function() {
if (is.null(sock2)) sock2 <<- socket
nano[["context_close"]] <- function() if (length(sock2)) {
r <- close(socket)
socket <<- sock2
sock2 <<- NULL
rm(list = c("context", "context_close"), envir = nano)
r
}
socket <<- nano[["context"]] <- context(sock2)
}
nano[["survey_time"]] <- function(value = 1000L)
survey_time(socket, value = value)
},
respondent = {
nano[["context_open"]] <- function() {
if (is.null(sock2)) sock2 <<- socket
nano[["context_close"]] <- function() if (length(sock2)) {
r <- close(socket)
socket <<- sock2
sock2 <<- NULL
rm(list = c("context", "context_close"), envir = nano)
r
}
socket <<- nano[["context"]] <- context(sock2)
}
},
NULL)
nano
}
#' @export
#'
print.nanoObject <- function(x, ...) {
cat(
sprintf(
"< nano object >\n - socket id: %d\n - state: %s\n - protocol: %s\n",
attr(.subset2(x, "socket"), "id"),
attr(.subset2(x, "socket"), "state"),
attr(.subset2(x, "socket"), "protocol")
),
file = stdout()
)
if (length(.subset2(x, "listener")))
cat(
" - listener:", as.character(lapply(.subset2(x, "listener"), attr, "url")),
sep = "\n ",
file = stdout()
)
if (length(.subset2(x, "dialer")))
cat(
" - dialer:", as.character(lapply(.subset2(x, "dialer"), attr, "url")),
sep = "\n ",
file = stdout()
)
invisible(x)
}
#' @export
#'
print.nanoSocket <- function(x, ...) {
cat(
sprintf(
"< nanoSocket >\n - id: %d\n - state: %s\n - protocol: %s\n",
attr(x, "id"),
attr(x, "state"),
attr(x, "protocol")
),
file = stdout()
)
if (length(attr(x, "listener")))
cat(
" - listener:", as.character(lapply(attr(x, "listener"), attr, "url")),
sep = "\n ",
file = stdout()
)
if (length(attr(x, "dialer")))
cat(
" - dialer:", as.character(lapply(attr(x, "dialer"), attr, "url")),
sep = "\n ",
file = stdout()
)
invisible(x)
}
#' @export
#'
print.nanoContext <- function(x, ...) {
cat(
sprintf(
"< nanoContext >\n - id: %d\n - socket: %d\n - state: %s\n - protocol: %s\n",
attr(x, "id"),
attr(x, "socket"),
attr(x, "state"),
attr(x, "protocol")
),
file = stdout()
)
invisible(x)
}
#' @export
#'
print.nanoDialer <- function(x, ...) {
cat(
sprintf(
"< nanoDialer >\n - id: %d\n - socket: %d\n - state: %s\n - url: %s\n",
attr(x, "id"),
attr(x, "socket"),
attr(x, "state"),
attr(x, "url")
),
file = stdout()
)
invisible(x)
}
#' @export
#'
print.nanoListener <- function(x, ...) {
cat(
sprintf(
"< nanoListener >\n - id: %d\n - socket: %d\n - state: %s\n - url: %s\n",
attr(x, "id"),
attr(x, "socket"),
attr(x, "state"),
attr(x, "url")
),
file = stdout()
)
invisible(x)
}
#' @export
#'
print.nanoStream <- function(x, ...) {
cat(
sprintf(
"< nanoStream >\n - mode: %s\n - state: %s\n - url: %s\n",
attr(x, "mode"),
attr(x, "state"),
attr(x, "url")
),
file = stdout()
)
invisible(x)
}
#' @export
#'
print.nanoMonitor <- function(x, ...) {
cat(
sprintf("< nanoMonitor >\n - socket: %s\n", attr(x, "socket")),
file = stdout()
)
invisible(x)
}
#' @export
#'
print.recvAio <- function(x, ...) {
cat("< recvAio | $data >\n", file = stdout())
invisible(x)
}
#' @export
#'
print.sendAio <- function(x, ...) {
cat("< sendAio | $result >\n", file = stdout())
invisible(x)
}
#' @export
#'
print.ncurlAio <- function(x, ...) {
cat("< ncurlAio | $status $headers $data >\n", file = stdout())
invisible(x)
}
#' @export
#'
print.ncurlSession <- function(x, ...) {
cat(
sprintf(
"< ncurlSession > - %s\n",
if (is.null(attr(x, "state"))) "transact() to return data" else "not active"
),
file = stdout()
)
invisible(x)
}
#' @export
#'
print.unresolvedValue <- function(x, ...) {
cat("'unresolved' logi NA\n", file = stdout())
invisible(x)
}
#' @export
#'
print.errorValue <- function(x, ...) {
cat(sprintf("'errorValue' int %s\n", nng_error(x)), file = stdout())
invisible(x)
}
#' @export
#'
print.conditionVariable <- function(x, ...) {
cat("< conditionVariable >\n", file = stdout())
invisible(x)
}
#' @export
#'
print.tlsConfig <- function(x, ...) {
cat(
sprintf("< TLS %s config | auth mode: %s >\n", attr(x, "spec"), attr(x, "mode")),
file = stdout()
)
invisible(x)
}
#' @export
#'
`[[.nano` <- function(x, i, exact = TRUE)
attr(x, i, exact = exact)
#' @export
#'
`[.nano` <- function(x, i, exact = TRUE)
attr(x, deparse(substitute(i)), exact = exact)
#' @export
#'
`$.nano` <- function(x, name)
attr(x, name, exact = TRUE)
#' @export
#'
`$<-.nano` <- function(x, name, value) x
#' @export
#'
`$<-.nanoObject` <- function(x, name, value) x
#' @export
#'
`[.recvAio` <- function(x, i) collect_aio_(x)
#' @export
#'
`$<-.recvAio` <- function(x, name, value) x
#' @export
#'
`[.sendAio` <- function(x, i) collect_aio_(x)
#' @export
#'
`$<-.sendAio` <- function(x, name, value) x
#' @exportS3Method utils::.DollarNames
#'
.DollarNames.nano <- function(x, pattern = "")
grep(pattern, names(attributes(x)), value = TRUE, fixed = TRUE)
#' @exportS3Method utils::.DollarNames
#'
.DollarNames.recvAio <- function(x, pattern = "")
if (startsWith("data", pattern)) "data" else character()
#' @exportS3Method utils::.DollarNames
#'
.DollarNames.sendAio <- function(x, pattern = "")
if (startsWith("result", pattern)) "result" else character()
#' @exportS3Method utils::.DollarNames
#'
.DollarNames.ncurlAio <- function(x, pattern = "")
grep(pattern, c("status", "headers", "data"), value = TRUE, fixed = TRUE)
nanonext/R/stream.R 0000644 0001762 0000144 00000006033 15164012341 013722 0 ustar ligges users # nanonext - Byte Stream Interface ---------------------------------------------
#' Open Stream
#'
#' Open a Stream by either dialing (establishing an outgoing connection) or
#' listening (accepting an incoming connection) at an address. This is a
#' low-level interface intended for communicating with non-NNG endpoints.
#'
#' A Stream is used for raw byte stream connections. Byte streams are reliable
#' in that data will not be delivered out of order, or with portions missing.
#'
#' Can be used to dial a (secure) websocket address starting 'ws://' or
#' 'wss://'. It is often the case that `textframes` needs to be set to `TRUE`.
#'
#' Specify only one of `dial` or `listen`. If both are specified, `listen` will
#' be ignored.
#'
#' Closing a stream renders it invalid and attempting to perform additional
#' operations on it will error.
#'
#' @param dial a URL to dial, specifying the transport and address as a
#' character string e.g. 'ipc:///tmp/anyvalue' or 'tcp://127.0.0.1:5555' (not
#' all transports are supported).
#' @param listen a URL to listen at, specifying the transport and address as a
#' character string e.g. 'ipc:///tmp/anyvalue' or 'tcp://127.0.0.1:5555' (not
#' all transports are supported).
#' @param textframes \[default FALSE\] applicable to the websocket transport
#' only, enables sending and receiving of TEXT frames (ignored otherwise).
#' @param headers (optional) applicable to websocket connections only, a
#' **named** character vector specifying custom request headers to send during
#' the WebSocket upgrade handshake e.g.
#' `c(Authorization = "Bearer token", Custom = "value")` (ignored for
#' non-websocket transports).
#' @param tls (optional) applicable to secure websockets only, a client or
#' server TLS configuration object created by [tls_config()]. If missing or
#' NULL, certificates are not validated.
#' @param buffer \[default 65536L\] applicable to non-websocket streams only, the
#' maximum number of bytes to receive. Can be an over-estimate, but note that
#' a buffer of this size is reserved. Not used for websocket connections,
#' which handle framing automatically.
#'
#' @return A Stream (object of class 'nanoStream' and 'nano').
#'
#' @examples
#' # Will succeed only if there is an open connection at the address:
#' s <- tryCatch(stream(dial = "tcp://127.0.0.1:5555"), error = identity)
#' s
#' @examplesIf interactive()
#' # Run in interactive sessions only as connection is not always available:
#' s <- tryCatch(
#' stream(dial = "wss://echo.websocket.events/", textframes = TRUE),
#' error = identity
#' )
#' s
#' if (is_nano(s)) recv(s)
#' if (is_nano(s)) send(s, "hello")
#' if (is_nano(s)) recv(s)
#' if (is_nano(s)) close(s)
#'
#' @export
#'
stream <- function(dial = NULL, listen = NULL, textframes = FALSE,
headers = NULL, tls = NULL, buffer = 65536L)
.Call(rnng_stream_open, dial, listen, textframes, headers, tls, buffer)
#' @rdname close
#' @method close nanoStream
#' @export
#'
close.nanoStream <- function(con, ...) invisible(.Call(rnng_stream_close, con))
nanonext/R/docs.R 0000644 0001762 0000144 00000033024 15077362607 013377 0 ustar ligges users # nanonext - Documentation -----------------------------------------------------
#' Protocols (Documentation)
#'
#' @description Protocols implemented by \pkg{nanonext}.
#'
#' For an authoritative guide please refer to the online documentation for the
#' NNG library at .
#'
#' @section Bus (mesh networks):
#'
#' **\[protocol, bus\]** The bus protocol is useful for routing applications or
#' for building mesh networks where every peer is connected to every other peer.
#'
#' In this protocol, each message sent by a node is sent to every one of its
#' directly-connected peers. This protocol may be used to send and receive
#' messages. Sending messages will attempt to deliver to each directly connected
#' peer. Indirectly-connected peers will not receive messages. When using this
#' protocol to build mesh networks, it is therefore important that a
#' fully-connected mesh network be constructed.
#'
#' All message delivery in this pattern is best-effort, which means that peers
#' may not receive messages. Furthermore, delivery may occur to some, all, or
#' none of the directly connected peers (messages are not delivered when peer
#' nodes are unable to receive). Hence, send operations will never block;
#' instead if the message cannot be delivered for any reason it is discarded.
#'
#' @section Pair (two-way radio):
#'
#' **\[protocol, pair\]** This is NNG's pair v0. The pair protocol implements a
#' peer-to-peer pattern, where relationships between peers are one-to-one. Only
#' one peer may be connected to another peer at a time, but both may send and
#' receive messages freely.
#'
#' Normally, this pattern will block when attempting to send a message if no
#' peer is able to receive the message.
#'
#' @section Poly (one-to-one of many):
#'
#' **\[protocol, poly\]** This is NNG's pair v1 polyamorous mode. It allows a
#' socket to communicate with multiple directly-connected peers.
#'
#' If no remote peer is specified by the sender, then the protocol willselect
#' any available connected peer.
#'
#' If the peer on the given pipe is not able to receive (or the pipe is no
#' longer available, such as if the peer has disconnected), then the message
#' will be discarded with no notification to the sender.
#'
#' @section Push/Pull (one-way pipeline):
#'
#' In the pipeline pattern, pushers distribute messages to pullers, hence useful
#' for solving producer/consumer problems.
#'
#' If multiple peers are connected, the pattern attempts to distribute fairly.
#' Each message sent by a pusher will be sent to one of its peer pullers, chosen
#' in a round-robin fashion. This property makes this pattern useful in
#' load-balancing scenarios.
#'
#' **\[protocol, push\]** The push protocol is one half of a pipeline pattern.
#' The other side is the pull protocol.
#'
#' **\[protocol, pull\]** The pull protocol is one half of a pipeline pattern.
#' The other half is the push protocol.
#'
#' @section Publisher/Subscriber (topics & broadcast):
#'
#' In a publisher/subscriber pattern, a publisher sends data, which is broadcast
#' to all subscribers. The subscriber only see the data to which they have
#' subscribed.
#'
#' **\[protocol, pub\]** The pub protocol is one half of a publisher/subscriber
#' pattern. This protocol may be used to send messages, but is unable to receive
#' them.
#'
#' **\[protocol, sub\]** The sub protocol is one half of a publisher/subscriber
#' pattern. This protocol may be used to receive messages, but is unable to send
#' them.
#'
#' @section Request/Reply (RPC):
#'
#' In a request/reply pattern, a requester sends a message to one replier, who
#' is expected to reply with a single answer. This is used for synchronous
#' communications, for example remote procedure calls (RPCs).
#'
#' The request is resent automatically if no reply arrives, until a reply is
#' received or the request times out.
#'
#' **\[protocol, req\]** The req protocol is one half of a request/reply
#' pattern. This socket may be used to send messages (requests), and then to
#' receive replies. Generally a reply can only be received after sending a
#' request.
#'
#' **\[protocol, rep\]** The rep protocol is one half of a request/reply
#' pattern. This socket may be used to receive messages (requests), and then to
#' send replies. Generally a reply can only be sent after receiving a request.
#'
#' @section Surveyor/Respondent (voting & service discovery):
#'
#' In a survey pattern, a surveyor sends a survey, which is broadcast to all
#' peer respondents. The respondents then have a chance to reply (but are not
#' obliged). The survey itself is a timed event, so that responses received
#' after the survey has finished are discarded.
#'
#' **\[protocol, surveyor\]** The surveyor protocol is one half of a survey
#' pattern. This protocol may be used to send messages (surveys), and then to
#' receive replies. A reply can only be received after sending a survey. A
#' surveyor can normally expect to receive at most one reply from each responder
#' (messages may be duplicated in some topologies, so there is no guarantee of
#' this).
#'
#' **\[protocol, respondent\]** The respondent protocol is one half of a survey
#' pattern. This protocol may be used to receive messages, and then to send
#' replies. A reply can only be sent after receiving a survey, and generally the
#' reply will be sent to the surveyor from which the last survey was received.
#'
#' @name protocols
#'
NULL
#' Transports (Documentation)
#'
#' @description Transports supported by \pkg{nanonext}.
#'
#' For an authoritative guide please refer to the online documentation for the
#' NNG library at .
#'
#' @section Inproc:
#'
#' The inproc transport provides communication support between sockets within
#' the same process. This may be used as an alternative to slower transports
#' when data must be moved within the same process. This transport tries hard to
#' avoid copying data, and thus is very light-weight.
#'
#' **\[URI, inproc://\]** This transport uses URIs using the scheme inproc://,
#' followed by an arbitrary string of text, terminated by a NUL byte.
#' inproc://nanonext is a valid example URL.
#'
#' \itemize{
#' \item Multiple URIs can be used within the same application, and they will
#' not interfere with one another.
#'
#' \item Two applications may also use the same URI without interfering with
#' each other, and they will be unable to communicate with each other using
#' that URI.
#' }
#'
#' @section IPC:
#'
#' The IPC transport provides communication support between sockets within
#' different processes on the same host. For POSIX platforms, this is
#' implemented using UNIX domain sockets. For Windows, this is implemented using
#' Windows Named Pipes. Other platforms may have different implementation
#' strategies.
#'
#' *Traditional Names*
#'
#' **\[URI, ipc://\]** This transport uses URIs using the scheme ipc://,
#' followed by a path name in the file system where the socket or named pipe
#' should be created.
#'
#' \itemize{
#' \item On POSIX platforms, the path is taken literally, and is relative to
#' the current directory, unless it begins with /, in which case it is
#' relative to the root directory. For example, ipc://nanonext refers to the
#' name nanonext in the current directory, whereas ipc:///tmp/nanonext refers
#' to nanonext located in /tmp.
#' \item On Windows, all names are prefixed by \\.\ pipe\ and do not reside in
#' the normal file system - the required prefix is added automatically by NNG,
#' so a URL of the form ipc://nanonext is fine.
#' }
#'
#' *UNIX Aliases*
#'
#' **\[URI, unix://\]** The unix:// scheme is an alias for ipc:// and can be
#' used inter-changeably, but only on POSIX systems. The purpose of this scheme
#' is to support a future transport making use of AF_UNIX on Windows systems, at
#' which time it will be necessary to discriminate between the Named Pipes and
#' the AF_UNIX based transports.
#'
#' *Abstract Names*
#'
#' **\[URI, abstract://\]** On Linux, this transport also can support abstract
#' sockets. Abstract sockets use a URI-encoded name after the scheme, which
#' allows arbitrary values to be conveyed in the path, including embedded NUL
#' bytes. abstract://nanonext is a valid example URL.
#'
#' \itemize{
#' \item Abstract sockets do not have any representation in the file system,
#' and are automatically freed by the system when no longer in use. Abstract
#' sockets ignore socket permissions, but it is still possible to determine
#' the credentials of the peer.
#' }
#'
#' @section TCP/IP:
#'
#' The TCP transport provides communication support between sockets across a
#' TCP/IP network. Both IPv4 and IPv6 are supported when supported by the
#' underlying platform.
#'
#' **\[URI, tcp://\]** This transport uses URIs using the scheme tcp://,
#' followed by an IP address or hostname, followed by a colon and finally a TCP
#' port number. For example, to contact port 80 on the localhost either of the
#' following URIs could be used: tcp://127.0.0.1:80 or tcp://localhost:80.
#'
#' \itemize{
#' \item A URI may be restricted to IPv6 using the scheme tcp6://, and may be
#' restricted to IPv4 using the scheme tcp4://
#'
#' \item Note: Specifying tcp6:// may not prevent IPv4 hosts from being used
#' with IPv4-in-IPv6 addresses, particularly when using a wildcard hostname
#' with listeners. The details of this varies across operating systems.
#'
#' \item Note: both tcp6:// and tcp4:// are specific to NNG, and might not be
#' understood by other implementations.
#'
#' \item It is recommended to use either numeric IP addresses, or names that
#' are specific to either IPv4 or IPv6 to prevent confusion and surprises.
#'
#' \item When specifying IPv6 addresses, the address must be enclosed in
#' square brackets (\[\]) to avoid confusion with the final colon separating
#' the port. For example, the same port 80 on the IPv6 loopback address (::1)
#' would be specified as tcp://\[::1\]:80.
#'
#' \item The special value of 0 (INADDR_ANY) can be used for a listener to
#' indicate that it should listen on all interfaces on the host. A shorthand
#' for this form is to either omit the address, or specify the asterisk (*)
#' character. For example, the following three URIs are all equivalent, and
#' could be used to listen to port 9999 on the host: (1) tcp://0.0.0.0:9999
#' (2) tcp://*:9999 (3) tcp://:9999
#' }
#'
#' @section TLS:
#'
#' The TLS transport provides communication support between peers across a
#' TCP/IP network using TLS v1.2 on top of TCP. Both IPv4 and IPv6 are supported
#' when supported by the underlying platform.
#'
#' **\[URI, tls+tcp://\]** This transport uses URIs using the scheme tls+tcp://,
#' followed by an IP address or hostname, followed by a colon and finally a TCP
#' port number. For example, to contact port 4433 on the localhost either of the
#' following URIs could be used: tls+tcp://127.0.0.1:4433 or
#' tls+tcp://localhost:4433.
#' \itemize{
#' \item A URI may be restricted to IPv6 using the scheme tls+tcp6://, or IPv4
#' using the scheme tls+tcp4://.
#' }
#'
#' @section WebSocket:
#'
#' The ws and wss transport provides communication support between peers across
#' a TCP/IP network using WebSockets. Both IPv4 and IPv6 are supported when
#' supported by the underlying platform.
#'
#' **\[URI, ws://\]** This transport uses URIs using the scheme ws://, followed
#' by an IP address or hostname, optionally followed by a colon and a TCP port
#' number, optionally followed by a path. (If no port number is specified then
#' port 80 is assumed. If no path is specified then a path of / is assumed.) For
#' example, the URI ws://localhost/app/pubsub would use port 80 on localhost,
#' with the path /app/pubsub.
#'
#' **\[URI, wss://\]** Secure WebSockets use the scheme wss://, and the default
#' TCP port number of 443. Otherwise the format is the same as for regular
#' WebSockets.
#'
#' \itemize{
#' \item A URI may be restricted to IPv6 using the scheme ws6:// or wss6://,
#' or IPv4 using the scheme ws4:// or wss4://.
#'
#' \item When specifying IPv6 addresses, the address must be enclosed in
#' square brackets (\[\]) to avoid confusion with the final colon separating
#' the port. For example, the same path and port on the IPv6 loopback address
#' (::1) would be specified as ws://\[::1\]/app/pubsub.
#'
#' \item Note: The value specified as the host, if any, will also be used in
#' the Host: HTTP header during HTTP negotiation.
#'
#' \item To listen to all ports on the system, the host name may be elided
#' from the URL on the listener. This will wind up listening to all interfaces
#' on the system, with possible caveats for IPv4 and IPv6 depending on what
#' the underlying system supports. (On most modern systems it will map to the
#' special IPv6 address ::, and both IPv4 and IPv6 connections will be
#' permitted, with IPv4 addresses mapped to IPv6 addresses.)
#'
#' \item This transport makes use of shared HTTP server instances, permitting
#' multiple sockets or listeners to be configured with the same hostname and
#' port. When creating a new listener, it is registered with an existing HTTP
#' server instance if one can be found. Note that the matching algorithm is
#' somewhat simple, using only a string based hostname or IP address and port
#' to match. Therefore it is recommended to use only IP addresses or the empty
#' string as the hostname in listener URLs.
#'
#' \item All sharing of server instances is only typically possible within the
#' same process.
#'
#' \item The server may also be used by other things (for example to serve
#' static content), in the same process.
#' }
#'
#' @name transports
#'
NULL
nanonext/cleanup.win 0000755 0001762 0000144 00000000054 15142221674 014260 0 ustar ligges users rm -rf src/Makevars nano-build nano-install
nanonext/LICENSE.note 0000644 0001762 0000144 00000034475 15166504164 014102 0 ustar ligges users License notices:
nanonext links to the NNG library with the following licence:
The MIT License
Copyright 2021 Staysail Systems, Inc.
Copyright 2018 Capitar IT Group BV
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom
the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
nanonext also links to the Mbed TLS library with the following licence:
Copyright The Mbed TLS Contributors
SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
The serialization hooks implementation uses code from the sakura project with
the following licence:
The MIT License
Copyright (c) 2025 sakura authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
The L'Ecuyer-CMRG RNG stream jumping implementation in src/dispatcher.c uses
moduli and jump matrix constants from the RngStreams package by Pierre L'Ecuyer,
University of Montreal (https://github.com/umontreal-simul/RngStreams), licensed
under the Apache License, Version 2.0 (same terms as reproduced above for
Mbed TLS). The original copyright notice requests citation of:
P. L'Ecuyer, "Good Parameter Sets for Combined Multiple Recursive Random
Number Generators", Operations Research, 47, 1 (1999), 159-164.
P. L'Ecuyer, R. Simard, E. J. Chen, and W. D. Kelton, "An Objected-
Oriented Random-Number Package with Many Long Streams and Substreams",
Operations Research, 50, 6 (2002), 1073-1075.
nanonext/cleanup 0000755 0001762 0000144 00000000066 15176113106 013464 0 ustar ligges users #!/bin/sh
rm -rf src/Makevars nano-build nano-install
nanonext/vignettes/ 0000755 0001762 0000144 00000000000 15176113105 014114 5 ustar ligges users nanonext/vignettes/nanonext.Rmd 0000644 0001762 0000144 00000022407 15142221674 016423 0 ustar ligges users ---
title: "nanonext - Quick Reference"
vignette: >
%\VignetteIndexEntry{nanonext - Quick Reference}
%\VignetteEngine{litedown::vignette}
%\VignetteEncoding{UTF-8}
---
## Core Concepts
**nanonext** provides bindings to NNG (Nanomsg Next Gen), a high-performance messaging library for building distributed systems.
This is a cheatsheet. Refer to the other vignettes for detailed introductions:
- [Messaging and Async I/O](v01-messaging.html) - cross-language exchange, async operations, synchronisation
- [Scalability Protocols](v02-protocols.html) - req/rep, pub/sub, surveyor/respondent
- [Configuration and Security](v03-configuration.html) - TLS, options, serialization, statistics
- [Web Toolkit](v04-web.html) - HTTP client/server, WebSocket, streaming
## Key Takeaways
- **Sockets** connect via URLs using scalability protocols (req/rep, pub/sub, etc.)
- **Transports**: `inproc://` (in-process), `ipc://` (inter-process), `tcp://`, `ws://`, `wss://`, `tls+tcp://`
- **Async I/O**: `send_aio()` / `recv_aio()` return immediately; access results via `$data` or `$result`
- **Modes**: `"serial"` (R objects), `"raw"` (bytes), `"double"`, `"integer"`, `"character"`, etc.
- **Condition variables**: `cv()` for zero-latency event synchronisation
## 1. Sockets and Connections
### Create Sockets
```r
library(nanonext)
# Functional interface
s <- socket("pair")
listen(s, "tcp://127.0.0.1:5555")
dial(s, "tcp://127.0.0.1:5555")
# Object-oriented interface
n <- nano("pair", listen = "tcp://127.0.0.1:5555")
n$dial("tcp://127.0.0.1:5556")
# Close when done
close(s)
n$close()
```
### Protocols
| Protocol | Description | Socket Types |
|----------|-------------|--------------|
| **Pair** | 1-to-1 bidirectional | `"pair"` |
| **Poly** | Polyamorous pair | `"poly"` |
| **Pipeline** | One-way data flow | `"push"`, `"pull"` |
| **Req/Rep** | RPC pattern | `"req"`, `"rep"` |
| **Pub/Sub** | Broadcast/subscribe | `"pub"`, `"sub"` |
| **Survey** | Query all peers | `"surveyor"`, `"respondent"` |
| **Bus** | Many-to-many mesh | `"bus"` |
### Transports
| URL Scheme | Description |
|------------|-------------|
| `inproc://name` | In-process (fastest, same process) |
| `ipc:///path` | Inter-process (Unix socket / named pipe) |
| `tcp://host:port` | TCP/IP network |
| `ws://host:port/path` | WebSocket |
| `wss://host:port/path` | WebSocket over TLS |
| `tls+tcp://host:port` | TLS encrypted TCP |
## 2. Send and Receive
### Synchronous
```r
# Send R object (serialized)
send(s, data.frame(a = 1, b = 2))
# Receive R object
recv(s)
# Send raw bytes (for cross-language exchange)
send(s, c(1.1, 2.2, 3.3), mode = "raw")
# Receive as specific type
recv(s, mode = "double")
recv(s, mode = "character")
recv(s, mode = "raw")
```
### Receive Modes
| Mode | Description |
|------|-------------|
| `"serial"` / `1` | R serialization (default) |
| `"character"` / `2` | Coerce to character |
| `"complex"` / `3` | Coerce to complex |
| `"double"` / `4` | Coerce to double |
| `"integer"` / `5` | Coerce to integer |
| `"logical"` / `6` | Coerce to logical |
| `"numeric"` / `7` | Coerce to numeric |
| `"raw"` / `8` | Raw bytes |
| `"string"` / `9` | Fast option for length-1 character |
## 3. Async I/O
### Basic Async
```r
# Async send - returns immediately
res <- send_aio(s, data)
res$result # 0 = success, error code otherwise
# Async receive - returns immediately
msg <- recv_aio(s)
msg$data # Value when resolved, 'unresolved' NA otherwise
# Check if resolved
unresolved(msg) # TRUE while pending
# Wait for resolution
call_aio(msg) # Blocks, returns Aio object
collect_aio(msg) # Blocks, returns value directly
msg[] # Blocks (user-interruptible), returns value
```
### Non-blocking Patterns
```r
# Poll while doing other work
while (unresolved(msg)) {
# do other tasks
}
result <- msg$data
# Multiple async operations
msg1 <- recv_aio(s1)
msg2 <- recv_aio(s2)
# Both run concurrently
```
## 4. Condition Variables
### Basics
```r
# Create condition variable
cv <- cv()
# Check/signal
cv_value(cv) # Get counter value
cv_signal(cv) # Increment counter
cv_reset(cv) # Reset to zero
# Wait (blocks until counter > 0, then decrements)
wait(cv)
# Wait with timeout (ms), returns FALSE on timeout
until(cv, 1000)
```
### Pipe Notifications
```r
# Signal on connection/disconnection
pipe_notify(socket, cv = cv, add = TRUE, remove = TRUE)
# Distinguish message vs disconnect with flag
pipe_notify(socket, cv = cv, remove = TRUE, flag = TRUE)
r <- recv_aio(socket, cv = cv)
wait(cv) || stop("disconnected") # FALSE = pipe event
```
### Async with CV
```r
cv <- cv()
msg <- recv_aio(s, cv = cv)
wait(cv) # Wake on receive completion
msg$data
```
## 5. Request/Reply (RPC)
### Server
```r
rep <- socket("rep", listen = "tcp://127.0.0.1:5555")
ctx <- context(rep)
# reply() blocks, waiting for request
reply(ctx, execute = my_function, send_mode = "raw")
close(rep)
```
### Client
```r
req <- socket("req", dial = "tcp://127.0.0.1:5555")
ctx <- context(req)
# request() returns immediately
aio <- request(ctx, data = args, recv_mode = "double")
# Do other work while server processes...
# Get result when needed
result <- aio[]
close(req)
```
## 6. Pub/Sub
```r
pub <- socket("pub", listen = "inproc://pubsub")
sub <- socket("sub", dial = "inproc://pubsub")
# Subscribe to topic (prefix matching)
subscribe(sub, topic = "news")
subscribe(sub, topic = NULL) # All topics
# Unsubscribe
unsubscribe(sub, topic = "news")
# Publish (topic is message prefix)
send(pub, c("news", "headline"), mode = "raw")
# Receive (includes topic)
recv(sub, mode = "character")
close(pub)
close(sub)
```
## 7. Surveyor/Respondent
```r
sur <- socket("surveyor", listen = "inproc://survey")
res1 <- socket("respondent", dial = "inproc://survey")
res2 <- socket("respondent", dial = "inproc://survey")
# Set survey timeout (ms)
survey_time(sur, 500)
# Broadcast survey
send(sur, "ping")
# Collect responses (async)
aio1 <- recv_aio(sur)
aio2 <- recv_aio(sur)
# Respondents reply
recv(res1)
send(res1, "pong1")
# Late/missing responses timeout (errorValue 5)
msleep(500)
aio2$data # errorValue if no response
close(sur)
close(res1)
close(res2)
```
## 8. TLS Secure Connections
### Self-signed Certificates
```r
# Generate certificate (cn must match URL host exactly)
cert <- write_cert(cn = "127.0.0.1")
# Create TLS configs
server_tls <- tls_config(server = cert$server)
client_tls <- tls_config(client = cert$client)
# Use with tls+tcp:// or wss://
s1 <- socket(listen = "tls+tcp://127.0.0.1:5555", tls = server_tls)
s2 <- socket(dial = "tls+tcp://127.0.0.1:5555", tls = client_tls)
```
### CA Certificates
```r
# Client with CA cert file
client_tls <- tls_config(client = "/path/to/ca-cert.pem")
# Server with cert + key
server_tls <- tls_config(server = c("/path/to/cert.pem", "/path/to/key.pem"))
```
## 9. Options and Statistics
### Get/Set Options
```r
# Delayed start for configuration
s <- socket(listen = "tcp://127.0.0.1:5555", autostart = FALSE)
# Get option
opt(s$listener[[1]], "recv-size-max")
# Set option
opt(s$listener[[1]], "recv-size-max") <- 8192L
# Start after configuration
start(s$listener[[1]])
```
### Common Options
| Option | Description |
|--------|-------------|
| `"recv-size-max"` | Max message size (0 = unlimited) |
| `"send-timeout"` | Send timeout (ms) |
| `"recv-timeout"` | Receive timeout (ms) |
| `"reconnect-time-min"` | Min reconnect interval (ms) |
| `"reconnect-time-max"` | Max reconnect interval (ms) |
| `"req:resend-time"` | Request retry interval |
| `"sub:prefnew"` | Prefer newer messages |
### Custom Serialization
```r
# Register custom serializer for a class
serial <- serial_config(
"class_name",
function(x) serialize(x, NULL), # serialize
unserialize # unserialize
)
opt(socket, "serial") <- serial
```
### Statistics
```r
stat(socket, "pipes") # Active connections
stat(listener, "accept") # Connection attempts
stat(dialer, "reject") # Rejected connections
```
## 10. Contexts
Contexts enable concurrent operations on a single socket (for req/rep, surveyor/respondent).
```r
s <- socket("req", dial = "tcp://127.0.0.1:5555")
# Create independent contexts
ctx1 <- context(s)
ctx2 <- context(s)
# Concurrent requests
aio1 <- request(ctx1, data1)
aio2 <- request(ctx2, data2)
# Close contexts (or they close with socket)
close(ctx1)
close(ctx2)
close(s)
```
## 11. Cross-language Exchange
### R to Python (NumPy)
```r
# R: send raw doubles
n <- nano("pair", dial = "ipc:///tmp/nanonext")
n$send(c(1.1, 2.2, 3.3), mode = "raw")
result <- n$recv(mode = "double")
```
```python
# Python: receive as NumPy array
import numpy as np
import pynng
socket = pynng.Pair0(listen="ipc:///tmp/nanonext")
array = np.frombuffer(socket.recv())
socket.send(array.tobytes())
```
## 12. Error Handling
```r
# Errors return as 'errorValue' class
result <- recv(s, block = FALSE)
# Check for errors
is_error_value(result)
# Error codes
# 5 = Timed out
# 6 = Connection refused
# 8 = Try again (non-blocking, no message)
# Get error message
nng_error(5) # "Timed out"
```
## 13. Utilities
```r
# Sleep (uninterruptible, ms)
msleep(100)
# Random bytes
random(8) # 8 random bytes as hex string
random(8, convert = FALSE) # As raw vector
# Parse URL
parse_url("tcp://127.0.0.1:5555")
```
nanonext/vignettes/v01-messaging.Rmd 0000644 0001762 0000144 00000012723 15142221674 017152 0 ustar ligges users ---
title: "nanonext - Messaging and Async I/O"
vignette: >
%\VignetteIndexEntry{nanonext - Messaging and Async I/O}
%\VignetteEngine{litedown::vignette}
%\VignetteEncoding{UTF-8}
---
### 1. Cross-language Exchange
`nanonext` provides a fast, reliable data interface between different programming languages where NNG has an implementation, including C, C++, Java, Python, Go, and Rust.
This messaging interface is lightweight, robust, and has limited points of failure. It enables:
- Communication between processes in the same or different languages
- Distributed computing across networks or on the same machine
- Real-time data pipelines where computation times exceed data frequency
- Modular software design following Unix philosophy
This example demonstrates numerical data exchange between R and Python (NumPy).
Create socket in Python using the NNG binding 'pynng':
``` python
import numpy as np
import pynng
socket = pynng.Pair0(listen="ipc:///tmp/nanonext.socket")
```
Create nano object in R using `nanonext`, then send a vector of 'doubles', specifying mode as 'raw':
``` r
library(nanonext)
n <- nano("pair", dial = "ipc:///tmp/nanonext.socket")
n$send(c(1.1, 2.2, 3.3, 4.4, 5.5), mode = "raw")
#> [1] 0
```
Receive in Python as a NumPy array of 'floats', and send back to R:
``` python
raw = socket.recv()
array = np.frombuffer(raw)
print(array)
#> [1.1 2.2 3.3 4.4 5.5]
msg = array.tobytes()
socket.send(msg)
socket.close()
```
Receive in R, specifying the receive mode as 'double':
``` r
n$recv(mode = "double")
#> [1] 1.1 2.2 3.3 4.4 5.5
n$close()
```
### 2. Async and Concurrency
`nanonext` implements true async send and receive, leveraging NNG as a massively-scalable concurrency framework.
``` r
s1 <- socket("pair", listen = "inproc://nano")
s2 <- socket("pair", dial = "inproc://nano")
```
`send_aio()` and `recv_aio()` return immediately with an 'Aio' object that performs operations asynchronously. Aio objects return an unresolved value while the operation is ongoing, then automatically resolve once complete.
``` r
# async receive requested, but no messages waiting yet
msg <- recv_aio(s2)
msg
#> < recvAio | $data >
msg$data
#> 'unresolved' logi NA
```
For 'sendAio' objects, the result is stored at `$result`. For 'recvAio' objects, the message is stored at `$data`.
``` r
res <- send_aio(s1, data.frame(a = 1, b = 2))
res
#> < sendAio | $result >
res$result
#> [1] 0
```
> 0 indicates successful send - the message has been accepted by the socket for sending but may still be buffered within the system.
``` r
# once a message is sent, the recvAio resolves automatically
msg$data
#> a b
#> 1 1 2
```
Use `unresolved()` in control flow to perform actions before or after Aio resolution without blocking.
``` r
msg <- recv_aio(s2)
# unresolved() checks resolution status
while (unresolved(msg)) {
# perform other tasks while waiting
send_aio(s1, "resolved")
cat("unresolved")
}
#> unresolved
# access resolved value
msg$data
#> [1] "resolved"
```
Explicitly wait for completion with `call_aio()` (blocking).
``` r
# wait for completion and return resolved Aio
call_aio(msg)
# access resolved value (waiting if required):
call_aio(msg)$data
#> [1] "resolved"
# or directly:
collect_aio(msg)
#> [1] "resolved"
# or user-interruptible:
msg[]
#> [1] "resolved"
close(s1)
close(s2)
```
### 3. Synchronisation Primitives
`nanonext` implements cross-platform synchronisation primitives from the NNG library, enabling synchronisation between NNG events and the main R execution thread.
Condition variables can signal events such as asynchronous receive completions and pipe events (connections established or dropped). Each condition variable has a value (counter) and flag (boolean). Signals increment the value; successful `wait()` or `until()` calls decrement it. A non-zero value allows waiting threads to continue.
This approach is more efficient than polling - consuming no resources while waiting and synchronising with zero latency.
**Example 1: Wait for connection**
``` r
sock <- socket("pair", listen = "inproc://nanopipe")
cv <- cv()
cv_value(cv)
#> [1] 0
pipe_notify(sock, cv = cv, add = TRUE, remove = TRUE)
# wait(cv) would block until connection established
# for illustration:
sock2 <- socket("pair", dial = "inproc://nanopipe")
cv_value(cv) # incremented when pipe created
#> [1] 1
wait(cv) # does not block as cv value is non-zero
cv_value(cv) # decremented by wait()
#> [1] 0
close(sock2)
cv_value(cv) # incremented when pipe destroyed
#> [1] 1
close(sock)
```
**Example 2: Wait for message or disconnection**
``` r
sock <- socket("pair", listen = "inproc://nanosignal")
sock2 <- socket("pair", dial = "inproc://nanosignal")
cv <- cv()
cv_value(cv)
#> [1] 0
pipe_notify(sock, cv = cv, add = FALSE, remove = TRUE, flag = TRUE)
send(sock2, "this message will wake waiting thread")
#> [1] 0
r <- recv_aio(sock, cv = cv)
# wakes when async receive completes
wait(cv) || stop("peer disconnected")
#> [1] TRUE
r$data
#> [1] "this message will wake waiting thread"
close(sock)
close(sock2)
```
When `flag = TRUE` is set for pipe notifications, `wait()` returns FALSE for pipe events (rather than TRUE for message events). This distinguishes between disconnections and successful receives, something not possible using `call_aio()` alone.
This mechanism enables waiting simultaneously on multiple events while distinguishing between them. `pipe_notify()` can signal up to two condition variables per event for additional flexibility in concurrent applications.
nanonext/vignettes/v04-web.Rmd 0000644 0001762 0000144 00000041644 15163546346 015772 0 ustar ligges users ---
title: "nanonext - Web Toolkit"
vignette: >
%\VignetteIndexEntry{nanonext - Web Toolkit}
%\VignetteEngine{litedown::vignette}
%\VignetteEncoding{UTF-8}
---
``` r
library(nanonext)
```
nanonext provides high-performance HTTP/WebSocket client and server capabilities built on NNG's networking stack with Mbed TLS for secure connections.
### 1. HTTP Client
#### ncurl: Basic Requests
`ncurl()` is a minimalist HTTP(S) client. Basic usage requires only a URL.
``` r
ncurl("https://postman-echo.com/get")
#> $status
#> [1] 200
#>
#> $headers
#> NULL
#>
#> $data
#> [1] "{\"args\":{},\"headers\":{\"host\":\"postman-echo.com\",\"accept-encoding\":\"gzip, br\",\"x-forwarded-proto\":\"https\"},\"url\":\"https://postman-echo.com/get\"}"
```
Advanced usage supports all HTTP methods (POST, PUT, DELETE, etc.), custom headers, and request bodies.
``` r
ncurl("https://postman-echo.com/post",
method = "POST",
headers = c(`Content-Type` = "application/json", Authorization = "Bearer APIKEY"),
data = '{"key": "value"}',
response = "date")
#> $status
#> [1] 200
#>
#> $headers
#> $headers$date
#> [1] "Mon, 30 Mar 2026 22:16:56 GMT"
#>
#>
#> $data
#> [1] "{\"args\":{},\"data\":{\"key\":\"value\"},\"files\":{},\"form\":{},\"headers\":{\"host\":\"postman-echo.com\",\"accept-encoding\":\"gzip, br\",\"content-length\":\"16\",\"authorization\":\"Bearer APIKEY\",\"content-type\":\"application/json\",\"x-forwarded-proto\":\"https\"},\"json\":{\"key\":\"value\"},\"url\":\"https://postman-echo.com/post\"}"
```
Specify `response = TRUE` to return all response headers.
``` r
ncurl("https://postman-echo.com/get",
response = TRUE)
#> $status
#> [1] 200
#>
#> $headers
#> $headers$Date
#> [1] "Mon, 30 Mar 2026 22:16:56 GMT"
#>
#> $headers$`Content-Type`
#> [1] "application/json; charset=utf-8"
#>
#> $headers$`Content-Length`
#> [1] "143"
#>
#> $headers$Connection
#> [1] "close"
#>
#> $headers$etag
#> [1] "W/\"8f-7zN8nSad8A9WlFJjKQZB04z5nHE\""
#>
#> $headers$vary
#> [1] "Accept-Encoding"
#>
#> $headers$`x-envoy-upstream-service-time`
#> [1] "5"
#>
#> $headers$`cf-cache-status`
#> [1] "DYNAMIC"
#>
#> $headers$`Set-Cookie`
#> [1] "sails.sid=s%3AtfbN2TazlwshVuIc_Y-l_CKCt7WScK3s.5Z30lz615FemZ1kCseuVIUD4G%2BEAGJfIiYaJhfFDiGU; Path=/; HttpOnly, __cf_bm=mX8EtQ27DKZ2X6uAi8_krT8AN00Bf9rEnI4aauRi_.Q-1774909016.466189-1.0.1.1-Wtzs3qUkHi8sLuARpkSeFbJ8vXysEL7bXJdqKa3EqeWEanTLfzrcxvOudXHbpT8moKuZq5KuQXmZr0dSvqRdXUuc_yS8Ms47TY9OL3jb0muL_3gxejNDW7cDvtDcmLKh; HttpOnly; Secure; Path=/; Domain=postman-echo.com; Expires=Mon, 30 Mar 2026 22:46:56 GMT, _cfuvid=PgOu7XWt97qqSBedwhgeqXxkMVIYi18WzlBWroe865k-1774909016.466189-1.0.1.1-z9Y3X8DXI.zgMtblcU7939qcoMyvq2dQUGtA9Mbi59o; HttpOnly; SameSite=None; Secure; Path=/; Domain=postman-echo.com"
#>
#> $headers$Server
#> [1] "cloudflare"
#>
#> $headers$`CF-RAY`
#> [1] "9e4a7b48ed31cd3a-LHR"
#>
#>
#> $data
#> [1] "{\"args\":{},\"headers\":{\"host\":\"postman-echo.com\",\"accept-encoding\":\"gzip, br\",\"x-forwarded-proto\":\"https\"},\"url\":\"https://postman-echo.com/get\"}"
```
#### ncurl_aio: Async Requests
`ncurl_aio()` performs asynchronous requests, returning immediately with an 'ncurlAio' object that resolves when the response arrives.
``` r
res <- ncurl_aio("https://postman-echo.com/post",
method = "POST",
headers = c(`Content-Type` = "application/json"),
data = '{"async": true}',
response = "date")
res
#> < ncurlAio | $status $headers $data >
call_aio(res)$headers
#> $date
#> [1] "Mon, 30 Mar 2026 22:16:56 GMT"
res$status
#> [1] 200
res$data
#> [1] "{\"args\":{},\"data\":{\"async\":true},\"files\":{},\"form\":{},\"headers\":{\"host\":\"postman-echo.com\",\"accept-encoding\":\"gzip, br\",\"content-type\":\"application/json\",\"content-length\":\"15\",\"x-forwarded-proto\":\"https\"},\"json\":{\"async\":true},\"url\":\"https://postman-echo.com/post\"}"
```
##### Promises Integration
'ncurlAio' objects work anywhere that accepts a 'promise' from the promises package, including Shiny ExtendedTask.
``` r
library(promises)
p <- ncurl_aio("https://postman-echo.com/get") |> then(\(x) cat(x$data))
is.promise(p)
#> [1] TRUE
```
#### ncurl_session: Persistent Connections
`ncurl_session()` creates a reusable connection for efficient repeated requests to an API endpoint. Use `transact()` to send requests over the session.
``` r
sess <- ncurl_session("https://postman-echo.com/get",
convert = FALSE,
headers = c(`Content-Type` = "application/json"),
response = c("Date", "Content-Type"))
sess
#> < ncurlSession > - transact() to return data
transact(sess)
#> $status
#> [1] 200
#>
#> $headers
#> $headers$Date
#> [1] "Mon, 30 Mar 2026 22:16:57 GMT"
#>
#> $headers$`Content-Type`
#> [1] "application/json; charset=utf-8"
#>
#>
#> $data
#> [1] 7b 22 61 72 67 73 22 3a 7b 7d 2c 22 68 65 61 64 65 72 73 22 3a 7b 22 68 6f
#> [26] 73 74 22 3a 22 70 6f 73 74 6d 61 6e 2d 65 63 68 6f 2e 63 6f 6d 22 2c 22 63
#> [51] 6f 6e 74 65 6e 74 2d 74 79 70 65 22 3a 22 61 70 70 6c 69 63 61 74 69 6f 6e
#> [76] 2f 6a 73 6f 6e 22 2c 22 78 2d 66 6f 72 77 61 72 64 65 64 2d 70 72 6f 74 6f
#> [101] 22 3a 22 68 74 74 70 73 22 2c 22 61 63 63 65 70 74 2d 65 6e 63 6f 64 69 6e
#> [126] 67 22 3a 22 67 7a 69 70 2c 20 62 72 22 7d 2c 22 75 72 6c 22 3a 22 68 74 74
#> [151] 70 73 3a 2f 2f 70 6f 73 74 6d 61 6e 2d 65 63 68 6f 2e 63 6f 6d 2f 67 65 74
#> [176] 22 7d
close(sess)
```
### 2. WebSocket Client
`stream()` provides a low-level byte stream interface for communicating with WebSocket servers and other non-NNG endpoints.
Use `textframes = TRUE` for servers that expect text frames (most WebSocket servers).
``` r
s <- stream(dial = "wss://echo.websocket.org/", textframes = TRUE)
s
#> < nanoStream >
#> - mode: dialer text frames
#> - state: opened
#> - url: wss://echo.websocket.org/
```
`send()` and `recv()`, along with their async counterparts `send_aio()` and `recv_aio()`, work on Streams just like Sockets.
``` r
s |> recv()
#> [1] "Request served by 4d896d95b55478"
s |> send("hello websocket")
#> [1] 0
s |> recv()
#> [1] "hello websocket"
s |> recv_aio() -> r
s |> send("async message")
#> [1] 0
r[]
#> [1] "async message"
close(s)
```
### 3. Unified HTTP/WebSocket Server
`http_server()` creates a single server that can handle HTTP requests, WebSocket connections, and HTTP streaming, all on the same port.
A single call to `http_server()` sets up one NNG server instance with a list of handlers. HTTP routes, WebSocket endpoints, streaming endpoints, and static file handlers all share the same underlying server -- there is no need to run separate processes or bind additional ports. WebSocket clients connect via the standard HTTP upgrade mechanism, so a browser can load a page over HTTP and open a WebSocket connection to the same origin without any cross-origin configuration.
``` r
server <- http_server(
url = "http://127.0.0.1:8080",
handlers = list(
handler("/", function(req) {
list(status = 200L, body = "Hello from nanonext!")
}),
handler("/api/data", function(req) {
list(
status = 200L,
headers = c("Content-Type" = "application/json"),
body = '{"value": 42}'
)
}, method = "GET")
)
)
server$serve()
```
`$serve()` is a blocking call that starts the server, runs the event loop to process requests, and automatically closes the server on interrupt (Ctrl+C). For non-blocking use, call `$start()` and `$close()` separately, with `repeat later::run_now(Inf)` to run the event loop manually.
Specifying port `0` in the URL lets the OS assign an available port. The actual port is reflected in `server$url` after `$start()` or `$serve()`, making it easy to set up test servers without port conflicts.
#### Handler Types
All handler types can be freely mixed in a single server's handler list:
| Handler | Purpose |
|:--------|:--------|
| `handler()` | HTTP request/response with R callback |
| `handler_ws()` | WebSocket with `on_message`, `on_open`, `on_close` callbacks |
| `handler_stream()` | Chunked HTTP streaming (SSE, NDJSON, custom) |
| `handler_file()` | Serve a single static file |
| `handler_directory()` | Serve a directory tree with automatic MIME types |
| `handler_inline()` | Serve in-memory content |
| `handler_redirect()` | HTTP redirect |
#### HTTP Request Handlers
`handler()` creates HTTP route handlers. The callback receives a request list with `method`, `uri`, `headers`, and `body`, and returns a response list with `status`, optional `headers`, and `body`.
``` r
# GET endpoint
h1 <- handler("/hello", function(req) {
list(status = 200L, body = "Hello!")
})
# POST endpoint echoing the request body
h2 <- handler("/echo", function(req) {
list(status = 200L, body = req$body)
}, method = "POST")
# Catch-all for any method under a path prefix
h3 <- handler("/api", function(req) {
list(
status = 200L,
headers = c("Content-Type" = "application/json"),
body = sprintf('{"method":"%s","uri":"%s"}', req$method, req$uri)
)
}, method = "*", prefix = TRUE)
```
#### Static Content Handlers
``` r
# Serve a single file
h_file <- handler_file("/favicon.ico", "path/to/favicon.ico")
# Serve a directory tree (automatic MIME type detection)
h_dir <- handler_directory("/static", "www/assets")
# Serve inline content
h_inline <- handler_inline("/robots.txt", "User-agent: *\nDisallow:",
content_type = "text/plain")
# Redirect requests
h_redirect <- handler_redirect("/old-page", "/new-page", status = 301L)
```
#### WebSocket Handlers
WebSockets provide full bidirectional communication -- the server can push messages to the client, and the client can send messages back.
`handler_ws()` creates WebSocket endpoints. NNG handles the HTTP upgrade handshake and all WebSocket framing (RFC 6455) automatically. Because WebSocket handlers share the same server as HTTP handlers, the browser can load a page and open a WebSocket to the same host and port with no additional setup.
``` r
clients <- list()
server <- http_server(
url = "http://127.0.0.1:8080",
handlers = list(
handler_ws(
"/chat",
on_message = function(ws, data) {
# Broadcast to all connected clients
for (client in clients) client$send(data)
},
on_open = function(ws, req) {
clients[[as.character(ws$id)]] <<- ws
},
on_close = function(ws) {
clients[[as.character(ws$id)]] <<- NULL
},
textframes = TRUE
)
)
)
server$serve()
```
The `ws` connection object provides:
- `ws$send(data)` - Send a message to the client
- `ws$close()` - Close the connection
- `ws$id` - Unique integer connection identifier
Multiple WebSocket endpoints can coexist on the same server, each with independent callbacks and connection tracking. Connection IDs are unique across the entire server, so they are safe to use as keys in a shared data structure spanning multiple handlers.
#### HTTP Streaming Handlers
When you only need to push data in one direction -- server to client -- streaming is a lighter-weight alternative to WebSockets. It works over plain HTTP, so any client that speaks HTTP can consume the stream without needing a WebSocket library.
`handler_stream()` enables HTTP streaming using chunked transfer encoding, supporting Server-Sent Events (SSE), newline-delimited JSON (NDJSON), and custom streaming formats. Like WebSocket handlers, streaming endpoints share the same server as all other handlers.
``` r
conns <- list()
server <- http_server(
url = "http://127.0.0.1:8080",
handlers = list(
# SSE endpoint
handler_stream("/events",
on_request = function(conn, req) {
conn$set_header("Content-Type", "text/event-stream")
conn$set_header("Cache-Control", "no-cache")
conns[[as.character(conn$id)]] <<- conn
conn$send(format_sse(data = "connected", id = "1"))
},
on_close = function(conn) {
conns[[as.character(conn$id)]] <<- NULL
}
),
# Trigger broadcast via POST
handler("/broadcast", function(req) {
msg <- format_sse(data = rawToChar(req$body), event = "message")
lapply(conns, function(c) c$send(msg))
list(status = 200L, body = "sent")
}, method = "POST")
)
)
server$serve()
```
#### Server-Sent Events
`format_sse()` formats messages according to the SSE specification for browser `EventSource` clients.
``` r
format_sse(data = "Hello")
#> [1] "data: Hello\n\n"
format_sse(data = "Update available", event = "notification", id = "42")
#> [1] "event: notification\nid: 42\ndata: Update available\n\n"
format_sse(data = "Line 1\nLine 2")
#> [1] "data: Line 1\ndata: Line 2\n\n"
```
The streaming connection object provides:
- `conn$send(data)` - Send a data chunk
- `conn$close()` - Close the connection
- `conn$set_status(code)` - Set HTTP status (before first send)
- `conn$set_header(name, value)` - Set response header (before first send)
- `conn$id` - Unique connection identifier
### 4. Secure Connections (TLS)
All web functions support TLS for secure HTTPS/WSS connections via `tls_config()`.
#### Public Internet HTTPS
When making HTTPS requests over the public internet, you should supply a TLS configuration to validate server certificates.
Root CA certificates in PEM format may be found at:
- Linux: `/etc/ssl/certs/ca-certificates.crt` or `/etc/pki/tls/certs/ca-bundle.crt`
- macOS: `/etc/ssl/cert.pem`
- Windows: download from the [Common CA Database](https://www.ccadb.org/resources) site run by Mozilla (select the Server Authentication SSL/TLS certificates text file). *This link is not endorsed; use at your own risk.*
``` r
tls <- tls_config(client = "/etc/ssl/cert.pem")
ncurl("https://www.google.com", tls = tls)
```
#### Self-Signed Certificates
For internal services or testing, generate self-signed certificates using `write_cert()`.
``` r
# Generate self-signed certificate for testing
cert <- write_cert(cn = "127.0.0.1")
# Server TLS configuration
ser <- tls_config(server = cert$server)
# Client TLS configuration
cli <- tls_config(client = cert$client)
```
Use the configurations with servers and clients:
``` r
# HTTPS server
server <- http_server(
url = "https://127.0.0.1:0",
handlers = list(
handler("/", function(req) list(status = 200L, body = "Secure!"))
),
tls = ser
)
server$start()
server
#> < nanoServer >
#> - url: https://127.0.0.1:55055
#> - state: started
# HTTPS client request
aio <- ncurl_aio(paste0(server$url, "/"), tls = cli)
while (unresolved(aio)) later::run_now(1)
#> {"args":{},"headers":{"host":"postman-echo.com","accept-encoding":"gzip, br","x-forwarded-proto":"https"},"url":"https://postman-echo.com/get"}
aio$status
#> [1] 200
aio$data
#> [1] "Secure!"
server$close()
```
### 5. Client Example: Shiny ExtendedTask
This example demonstrates using `ncurl_aio()` with Shiny's ExtendedTask for non-blocking HTTP requests.
If your Shiny app calls an external API, a slow or unresponsive endpoint will block the R process and freeze the app for *all* users, not just the one who triggered the request.
`ncurl_aio()` avoids this -- it performs the HTTP call on a background thread and returns a promise, so the R process stays free to serve other sessions.
It works anywhere that accepts a promise, including Shiny's ExtendedTask:
``` r
library(shiny)
library(bslib)
library(nanonext)
ui <- page_fluid(
p("The time is ", textOutput("current_time", inline = TRUE)),
hr(),
input_task_button("btn", "Fetch data"),
verbatimTextOutput("result")
)
server <- function(input, output, session) {
output$current_time <- renderText({
invalidateLater(1000)
format(Sys.time(), "%H:%M:%S %p")
})
task <- ExtendedTask$new(
function() ncurl_aio("https://postman-echo.com/get", response = TRUE)
) |> bind_task_button("btn")
observeEvent(input$btn, task$invoke())
output$result <- renderPrint(task$result()$headers)
}
shinyApp(ui, server)
```
### 6. Server Example: Quarto Site with Dynamic API
This example shows how the unified server architecture makes it straightforward to combine HTTP or WebSocket handlers to serve different content over the same port.
If you've rendered a Quarto website and want to serve it locally -- but also expose a dynamic API endpoint alongside it, that's possible with a single `http_server()` call:
``` r
library(nanonext)
server <- http_server(
url = "http://127.0.0.1:0",
handlers = list(
# Serve your rendered Quarto site
handler_directory("/", "_site"),
# Add a prediction API endpoint
handler("/api/predict", function(req) {
input <- secretbase::jsondec(req$body)
pred <- predict(model, newdata = input)
list(
status = 200L,
headers = c("Content-Type" = "application/json"),
body = secretbase::jsonenc(list(prediction = pred))
)
}, method = "POST")
)
)
server$start()
server$url
# Browse to the URL to see your Quarto site with a live API behind it
```
Static pages are served at native speed by NNG while the prediction endpoint is handled by R -- no separate processes or ports required. Adding TLS is a single argument.
nanonext/vignettes/v03-configuration.Rmd 0000644 0001762 0000144 00000006664 15142221674 020055 0 ustar ligges users ---
title: "nanonext - Configuration and Security"
vignette: >
%\VignetteIndexEntry{nanonext - Configuration and Security}
%\VignetteEngine{litedown::vignette}
%\VignetteEncoding{UTF-8}
---
``` r
library(nanonext)
```
### 1. TLS Secure Connections
Secure connections use NNG and Mbed TLS libraries. Enable them by:
1. Specifying a secure `tls+tcp://` or `wss://` URL
2. Passing a TLS configuration object to the 'tls' argument of `listen()` or `dial()`
Create TLS configurations with `tls_config()`:
- Client configuration: requires PEM-encoded CA certificate to verify server identity
- Server configuration: requires certificate and private key
Certificates may be supplied as files or character vectors. Valid X.509 certificates from Certificate Authorities are supported.
The convenience function `write_cert()` generates a 4096-bit RSA key pair and self-signed X.509 certificate. The 'cn' argument must match exactly the hostname/IP address of the URL (e.g., use '127.0.0.1' throughout, or 'localhost' throughout, not mixed).
``` r
cert <- write_cert(cn = "127.0.0.1")
str(cert)
#> List of 2
#> $ server: chr [1:2] "-----BEGIN CERTIFICATE-----\nMIIFOTCCAyGgAwIBAgIBATANBgkqhkiG9w0BAQsFADA0MRIwEAYDVQQDDAkxMjcu\nMC4wLjExETAPBgNV"| __truncated__ "-----BEGIN RSA PRIVATE KEY-----\nMIIJKAIBAAKCAgEA7bh7hshxv3wfY81Gkct1ffRlFB4XJj3vAH+wiM1l8Q9WAllX\nIfyEVwGdC665"| __truncated__
#> $ client: chr [1:2] "-----BEGIN CERTIFICATE-----\nMIIFOTCCAyGgAwIBAgIBATANBgkqhkiG9w0BAQsFADA0MRIwEAYDVQQDDAkxMjcu\nMC4wLjExETAPBgNV"| __truncated__ ""
ser <- tls_config(server = cert$server)
ser
#> < TLS server config | auth mode: optional >
cli <- tls_config(client = cert$client)
cli
#> < TLS client config | auth mode: required >
s <- socket(listen = "tls+tcp://127.0.0.1:5558", tls = ser)
s1 <- socket(dial = "tls+tcp://127.0.0.1:5558", tls = cli)
# secure TLS connection established
close(s1)
close(s)
```
### 2. Options
Use `opt()` and `'opt<-'()` to get and set options on Sockets, Contexts, Streams, Listeners, or Dialers. See function documentation for available options.
To configure dialers or listeners after creation, specify `autostart = FALSE` (configuration cannot be changed after starting).
``` r
s <- socket(listen = "inproc://options", autostart = FALSE)
# no maximum message size
opt(s$listener[[1]], "recv-size-max")
#> [1] 0
# enforce maximum message size to protect against denial-of-service attacks
opt(s$listener[[1]], "recv-size-max") <- 8192L
opt(s$listener[[1]], "recv-size-max")
#> [1] 8192
start(s$listener[[1]])
```
### 3. Custom Serialization
The special write-only option 'serial' sets a serialization configuration via `serial_config()`. This registers custom functions for serializing/unserializing reference objects using R's 'refhook' system, enabling transparent send/receive with mode 'serial'. Configurations apply to the Socket and all Contexts created from it.
``` r
serial <- serial_config("obj_class", function(x) serialize(x, NULL), unserialize)
opt(s, "serial") <- serial
close(s)
```
### 4. Statistics
Use `stat()` to access NNG's statistics framework. Query Sockets, Listeners, or Dialers for statistics such as connection attempts and current connections. See function documentation for available statistics.
``` r
s <- socket(listen = "inproc://stat")
# no active connections (pipes)
stat(s, "pipes")
#> [1] 0
s1 <- socket(dial = "inproc://stat")
# one now that the dialer has connected
stat(s, "pipes")
#> [1] 1
close(s)
```
nanonext/vignettes/v02-protocols.Rmd 0000644 0001762 0000144 00000012044 15142221674 017216 0 ustar ligges users ---
title: "nanonext - Scalability Protocols"
vignette: >
%\VignetteIndexEntry{nanonext - Scalability Protocols}
%\VignetteEngine{litedown::vignette}
%\VignetteEncoding{UTF-8}
---
``` r
library(nanonext)
```
### 1. Request Reply Protocol
`nanonext` implements remote procedure calls (RPC) using NNG's req/rep protocol for distributed computing. Use this for computationally-expensive calculations or I/O-bound operations in separate server processes.
**[S] Server process:** `reply()` waits for a message, applies a function, and sends back the result. Started in a background 'mirai' process.
``` r
m <- mirai::mirai({
library(nanonext)
rep <- socket("rep", listen = "tcp://127.0.0.1:6556")
reply(context(rep), execute = rnorm, send_mode = "raw")
Sys.sleep(2) # linger period to flush system socket send
})
```
**[C] Client process:** `request()` performs async send/receive, returning immediately with a `recvAio` object.
``` r
req <- socket("req", dial = "tcp://127.0.0.1:6556")
aio <- request(context(req), data = 1e8, recv_mode = "double")
```
The client can now run additional code while the server processes the request.
``` r
# do more...
```
When the result is needed, call the recvAio using `call_aio()` to retrieve the value at `$data`.
``` r
call_aio(aio)$data |> str()
#> num [1:100000000] -0.63 0.883 1.134 -0.474 -0.237 ...
```
Since `call_aio()` blocks, alternatively query `aio$data` directly, which returns 'unresolved' (logical NA) if incomplete.
For server-side operations (e.g., writing to disk), calling or querying the value confirms completion and provides the function's return value (typically NULL or an exit code).
The [`mirai`](https://doi.org/10.5281/zenodo.7912722) package () uses `nanonext` as the back-end to provide asynchronous execution of arbitrary R code using the RPC model.
### 2. Publisher Subscriber Protocol
`nanonext` implements NNG's pub/sub protocol. Subscribers can subscribe to one or multiple topics broadcast by a publisher.
``` r
pub <- socket("pub", listen = "inproc://nanobroadcast")
sub <- socket("sub", dial = "inproc://nanobroadcast")
sub |> subscribe(topic = "examples")
pub |> send(c("examples", "this is an example"), mode = "raw")
#> [1] 0
sub |> recv(mode = "character")
#> [1] "examples" "this is an example"
pub |> send("examples at the start of a single text message", mode = "raw")
#> [1] 0
sub |> recv(mode = "character")
#> [1] "examples at the start of a single text message"
pub |> send(c("other", "this other topic will not be received"), mode = "raw")
#> [1] 0
sub |> recv(mode = "character")
#> 'errorValue' int 8 | Try again
# specify NULL to subscribe to ALL topics
sub |> subscribe(topic = NULL)
pub |> send(c("newTopic", "this is a new topic"), mode = "raw")
#> [1] 0
sub |> recv("character")
#> [1] "newTopic" "this is a new topic"
sub |> unsubscribe(topic = NULL)
pub |> send(c("newTopic", "this topic will now not be received"), mode = "raw")
#> [1] 0
sub |> recv("character")
#> 'errorValue' int 8 | Try again
# however the topics explicitly subscribed to are still received
pub |> send(c("examples will still be received"), mode = "raw")
#> [1] 0
sub |> recv(mode = "character")
#> [1] "examples will still be received"
```
The subscribed topic can be of any atomic type (not just character), allowing integer, double, logical, complex and raw vectors to be sent and received.
``` r
sub |> subscribe(topic = 1)
pub |> send(c(1, 10, 10, 20), mode = "raw")
#> [1] 0
sub |> recv(mode = "double")
#> [1] 1 10 10 20
pub |> send(c(2, 10, 10, 20), mode = "raw")
#> [1] 0
sub |> recv(mode = "double")
#> 'errorValue' int 8 | Try again
close(pub)
close(sub)
```
### 3. Surveyor Respondent Protocol
Useful for service discovery and similar applications. A surveyor broadcasts a survey to all respondents, who may reply within a timeout period. Late responses are discarded.
``` r
sur <- socket("surveyor", listen = "inproc://nanoservice")
res1 <- socket("respondent", dial = "inproc://nanoservice")
res2 <- socket("respondent", dial = "inproc://nanoservice")
# sur sets a survey timeout, applying to this and subsequent surveys
sur |> survey_time(value = 500)
# sur sends a message and then requests 2 async receives
sur |> send("service check")
#> [1] 0
aio1 <- sur |> recv_aio()
aio2 <- sur |> recv_aio()
# res1 receives the message and replies using an aio send function
res1 |> recv()
#> [1] "service check"
res1 |> send_aio("res1")
# res2 receives the message but fails to reply
res2 |> recv()
#> [1] "service check"
# checking the aio - only the first will have resolved
aio1$data
#> [1] "res1"
aio2$data
#> 'unresolved' logi NA
# after the survey expires, the second resolves into a timeout error
msleep(500)
aio2$data
#> 'errorValue' int 5 | Timed out
close(sur)
close(res1)
close(res2)
```
`msleep()` is an uninterruptible sleep function (using NNG) that takes a time in milliseconds.
The final value resolves to a timeout error (integer 5 classed as 'errorValue'). All error codes are classed as 'errorValue' for easy distinction from integer message values.
nanonext/configure.ucrt 0000644 0001762 0000144 00000001121 15142221674 014763 0 ustar ligges users # Find compiler and export flags
CC=`"${R_HOME}/bin/R" CMD config CC`
CFLAGS=`"${R_HOME}/bin/R" CMD config CFLAGS`
LDFLAGS=`"${R_HOME}/bin/R" CMD config LDFLAGS`
export CC CFLAGS LDFLAGS
echo "Compiling 'libmbedtls' from source ..."
cmake -S src/mbedtls -B nano-build -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=nano-install
cmake --build nano-build --target install
rm -rf nano-build
echo "Compiling 'libnng' from source ..."
cmake -S src/nng -B nano-build -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=nano-install
cmake --build nano-build --target install
rm -rf nano-build
# Success
exit 0
nanonext/src/ 0000755 0001762 0000144 00000000000 15176113105 012673 5 ustar ligges users nanonext/src/utils.c 0000644 0001762 0000144 00000043064 15176112256 014214 0 ustar ligges users // nanonext - C level - Utilities ----------------------------------------------
#include "nanonext.h"
// utils -----------------------------------------------------------------------
SEXP rnng_strerror(SEXP error) {
const int xc = nano_integer(error);
char nano_errbuf[NANONEXT_STR_SIZE];
snprintf(nano_errbuf, NANONEXT_STR_SIZE, "%d | %s", xc, nng_strerror(xc));
return Rf_mkString(nano_errbuf);
}
SEXP rnng_clock(void) {
const double time = (double) nng_clock();
return Rf_ScalarReal(time);
}
SEXP rnng_sleep(SEXP msec) {
int time;
switch (TYPEOF(msec)) {
case INTSXP:
time = NANO_INTEGER(msec);
if (time > 0) nng_msleep((nng_duration) time);
break;
case REALSXP:
time = Rf_asInteger(msec);
if (time > 0) nng_msleep((nng_duration) time);
break;
}
return R_NilValue;
}
SEXP rnng_url_parse(SEXP url) {
const char *up = CHAR(STRING_ELT(url, 0));
nng_url *urlp;
const int xc = nng_url_parse(&urlp, up);
if (xc)
ERROR_OUT(xc);
SEXP out;
const char *names[] = {"scheme", "userinfo", "hostname", "port", "path",
"query", "fragment", ""};
PROTECT(out = Rf_mkNamed(STRSXP, names));
SET_STRING_ELT(out, 0, Rf_mkChar(urlp->u_scheme == NULL ? "" : urlp->u_scheme));
SET_STRING_ELT(out, 1, Rf_mkChar(urlp->u_userinfo == NULL ? "" : urlp->u_userinfo));
SET_STRING_ELT(out, 2, Rf_mkChar(urlp->u_hostname == NULL ? "" : urlp->u_hostname));
SET_STRING_ELT(out, 3, Rf_mkChar(urlp->u_port == NULL ? "" : urlp->u_port));
SET_STRING_ELT(out, 4, Rf_mkChar(urlp->u_path == NULL ? "" : urlp->u_path));
SET_STRING_ELT(out, 5, Rf_mkChar(urlp->u_query == NULL ? "" : urlp->u_query));
SET_STRING_ELT(out, 6, Rf_mkChar(urlp->u_fragment == NULL ? "" : urlp->u_fragment));
nng_url_free(urlp);
UNPROTECT(1);
return out;
}
SEXP rnng_status_code(SEXP x) {
const int status = nano_integer(x);
char *code;
switch (status) {
case 100: code = "Continue"; break;
case 101: code = "Switching Protocols"; break;
case 102: code = "Processing"; break;
case 103: code = "Early Hints"; break;
case 200: code = "OK"; break;
case 201: code = "Created"; break;
case 202: code = "Accepted"; break;
case 203: code = "Non-Authoritative Information"; break;
case 204: code = "No Content"; break;
case 205: code = "Reset Content"; break;
case 206: code = "Partial Content"; break;
case 207: code = "Multi-Status"; break;
case 208: code = "Already Reported"; break;
case 226: code = "IM Used"; break;
case 300: code = "Multiple Choices"; break;
case 301: code = "Moved Permanently"; break;
case 302: code = "Found"; break;
case 303: code = "See Other"; break;
case 304: code = "Not Modified"; break;
case 305: code = "Use Proxy"; break;
case 306: code = "Switch Proxy"; break;
case 307: code = "Temporary Redirect"; break;
case 308: code = "Permanent Redirect"; break;
case 400: code = "Bad Request"; break;
case 401: code = "Unauthorized"; break;
case 402: code = "Payment Required"; break;
case 403: code = "Forbidden"; break;
case 404: code = "Not Found"; break;
case 405: code = "Method Not Allowed"; break;
case 406: code = "Not Acceptable"; break;
case 407: code = "Proxy Authentication Required"; break;
case 408: code = "Request Timeout"; break;
case 409: code = "Conflict"; break;
case 410: code = "Gone"; break;
case 411: code = "Length Required"; break;
case 412: code = "Precondition Failed"; break;
case 413: code = "Payload Too Large"; break;
case 414: code = "URI Too Long"; break;
case 415: code = "Unsupported Media Type"; break;
case 416: code = "Range Not Satisfiable"; break;
case 417: code = "Expectation Failed"; break;
case 418: code = "I'm a teapot"; break;
case 421: code = "Misdirected Request"; break;
case 422: code = "Unprocessable Entity"; break;
case 423: code = "Locked"; break;
case 424: code = "Failed Dependency"; break;
case 425: code = "Too Early"; break;
case 426: code = "Upgrade Required"; break;
case 428: code = "Precondition Required"; break;
case 429: code = "Too Many Requests"; break;
case 431: code = "Request Header Fields Too Large"; break;
case 451: code = "Unavailable For Legal Reasons"; break;
case 500: code = "Internal Server Error"; break;
case 501: code = "Not Implemented"; break;
case 502: code = "Bad Gateway"; break;
case 503: code = "Service Unavailable"; break;
case 504: code = "Gateway Timeout"; break;
case 505: code = "HTTP Version Not Supported"; break;
case 506: code = "Variant Also Negotiates"; break;
case 507: code = "Insufficient Storage"; break;
case 508: code = "Loop Detected"; break;
case 510: code = "Not Extended"; break;
case 511: code = "Network Authentication Required"; break;
default: code = "Unknown HTTP Status"; break;
}
char out[strlen(code) + 7];
snprintf(out, sizeof(out), "%d | %s", status, code);
return Rf_mkString(out);
}
SEXP rnng_is_nul_byte(SEXP x) {
return Rf_ScalarLogical(TYPEOF(x) == RAWSXP && XLENGTH(x) == 1 && RAW(x)[0] == 0);
}
SEXP rnng_is_error_value(SEXP x) {
return Rf_ScalarLogical(Rf_inherits(x, "errorValue"));
}
// options ---------------------------------------------------------------------
SEXP rnng_set_opt(SEXP object, SEXP opt, SEXP value) {
const char *op = CHAR(STRING_ELT(opt, 0));
const int typ = TYPEOF(value);
int xc, val;
if (!NANO_PTR_CHECK(object, nano_SocketSymbol)) {
nng_socket *sock = (nng_socket *) NANO_PTR(object);
switch (typ) {
case NILSXP:
xc = nng_socket_set(*sock, op, NULL, 0);
break;
case STRSXP:
xc = nng_socket_set_string(*sock, op, CHAR(STRING_ELT(value, 0)));
break;
case REALSXP:
case INTSXP:
val = nano_integer(value);
xc = nng_socket_set_ms(*sock, op, (nng_duration) val);
if (xc == 0) break;
xc = nng_socket_set_size(*sock, op, (size_t) val);
if (xc == 0) break;
xc = nng_socket_set_int(*sock, op, val);
if (xc == 0) break;
xc = nng_socket_set_uint64(*sock, op, (uint64_t) val);
break;
case LGLSXP:
xc = nng_socket_set_bool(*sock, op, (bool) NANO_INTEGER(value));
break;
case VECSXP:
if (strncmp(op, "serial", 6) || TYPEOF(value) != VECSXP)
Rf_error("type of `value` not supported");
NANO_SET_PROT(object, Rf_xlength(value) ? value : R_NilValue);
xc = 0;
break;
default:
Rf_error("type of `value` not supported");
}
} else if (!NANO_PTR_CHECK(object, nano_ContextSymbol)) {
nng_ctx *ctx = (nng_ctx *) NANO_PTR(object);
switch (typ) {
case NILSXP:
xc = nng_ctx_set(*ctx, op, NULL, 0);
break;
case STRSXP:
xc = nng_ctx_set_string(*ctx, op, CHAR(STRING_ELT(value, 0)));
break;
case REALSXP:
case INTSXP:
val = nano_integer(value);
xc = nng_ctx_set_ms(*ctx, op, (nng_duration) val);
if (xc == 0) break;
xc = nng_ctx_set_size(*ctx, op, (size_t) val);
if (xc == 0) break;
xc = nng_ctx_set_int(*ctx, op, val);
if (xc == 0) break;
xc = nng_ctx_set_uint64(*ctx, op, (uint64_t) val);
break;
case LGLSXP:
xc = nng_ctx_set_bool(*ctx, op, (bool) NANO_INTEGER(value));
break;
default:
Rf_error("type of `value` not supported");
}
} else if (!NANO_PTR_CHECK(object, nano_StreamSymbol)) {
nng_stream **st = (nng_stream **) NANO_PTR(object);
switch (typ) {
case NILSXP:
xc = nng_stream_set(*st, op, NULL, 0);
break;
case STRSXP:
xc = nng_stream_set_string(*st, op, CHAR(STRING_ELT(value, 0)));
break;
case REALSXP:
case INTSXP:
val = nano_integer(value);
xc = nng_stream_set_ms(*st, op, (nng_duration) val);
if (xc == 0) break;
xc = nng_stream_set_size(*st, op, (size_t) val);
if (xc == 0) break;
xc = nng_stream_set_int(*st, op, val);
if (xc == 0) break;
xc = nng_stream_set_uint64(*st, op, (uint64_t) val);
break;
case LGLSXP:
xc = nng_stream_set_bool(*st, op, (bool) NANO_INTEGER(value));
break;
default:
Rf_error("type of `value` not supported");
}
} else if (!NANO_PTR_CHECK(object, nano_ListenerSymbol)) {
nng_listener *list = (nng_listener *) NANO_PTR(object);
switch (typ) {
case NILSXP:
xc = nng_listener_set(*list, op, NULL, 0);
break;
case STRSXP:
xc = nng_listener_set_string(*list, op, CHAR(STRING_ELT(value, 0)));
break;
case REALSXP:
case INTSXP:
val = nano_integer(value);
xc = nng_listener_set_ms(*list, op, (nng_duration) val);
if (xc == 0) break;
xc = nng_listener_set_size(*list, op, (size_t) val);
if (xc == 0) break;
xc = nng_listener_set_int(*list, op, val);
if (xc == 0) break;
xc = nng_listener_set_uint64(*list, op, (uint64_t) val);
break;
case LGLSXP:
xc = nng_listener_set_bool(*list, op, (bool) NANO_INTEGER(value));
break;
default:
Rf_error("type of `value` not supported");
}
} else if (!NANO_PTR_CHECK(object, nano_DialerSymbol)) {
nng_dialer *dial = (nng_dialer *) NANO_PTR(object);
switch (typ) {
case NILSXP:
xc = nng_dialer_set(*dial, op, NULL, 0);
break;
case STRSXP:
xc = nng_dialer_set_string(*dial, op, CHAR(STRING_ELT(value, 0)));
break;
case REALSXP:
case INTSXP:
val = nano_integer(value);
xc = nng_dialer_set_ms(*dial, op, (nng_duration) val);
if (xc == 0) break;
xc = nng_dialer_set_size(*dial, op, (size_t) val);
if (xc == 0) break;
xc = nng_dialer_set_int(*dial, op, val);
if (xc == 0) break;
xc = nng_dialer_set_uint64(*dial, op, (uint64_t) val);
break;
case LGLSXP:
xc = nng_dialer_set_bool(*dial, op, (bool) NANO_INTEGER(value));
break;
default:
Rf_error("type of `value` not supported");
}
} else {
Rf_error("`object` is not a valid Socket, Context, Stream, Listener or Dialer");
}
if (xc)
ERROR_OUT(xc);
return object;
}
SEXP rnng_subscribe(SEXP object, SEXP value, SEXP sub) {
const char *op = NANO_INTEGER(sub) ? "sub:subscribe" : "sub:unsubscribe";
nano_buf buf;
int xc;
if (!NANO_PTR_CHECK(object, nano_SocketSymbol)) {
nng_socket *sock = (nng_socket *) NANO_PTR(object);
nano_encode(&buf, value);
xc = nng_socket_set(*sock, op, buf.buf, buf.cur - (TYPEOF(value) == STRSXP));
} else if (!NANO_PTR_CHECK(object, nano_ContextSymbol)) {
nng_ctx *ctx = (nng_ctx *) NANO_PTR(object);
nano_encode(&buf, value);
xc = nng_ctx_set(*ctx, op, buf.buf, buf.cur - (TYPEOF(value) == STRSXP));
} else {
Rf_error("`object` is not a valid Socket or Context");
}
if (xc)
ERROR_OUT(xc);
return object;
}
SEXP rnng_get_opt(SEXP object, SEXP opt) {
const char *op = CHAR(STRING_ELT(opt, 0));
SEXP out;
int xc, typ;
nano_opt optval;
if (!NANO_PTR_CHECK(object, nano_SocketSymbol)) {
nng_socket *sock = (nng_socket *) NANO_PTR(object);
for (;;) {
xc = nng_socket_get_string(*sock, op, &optval.str);
if (xc == 0) { typ = 1; break; }
xc = nng_socket_get_ms(*sock, op, &optval.d);
if (xc == 0) { typ = 2; break; }
xc = nng_socket_get_size(*sock, op, &optval.s);
if (xc == 0) { typ = 3; break; }
xc = nng_socket_get_int(*sock, op, &optval.i);
if (xc == 0) { typ = 4; break; }
xc = nng_socket_get_bool(*sock, op, &optval.b);
if (xc == 0) { typ = 5; break; }
xc = nng_socket_get_uint64(*sock, op, &optval.u);
typ = 6; break;
}
} else if (!NANO_PTR_CHECK(object, nano_ContextSymbol)) {
nng_ctx *ctx = (nng_ctx *) NANO_PTR(object);
for (;;) {
xc = nng_ctx_get_string(*ctx, op, &optval.str);
if (xc == 0) { typ = 1; break; }
xc = nng_ctx_get_ms(*ctx, op, &optval.d);
if (xc == 0) { typ = 2; break; }
xc = nng_ctx_get_size(*ctx, op, &optval.s);
if (xc == 0) { typ = 3; break; }
xc = nng_ctx_get_int(*ctx, op, &optval.i);
if (xc == 0) { typ = 4; break; }
xc = nng_ctx_get_bool(*ctx, op, &optval.b);
if (xc == 0) { typ = 5; break; }
xc = nng_ctx_get_uint64(*ctx, op, &optval.u);
typ = 6; break;
}
} else if (!NANO_PTR_CHECK(object, nano_StreamSymbol)) {
nng_stream **st = (nng_stream **) NANO_PTR(object);
for (;;) {
xc = nng_stream_get_string(*st, op, &optval.str);
if (xc == 0) { typ = 1; break; }
xc = nng_stream_get_ms(*st, op, &optval.d);
if (xc == 0) { typ = 2; break; }
xc = nng_stream_get_size(*st, op, &optval.s);
if (xc == 0) { typ = 3; break; }
xc = nng_stream_get_int(*st, op, &optval.i);
if (xc == 0) { typ = 4; break; }
xc = nng_stream_get_bool(*st, op, &optval.b);
if (xc == 0) { typ = 5; break; }
xc = nng_stream_get_uint64(*st, op, &optval.u);
typ = 6; break;
}
} else if (!NANO_PTR_CHECK(object, nano_ListenerSymbol)) {
nng_listener *list = (nng_listener *) NANO_PTR(object);
for (;;) {
xc = nng_listener_get_string(*list, op, &optval.str);
if (xc == 0) { typ = 1; break; }
xc = nng_listener_get_ms(*list, op, &optval.d);
if (xc == 0) { typ = 2; break; }
xc = nng_listener_get_size(*list, op, &optval.s);
if (xc == 0) { typ = 3; break; }
xc = nng_listener_get_int(*list, op, &optval.i);
if (xc == 0) { typ = 4; break; }
xc = nng_listener_get_bool(*list, op, &optval.b);
if (xc == 0) { typ = 5; break; }
xc = nng_listener_get_uint64(*list, op, &optval.u);
typ = 6; break;
}
} else if (!NANO_PTR_CHECK(object, nano_DialerSymbol)) {
nng_dialer *dial = (nng_dialer *) NANO_PTR(object);
for (;;) {
xc = nng_dialer_get_string(*dial, op, &optval.str);
if (xc == 0) { typ = 1; break; }
xc = nng_dialer_get_ms(*dial, op, &optval.d);
if (xc == 0) { typ = 2; break; }
xc = nng_dialer_get_size(*dial, op, &optval.s);
if (xc == 0) { typ = 3; break; }
xc = nng_dialer_get_int(*dial, op, &optval.i);
if (xc == 0) { typ = 4; break; }
xc = nng_dialer_get_bool(*dial, op, &optval.b);
if (xc == 0) { typ = 5; break; }
xc = nng_dialer_get_uint64(*dial, op, &optval.u);
typ = 6; break;
}
} else {
Rf_error("`object` is not a valid Socket, Context, Stream, Listener, or Dialer");
}
if (xc)
ERROR_OUT(xc);
switch (typ) {
case 1:
out = Rf_mkString(optval.str);
nng_strfree(optval.str);
break;
case 2:
out = Rf_ScalarInteger((int) optval.d);
break;
case 3:
out = Rf_ScalarInteger((int) optval.s);
break;
case 4:
out = Rf_ScalarInteger(optval.i);
break;
case 5:
out = Rf_ScalarLogical((int) optval.b);
break;
default:
out = Rf_ScalarReal((double) optval.u);
}
return out;
}
// statistics ------------------------------------------------------------------
SEXP rnng_stats_get(SEXP object, SEXP stat) {
const char *statname = CHAR(STRING_ELT(stat, 0));
SEXP out;
int xc;
nng_stat *nst, *sst;
if (!NANO_PTR_CHECK(object, nano_SocketSymbol)) {
if ((xc = nng_stats_get(&nst)))
ERROR_OUT(xc);
nng_socket *sock = (nng_socket *) NANO_PTR(object);
sst = nng_stat_find_socket(nst, *sock);
} else if (!NANO_PTR_CHECK(object, nano_ListenerSymbol)) {
if ((xc = nng_stats_get(&nst)))
ERROR_OUT(xc);
nng_listener *list = (nng_listener *) NANO_PTR(object);
sst = nng_stat_find_listener(nst, *list);
} else if (!NANO_PTR_CHECK(object, nano_DialerSymbol)) {
if ((xc = nng_stats_get(&nst)))
ERROR_OUT(xc);
nng_dialer *dial = (nng_dialer *) NANO_PTR(object);
sst = nng_stat_find_dialer(nst, *dial);
} else {
Rf_error("`object` is not a valid Socket, Listener or Dialer");
}
sst = nng_stat_find(sst, statname);
if (sst == NULL) {
nng_stats_free(nst);
return R_NilValue;
}
out = nng_stat_type(sst) == NNG_STAT_STRING ? Rf_mkString(nng_stat_string(sst)) : Rf_ScalarReal((double) nng_stat_value(sst));
nng_stats_free(nst);
return out;
}
// serialization config --------------------------------------------------------
SEXP rnng_serial_config(SEXP klass, SEXP sfunc, SEXP ufunc) {
SEXP out;
PROTECT(out = Rf_allocVector(VECSXP, 3));
if (TYPEOF(klass) != STRSXP)
Rf_error("`class` must be a character vector");
SET_VECTOR_ELT(out, 0, klass);
R_xlen_t xlen = XLENGTH(klass);
if (Rf_xlength(sfunc) != xlen || Rf_xlength(ufunc) != xlen)
Rf_error("`class`, `sfunc` and `ufunc` must all be the same length");
switch (TYPEOF(sfunc)) {
case VECSXP:
SET_VECTOR_ELT(out, 1, sfunc);
break;
case CLOSXP:
case SPECIALSXP:
case BUILTINSXP: {
SEXP slist = Rf_allocVector(VECSXP, 1);
SET_VECTOR_ELT(out, 1, slist);
SET_VECTOR_ELT(slist, 0, sfunc);
break;
}
default:
Rf_error("`sfunc` must be a function or list of functions");
}
switch (TYPEOF(ufunc)) {
case VECSXP:
SET_VECTOR_ELT(out, 2, ufunc);
break;
case CLOSXP:
case SPECIALSXP:
case BUILTINSXP: {
SEXP ulist = Rf_allocVector(VECSXP, 1);
SET_VECTOR_ELT(out, 2, ulist);
SET_VECTOR_ELT(ulist, 0, ufunc);
break;
}
default:
Rf_error("`ufunc` must be a function or list of functions");
}
UNPROTECT(1);
return out;
}
// specials --------------------------------------------------------------------
SEXP rnng_advance_rng_state(void) {
GetRNGstate();
(void) exp_rand();
PutRNGstate();
return R_NilValue;
}
SEXP rnng_fini_priors(void) {
nano_thread_shutdown();
nano_list_do(SHUTDOWN, NULL);
return R_NilValue;
}
SEXP rnng_fini(void) {
nng_fini();
return R_NilValue;
}
SEXP rnng_traverse_precious(void) {
SEXP list = nano_precious;
int x = 0;
for (SEXP head = CDR(list); head != R_NilValue; head = CDR(head))
if (TAG(head) == R_NilValue) ++x;
return Rf_ScalarInteger(x);
}
nanonext/src/Makevars.win 0000644 0001762 0000144 00000001104 15142221674 015163 0 ustar ligges users PKG_CFLAGS = -I../nano-install${R_ARCH}/include -DNNG_STATIC_LIB $(C_VISIBILITY)
PKG_LIBS = ../nano-install${R_ARCH}/lib/libnng.a ../nano-install${R_ARCH}/lib/libmbedtls.a ../nano-install${R_ARCH}/lib/libmbedx509.a ../nano-install${R_ARCH}/lib/libmbedcrypto.a -lbcrypt -liphlpapi -lws2_32
SOURCES = aio.c comms.c core.c dispatcher.c init.c ncurl.c net.c proto.c server.c sync.c thread.c tls.c utils.c
OBJECTS = $(SOURCES:.c=.o)
.PHONY: all cleanup clean
all: cleanup
cleanup: $(SHLIB)
@rm -rf ../nano-install${R_ARCH}
$(SHLIB): $(OBJECTS)
clean:
rm -f $(OBJECTS) $(SHLIB)
nanonext/src/ncurl.c 0000644 0001762 0000144 00000054005 15176112256 014174 0 ustar ligges users // nanonext - C level - ncurl --------------------------------------------------
#define NANONEXT_HTTP
#include "nanonext.h"
// internals -------------------------------------------------------------------
// Helper to build named list of all response headers
static SEXP build_all_headers(nng_http_res *res) {
int count = 0;
for (nano_http_header_s *h = nano_http_header_first(res); h != NULL;
h = nano_http_header_next(res, h))
count++;
if (count == 0)
return R_NilValue;
SEXP rvec, rnames;
PROTECT(rvec = Rf_allocVector(VECSXP, count));
rnames = Rf_allocVector(STRSXP, count);
Rf_namesgets(rvec, rnames);
int i = 0;
for (nano_http_header_s *h = nano_http_header_first(res); h != NULL;
h = nano_http_header_next(res, h)) {
SET_STRING_ELT(rnames, i, Rf_mkChar(h->name));
SET_VECTOR_ELT(rvec, i, Rf_mkString(h->value));
i++;
}
UNPROTECT(1);
return rvec;
}
static SEXP mk_error_haio(const int xc, SEXP env) {
SEXP err;
PROTECT(err = Rf_ScalarInteger(xc));
Rf_classgets(err, nano_error);
Rf_defineVar(nano_ResultSymbol, err, env);
Rf_defineVar(nano_ProtocolSymbol, err, env);
Rf_defineVar(nano_ValueSymbol, err, env);
Rf_defineVar(nano_AioSymbol, R_NilValue, env);
UNPROTECT(1);
return err;
}
static SEXP mk_error_ncurl(const int xc) {
const char *names[] = {"status", "headers", "data", ""};
SEXP out, err;
PROTECT(out = Rf_mkNamed(VECSXP, names));
PROTECT(err = Rf_ScalarInteger(xc));
Rf_classgets(err, nano_error);
SET_VECTOR_ELT(out, 0, err);
SET_VECTOR_ELT(out, 1, err);
SET_VECTOR_ELT(out, 2, err);
UNPROTECT(2);
return out;
}
static SEXP mk_error_ncurlaio(const int xc) {
SEXP env, err;
PROTECT(env = R_NewEnv(R_NilValue, 0, 0));
NANO_CLASS2(env, "ncurlAio", "recvAio");
PROTECT(err = Rf_ScalarInteger(xc));
Rf_classgets(err, nano_error);
Rf_defineVar(nano_ResultSymbol, err, env);
Rf_defineVar(nano_StatusSymbol, err, env);
Rf_defineVar(nano_ProtocolSymbol, err, env);
Rf_defineVar(nano_HeadersSymbol, err, env);
Rf_defineVar(nano_ValueSymbol, err, env);
Rf_defineVar(nano_DataSymbol, err, env);
UNPROTECT(2);
return env;
}
static nano_buf nano_char_buf(const SEXP data) {
nano_buf buf = {0};
switch (TYPEOF(data)) {
case STRSXP: {
const char *s = CHAR(STRING_ELT(data, 0));
NANO_INIT(&buf, (unsigned char *) s, strlen(s));
break;
}
case RAWSXP:
NANO_INIT(&buf, NANO_DATAPTR(data), XLENGTH(data));
break;
}
return buf;
}
// aio completion callbacks ----------------------------------------------------
static void haio_complete(void *arg) {
nano_aio *haio = (nano_aio *) arg;
const int res = nng_aio_result(haio->aio);
haio->result = res - !res;
if (haio->cb != NULL)
later2(haio_invoke_cb, haio->cb);
}
static void session_complete(void *arg) {
nano_aio *haio = (nano_aio *) arg;
const int res = nng_aio_result(haio->aio);
haio->result = res - !res;
}
// finalisers ------------------------------------------------------------------
static void haio_finalizer(SEXP xptr) {
if (NANO_PTR(xptr) == NULL) return;
nano_aio *xp = (nano_aio *) NANO_PTR(xptr);
nano_handle *handle = (nano_handle *) xp->next;
nng_aio_free(xp->aio);
if (handle->cfg != NULL)
nng_tls_config_free(handle->cfg);
nng_http_res_free(handle->res);
nng_http_req_free(handle->req);
nng_http_client_free(handle->cli);
nng_url_free(handle->url);
free(handle);
free(xp);
}
static void session_finalizer(SEXP xptr) {
if (NANO_PTR(xptr) == NULL) return;
nano_aio *xp = (nano_aio *) NANO_PTR(xptr);
nano_handle *handle = (nano_handle *) xp->next;
if (xp->data != NULL)
nng_http_conn_close((nng_http_conn *) xp->data);
nng_aio_free(xp->aio);
if (handle->cfg != NULL)
nng_tls_config_free(handle->cfg);
nng_http_res_free(handle->res);
nng_http_req_free(handle->req);
nng_http_client_free(handle->cli);
nng_url_free(handle->url);
free(handle);
free(xp);
}
// ncurl - internal ------------------------------------------------------------
static inline SEXP create_aio_http(SEXP env, nano_aio *haio, int typ) {
if (haio->result > 0)
return mk_error_haio(haio->result, env);
void *dat;
size_t sz;
SEXP out, vec, rvec, response;
nano_handle *handle = (nano_handle *) haio->next;
PROTECT(response = nano_findVarInFrame(env, nano_ResponseSymbol, NULL));
const int all_resp = response != R_NilValue && TYPEOF(response) == LGLSXP && LOGICAL(response)[0] == 1;
int chk_resp = response != R_NilValue && TYPEOF(response) == STRSXP;
const uint16_t code = nng_http_res_get_status(handle->res), relo = code >= 300 && code < 400;
Rf_defineVar(nano_ResultSymbol, Rf_ScalarInteger(code), env);
if (all_resp) {
rvec = build_all_headers(handle->res);
} else {
if (relo) {
if (chk_resp) {
const R_xlen_t rlen = XLENGTH(response);
PROTECT(response = Rf_xlengthgets(response, rlen + 1));
SET_STRING_ELT(response, rlen, Rf_mkChar("Location"));
} else {
PROTECT(response = Rf_mkString("Location"));
chk_resp = 1;
}
}
if (chk_resp) {
const R_xlen_t rlen = XLENGTH(response);
PROTECT(rvec = Rf_allocVector(VECSXP, rlen));
Rf_namesgets(rvec, response);
const SEXP *response_p = STRING_PTR_RO(response);
for (R_xlen_t i = 0; i < rlen; i++) {
const char *r = nng_http_res_get_header(handle->res, CHAR(response_p[i]));
SET_VECTOR_ELT(rvec, i, r == NULL ? R_NilValue : Rf_mkString(r));
}
UNPROTECT(1);
} else {
rvec = R_NilValue;
}
if (relo) UNPROTECT(1);
}
UNPROTECT(1);
Rf_defineVar(nano_ProtocolSymbol, rvec, env);
nng_http_res_get_data(handle->res, &dat, &sz);
if (haio->mode) {
vec = nano_raw_char(dat, sz);
} else {
vec = Rf_allocVector(RAWSXP, sz);
if (dat != NULL)
memcpy(NANO_DATAPTR(vec), dat, sz);
}
Rf_defineVar(nano_ValueSymbol, vec, env);
Rf_defineVar(nano_AioSymbol, R_NilValue, env);
switch (typ) {
case 0: out = nano_findVarInFrame(env, nano_ResultSymbol, NULL); break;
case 1: out = nano_findVarInFrame(env, nano_ProtocolSymbol, NULL); break;
default: out = nano_findVarInFrame(env, nano_ValueSymbol, NULL); break;
}
return out;
}
static inline SEXP nano_aio_http_impl(SEXP env, const int typ) {
int found;
SEXP exist;
switch (typ) {
case 0: exist = nano_findVarInFrame(env, nano_ResultSymbol, &found); break;
case 1: exist = nano_findVarInFrame(env, nano_ProtocolSymbol, &found); break;
default: exist = nano_findVarInFrame(env, nano_ValueSymbol, &found); break;
}
if (found)
return exist;
const SEXP aio = nano_findVarInFrame(env, nano_AioSymbol, NULL);
nano_aio *haio = (nano_aio *) NANO_PTR(aio);
if (nng_aio_busy(haio->aio))
return nano_unresolved;
return create_aio_http(env, haio, typ);
}
SEXP nano_aio_http_status(SEXP env) {
int found;
SEXP exist = nano_findVarInFrame(env, nano_ResultSymbol, &found);
if (found)
return exist;
const SEXP aio = nano_findVarInFrame(env, nano_AioSymbol, NULL);
nano_aio *haio = (nano_aio *) NANO_PTR(aio);
return create_aio_http(env, haio, 0);
}
// ncurl - minimalist http client ----------------------------------------------
SEXP rnng_ncurl(SEXP http, SEXP convert, SEXP follow, SEXP method, SEXP headers,
SEXP data, SEXP response, SEXP timeout, SEXP tls) {
const char *addr = CHAR(STRING_ELT(http, 0));
const char *mthd = method != R_NilValue ? CHAR(STRING_ELT(method, 0)) : NULL;
const nng_duration dur = timeout == R_NilValue ? NNG_DURATION_DEFAULT : (nng_duration) nano_integer(timeout);
if (tls != R_NilValue && NANO_PTR_CHECK(tls, nano_TlsSymbol))
Rf_error("`tls` is not a valid TLS Configuration");
const int all_resp = response != R_NilValue && TYPEOF(response) == LGLSXP && LOGICAL(response)[0] == 1;
int chk_resp = response != R_NilValue && TYPEOF(response) == STRSXP;
nng_url *url = NULL;
nng_http_client *client = NULL;
nng_http_req *req = NULL;
nng_http_res *res = NULL;
nng_aio *aio = NULL;
nng_tls_config *cfg = NULL;
uint16_t code, relo;
int xc;
if ((xc = nng_url_parse(&url, addr)))
goto fail;
relocall:
if ((xc = nng_http_client_alloc(&client, url)) ||
(xc = nng_http_req_alloc(&req, url)) ||
(xc = nng_http_res_alloc(&res)) ||
(xc = nng_aio_alloc(&aio, NULL, NULL)))
goto fail;
if (mthd != NULL && (xc = nng_http_req_set_method(req, mthd)))
goto fail;
if (headers != R_NilValue && TYPEOF(headers) == STRSXP) {
const R_xlen_t hlen = XLENGTH(headers);
SEXP hnames = Rf_getAttrib(headers, R_NamesSymbol);
if (TYPEOF(hnames) == STRSXP && XLENGTH(hnames) == hlen) {
const SEXP *hnames_p = STRING_PTR_RO(hnames);
const SEXP *headers_p = STRING_PTR_RO(headers);
for (R_xlen_t i = 0; i < hlen; i++) {
if ((xc = nng_http_req_set_header(req, CHAR(hnames_p[i]), CHAR(headers_p[i]))))
goto fail;
}
}
}
if (data != R_NilValue) {
nano_buf enc = nano_char_buf(data);
if (enc.cur && (xc = nng_http_req_set_data(req, enc.buf, enc.cur)))
goto fail;
}
if (!strcmp(url->u_scheme, "https")) {
if (tls == R_NilValue) {
if ((xc = nng_tls_config_alloc(&cfg, NNG_TLS_MODE_CLIENT)) ||
(xc = nng_tls_config_server_name(cfg, url->u_hostname)) ||
(xc = nng_tls_config_auth_mode(cfg, NNG_TLS_AUTH_MODE_NONE)) ||
(xc = nng_http_client_set_tls(client, cfg)))
goto fail;
} else {
cfg = (nng_tls_config *) NANO_PTR(tls);
nng_tls_config_hold(cfg);
if ((xc = nng_tls_config_server_name(cfg, url->u_hostname)) ||
(xc = nng_http_client_set_tls(client, cfg)))
goto fail;
}
}
nng_aio_set_timeout(aio, dur);
nng_http_client_transact(client, req, res, aio);
nng_aio_wait(aio);
if ((xc = nng_aio_result(aio)))
goto fail;
if (cfg != NULL)
nng_tls_config_free(cfg);
nng_aio_free(aio);
code = nng_http_res_get_status(res), relo = code >= 300 && code < 400;
if (relo && NANO_INTEGER(follow)) {
const char *location = nng_http_res_get_header(res, "Location");
if (location == NULL) goto resume;
nng_url *oldurl = url;
xc = nng_url_parse(&url, location);
if (xc) goto resume;
nng_http_res_free(res);
res = NULL;
nng_http_req_free(req);
req = NULL;
nng_http_client_free(client);
client = NULL;
nng_url_free(oldurl);
cfg = NULL;
goto relocall;
}
resume: ;
SEXP out, vec, rvec;
void *dat;
size_t sz;
const char *names[] = {"status", "headers", "data", ""};
PROTECT(out = Rf_mkNamed(VECSXP, names));
SET_VECTOR_ELT(out, 0, Rf_ScalarInteger(code));
if (all_resp) {
rvec = build_all_headers(res);
SET_VECTOR_ELT(out, 1, rvec);
} else {
if (relo) {
if (chk_resp) {
const R_xlen_t rlen = XLENGTH(response);
PROTECT(response = Rf_xlengthgets(response, rlen + 1));
SET_STRING_ELT(response, rlen, Rf_mkChar("Location"));
} else {
PROTECT(response = Rf_mkString("Location"));
chk_resp = 1;
}
}
if (chk_resp) {
const R_xlen_t rlen = XLENGTH(response);
rvec = Rf_allocVector(VECSXP, rlen);
SET_VECTOR_ELT(out, 1, rvec);
Rf_namesgets(rvec, response);
const SEXP *response_p = STRING_PTR_RO(response);
for (R_xlen_t i = 0; i < rlen; i++) {
const char *r = nng_http_res_get_header(res, CHAR(response_p[i]));
SET_VECTOR_ELT(rvec, i, r == NULL ? R_NilValue : Rf_mkString(r));
}
} else {
rvec = R_NilValue;
SET_VECTOR_ELT(out, 1, rvec);
}
if (relo) UNPROTECT(1);
}
nng_http_res_get_data(res, &dat, &sz);
if (NANO_INTEGER(convert)) {
vec = nano_raw_char(dat, sz);
} else {
vec = Rf_allocVector(RAWSXP, sz);
if (dat != NULL)
memcpy(NANO_DATAPTR(vec), dat, sz);
}
SET_VECTOR_ELT(out, 2, vec);
nng_http_res_free(res);
nng_http_req_free(req);
nng_http_client_free(client);
nng_url_free(url);
UNPROTECT(1);
return out;
fail:
if (cfg != NULL)
nng_tls_config_free(cfg);
nng_aio_free(aio);
if (res != NULL)
nng_http_res_free(res);
if (req != NULL)
nng_http_req_free(req);
if (client != NULL)
nng_http_client_free(client);
nng_url_free(url);
return mk_error_ncurl(xc);
}
// ncurl aio -------------------------------------------------------------------
SEXP rnng_ncurl_aio(SEXP http, SEXP convert, SEXP method, SEXP headers, SEXP data,
SEXP response, SEXP timeout, SEXP tls, SEXP clo) {
const char *httr = CHAR(STRING_ELT(http, 0));
const char *mthd = method != R_NilValue ? CHAR(STRING_ELT(method, 0)) : NULL;
const nng_duration dur = timeout == R_NilValue ? NNG_DURATION_DEFAULT : (nng_duration) nano_integer(timeout);
if (tls != R_NilValue && NANO_PTR_CHECK(tls, nano_TlsSymbol))
Rf_error("`tls` is not a valid TLS Configuration");
SEXP aio, env, fun;
int xc;
nano_aio *haio = NULL;
nano_handle *handle = NULL;
haio = calloc(1, sizeof(nano_aio));
NANO_ENSURE_ALLOC(haio);
handle = calloc(1, sizeof(nano_handle));
NANO_ENSURE_ALLOC(handle);
haio->type = HTTP_AIO;
haio->mode = (uint8_t) NANO_INTEGER(convert);
haio->next = handle;
if ((xc = nng_url_parse(&handle->url, httr)) ||
(xc = nng_http_client_alloc(&handle->cli, handle->url)) ||
(xc = nng_http_req_alloc(&handle->req, handle->url)) ||
(xc = nng_http_res_alloc(&handle->res)) ||
(xc = nng_aio_alloc(&haio->aio, haio_complete, haio)))
goto fail;
if (mthd != NULL && (xc = nng_http_req_set_method(handle->req, mthd)))
goto fail;
if (headers != R_NilValue && TYPEOF(headers) == STRSXP) {
const R_xlen_t hlen = XLENGTH(headers);
SEXP hnames = Rf_getAttrib(headers, R_NamesSymbol);
if (TYPEOF(hnames) == STRSXP && XLENGTH(hnames) == hlen) {
const SEXP *hnames_p = STRING_PTR_RO(hnames);
const SEXP *headers_p = STRING_PTR_RO(headers);
for (R_xlen_t i = 0; i < hlen; i++) {
if ((xc = nng_http_req_set_header(handle->req, CHAR(hnames_p[i]), CHAR(headers_p[i]))))
goto fail;
}
}
}
if (data != R_NilValue) {
nano_buf enc = nano_char_buf(data);
if (enc.cur && (xc = nng_http_req_copy_data(handle->req, enc.buf, enc.cur)))
goto fail;
}
if (!strcmp(handle->url->u_scheme, "https")) {
if (tls == R_NilValue) {
if ((xc = nng_tls_config_alloc(&handle->cfg, NNG_TLS_MODE_CLIENT)) ||
(xc = nng_tls_config_server_name(handle->cfg, handle->url->u_hostname)) ||
(xc = nng_tls_config_auth_mode(handle->cfg, NNG_TLS_AUTH_MODE_NONE)) ||
(xc = nng_http_client_set_tls(handle->cli, handle->cfg)))
goto fail;
} else {
handle->cfg = (nng_tls_config *) NANO_PTR(tls);
nng_tls_config_hold(handle->cfg);
if ((xc = nng_tls_config_server_name(handle->cfg, handle->url->u_hostname)) ||
(xc = nng_http_client_set_tls(handle->cli, handle->cfg)))
goto fail;
}
}
nng_aio_set_timeout(haio->aio, dur);
nng_http_client_transact(handle->cli, handle->req, handle->res, haio->aio);
PROTECT(aio = R_MakeExternalPtr(haio, nano_AioSymbol, R_NilValue));
R_RegisterCFinalizerEx(aio, haio_finalizer, TRUE);
PROTECT(env = R_NewEnv(R_NilValue, 0, 0));
NANO_CLASS2(env, "ncurlAio", "recvAio");
Rf_defineVar(nano_AioSymbol, aio, env);
Rf_defineVar(nano_ResponseSymbol, response, env);
int i = 0;
for (SEXP fnlist = nano_aioNFuncs; fnlist != R_NilValue; fnlist = CDR(fnlist)) {
PROTECT(fun = R_mkClosure(R_NilValue, CAR(fnlist), clo));
switch (++i) {
case 1: R_MakeActiveBinding(nano_StatusSymbol, fun, env);
case 2: R_MakeActiveBinding(nano_HeadersSymbol, fun, env);
case 3: R_MakeActiveBinding(nano_DataSymbol, fun, env);
}
UNPROTECT(1);
}
UNPROTECT(2);
return env;
fail:
if (handle->cfg != NULL)
nng_tls_config_free(handle->cfg);
nng_aio_free(haio->aio);
if (handle->res != NULL)
nng_http_res_free(handle->res);
if (handle->req != NULL)
nng_http_req_free(handle->req);
if (handle->cli != NULL)
nng_http_client_free(handle->cli);
nng_url_free(handle->url);
failmem:
free(handle);
free(haio);
return mk_error_ncurlaio(xc);
}
SEXP rnng_aio_http_status(SEXP env) {
return nano_aio_http_impl(env, 0);
}
SEXP rnng_aio_http_headers(SEXP env) {
return nano_aio_http_impl(env, 1);
}
SEXP rnng_aio_http_data(SEXP env) {
return nano_aio_http_impl(env, 2);
}
// ncurl session ---------------------------------------------------------------
SEXP rnng_ncurl_session(SEXP http, SEXP convert, SEXP method, SEXP headers, SEXP data,
SEXP response, SEXP timeout, SEXP tls) {
const char *httr = CHAR(STRING_ELT(http, 0));
const char *mthd = method != R_NilValue ? CHAR(STRING_ELT(method, 0)) : NULL;
const nng_duration dur = timeout == R_NilValue ? NNG_DURATION_DEFAULT : (nng_duration) nano_integer(timeout);
if (tls != R_NilValue && NANO_PTR_CHECK(tls, nano_TlsSymbol))
Rf_error("`tls` is not a valid TLS Configuration");
SEXP sess;
int xc;
nano_aio *haio = NULL;
nano_handle *handle = NULL;
haio = calloc(1, sizeof(nano_aio));
NANO_ENSURE_ALLOC(haio);
handle = calloc(1, sizeof(nano_handle));
NANO_ENSURE_ALLOC(handle);
haio->type = HTTP_AIO;
haio->mode = (uint8_t) NANO_INTEGER(convert);
haio->next = handle;
if ((xc = nng_url_parse(&handle->url, httr)) ||
(xc = nng_http_client_alloc(&handle->cli, handle->url)) ||
(xc = nng_http_req_alloc(&handle->req, handle->url)) ||
(xc = nng_http_res_alloc(&handle->res)) ||
(xc = nng_aio_alloc(&haio->aio, session_complete, haio)))
goto fail;
if (mthd != NULL && (xc = nng_http_req_set_method(handle->req, mthd)))
goto fail;
if (headers != R_NilValue && TYPEOF(headers) == STRSXP) {
const R_xlen_t hlen = XLENGTH(headers);
SEXP hnames = Rf_getAttrib(headers, R_NamesSymbol);
if (TYPEOF(hnames) == STRSXP && XLENGTH(hnames) == hlen) {
const SEXP *hnames_p = STRING_PTR_RO(hnames);
const SEXP *headers_p = STRING_PTR_RO(headers);
for (R_xlen_t i = 0; i < hlen; i++) {
if ((xc = nng_http_req_set_header(handle->req, CHAR(hnames_p[i]), CHAR(headers_p[i]))))
goto fail;
}
}
}
if (data != R_NilValue) {
nano_buf enc = nano_char_buf(data);
if (enc.cur && (xc = nng_http_req_copy_data(handle->req, enc.buf, enc.cur)))
goto fail;
}
if (!strcmp(handle->url->u_scheme, "https")) {
if (tls == R_NilValue) {
if ((xc = nng_tls_config_alloc(&handle->cfg, NNG_TLS_MODE_CLIENT)) ||
(xc = nng_tls_config_server_name(handle->cfg, handle->url->u_hostname)) ||
(xc = nng_tls_config_auth_mode(handle->cfg, NNG_TLS_AUTH_MODE_NONE)) ||
(xc = nng_http_client_set_tls(handle->cli, handle->cfg)))
goto fail;
} else {
handle->cfg = (nng_tls_config *) NANO_PTR(tls);
nng_tls_config_hold(handle->cfg);
if ((xc = nng_tls_config_server_name(handle->cfg, handle->url->u_hostname)) ||
(xc = nng_http_client_set_tls(handle->cli, handle->cfg)))
goto fail;
}
}
nng_aio_set_timeout(haio->aio, dur);
nng_http_client_connect(handle->cli, haio->aio);
nng_aio_wait(haio->aio);
if ((xc = haio->result) > 0)
goto fail;
nng_http_conn *conn = nng_aio_get_output(haio->aio, 0);
haio->data = conn;
int store_resp = response != R_NilValue &&
(TYPEOF(response) == STRSXP || (TYPEOF(response) == LGLSXP && LOGICAL(response)[0] == 1));
PROTECT(sess = R_MakeExternalPtr(haio, nano_StatusSymbol, store_resp ? response : R_NilValue));
R_RegisterCFinalizerEx(sess, session_finalizer, TRUE);
Rf_classgets(sess, Rf_mkString("ncurlSession"));
UNPROTECT(1);
return sess;
fail:
if (handle->cfg != NULL)
nng_tls_config_free(handle->cfg);
nng_aio_free(haio->aio);
if (handle->res != NULL)
nng_http_res_free(handle->res);
if (handle->req != NULL)
nng_http_req_free(handle->req);
if (handle->cli != NULL)
nng_http_client_free(handle->cli);
nng_url_free(handle->url);
failmem:
free(handle);
free(haio);
ERROR_RET(xc);
}
SEXP rnng_ncurl_transact(SEXP session) {
if (NANO_PTR_CHECK(session, nano_StatusSymbol))
Rf_error("`session` is not a valid or active ncurlSession");
nano_aio *haio = (nano_aio *) NANO_PTR(session);
if (haio->data == NULL)
return mk_error_ncurl(7);
nng_http_conn *conn = (nng_http_conn *) haio->data;
nano_handle *handle = (nano_handle *) haio->next;
nng_http_conn_transact(conn, handle->req, handle->res, haio->aio);
nng_aio_wait(haio->aio);
if (haio->result > 0)
return mk_error_ncurl(haio->result);
SEXP out, vec, rvec, response;
void *dat;
size_t sz;
const char *names[] = {"status", "headers", "data", ""};
PROTECT(out = Rf_mkNamed(VECSXP, names));
const uint16_t code = nng_http_res_get_status(handle->res);
SET_VECTOR_ELT(out, 0, Rf_ScalarInteger(code));
response = NANO_PROT(session);
if (response != R_NilValue && TYPEOF(response) == LGLSXP && LOGICAL(response)[0] == 1) {
rvec = build_all_headers(handle->res);
SET_VECTOR_ELT(out, 1, rvec);
} else if (response != R_NilValue && TYPEOF(response) == STRSXP) {
const R_xlen_t rlen = XLENGTH(response);
rvec = Rf_allocVector(VECSXP, rlen);
SET_VECTOR_ELT(out, 1, rvec);
Rf_namesgets(rvec, response);
const SEXP *response_p = STRING_PTR_RO(response);
for (R_xlen_t i = 0; i < rlen; i++) {
const char *r = nng_http_res_get_header(handle->res, CHAR(response_p[i]));
SET_VECTOR_ELT(rvec, i, r == NULL ? R_NilValue : Rf_mkString(r));
}
} else {
rvec = R_NilValue;
SET_VECTOR_ELT(out, 1, rvec);
}
nng_http_res_get_data(handle->res, &dat, &sz);
if (haio->mode) {
vec = nano_raw_char(dat, sz);
} else {
vec = Rf_allocVector(RAWSXP, sz);
if (dat != NULL)
memcpy(NANO_DATAPTR(vec), dat, sz);
}
SET_VECTOR_ELT(out, 2, vec);
UNPROTECT(1);
return out;
}
SEXP rnng_ncurl_session_close(SEXP session) {
if (NANO_PTR_CHECK(session, nano_StatusSymbol))
Rf_error("`session` is not a valid or active ncurlSession");
nano_aio *haio = (nano_aio *) NANO_PTR(session);
if (haio->data == NULL)
ERROR_RET(7);
nng_http_conn_close((nng_http_conn *) haio->data);
haio->data = NULL;
Rf_setAttrib(session, nano_StateSymbol, Rf_mkString("closed"));
return nano_success;
}
nanonext/src/thread.c 0000644 0001762 0000144 00000024140 15176112256 014315 0 ustar ligges users // nanonext - C level - Threaded Applications ----------------------------------
#define NANONEXT_PROTOCOLS
#define NANONEXT_IO
#include "nanonext.h"
// threads --------------------------------------------------------------------
static nng_mtx *nano_wait_mtx = NULL;
static nng_cv *nano_wait_cv = NULL;
static nng_thread *nano_wait_thr = NULL;
static nng_aio *nano_shared_aio = NULL;
static int nano_wait_condition = 0;
void nano_thread_shutdown(void) {
if (nano_wait_thr == NULL)
return;
if (nano_shared_aio != NULL)
nng_aio_stop(nano_shared_aio);
nng_mtx_lock(nano_wait_mtx);
nano_wait_condition = -1;
nng_cv_wake(nano_wait_cv);
nng_mtx_unlock(nano_wait_mtx);
nng_thread_destroy(nano_wait_thr);
nng_cv_free(nano_wait_cv);
nng_mtx_free(nano_wait_mtx);
nano_wait_thr = NULL;
}
static void thread_finalizer(SEXP xptr) {
if (NANO_PTR(xptr) == NULL) return;
nng_thread *xp = (nng_thread *) NANO_PTR(xptr);
nng_thread_destroy(xp);
}
// # nocov start
// tested interactively
static void thread_aio_finalizer(SEXP xptr) {
if (NANO_PTR(xptr) == NULL) return;
nano_thread_aio *xp = (nano_thread_aio *) NANO_PTR(xptr);
nano_cv *ncv = xp->cv;
nng_mtx *mtx = ncv->mtx;
nng_cv *cv = ncv->cv;
nng_aio_stop(xp->aio);
nng_thread_destroy(xp->thr);
nng_cv_free(cv);
nng_mtx_free(mtx);
free(ncv);
free(xp);
}
static void rnng_wait_thread_single(void *args) {
nano_thread_aio *taio = (nano_thread_aio *) args;
nano_cv *ncv = taio->cv;
nng_mtx *mtx = ncv->mtx;
nng_cv *cv = ncv->cv;
nng_aio_wait(taio->aio);
nng_mtx_lock(mtx);
ncv->condition = 1;
nng_cv_wake(cv);
nng_mtx_unlock(mtx);
}
void single_wait_thread_create(SEXP x) {
nano_aio *aiop = (nano_aio *) NANO_PTR(x);
int xc, signalled;
nano_thread_aio *taio = NULL;
nano_cv *ncv = NULL;
taio = malloc(sizeof(nano_thread_aio));
NANO_ENSURE_ALLOC(taio);
ncv = calloc(1, sizeof(nano_cv));
NANO_ENSURE_ALLOC(ncv);
taio->aio = aiop->aio;
taio->cv = ncv;
nng_mtx *mtx = NULL;
nng_cv *cv = NULL;
if ((xc = nng_mtx_alloc(&mtx)) ||
(xc = nng_cv_alloc(&cv, mtx)))
goto fail;
ncv->mtx = mtx;
ncv->cv = cv;
if ((xc = nng_thread_create(&taio->thr, rnng_wait_thread_single, taio)))
goto fail;
SEXP xptr;
PROTECT(xptr = R_MakeExternalPtr(taio, R_NilValue, R_NilValue));
R_RegisterCFinalizerEx(xptr, thread_aio_finalizer, TRUE);
R_MakeWeakRef(x, xptr, R_NilValue, TRUE);
UNPROTECT(1);
nng_time time = nng_clock();
while (1) {
time = time + 400;
signalled = 1;
nng_mtx_lock(mtx);
while (ncv->condition == 0) {
if (nng_cv_until(cv, time) == NNG_ETIMEDOUT) {
signalled = 0;
break;
}
}
nng_mtx_unlock(mtx);
if (signalled) break;
R_CheckUserInterrupt();
}
return;
fail:
nng_cv_free(cv);
nng_mtx_free(mtx);
failmem:
free(ncv);
free(taio);
ERROR_OUT(xc);
}
// # nocov end
static void thread_duo_finalizer(SEXP xptr) {
if (NANO_PTR(xptr) == NULL) return;
nano_thread_duo *xp = (nano_thread_duo *) NANO_PTR(xptr);
nano_cv *ncv = xp->cv;
if (ncv != NULL) {
nng_mtx *mtx = ncv->mtx;
nng_cv *cv = ncv->cv;
nng_mtx_lock(mtx);
ncv->condition = -1;
nng_cv_wake(cv);
nng_mtx_unlock(mtx);
}
nng_thread_destroy(xp->thr);
free(xp);
}
static void rnng_wait_thread(void *args) {
while (1) {
nng_mtx_lock(nano_wait_mtx);
while (nano_wait_condition == 0)
nng_cv_wait(nano_wait_cv);
if (nano_wait_condition == -1) {
nng_mtx_unlock(nano_wait_mtx);
break;
}
nng_mtx_unlock(nano_wait_mtx);
nng_aio_wait(nano_shared_aio);
nng_mtx_lock(nano_wait_mtx);
nano_shared_aio = NULL;
nano_wait_condition = 0;
nng_cv_wake(nano_wait_cv);
nng_mtx_unlock(nano_wait_mtx);
}
}
SEXP rnng_wait_thread_create(SEXP x) {
const SEXPTYPE typ = TYPEOF(x);
if (typ == ENVSXP) {
const SEXP coreaio = nano_findVarInFrame(x, nano_AioSymbol, NULL);
if (NANO_PTR_CHECK(coreaio, nano_AioSymbol))
return x;
nano_aio *aiop = (nano_aio *) NANO_PTR(coreaio);
int xc, signalled;
if (nano_wait_thr == NULL) {
if ((xc = nng_mtx_alloc(&nano_wait_mtx)) ||
(xc = nng_cv_alloc(&nano_wait_cv, nano_wait_mtx)) ||
(xc = nng_thread_create(&nano_wait_thr, rnng_wait_thread, NULL)))
goto fail;
}
int thread_required = 0;
nng_mtx_lock(nano_wait_mtx);
if (nano_wait_condition == 0) {
nano_shared_aio = aiop->aio;
nano_wait_condition = 1;
nng_cv_wake(nano_wait_cv);
} else {
thread_required = nano_shared_aio != aiop->aio;
}
nng_mtx_unlock(nano_wait_mtx);
if (thread_required) {
PROTECT(coreaio);
single_wait_thread_create(coreaio);
UNPROTECT(1);
} else {
nng_time time = nng_clock();
while (1) {
time = time + 400;
signalled = 1;
nng_mtx_lock(nano_wait_mtx);
while (nano_wait_condition == 1) {
if (nng_cv_until(nano_wait_cv, time) == NNG_ETIMEDOUT) {
signalled = 0;
break;
}
}
nng_mtx_unlock(nano_wait_mtx);
if (signalled) break;
R_CheckUserInterrupt();
}
}
switch (aiop->type) {
case RECVAIO:
case REQAIO:
case IOV_RECVAIO:
case RECVAIOS:
case REQAIOS:
case IOV_RECVAIOS:
nano_aio_get_msg(x);
break;
case SENDAIO:
case IOV_SENDAIO:
nano_aio_result(x);
break;
case HTTP_AIO:
nano_aio_http_status(x);
break;
}
return x;
fail:
nng_cv_free(nano_wait_cv);
nng_mtx_free(nano_wait_mtx);
ERROR_OUT(xc);
} else if (typ == VECSXP) {
const R_xlen_t xlen = Rf_xlength(x);
const SEXP *xp = VECTOR_PTR_RO(x);
for (R_xlen_t i = 0; i < xlen; i++) {
rnng_wait_thread_create(xp[i]);
}
}
return x;
}
static void rnng_signal_thread(void *args) {
nano_thread_duo *duo = (nano_thread_duo *) args;
nano_cv *ncv = duo->cv;
nng_mtx *mtx = ncv->mtx;
nng_cv *cv = ncv->cv;
nano_cv *ncv2 = duo->cv2;
nng_mtx *mtx2 = ncv2->mtx;
nng_cv *cv2 = ncv2->cv;
int incr, cond = 0;
nng_mtx_lock(mtx);
while (ncv->condition == cond)
nng_cv_wait(cv);
if (ncv->condition < 0) {
ncv->condition = cond;
nng_mtx_unlock(mtx);
return;
}
incr = ncv->condition - cond;
cond = cond + incr;
nng_mtx_unlock(mtx);
while (1) {
nng_mtx_lock(mtx2);
ncv2->condition = ncv2->condition + incr;
nng_cv_wake(cv2);
nng_mtx_unlock(mtx2);
nng_mtx_lock(mtx);
while (ncv->condition == cond)
nng_cv_wait(cv);
if (ncv->condition < 0) {
ncv->condition = cond;
nng_mtx_unlock(mtx);
break;
}
incr = ncv->condition - cond;
cond = cond + incr;
nng_mtx_unlock(mtx);
}
}
SEXP rnng_signal_thread_create(SEXP cv, SEXP cv2) {
if (NANO_PTR_CHECK(cv, nano_CvSymbol))
Rf_error("`cv` is not a valid Condition Variable");
if (NANO_PTR_CHECK(cv2, nano_CvSymbol))
Rf_error("`cv2` is not a valid Condition Variable");
int xc;
nano_thread_duo *duo = malloc(sizeof(nano_thread_duo));
NANO_ENSURE_ALLOC(duo);
SEXP existing = Rf_getAttrib(cv, nano_ThreadSymbol);
if (existing != R_NilValue) {
thread_duo_finalizer(existing);
R_ClearExternalPtr(existing);
}
nano_cv *ncv = (nano_cv *) NANO_PTR(cv);
nano_cv *ncv2 = (nano_cv *) NANO_PTR(cv2);
duo->cv = ncv;
duo->cv2 = ncv2;
nng_mtx *dmtx = ncv->mtx;
nng_mtx_lock(dmtx);
ncv->condition = 0;
nng_mtx_unlock(dmtx);
if ((xc = nng_thread_create(&duo->thr, rnng_signal_thread, duo)))
goto fail;
SEXP xptr = R_MakeExternalPtr(duo, R_NilValue, R_NilValue);
Rf_setAttrib(cv, nano_ThreadSymbol, xptr);
R_RegisterCFinalizerEx(xptr, thread_duo_finalizer, TRUE);
return cv2;
fail:
free(duo);
Rf_setAttrib(cv, nano_ThreadSymbol, R_NilValue);
failmem:
ERROR_OUT(xc);
}
char *nano_readline(void) {
size_t sz = NANONEXT_INIT_BUFSIZE;
size_t cur = 0;
char *buf = malloc(sz);
if (buf == NULL) {
return NULL;
}
int c;
while ((c = fgetc(stdin)) != EOF) {
if (cur + 1 >= sz) {
sz += sz;
char *nbuf = realloc(buf, sz);
if (nbuf == NULL)
break;
buf = nbuf;
}
buf[cur++] = (char) c;
if (c == '\n')
break;
}
if (cur == 0 && c == EOF) {
free(buf);
return NULL;
}
buf[cur] = '\0';
return buf;
}
void nano_read_thread(void *arg) {
int xc = 0;
nng_socket sock = {0};
nng_dialer dp = {0};
if ((xc = nng_push0_open(&sock)) ||
(xc = nng_dialer_create(&dp, sock, "inproc://nanonext-reserved-reader")) ||
(xc = nng_dialer_start(dp, 0)))
goto cleanup;
char *buf = NULL;
while (1) {
buf = nano_readline();
if (buf == NULL)
break;
xc = nng_send(sock, buf, strlen(buf) + 1, 0);
free(buf);
if (xc)
break;
}
cleanup:
nng_close(sock);
}
SEXP rnng_read_stdin(SEXP interactive) {
if (NANO_INTEGER(interactive))
Rf_error("can only be used in non-interactive sessions");
int xc;
nng_socket *sock = NULL;
nng_listener *lp = NULL;
sock = malloc(sizeof(nng_socket));
NANO_ENSURE_ALLOC(sock);
lp = malloc(sizeof(nng_listener));
NANO_ENSURE_ALLOC(lp);
if ((xc = nng_pull0_open(sock)) ||
(xc = nng_listener_create(lp, *sock, "inproc://nanonext-reserved-reader")) ||
(xc = nng_listener_start(*lp, 0)))
goto fail;
nng_thread *thr;
if ((xc = nng_thread_create(&thr, nano_read_thread, NULL)))
ERROR_OUT(xc);
SEXP socket, con, thread;
PROTECT(thread = R_MakeExternalPtr(thr, R_NilValue, R_NilValue));
R_RegisterCFinalizerEx(thread, thread_finalizer, TRUE);
PROTECT(con = R_MakeExternalPtr(lp, R_NilValue, thread));
R_RegisterCFinalizerEx(con, listener_finalizer, TRUE);
PROTECT(socket = R_MakeExternalPtr(sock, nano_SocketSymbol, con));
R_RegisterCFinalizerEx(socket, socket_finalizer, TRUE);
NANO_CLASS2(socket, "nanoSocket", "nano");
Rf_setAttrib(socket, nano_IdSymbol, Rf_ScalarInteger(nng_socket_id(*sock)));
Rf_setAttrib(socket, nano_ProtocolSymbol, Rf_mkString("pull"));
Rf_setAttrib(socket, nano_StateSymbol, Rf_mkString("opened"));
UNPROTECT(3);
return socket;
fail:
nng_close(*sock);
failmem:
free(lp);
free(sock);
ERROR_OUT(xc);
}
nanonext/src/aio.c 0000644 0001762 0000144 00000052353 15176112256 013625 0 ustar ligges users // nanonext - C level - Async Functions ----------------------------------------
#define NANONEXT_SIGNALS
#include "nanonext.h"
// internals -------------------------------------------------------------------
static SEXP mk_error_aio(const int xc, SEXP env) {
SEXP err;
PROTECT(err = Rf_ScalarInteger(xc));
Rf_classgets(err, nano_error);
Rf_defineVar(nano_ValueSymbol, err, env);
Rf_defineVar(nano_AioSymbol, nano_success, env);
UNPROTECT(1);
return err;
}
// aio completion callbacks ----------------------------------------------------
void nano_list_do(nano_list_op listop, nano_aio *saio) {
static nano_aio *free_list = NULL;
static nng_mtx *free_mtx = NULL;
switch (listop) {
case INIT:
if (free_mtx == NULL && nng_mtx_alloc(&free_mtx))
Rf_error("NNG library init failure");
break;
case FINALIZE:
nng_mtx_lock(free_mtx);
nano_list_do(FREE, NULL);
if (saio->mode == 0x1) {
nng_mtx_unlock(free_mtx);
nng_aio_free(saio->aio);
if (saio->data != NULL)
free(saio->data);
free(saio);
} else {
saio->mode = 0x1;
nng_mtx_unlock(free_mtx);
}
break;
case COMPLETE:
nng_mtx_lock(free_mtx);
if (saio->mode == 0x1) {
saio->next = free_list;
free_list = saio;
} else {
saio->mode = 0x1;
}
nng_mtx_unlock(free_mtx);
break;
case SHUTDOWN:
if (free_mtx == NULL) break;
nng_mtx_lock(free_mtx);
nano_list_do(FREE, NULL);
nng_mtx_unlock(free_mtx);
nng_mtx_free(free_mtx);
free_mtx = NULL;
break;
case FREE: // must be entered under lock
while (free_list != NULL) {
nano_aio *current = free_list;
free_list = (nano_aio *) current->next;
nng_aio_free(current->aio);
if (current->data != NULL)
free(current->data);
free(current);
}
break;
}
}
static void saio_complete(void *arg) {
nano_aio *saio = (nano_aio *) arg;
const int res = nng_aio_result(saio->aio);
if (res)
nng_msg_free(nng_aio_get_msg(saio->aio));
saio->result = res - !res;
nano_list_do(COMPLETE, saio);
}
static void isaio_complete(void *arg) {
nano_aio *iaio = (nano_aio *) arg;
const int res = nng_aio_result(iaio->aio);
iaio->result = res - !res;
nano_list_do(COMPLETE, iaio);
}
static void raio_complete(void *arg) {
nano_aio *raio = (nano_aio *) arg;
int res = nng_aio_result(raio->aio);
if (res == 0) {
nng_msg *msg = nng_aio_get_msg(raio->aio);
raio->data = msg;
nng_pipe p = nng_msg_get_pipe(msg);
res = - (int) p.id;
}
if (raio->next != NULL) {
nano_cv *ncv = (nano_cv *) raio->next;
nng_cv *cv = ncv->cv;
nng_mtx *mtx = ncv->mtx;
nng_mtx_lock(mtx);
raio->result = res;
ncv->condition++;
nng_cv_wake(cv);
nng_mtx_unlock(mtx);
} else {
raio->result = res;
}
if (raio->cb != NULL)
later2(raio_invoke_cb, raio->cb);
}
static void raio_complete_interrupt(void *arg) {
nano_aio *raio = (nano_aio *) arg;
int res = nng_aio_result(raio->aio);
if (res == 0) {
nng_msg *msg = nng_aio_get_msg(raio->aio);
raio->data = msg;
nng_pipe p = nng_msg_get_pipe(msg);
res = - (int) p.id;
}
raio->result = res;
if (raio->cb != NULL)
later2(raio_invoke_cb, raio->cb);
if (res <= 0) {
#ifdef _WIN32
UserBreak = 1;
#else
R_interrupts_pending = 1;
kill(getpid(), SIGINT);
#endif
}
}
static void iraio_complete(void *arg) {
nano_aio *iaio = (nano_aio *) arg;
const int res = nng_aio_result(iaio->aio);
if (iaio->next != NULL) {
nano_cv *ncv = (nano_cv *) iaio->next;
nng_cv *cv = ncv->cv;
nng_mtx *mtx = ncv->mtx;
nng_mtx_lock(mtx);
iaio->result = res - !res;
ncv->condition++;
nng_cv_wake(cv);
nng_mtx_unlock(mtx);
} else {
iaio->result = res - !res;
}
if (iaio->cb != NULL)
later2(raio_invoke_cb, iaio->cb);
}
// finalisers ------------------------------------------------------------------
static void saio_finalizer(SEXP xptr) {
if (NANO_PTR(xptr) == NULL) return;
nano_aio *xp = (nano_aio *) NANO_PTR(xptr);
nano_list_do(FINALIZE, xp);
}
static void iaio_finalizer(SEXP xptr) {
if (NANO_PTR(xptr) == NULL) return;
nano_aio *xp = (nano_aio *) NANO_PTR(xptr);
nng_aio_free(xp->aio);
if (xp->data != NULL)
free(xp->data);
free(xp);
}
static void raio_finalizer(SEXP xptr) {
if (NANO_PTR(xptr) == NULL) return;
nano_aio *xp = (nano_aio *) NANO_PTR(xptr);
nng_aio_free(xp->aio);
if (xp->data != NULL)
nng_msg_free((nng_msg *) xp->data);
free(xp);
}
// core aio - internal ---------------------------------------------------------
static inline SEXP create_aio_result(SEXP env, nano_aio *saio) {
if (saio->result > 0)
return mk_error_aio(saio->result, env);
Rf_defineVar(nano_ValueSymbol, nano_success, env);
Rf_defineVar(nano_AioSymbol, R_NilValue, env);
return nano_success;
}
static inline SEXP create_aio_msg(SEXP env, SEXP aio, nano_aio *raio, int res) {
SEXP out, pipe;
unsigned char *buf;
size_t sz;
if (raio->type == IOV_RECVAIO || raio->type == IOV_RECVAIOS) {
buf = raio->data;
sz = nng_aio_count(raio->aio);
PROTECT(out = nano_decode(buf, sz, raio->mode, NANO_PROT(aio)));
free(raio->data);
} else {
nng_msg *msg = (nng_msg *) raio->data;
buf = nng_msg_body(msg);
sz = nng_msg_len(msg);
PROTECT(out = nano_decode(buf, sz, raio->mode, NANO_PROT(aio)));
nng_msg_free(msg);
}
raio->data = NULL;
PROTECT(pipe = Rf_ScalarInteger(-res));
Rf_defineVar(nano_ValueSymbol, out, env);
Rf_defineVar(nano_AioSymbol, pipe, env);
UNPROTECT(2);
return out;
}
SEXP nano_aio_result(SEXP env) {
int found;
SEXP exist = nano_findVarInFrame(env, nano_ValueSymbol, &found);
if (found)
return exist;
const SEXP aio = nano_findVarInFrame(env, nano_AioSymbol, NULL);
nano_aio *saio = (nano_aio *) NANO_PTR(aio);
return create_aio_result(env, saio);
}
SEXP nano_aio_get_msg(SEXP env) {
int found;
SEXP exist = nano_findVarInFrame(env, nano_ValueSymbol, &found);
if (found)
return exist;
const SEXP aio = nano_findVarInFrame(env, nano_AioSymbol, NULL);
nano_aio *raio = (nano_aio *) NANO_PTR(aio);
int res;
switch (raio->type) {
case RECVAIO:
case REQAIO:
case IOV_RECVAIO:
case RECVAIOS:
case REQAIOS:
case IOV_RECVAIOS:
res = raio->result;
if (res > 0)
return mk_error_aio(res, env);
break;
default:
/* not reached */
res = 0;
}
return create_aio_msg(env, aio, raio, res);
}
// core aio --------------------------------------------------------------------
SEXP rnng_aio_result(SEXP env) {
int found;
SEXP exist = nano_findVarInFrame(env, nano_ValueSymbol, &found);
if (found)
return exist;
const SEXP aio = nano_findVarInFrame(env, nano_AioSymbol, NULL);
nano_aio *saio = (nano_aio *) NANO_PTR(aio);
if (nng_aio_busy(saio->aio))
return nano_unresolved;
return create_aio_result(env, saio);
}
SEXP rnng_aio_get_msg(SEXP env) {
int found;
SEXP exist = nano_findVarInFrame(env, nano_ValueSymbol, &found);
if (found)
return exist;
const SEXP aio = nano_findVarInFrame(env, nano_AioSymbol, NULL);
nano_aio *raio = (nano_aio *) NANO_PTR(aio);
int res;
switch (raio->type) {
case RECVAIO:
case REQAIO:
case IOV_RECVAIO:
if (nng_aio_busy(raio->aio))
return nano_unresolved;
res = raio->result;
if (res > 0)
return mk_error_aio(res, env);
break;
case RECVAIOS:
case REQAIOS:
case IOV_RECVAIOS: {
nng_mtx *mtx = ((nano_cv *) raio->next)->mtx;
nng_mtx_lock(mtx);
res = raio->result;
nng_mtx_unlock(mtx);
if (res == 0)
return nano_unresolved;
if (res > 0)
return mk_error_aio(res, env);
break;
}
default:
/* not reached */
res = 0;
}
return create_aio_msg(env, aio, raio, res);
}
SEXP rnng_aio_call(SEXP x) {
switch (TYPEOF(x)) {
case ENVSXP: {
const SEXP coreaio = nano_findVarInFrame(x, nano_AioSymbol, NULL);
if (NANO_PTR_CHECK(coreaio, nano_AioSymbol))
return x;
nano_aio *aiop = (nano_aio *) NANO_PTR(coreaio);
nng_aio_wait(aiop->aio);
switch (aiop->type) {
case RECVAIO:
case REQAIO:
case IOV_RECVAIO:
case RECVAIOS:
case REQAIOS:
case IOV_RECVAIOS:
nano_aio_get_msg(x);
break;
case SENDAIO:
case IOV_SENDAIO:
nano_aio_result(x);
break;
case HTTP_AIO:
nano_aio_http_status(x);
break;
}
break;
}
case VECSXP: {
const R_xlen_t xlen = Rf_xlength(x);
for (R_xlen_t i = 0; i < xlen; i++) {
rnng_aio_call(VECTOR_PTR_RO(x)[i]);
}
break;
}
}
return x;
}
static SEXP rnng_aio_collect_impl(SEXP x, SEXP (*const func)(SEXP)) {
SEXP out;
int found;
switch (TYPEOF(x)) {
case ENVSXP: {
out = nano_findVarInFrame(func(x), nano_ValueSymbol, &found);
if (!found) goto fail;
break;
}
case VECSXP: {
SEXP env, val, names;
const R_xlen_t xlen = Rf_xlength(x);
PROTECT(out = Rf_allocVector(VECSXP, xlen));
for (R_xlen_t i = 0; i < xlen; i++) {
env = func(VECTOR_PTR_RO(x)[i]);
if (TYPEOF(env) != ENVSXP) goto fail;
val = nano_findVarInFrame(env, nano_ValueSymbol, &found);
if (!found) goto fail;
SET_VECTOR_ELT(out, i, val);
}
names = Rf_getAttrib(x, R_NamesSymbol);
if (names != R_NilValue)
out = Rf_namesgets(out, names);
UNPROTECT(1);
break;
}
default:
goto fail;
}
return out;
fail:
Rf_error("object is not an Aio or list of Aios");
}
SEXP rnng_aio_collect(SEXP x) {
return rnng_aio_collect_impl(x, rnng_aio_call);
}
SEXP rnng_aio_collect_safe(SEXP x) {
return rnng_aio_collect_impl(x, rnng_wait_thread_create);
}
SEXP rnng_aio_stop(SEXP x) {
switch (TYPEOF(x)) {
case ENVSXP: {
const SEXP coreaio = nano_findVarInFrame(x, nano_AioSymbol, NULL);
if (NANO_PTR_CHECK(coreaio, nano_AioSymbol)) break;
nano_aio *aiop = (nano_aio *) NANO_PTR(coreaio);
nng_aio_stop(aiop->aio);
// See #194, this is to reset the R interrupts state after an interrupt
// requested by `stop_request()` has already triggered
#ifdef _WIN32
UserBreak = 0;
#else
R_interrupts_pending = 0;
#endif
break;
}
case VECSXP: {
const R_xlen_t xlen = Rf_xlength(x);
for (R_xlen_t i = 0; i < xlen; i++) {
rnng_aio_stop(VECTOR_PTR_RO(x)[i]);
}
break;
}
}
return R_NilValue;
}
SEXP rnng_request_stop(SEXP x) {
SEXP out;
switch (TYPEOF(x)) {
case ENVSXP: {
int res = 0;
SEXP coreaio;
PROTECT(coreaio = nano_findVarInFrame(x, nano_AioSymbol, NULL));
if (NANO_PTR_CHECK(coreaio, nano_AioSymbol)) goto fail;
nano_aio *aiop = (nano_aio *) NANO_PTR(coreaio);
if (aiop->type != REQAIOS && aiop->type != REQAIO) goto fail;
nng_aio_stop(aiop->aio);
nano_saio *saio = (nano_saio *) aiop->cb;
if (saio->disp == NULL) goto fail;
if (saio->type) {
nng_msg *msgp = NULL;
nng_ctx *ctx = (nng_ctx *) saio->disp;
const nng_duration dur = NANONEXT_WAIT_DUR;
if (nng_ctx_set_ms(*ctx, "send-timeout", dur) ||
nng_ctx_set_ms(*ctx, "recv-timeout", dur) ||
nng_msg_alloc(&msgp, 0) ||
nng_msg_append_u32(msgp, 0) ||
nng_msg_append(msgp, &saio->id, sizeof(int)) ||
nng_ctx_sendmsg(*ctx, msgp, 0)) {
nng_msg_free(msgp);
goto fail;
}
msgp = NULL;
if (nng_ctx_recvmsg(*ctx, &msgp, 0)) {
nng_msg_free(msgp);
goto fail;
}
memcpy(&res, nng_msg_body(msgp), sizeof(int));
nng_msg_free(msgp);
} else {
res = dispatch_cancel_direct(saio->disp, saio->id);
}
fail:
UNPROTECT(1);
out = Rf_ScalarLogical(res != 0);
break;
}
case VECSXP: {
const R_xlen_t xlen = Rf_xlength(x);
PROTECT(out = Rf_allocVector(LGLSXP, xlen));
for (R_xlen_t i = xlen - 1; i >= 0; i--) {
SEXP item = rnng_request_stop(VECTOR_PTR_RO(x)[i]);
SET_LOGICAL_ELT(out, i, NANO_INTEGER(item));
}
UNPROTECT(1);
break;
}
default:
out = Rf_ScalarLogical(0);
}
return out;
}
static int rnng_unresolved_impl(SEXP x) {
int xc;
switch (TYPEOF(x)) {
case ENVSXP: {
const SEXP coreaio = nano_findVarInFrame(x, nano_AioSymbol, NULL);
if (NANO_PTR_CHECK(coreaio, nano_AioSymbol)) {
xc = 0; break;
}
SEXP value;
nano_aio *aio = (nano_aio *) NANO_PTR(coreaio);
switch (aio->type) {
case SENDAIO:
case IOV_SENDAIO:
value = rnng_aio_result(x);
break;
case HTTP_AIO:
value = rnng_aio_http_status(x);
break;
default:
value = rnng_aio_get_msg(x);
break;
}
xc = value == nano_unresolved;
break;
}
case LGLSXP:
xc = x == nano_unresolved;
break;
default:
xc = 0;
}
return xc;
}
SEXP rnng_unresolved(SEXP x) {
switch (TYPEOF(x)) {
case ENVSXP:
case LGLSXP:
return Rf_ScalarLogical(rnng_unresolved_impl(x));
case VECSXP: {
const R_xlen_t xlen = Rf_xlength(x);
for (R_xlen_t i = 0; i < xlen; i++) {
if (rnng_unresolved_impl(VECTOR_PTR_RO(x)[i]))
return Rf_ScalarLogical(1);
}
}
}
return Rf_ScalarLogical(0);
}
static int rnng_unresolved2_impl(SEXP x) {
if (TYPEOF(x) == ENVSXP) {
const SEXP coreaio = nano_findVarInFrame(x, nano_AioSymbol, NULL);
if (NANO_PTR_CHECK(coreaio, nano_AioSymbol))
return 0;
nano_aio *aiop = (nano_aio *) NANO_PTR(coreaio);
return nng_aio_busy(aiop->aio);
}
return 0;
}
SEXP rnng_unresolved2(SEXP x) {
switch (TYPEOF(x)) {
case ENVSXP:
return Rf_ScalarLogical(rnng_unresolved2_impl(x));
case VECSXP: {
int xc = 0;
const R_xlen_t xlen = Rf_xlength(x);
for (R_xlen_t i = 0; i < xlen; i++) {
xc += rnng_unresolved2_impl(VECTOR_PTR_RO(x)[i]);
}
return Rf_ScalarInteger(xc);
}
}
return Rf_ScalarLogical(0);
}
static inline R_xlen_t race_find_resolved(SEXP x, R_xlen_t xlen) {
for (R_xlen_t i = 0; i < xlen; i++) {
SEXP elem = VECTOR_PTR_RO(x)[i];
if (TYPEOF(elem) == ENVSXP && !rnng_unresolved2_impl(elem))
return i + 1;
}
return 0;
}
static inline int race_count_unresolved(SEXP x, R_xlen_t xlen) {
int n = 0;
for (R_xlen_t i = 0; i < xlen; i++)
n += rnng_unresolved2_impl(VECTOR_PTR_RO(x)[i]);
return n;
}
SEXP rnng_race_aio(SEXP x, SEXP cvar) {
if (TYPEOF(x) != VECSXP)
return Rf_ScalarInteger(0);
const R_xlen_t xlen = Rf_xlength(x);
if (xlen == 0)
return Rf_ScalarInteger(0);
R_xlen_t idx = race_find_resolved(x, xlen);
if (idx)
return Rf_ScalarInteger((int) idx);
if (NANO_PTR_CHECK(cvar, nano_CvSymbol))
return Rf_ScalarInteger(0);
nano_cv *ncv = (nano_cv *) NANO_PTR(cvar);
nng_cv *cv = ncv->cv;
nng_mtx *mtx = ncv->mtx;
nng_mtx_lock(mtx);
ncv->flag = ncv->flag < 0;
ncv->condition = 0;
int n = race_count_unresolved(x, xlen);
nng_mtx_unlock(mtx);
if (n == 0)
return Rf_ScalarInteger((int) race_find_resolved(x, xlen));
nng_time time = nng_clock();
int current_n, flag;
do {
time = time + 400;
int signalled = 1;
nng_mtx_lock(mtx);
while (ncv->condition == 0) {
if (nng_cv_until(cv, time) == NNG_ETIMEDOUT) {
signalled = 0;
break;
}
}
if (signalled)
ncv->condition--;
flag = ncv->flag;
nng_mtx_unlock(mtx);
if (flag < 0)
return Rf_ScalarInteger(0);
if (!signalled)
R_CheckUserInterrupt();
current_n = race_count_unresolved(x, xlen);
} while (current_n == n);
return Rf_ScalarInteger((int) race_find_resolved(x, xlen));
}
// send recv aio functions -----------------------------------------------------
SEXP rnng_send_aio(SEXP con, SEXP data, SEXP mode, SEXP timeout, SEXP pipe, SEXP clo) {
const nng_duration dur = timeout == R_NilValue ? NNG_DURATION_DEFAULT : (nng_duration) nano_integer(timeout);
const int raw = nano_encode_mode(mode);
SEXP aio, env, fun;
nano_aio *saio = NULL;
nano_buf buf;
int sock, xc;
if ((sock = !NANO_PTR_CHECK(con, nano_SocketSymbol)) || !NANO_PTR_CHECK(con, nano_ContextSymbol)) {
const int pipeid = sock ? nano_integer(pipe) : 0;
if (raw) {
nano_encode(&buf, data);
} else {
nano_serialize(&buf, data, NANO_PROT(con), 0);
}
nng_msg *msg = NULL;
saio = calloc(1, sizeof(nano_aio));
NANO_ENSURE_ALLOC(saio);
saio->type = SENDAIO;
if ((xc = nng_msg_alloc(&msg, 0)) ||
(xc = nng_aio_alloc(&saio->aio, saio_complete, saio))) {
nng_msg_free(msg);
goto fail;
}
nano_msg_set_body(msg, &buf);
if (pipeid) {
nng_pipe p;
p.id = (uint32_t) pipeid;
nng_msg_set_pipe(msg, p);
}
nng_aio_set_msg(saio->aio, msg);
nng_aio_set_timeout(saio->aio, dur);
sock ? nng_send_aio(*(nng_socket *) NANO_PTR(con), saio->aio) :
nng_ctx_send(*(nng_ctx *) NANO_PTR(con), saio->aio);
NANO_FREE(buf);
PROTECT(aio = R_MakeExternalPtr(saio, nano_AioSymbol, R_NilValue));
R_RegisterCFinalizerEx(aio, saio_finalizer, TRUE);
} else if (!NANO_PTR_CHECK(con, nano_StreamSymbol)) {
nano_encode(&buf, data);
nano_stream *nst = (nano_stream *) NANO_PTR(con);
nng_stream *sp = nst->stream;
saio = calloc(1, sizeof(nano_aio));
NANO_ENSURE_ALLOC(saio);
if (nst->msgmode) {
nng_msg *msg;
const size_t xlen = buf.cur - nst->textframes;
saio->type = SENDAIO;
if ((xc = nng_msg_alloc(&msg, xlen)))
goto fail;
memcpy(nng_msg_body(msg), buf.buf, xlen);
if ((xc = nng_aio_alloc(&saio->aio, saio_complete, saio))) {
nng_msg_free(msg);
goto fail;
}
nng_aio_set_msg(saio->aio, msg);
} else {
saio->type = IOV_SENDAIO;
saio->data = malloc(buf.cur);
NANO_ENSURE_ALLOC(saio->data);
memcpy(saio->data, buf.buf, buf.cur);
nng_iov iov = {
.iov_buf = saio->data,
.iov_len = buf.cur - nst->textframes
};
if ((xc = nng_aio_alloc(&saio->aio, isaio_complete, saio)) ||
(xc = nng_aio_set_iov(saio->aio, 1u, &iov)))
goto fail;
}
nng_aio_set_timeout(saio->aio, dur);
nng_stream_send(sp, saio->aio);
NANO_FREE(buf);
PROTECT(aio = R_MakeExternalPtr(saio, nano_AioSymbol, R_NilValue));
R_RegisterCFinalizerEx(aio, saio_finalizer, TRUE);
} else {
Rf_error("`con` is not a valid Socket, Context, or Stream");
}
PROTECT(env = R_NewEnv(R_NilValue, 0, 0));
Rf_classgets(env, nano_sendAio);
Rf_defineVar(nano_AioSymbol, aio, env);
PROTECT(fun = R_mkClosure(R_NilValue, nano_aioFuncRes, clo));
R_MakeActiveBinding(nano_ResultSymbol, fun, env);
UNPROTECT(3);
return env;
fail:
nng_aio_free(saio->aio);
free(saio->data);
failmem:
NANO_FREE(buf);
free(saio);
return mk_error_data(-xc);
}
SEXP rnng_recv_aio(SEXP con, SEXP mode, SEXP timeout, SEXP cvar, SEXP clo) {
const nng_duration dur = timeout == R_NilValue ? NNG_DURATION_DEFAULT : (nng_duration) nano_integer(timeout);
const int signal = cvar != R_NilValue && !NANO_PTR_CHECK(cvar, nano_CvSymbol);
const int interrupt = TYPEOF(cvar) == SYMSXP;
nano_cv *ncv = signal ? (nano_cv *) NANO_PTR(cvar) : NULL;
nano_aio *raio = NULL;
SEXP aio, env, fun;
int sock, xc;
if ((sock = !NANO_PTR_CHECK(con, nano_SocketSymbol)) || !NANO_PTR_CHECK(con, nano_ContextSymbol)) {
const uint8_t mod = (uint8_t) nano_matcharg(mode);
raio = calloc(1, sizeof(nano_aio));
NANO_ENSURE_ALLOC(raio);
raio->next = ncv;
raio->type = signal ? RECVAIOS : RECVAIO;
raio->mode = mod;
if ((xc = nng_aio_alloc(&raio->aio, interrupt ? raio_complete_interrupt : raio_complete, raio)))
goto fail;
nng_aio_set_timeout(raio->aio, dur);
sock ? nng_recv_aio(*(nng_socket *) NANO_PTR(con), raio->aio) :
nng_ctx_recv(*(nng_ctx *) NANO_PTR(con), raio->aio);
PROTECT(aio = R_MakeExternalPtr(raio, nano_AioSymbol, NANO_PROT(con)));
R_RegisterCFinalizerEx(aio, raio_finalizer, TRUE);
} else if (!NANO_PTR_CHECK(con, nano_StreamSymbol)) {
const uint8_t mod = (uint8_t) (nano_matcharg(mode) == 1 ? 2 : nano_matcharg(mode));
nano_stream *nst = (nano_stream *) NANO_PTR(con);
nng_stream *sp = nst->stream;
raio = calloc(1, sizeof(nano_aio));
NANO_ENSURE_ALLOC(raio);
raio->next = ncv;
raio->mode = mod;
if (nst->msgmode) {
raio->type = signal ? RECVAIOS : RECVAIO;
if ((xc = nng_aio_alloc(&raio->aio, raio_complete, raio)))
goto fail;
} else {
size_t xlen = nst->bufsize;
raio->type = signal ? IOV_RECVAIOS : IOV_RECVAIO;
raio->data = malloc(xlen);
NANO_ENSURE_ALLOC(raio->data);
nng_iov iov = {
.iov_buf = raio->data,
.iov_len = xlen
};
if ((xc = nng_aio_alloc(&raio->aio, iraio_complete, raio)) ||
(xc = nng_aio_set_iov(raio->aio, 1u, &iov)))
goto fail;
}
nng_aio_set_timeout(raio->aio, dur);
nng_stream_recv(sp, raio->aio);
PROTECT(aio = R_MakeExternalPtr(raio, nano_AioSymbol, R_NilValue));
R_RegisterCFinalizerEx(aio, nst->msgmode ? raio_finalizer : iaio_finalizer, TRUE);
} else {
Rf_error("`con` is not a valid Socket, Context or Stream");
}
PROTECT(env = R_NewEnv(R_NilValue, 0, 0));
Rf_classgets(env, nano_recvAio);
Rf_defineVar(nano_AioSymbol, aio, env);
PROTECT(fun = R_mkClosure(R_NilValue, nano_aioFuncMsg, clo));
R_MakeActiveBinding(nano_DataSymbol, fun, env);
UNPROTECT(3);
return env;
fail:
nng_aio_free(raio->aio);
free(raio->data);
failmem:
free(raio);
return mk_error_data(xc);
}
nanonext/src/net.c 0000644 0001762 0000144 00000006342 15077362607 013647 0 ustar ligges users // nanonext - C level - Net Utils ----------------------------------------------
#define NANONEXT_NET
#include "nanonext.h"
// IP Addresses ----------------------------------------------------------------
SEXP rnng_ip_addr(void) {
char buf[INET_ADDRSTRLEN];
int i = 0;
SEXP out, names;
PROTECT_INDEX pxi, pxn;
PROTECT_WITH_INDEX(out = Rf_allocVector(STRSXP, 1), &pxi);
PROTECT_WITH_INDEX(names = Rf_allocVector(STRSXP, 1), &pxn);
#ifdef _WIN32
DWORD ret;
ULONG flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER;
ULONG bufsize = 15000;
IP_ADAPTER_ADDRESSES *adapter, *addrs;
IP_ADAPTER_UNICAST_ADDRESS *addr;
int j = 0;
do {
addrs = malloc(bufsize);
if (addrs == NULL)
goto cleanup;
ret = GetAdaptersAddresses(AF_INET, flags, NULL, addrs, &bufsize);
if (ret == ERROR_BUFFER_OVERFLOW)
free(addrs);
} while ((ret == ERROR_BUFFER_OVERFLOW) && (++j == 1));
if (ret != NO_ERROR) {
free(addrs);
goto cleanup;
}
for (adapter = addrs; adapter != NULL; adapter = adapter->Next) {
if (adapter->OperStatus == IfOperStatusUp) {
for (addr = adapter->FirstUnicastAddress; addr != NULL; addr = addr->Next) {
if (addr->Address.lpSockaddr->sa_family == AF_INET) {
struct sockaddr_in *sa_in = (struct sockaddr_in *) addr->Address.lpSockaddr;
inet_ntop(AF_INET, &sa_in->sin_addr, buf, sizeof(buf));
if (i) {
REPROTECT(out = Rf_xlengthgets(out, i + 1), pxi);
REPROTECT(names = Rf_xlengthgets(names, i + 1), pxn);
}
SET_STRING_ELT(out, i, Rf_mkChar(buf));
int sz = WideCharToMultiByte(CP_UTF8, 0, adapter->FriendlyName, -1, NULL, 0, NULL, NULL);
char nbuf[sz];
if (WideCharToMultiByte(CP_UTF8, 0, adapter->FriendlyName, -1, nbuf, sz, NULL, NULL)) {}
SET_STRING_ELT(names, i++, Rf_mkChar(nbuf));
}
}
}
}
free(addrs);
#else
struct ifaddrs *ifaddr, *ifa;
if (getifaddrs(&ifaddr))
goto cleanup;
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
if ((ifa->ifa_addr != NULL) &&
(ifa->ifa_addr->sa_family == AF_INET) &&
!(ifa->ifa_flags & IFF_LOOPBACK)) {
struct sockaddr_in *sa_in = (struct sockaddr_in *) ifa->ifa_addr;
inet_ntop(AF_INET, &(sa_in->sin_addr), buf, sizeof(buf));
if (i) {
REPROTECT(out = Rf_xlengthgets(out, i + 1), pxi);
REPROTECT(names = Rf_xlengthgets(names, i + 1), pxn);
}
SET_STRING_ELT(out, i, Rf_mkChar(buf));
SET_STRING_ELT(names, i++, Rf_mkChar(ifa->ifa_name));
}
}
freeifaddrs(ifaddr);
#endif
Rf_namesgets(out, names);
cleanup:
UNPROTECT(2);
return out;
}
// misc utils ------------------------------------------------------------------
SEXP rnng_write_stdout(SEXP x) {
const char *buf = CHAR(STRING_ELT(x, 0));
#ifdef _WIN32
char nbuf[strlen(buf) + 2];
snprintf(nbuf, sizeof(nbuf), "%s\n", buf);
DWORD bytes;
if (WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), nbuf, (DWORD) strlen(nbuf), &bytes, NULL)) {}
#else
struct iovec iov[2] = {
{.iov_base = (void *) buf, .iov_len = strlen(buf)},
{.iov_base = "\n", .iov_len = 1}
};
if (writev(STDOUT_FILENO, iov, 2)) {}
#endif
return R_NilValue;
}
nanonext/src/dispatcher.c 0000644 0001762 0000144 00000072662 15176112256 015210 0 ustar ligges users // nanonext - dispatcher implementation -----------------------------------------
#define NANONEXT_PROTOCOLS
#include "nanonext.h"
// L'Ecuyer-CMRG RNG stream advancement ----------------------------------------
//
// Pure-C implementation of MRG32k3a stream jumping. Moduli and jump matrix
// constants below are from the RngStreams package by Pierre L'Ecuyer,
// University of Montreal (https://github.com/umontreal-simul/RngStreams),
// licensed under the Apache License, Version 2.0. The original copyright
// notice requests citation of:
//
// P. L'Ecuyer, "Good Parameter Sets for Combined Multiple Recursive Random
// Number Generators", Operations Research, 47, 1 (1999), 159-164.
// P. L'Ecuyer, R. Simard, E. J. Chen, and W. D. Kelton, "An Objected-
// Oriented Random-Number Package with Many Long Streams and Substreams",
// Operations Research, 50, 6 (2002), 1073-1075.
//
#define M1 4294967087ULL
#define M2 4294944443ULL
// Jump matrices A1^(2^127) mod m1 and A2^(2^127) mod m2
static const unsigned long long A1p127[3][3] = {
{ 2427906178ULL, 3580155704ULL, 949770784ULL },
{ 226153695ULL, 1230515664ULL, 3580155704ULL },
{ 1988835001ULL, 986791581ULL, 1230515664ULL }
};
static const unsigned long long A2p127[3][3] = {
{ 1464411153ULL, 277697599ULL, 1610723613ULL },
{ 32183930ULL, 1464411153ULL, 1022607788ULL },
{ 2824425944ULL, 32183930ULL, 2093834863ULL }
};
static void mat_vec_mod(const unsigned long long A[3][3],
const unsigned long long *v,
unsigned long long *out, unsigned long long m) {
for (int i = 0; i < 3; i++) {
unsigned long long s = 0;
for (int j = 0; j < 3; j++) {
s = (s + (A[i][j] * v[j]) % m) % m;
}
out[i] = s;
}
}
static void next_rng_stream_c(int *seed) {
unsigned long long v1[3] = { (unsigned int) seed[0], (unsigned int) seed[1], (unsigned int) seed[2] };
unsigned long long v2[3] = { (unsigned int) seed[3], (unsigned int) seed[4], (unsigned int) seed[5] };
unsigned long long out1[3], out2[3];
mat_vec_mod(A1p127, v1, out1, M1);
mat_vec_mod(A2p127, v2, out2, M2);
for (int i = 0; i < 3; i++) {
seed[i] = (int) out1[i];
seed[i + 3] = (int) out2[i];
}
}
// data structures -------------------------------------------------------------
#define DISPATCH_INITIAL_SIZE 16
typedef struct nano_dispatch_task_s {
nng_ctx ctx;
nng_msg *msg;
int msgid;
int is_sync;
struct nano_dispatch_task_s *next;
} nano_dispatch_task;
typedef struct nano_dispatch_daemon_s {
int pipe;
nng_ctx ctx;
int msgid;
int sync_gen;
} nano_dispatch_daemon;
typedef struct nano_dispatcher_s {
nng_socket *rep_sock;
nng_socket *poly_sock;
nano_cv *cv;
nano_monitor *monitor;
nano_dispatch_task *inq_head;
nano_dispatch_task *inq_tail;
nano_dispatch_daemon *daemons;
int inq_count;
int outq_capacity;
int outq_count;
int connections;
int count;
int executing;
nng_aio *host_aio;
nng_aio *daemon_aio;
nng_ctx host_ctx;
int host_recv_ready;
int daemon_recv_ready;
int rng_seed[6];
unsigned char *init_template;
size_t init_template_len;
size_t init_seed_offset;
unsigned char *conn_reset_buf;
size_t conn_reset_len;
int syncing;
int sync_generation;
int *pipe_events;
int pipe_events_size;
size_t limit_bytes;
size_t queued_bytes;
size_t peak_queued_bytes;
} nano_dispatcher;
// forward declarations --------------------------------------------------------
static void dispatch_shutdown(nano_dispatcher *d);
static void dispatch_handle_connect(nano_dispatcher *d, int pipe);
static void dispatch_handle_disconnect(nano_dispatcher *d, int pipe);
static void dispatch_dispatch_tasks(nano_dispatcher *d);
static int dispatch_cancel_inq(nano_dispatcher *d, int id);
static nano_dispatch_daemon *dispatch_find_idle_daemon(nano_dispatcher *d);
// daemon array operations -----------------------------------------------------
static nano_dispatch_daemon *dispatch_find_daemon(nano_dispatcher *d, int pipe) {
for (int i = 0; i < d->outq_count; i++)
if (d->daemons[i].pipe == pipe)
return &d->daemons[i];
return NULL;
}
static void dispatch_insert_daemon(nano_dispatcher *d, int pipe) {
if (d->outq_count >= d->outq_capacity) {
int new_cap = d->outq_capacity * 2;
nano_dispatch_daemon *new_arr = realloc(d->daemons, new_cap * sizeof(nano_dispatch_daemon));
if (new_arr == NULL) {
REprintf("dispatcher: allocation failed for pipe %d\n", pipe);
return;
}
d->daemons = new_arr;
d->outq_capacity = new_cap;
}
nano_dispatch_daemon *dd = &d->daemons[d->outq_count++];
dd->pipe = pipe;
dd->msgid = 0;
dd->sync_gen = d->sync_generation - 1;
}
// queue operations ------------------------------------------------------------
static void dispatch_enqueue(nano_dispatcher *d, nng_ctx ctx,
nng_msg *msg, int msgid, int is_sync) {
nano_dispatch_task *t = malloc(sizeof(nano_dispatch_task));
if (t == NULL) {
nng_ctx_close(ctx);
nng_msg_free(msg);
return;
}
t->ctx = ctx;
t->msg = msg;
t->msgid = msgid;
t->is_sync = is_sync;
t->next = NULL;
if (d->inq_tail)
d->inq_tail->next = t;
else
d->inq_head = t;
d->inq_tail = t;
d->inq_count++;
d->queued_bytes += nng_msg_len(msg);
if (d->queued_bytes > d->peak_queued_bytes)
d->peak_queued_bytes = d->queued_bytes;
}
static void dispatch_dequeue(nano_dispatcher *d) {
nano_dispatch_task *t = d->inq_head;
if (t) {
d->inq_head = t->next;
if (d->inq_head == NULL)
d->inq_tail = NULL;
d->inq_count--;
free(t);
}
}
static void dispatch_free_task(nano_dispatch_task *t) {
nng_ctx_close(t->ctx);
if (t->msg)
nng_msg_free(t->msg);
free(t);
}
// send operations -------------------------------------------------------------
static int dispatch_send_to_daemon(nano_dispatcher *d, int pipe,
unsigned char *data, size_t len) {
nng_msg *msg;
if (nng_msg_alloc(&msg, len))
return -1;
if (len)
memcpy(nng_msg_body(msg), data, len);
nng_msg_set_pipe(msg, (nng_pipe) {.id = (uint32_t) pipe});
int xc = nng_sendmsg(*d->poly_sock, msg, 0);
if (xc != 0)
nng_msg_free(msg);
return xc;
}
static int dispatch_send_msg_to_daemon(nano_dispatcher *d, int pipe, nng_msg *msg) {
nng_msg_set_pipe(msg, (nng_pipe) {.id = (uint32_t) pipe});
return nng_sendmsg(*d->poly_sock, msg, 0);
}
static int dispatch_send_reply(nng_ctx ctx, unsigned char *data, size_t len) {
nng_msg *msg;
if (nng_msg_alloc(&msg, len))
return -1;
memcpy(nng_msg_body(msg), data, len);
int xc = nng_ctx_sendmsg(ctx, msg, 0);
if (xc != 0)
nng_msg_free(msg);
return xc;
}
static int dispatch_send_msg_reply(nng_ctx ctx, nng_msg *msg) {
return nng_ctx_sendmsg(ctx, msg, 0);
}
// AIO Callbacks ---------------------------------------------------------------
static void host_recv_cb(void *arg) {
nano_dispatcher *d = (nano_dispatcher *) arg;
nng_mtx *mtx = d->cv->mtx;
nng_mtx_lock(mtx);
d->host_recv_ready = 1;
d->cv->condition++;
nng_cv_wake(d->cv->cv);
nng_mtx_unlock(mtx);
}
static void daemon_recv_cb(void *arg) {
nano_dispatcher *d = (nano_dispatcher *) arg;
nng_mtx *mtx = d->cv->mtx;
nng_mtx_lock(mtx);
d->daemon_recv_ready = 1;
d->cv->condition++;
nng_cv_wake(d->cv->cv);
nng_mtx_unlock(mtx);
}
// message utilities -----------------------------------------------------------
static inline void dispatch_read_msg_info(unsigned char *buf, size_t len,
int *msgid, int *is_sync) {
if (len > 12 && buf[0] == 0x7) {
memcpy(msgid, buf + 4, sizeof(int));
*is_sync = buf[3] == 0x1;
} else {
*msgid = 0;
*is_sync = 0;
}
}
// monitor processing ----------------------------------------------------------
static int dispatch_ensure_pipe_events(nano_dispatcher *d, int needed) {
if (needed <= d->pipe_events_size)
return 1;
int new_size = d->pipe_events_size ? d->pipe_events_size : DISPATCH_INITIAL_SIZE;
while (new_size < needed)
new_size *= 2;
int *new_buf = realloc(d->pipe_events, new_size * sizeof(int));
if (new_buf == NULL)
return 0;
d->pipe_events = new_buf;
d->pipe_events_size = new_size;
return 1;
}
static void dispatch_process_monitor(nano_dispatcher *d, int count) {
for (int i = 0; i < count; i++) {
if (d->pipe_events[i] > 0)
dispatch_handle_connect(d, d->pipe_events[i]);
else
dispatch_handle_disconnect(d, -d->pipe_events[i]);
}
}
// init template ---------------------------------------------------------------
static int dispatch_prepare_init_template(nano_dispatcher *d, SEXP stream,
SEXP serial) {
int *sdata = INTEGER(stream);
for (int i = 0; i < 6; i++)
d->rng_seed[i] = sdata[i + 1];
int saved[6];
memcpy(saved, sdata + 1, 6 * sizeof(int));
const int sentinel_val = 0x7F7F7F7F;
for (int i = 0; i < 6; i++)
sdata[i + 1] = sentinel_val;
SEXP init_data;
PROTECT(init_data = Rf_allocVector(VECSXP, 2));
SET_VECTOR_ELT(init_data, 0, stream);
SET_VECTOR_ELT(init_data, 1, serial);
nano_buf buf;
nano_serialize(&buf, init_data, serial, 0);
UNPROTECT(1);
memcpy(sdata + 1, saved, 6 * sizeof(int));
unsigned char sentinel[24];
memset(sentinel, 0x7F, 24);
size_t offset = 0;
int found = 0;
for (size_t i = 0; i + 24 <= buf.cur; i++) {
if (memcmp(buf.buf + i, sentinel, 24) == 0) {
offset = i;
found = 1;
break;
}
}
if (!found) {
NANO_FREE(buf);
return -1;
}
d->init_template = malloc(buf.cur);
if (d->init_template == NULL) {
NANO_FREE(buf);
return -1;
}
memcpy(d->init_template, buf.buf, buf.cur);
d->init_template_len = buf.cur;
d->init_seed_offset = offset;
NANO_FREE(buf);
return 0;
}
// event handlers --------------------------------------------------------------
static void dispatch_handle_connect(nano_dispatcher *d, int pipe) {
next_rng_stream_c(d->rng_seed);
nng_msg *msg;
if (nng_msg_alloc(&msg, d->init_template_len))
return;
unsigned char *buf = nng_msg_body(msg);
memcpy(buf, d->init_template, d->init_template_len);
memcpy(buf + d->init_seed_offset, d->rng_seed, sizeof(d->rng_seed));
if (dispatch_send_msg_to_daemon(d, pipe, msg) != 0)
nng_msg_free(msg);
else {
nng_mtx_lock(d->cv->mtx);
dispatch_insert_daemon(d, pipe);
d->connections++;
nng_mtx_unlock(d->cv->mtx);
}
}
static void dispatch_handle_disconnect(nano_dispatcher *d, int pipe) {
nng_mtx_lock(d->cv->mtx);
nano_dispatch_daemon *dd = dispatch_find_daemon(d, pipe);
if (dd == NULL) {
nng_mtx_unlock(d->cv->mtx);
return;
}
int busy = dd->msgid != 0;
nng_ctx ctx;
if (busy) {
d->executing--;
ctx = dd->ctx;
}
*dd = d->daemons[--d->outq_count];
nng_mtx_unlock(d->cv->mtx);
if (busy) {
dispatch_send_reply(ctx, d->conn_reset_buf, d->conn_reset_len);
nng_ctx_close(ctx);
}
}
static void dispatch_handle_host_recv(nano_dispatcher *d) {
nng_msg *msg = nng_aio_get_msg(d->host_aio);
unsigned char *buf = nng_msg_body(msg);
size_t len = nng_msg_len(msg);
if (buf[0] == 0) {
int id = 0;
if (len >= 8)
memcpy(&id, buf + 4, sizeof(int));
nng_mtx_lock(d->cv->mtx);
if (id == 0) {
int completed = d->count - d->inq_count - d->executing;
int result[5] = {d->outq_count, d->connections, d->inq_count, d->executing, completed};
nng_mtx_unlock(d->cv->mtx);
dispatch_send_reply(d->host_ctx, (unsigned char *) result, sizeof(result));
} else {
int found = 0;
int cancel_pipe = 0;
for (int i = 0; i < d->outq_count; i++) {
if (d->daemons[i].msgid == id) {
cancel_pipe = d->daemons[i].pipe;
found = 1;
break;
}
}
if (!found)
found = dispatch_cancel_inq(d, id);
nng_mtx_unlock(d->cv->mtx);
if (cancel_pipe)
dispatch_send_to_daemon(d, cancel_pipe, NULL, 0);
dispatch_send_reply(d->host_ctx, (unsigned char *) &found, sizeof(int));
}
nng_ctx_close(d->host_ctx);
} else {
int msgid, is_sync;
dispatch_read_msg_info(buf, len, &msgid, &is_sync);
int send_pipe = 0;
nng_mtx_lock(d->cv->mtx);
d->count++;
nano_dispatch_daemon *dd = dispatch_find_idle_daemon(d);
if (dd != NULL) {
send_pipe = dd->pipe;
dd->ctx = d->host_ctx;
dd->msgid = msgid;
d->executing++;
if (is_sync) {
dd->sync_gen = d->sync_generation;
d->syncing = 1;
} else if (d->syncing) {
d->syncing = 0;
d->sync_generation++;
}
} else {
dispatch_enqueue(d, d->host_ctx, msg, msgid, is_sync);
msg = NULL;
}
nng_mtx_unlock(d->cv->mtx);
if (send_pipe) {
if (dispatch_send_msg_to_daemon(d, send_pipe, msg) == 0)
msg = NULL;
}
}
if (msg)
nng_msg_free(msg);
if (nng_ctx_open(&d->host_ctx, *d->rep_sock) == 0) {
nng_ctx_recv(d->host_ctx, d->host_aio);
} else {
nng_mtx_lock(d->cv->mtx);
d->cv->flag = -1;
nng_mtx_unlock(d->cv->mtx);
}
}
static void dispatch_handle_daemon_recv(nano_dispatcher *d) {
nng_msg *msg = nng_aio_get_msg(d->daemon_aio);
nng_pipe pipe = nng_msg_get_pipe(msg);
int pipe_id = (int) pipe.id;
int dummy, is_marker;
dispatch_read_msg_info(nng_msg_body(msg), nng_msg_len(msg), &dummy, &is_marker);
nng_mtx_lock(d->cv->mtx);
nano_dispatch_daemon *dd = dispatch_find_daemon(d, pipe_id);
if (dd != NULL && dd->msgid != 0) {
d->executing--;
nng_ctx ctx = dd->ctx;
if (is_marker) {
*dd = d->daemons[--d->outq_count];
} else {
dd->msgid = 0;
}
nng_mtx_unlock(d->cv->mtx);
if (dispatch_send_msg_reply(ctx, msg) == 0)
msg = NULL;
nng_ctx_close(ctx);
if (is_marker)
dispatch_send_to_daemon(d, pipe_id, NULL, 0);
} else {
nng_mtx_unlock(d->cv->mtx);
}
if (msg)
nng_msg_free(msg);
nng_recv_aio(*d->poly_sock, d->daemon_aio);
}
// helper functions ------------------------------------------------------------
static int dispatch_cancel_inq(nano_dispatcher *d, int id) {
nano_dispatch_task **pp = &d->inq_head;
nano_dispatch_task *prev = NULL;
while (*pp) {
if ((*pp)->msgid == id) {
nano_dispatch_task *t = *pp;
*pp = t->next;
if (t == d->inq_tail)
d->inq_tail = prev;
d->queued_bytes -= nng_msg_len(t->msg);
dispatch_free_task(t);
d->inq_count--;
if (d->limit_bytes > 0)
nng_cv_wake(d->cv->cv);
return 1;
}
prev = *pp;
pp = &(*pp)->next;
}
return 0;
}
static nano_dispatch_daemon *dispatch_find_idle_daemon(nano_dispatcher *d) {
for (int i = 0; i < d->outq_count; i++)
if (d->daemons[i].msgid == 0 && !(d->syncing && d->daemons[i].sync_gen == d->sync_generation))
return &d->daemons[i];
return NULL;
}
// task dispatcher -------------------------------------------------------------
static void dispatch_dispatch_tasks(nano_dispatcher *d) {
struct { int pipe; nng_msg *msg; } batch[32];
int nsends;
do {
nsends = 0;
nng_mtx_lock(d->cv->mtx);
while (d->inq_head && nsends < 32) {
nano_dispatch_task *t = d->inq_head;
nano_dispatch_daemon *dd = dispatch_find_idle_daemon(d);
if (dd == NULL)
break;
size_t len = nng_msg_len(t->msg);
batch[nsends].pipe = dd->pipe;
batch[nsends].msg = t->msg;
nsends++;
t->msg = NULL;
d->queued_bytes -= len;
dd->ctx = t->ctx;
dd->msgid = t->msgid;
d->executing++;
if (t->is_sync) {
dd->sync_gen = d->sync_generation;
d->syncing = 1;
} else if (d->syncing) {
d->syncing = 0;
d->sync_generation++;
}
dispatch_dequeue(d);
}
if (d->limit_bytes > 0 && nsends > 0)
nng_cv_wake(d->cv->cv);
nng_mtx_unlock(d->cv->mtx);
for (int i = 0; i < nsends; i++) {
if (dispatch_send_msg_to_daemon(d, batch[i].pipe, batch[i].msg) != 0)
nng_msg_free(batch[i].msg);
}
} while (nsends == 32);
}
// main event loop -------------------------------------------------------------
static int dispatch_wait_cv(nano_dispatcher *d, int *host_ready,
int *daemon_ready, int *monitor_count) {
nng_mtx *mtx = d->cv->mtx;
nng_cv *cv = d->cv->cv;
nano_monitor *m = d->monitor;
int stop;
nng_mtx_lock(mtx);
while (d->cv->condition == 0 && d->cv->flag >= 0)
nng_cv_wait(cv);
stop = d->cv->flag < 0;
d->cv->condition = 0;
*host_ready = d->host_recv_ready;
*daemon_ready = d->daemon_recv_ready;
d->host_recv_ready = 0;
d->daemon_recv_ready = 0;
int count = m->updates;
if (count && dispatch_ensure_pipe_events(d, count)) {
memcpy(d->pipe_events, m->ids, count * sizeof(int));
m->updates = 0;
*monitor_count = count;
} else {
*monitor_count = 0;
}
nng_mtx_unlock(mtx);
return stop;
}
static int dispatch_loop(nano_dispatcher *d) {
int host_ready, daemon_ready, monitor_events;
while (1) {
if (dispatch_wait_cv(d, &host_ready, &daemon_ready, &monitor_events))
return 0;
if (monitor_events)
dispatch_process_monitor(d, monitor_events);
if (host_ready) {
if (nng_aio_result(d->host_aio) == 0)
dispatch_handle_host_recv(d);
else
nng_ctx_recv(d->host_ctx, d->host_aio);
}
if (daemon_ready) {
if (nng_aio_result(d->daemon_aio) == 0)
dispatch_handle_daemon_recv(d);
else
nng_recv_aio(*d->poly_sock, d->daemon_aio);
}
if (d->inq_head)
dispatch_dispatch_tasks(d);
}
}
// shutdown --------------------------------------------------------------------
static void dispatch_shutdown(nano_dispatcher *d) {
if (d == NULL) return;
if (d->host_aio) {
nng_aio_stop(d->host_aio);
nng_aio_wait(d->host_aio);
}
if (d->daemon_aio) {
nng_aio_stop(d->daemon_aio);
nng_aio_wait(d->daemon_aio);
}
nng_ctx_close(d->host_ctx);
if (d->daemons) {
for (int i = 0; i < d->outq_count; i++)
if (d->daemons[i].msgid != 0)
nng_ctx_close(d->daemons[i].ctx);
}
while (d->inq_head) {
nano_dispatch_task *t = d->inq_head;
d->inq_head = t->next;
nng_ctx_close(t->ctx);
if (t->msg)
nng_msg_free(t->msg);
free(t);
}
free(d->daemons);
nng_aio_free(d->host_aio);
nng_aio_free(d->daemon_aio);
free(d->init_template);
free(d->conn_reset_buf);
free(d->pipe_events);
free(d);
}
// public entry point ----------------------------------------------------------
SEXP rnng_dispatcher_run(SEXP rep, SEXP poly, SEXP mon, SEXP reset,
SEXP serial, SEXP envir, SEXP next_stream_fun) {
if (NANO_PTR_CHECK(rep, nano_SocketSymbol))
Rf_error("`rep` is not a valid Socket");
if (NANO_PTR_CHECK(poly, nano_SocketSymbol))
Rf_error("`poly` is not a valid Socket");
if (NANO_PTR_CHECK(mon, nano_MonitorSymbol))
Rf_error("`mon` is not a valid Monitor");
SEXP call, stream;
PROTECT(call = Rf_lang2(next_stream_fun, envir));
PROTECT(stream = Rf_eval(call, R_GlobalEnv));
int xc = 2;
nano_dispatcher *d = calloc(1, sizeof(nano_dispatcher));
if (d == NULL) {
UNPROTECT(2);
goto fail;
}
d->rep_sock = (nng_socket *) NANO_PTR(rep);
d->poly_sock = (nng_socket *) NANO_PTR(poly);
d->monitor = (nano_monitor *) NANO_PTR(mon);
d->cv = d->monitor->cv;
size_t reset_len = XLENGTH(reset);
d->conn_reset_buf = malloc(reset_len);
if (d->conn_reset_buf == NULL) {
UNPROTECT(2);
goto fail;
}
memcpy(d->conn_reset_buf, DATAPTR_RO(reset), reset_len);
d->conn_reset_len = reset_len;
if (dispatch_prepare_init_template(d, stream, serial)) {
UNPROTECT(2);
goto fail;
}
UNPROTECT(2);
d->outq_capacity = DISPATCH_INITIAL_SIZE;
d->daemons = calloc(d->outq_capacity, sizeof(nano_dispatch_daemon));
if (d->daemons == NULL)
goto fail;
if ((xc = nng_aio_alloc(&d->host_aio, host_recv_cb, d)) ||
(xc = nng_aio_alloc(&d->daemon_aio, daemon_recv_cb, d)) ||
(xc = nng_ctx_open(&d->host_ctx, *d->rep_sock)))
goto fail;
nng_ctx_recv(d->host_ctx, d->host_aio);
nng_recv_aio(*d->poly_sock, d->daemon_aio);
xc = dispatch_loop(d);
dispatch_shutdown(d);
return Rf_ScalarInteger(xc);
fail:
dispatch_shutdown(d);
ERROR_OUT(xc);
}
// in-process dispatcher -------------------------------------------------------
typedef struct nano_dispatcher_handle_s {
nano_dispatcher *d;
nng_thread *thr;
nng_socket poly_sock;
nng_socket rep_sock;
nano_cv *priv_cv;
nano_monitor *monitor;
int owns_resources;
} nano_dispatcher_handle;
static void dispatch_thread_func(void *arg) {
nano_dispatcher *d = (nano_dispatcher *) arg;
dispatch_loop(d);
}
static void dispatcher_handle_release(nano_dispatcher_handle *h) {
if (!h->owns_resources) return;
nng_mtx_lock(h->priv_cv->mtx);
h->priv_cv->flag = -1;
nng_cv_wake(h->priv_cv->cv);
nng_mtx_unlock(h->priv_cv->mtx);
nng_thread_destroy(h->thr);
dispatch_shutdown(h->d);
h->d = NULL;
nng_close(h->poly_sock);
nng_close(h->rep_sock);
if (h->monitor) {
free(h->monitor->ids);
free(h->monitor);
}
free(h->priv_cv);
h->owns_resources = 0;
}
static void dispatcher_handle_finalizer(SEXP xptr) {
if (NANO_PTR(xptr) == NULL) return;
nano_dispatcher_handle *h = (nano_dispatcher_handle *) NANO_PTR(xptr);
dispatcher_handle_release(h);
free(h);
}
int dispatch_cancel_direct(void *handle, int id) {
nano_dispatcher_handle *h = (nano_dispatcher_handle *) handle;
nano_dispatcher *d = h->d;
if (d == NULL)
return 0;
int found = 0;
int pipe = 0;
nng_mtx_lock(d->cv->mtx);
for (int i = 0; i < d->outq_count; i++) {
if (d->daemons[i].msgid == id) {
pipe = d->daemons[i].pipe;
found = 1;
break;
}
}
if (!found)
found = dispatch_cancel_inq(d, id);
nng_mtx_unlock(d->cv->mtx);
if (pipe)
dispatch_send_to_daemon(d, pipe, NULL, 0);
return found;
}
SEXP rnng_dispatcher_start(SEXP url, SEXP disp_url, SEXP tls,
SEXP serial, SEXP stream,
SEXP capacity, SEXP cvar) {
int xc;
nng_listener listener = NNG_LISTENER_INITIALIZER;
nano_dispatcher_handle *h = NULL;
nano_dispatcher *d = NULL;
nano_cv *priv = NULL;
h = calloc(1, sizeof(nano_dispatcher_handle));
if (h == NULL) { xc = 2; goto fail; }
d = calloc(1, sizeof(nano_dispatcher));
if (d == NULL) { xc = 2; goto fail; }
h->d = d;
// Private CV sharing mutex/cv with shared CV
nano_cv *shared = (nano_cv *) NANO_PTR(cvar);
priv = calloc(1, sizeof(nano_cv));
if (priv == NULL) { xc = 2; goto fail; }
priv->mtx = shared->mtx;
priv->cv = shared->cv;
h->priv_cv = priv;
d->cv = priv;
if (capacity == R_NilValue) {
d->limit_bytes = 0;
} else {
double mb = Rf_asReal(capacity);
d->limit_bytes = (R_FINITE(mb) && mb > 0.0) ? (size_t) (mb * 1e6) : 0;
}
// POLY socket for daemon connections
if ((xc = nng_pair1_open_poly(&h->poly_sock)))
goto fail;
d->poly_sock = &h->poly_sock;
// Monitor on the POLY socket
const int n = 8;
nano_monitor *monitor = calloc(1, sizeof(nano_monitor));
if (monitor == NULL) { xc = 2; goto fail; }
monitor->ids = calloc(n, sizeof(int));
if (monitor->ids == NULL) { free(monitor); xc = 2; goto fail; }
monitor->size = n;
monitor->cv = priv;
h->monitor = monitor;
d->monitor = monitor;
if ((xc = nng_pipe_notify(h->poly_sock, NNG_PIPE_EV_ADD_POST, pipe_cb_monitor, monitor)))
goto fail;
if ((xc = nng_pipe_notify(h->poly_sock, NNG_PIPE_EV_REM_POST, pipe_cb_monitor, monitor)))
goto fail;
// Listen for daemon connections
const char *url_str = CHAR(STRING_ELT(url, 0));
if (TYPEOF(tls) != NILSXP && !NANO_PTR_CHECK(tls, nano_TlsSymbol)) {
nng_tls_config *cfg = (nng_tls_config *) NANO_PTR(tls);
if ((xc = nng_listener_create(&listener, h->poly_sock, url_str)) ||
(xc = nng_listener_set_ptr(listener, NNG_OPT_TLS_CONFIG, cfg)) ||
(xc = nng_listener_start(listener, 0)))
goto fail;
} else {
if ((xc = nng_listen(h->poly_sock, url_str, &listener, 0)))
goto fail;
}
// REP socket dials into the host's inproc:// REQ socket
if ((xc = nng_rep0_open(&h->rep_sock)))
goto fail;
d->rep_sock = &h->rep_sock;
const char *disp_url_str = CHAR(STRING_ELT(disp_url, 0));
if ((xc = nng_dial(h->rep_sock, disp_url_str, NULL, 0)))
goto fail;
// Serialize mk_error(19) for conn_reset_buf
SEXP err;
PROTECT(err = mk_error(19));
nano_buf reset_buf;
nano_serialize(&reset_buf, err, R_NilValue, 0);
UNPROTECT(1);
d->conn_reset_buf = reset_buf.buf;
d->conn_reset_len = reset_buf.cur;
// Prepare init template
if (dispatch_prepare_init_template(d, stream, serial)) { xc = 2; goto fail; }
// Allocate daemon array
d->outq_capacity = DISPATCH_INITIAL_SIZE;
d->daemons = calloc(d->outq_capacity, sizeof(nano_dispatch_daemon));
if (d->daemons == NULL) { xc = 2; goto fail; }
// Allocate AIOs and open host context
if ((xc = nng_aio_alloc(&d->host_aio, host_recv_cb, d)) ||
(xc = nng_aio_alloc(&d->daemon_aio, daemon_recv_cb, d)) ||
(xc = nng_ctx_open(&d->host_ctx, *d->rep_sock)))
goto fail;
// Start initial receive operations
nng_ctx_recv(d->host_ctx, d->host_aio);
nng_recv_aio(*d->poly_sock, d->daemon_aio);
// Create the dispatcher thread
if ((xc = nng_thread_create(&h->thr, dispatch_thread_func, d)))
goto fail;
h->owns_resources = 1;
// Build external pointer with finalizer
SEXP xptr;
PROTECT(xptr = R_MakeExternalPtr(h, nano_ThreadSymbol, R_NilValue));
R_RegisterCFinalizerEx(xptr, dispatcher_handle_finalizer, TRUE);
// Attach resolved listener URL as attribute
SEXP resolved_url = url;
nng_url *up;
if (nng_url_parse(&up, url_str) == 0) {
if (up->u_port != NULL && up->u_port[0] == '0' && up->u_port[1] == '\0') {
int port;
if (nng_listener_get_int(listener, NNG_OPT_TCP_BOUND_PORT, &port) == 0)
resolved_url = nano_url_with_port(up, port);
}
nng_url_free(up);
}
Rf_setAttrib(xptr, nano_UrlSymbol, resolved_url);
UNPROTECT(1);
return xptr;
fail:
if (d) {
if (d->daemon_aio) { nng_aio_stop(d->daemon_aio); nng_aio_free(d->daemon_aio); }
if (d->host_aio) { nng_aio_stop(d->host_aio); nng_aio_free(d->host_aio); }
free(d->daemons);
free(d->init_template);
free(d->conn_reset_buf);
free(d->pipe_events);
free(d);
}
if (h) {
nng_close(h->rep_sock);
nng_close(h->poly_sock);
if (h->monitor) { free(h->monitor->ids); free(h->monitor); }
free(h);
}
free(priv);
ERROR_OUT(xc);
}
SEXP rnng_dispatcher_stop(SEXP disp) {
if (NANO_PTR_CHECK(disp, nano_ThreadSymbol))
return R_NilValue;
nano_dispatcher_handle *h = (nano_dispatcher_handle *) NANO_PTR(disp);
dispatcher_handle_release(h);
NANO_SET_TAG(disp, R_NilValue);
return R_NilValue;
}
SEXP rnng_dispatcher_wait(SEXP disp, SEXP n) {
if (NANO_PTR_CHECK(disp, nano_ThreadSymbol))
return R_NilValue;
nano_dispatcher_handle *h = (nano_dispatcher_handle *) NANO_PTR(disp);
nano_dispatcher *d = h->d;
const int target = nano_integer(n);
nng_cv *cv = d->cv->cv;
nng_mtx *mtx = d->cv->mtx;
nng_mtx_lock(mtx);
while (d->outq_count < target && d->cv->flag >= 0) {
nng_time time = nng_clock() + 400;
nng_cv_until(cv, time);
nng_mtx_unlock(mtx);
R_CheckUserInterrupt();
nng_mtx_lock(mtx);
}
nng_mtx_unlock(mtx);
return R_NilValue;
}
SEXP rnng_dispatcher_info(SEXP disp) {
if (NANO_PTR_CHECK(disp, nano_ThreadSymbol))
return Rf_allocVector(INTSXP, 5);
nano_dispatcher_handle *h = (nano_dispatcher_handle *) NANO_PTR(disp);
nano_dispatcher *d = h->d;
int result[5];
nng_mtx_lock(d->cv->mtx);
result[0] = d->outq_count;
result[1] = d->connections;
result[2] = d->inq_count;
result[3] = d->executing;
result[4] = d->count - d->inq_count - d->executing;
nng_mtx_unlock(d->cv->mtx);
SEXP out = PROTECT(Rf_allocVector(INTSXP, 5));
memcpy(NANO_DATAPTR(out), result, sizeof(result));
UNPROTECT(1);
return out;
}
SEXP rnng_dispatcher_capacity(SEXP disp) {
static const char *names[] = {"used", "peak", "capacity", ""};
SEXP out = PROTECT(Rf_mkNamed(REALSXP, names));
double *p = REAL(out);
if (NANO_PTR_CHECK(disp, nano_ThreadSymbol)) {
p[0] = NA_REAL;
p[1] = NA_REAL;
p[2] = NA_REAL;
UNPROTECT(1);
return out;
}
nano_dispatcher_handle *h = (nano_dispatcher_handle *) NANO_PTR(disp);
nano_dispatcher *d = h->d;
size_t queued, peak, limit;
nng_mtx_lock(d->cv->mtx);
queued = d->queued_bytes;
peak = d->peak_queued_bytes;
limit = d->limit_bytes;
nng_mtx_unlock(d->cv->mtx);
p[0] = (double) queued / 1e6;
p[1] = (double) peak / 1e6;
p[2] = limit > 0 ? (double) limit / 1e6 : NA_REAL;
UNPROTECT(1);
return out;
}
SEXP rnng_dispatcher_gate(SEXP disp) {
if (NANO_PTR_CHECK(disp, nano_ThreadSymbol))
return R_NilValue;
nano_dispatcher_handle *h = (nano_dispatcher_handle *) NANO_PTR(disp);
nano_dispatcher *d = h->d;
if (d->limit_bytes > 0) {
nng_mtx_lock(d->cv->mtx);
while (d->queued_bytes >= d->limit_bytes) {
nng_time time = nng_clock() + 400;
nng_cv_until(d->cv->cv, time);
nng_mtx_unlock(d->cv->mtx);
R_CheckUserInterrupt();
nng_mtx_lock(d->cv->mtx);
}
nng_mtx_unlock(d->cv->mtx);
}
return Rf_ScalarLogical(1);
}
SEXP rnng_dispatcher_try_gate(SEXP disp) {
if (NANO_PTR_CHECK(disp, nano_ThreadSymbol))
return R_NilValue;
nano_dispatcher_handle *h = (nano_dispatcher_handle *) NANO_PTR(disp);
nano_dispatcher *d = h->d;
int allowed = 1;
if (d->limit_bytes > 0) {
nng_mtx_lock(d->cv->mtx);
allowed = d->queued_bytes < d->limit_bytes;
nng_mtx_unlock(d->cv->mtx);
}
return Rf_ScalarLogical(allowed);
}
nanonext/src/comms.c 0000644 0001762 0000144 00000040732 15166504164 014173 0 ustar ligges users // nanonext - C level - Communications Functions -------------------------------
#include "nanonext.h"
// internal --------------------------------------------------------------------
static int nano_fail_mode(SEXP mode) {
if (TYPEOF(mode) == INTSXP)
return NANO_INTEGER(mode);
const char *mod = CHAR(STRING_ELT(mode, 0));
const size_t slen = strlen(mod);
switch (slen) {
case 4:
if (!memcmp(mod, "warn", slen)) return 1;
if (!memcmp(mod, "none", slen)) return 3;
break;
case 5:
if (!memcmp(mod, "error", slen)) return 2;
break;
}
Rf_error("`fail` should be one of: warn, error, none");
}
// finalizers ------------------------------------------------------------------
static void context_finalizer(SEXP xptr) {
if (NANO_PTR(xptr) == NULL) return;
nng_ctx *xp = (nng_ctx *) NANO_PTR(xptr);
nng_ctx_close(*xp);
free(xp);
}
// contexts --------------------------------------------------------------------
SEXP rnng_ctx_open(SEXP socket) {
if (NANO_PTR_CHECK(socket, nano_SocketSymbol))
Rf_error("`socket` is not a valid Socket");
nng_socket *sock = (nng_socket *) NANO_PTR(socket);
SEXP context;
int xc;
nng_ctx *ctx = malloc(sizeof(nng_ctx));
NANO_ENSURE_ALLOC(ctx);
if ((xc = nng_ctx_open(ctx, *sock)))
goto fail;
PROTECT(context = R_MakeExternalPtr(ctx, nano_ContextSymbol, NANO_PROT(socket)));
R_RegisterCFinalizerEx(context, context_finalizer, TRUE);
NANO_CLASS2(context, "nanoContext", "nano");
Rf_setAttrib(context, nano_IdSymbol, Rf_ScalarInteger(nng_ctx_id(*ctx)));
Rf_setAttrib(context, nano_StateSymbol, Rf_mkString("opened"));
Rf_setAttrib(context, nano_ProtocolSymbol, Rf_getAttrib(socket, nano_ProtocolSymbol));
Rf_setAttrib(context, nano_SocketSymbol, Rf_ScalarInteger(nng_socket_id(*sock)));
UNPROTECT(1);
return context;
fail:
free(ctx);
failmem:
ERROR_OUT(xc);
}
SEXP rnng_ctx_create(SEXP socket) {
if (NANO_PTR_CHECK(socket, nano_SocketSymbol))
Rf_error("`socket` is not a valid Socket");
nng_socket *sock = (nng_socket *) NANO_PTR(socket);
SEXP context;
int xc;
nng_ctx *ctx = malloc(sizeof(nng_ctx));
NANO_ENSURE_ALLOC(ctx);
if ((xc = nng_ctx_open(ctx, *sock)))
goto fail;
PROTECT(context = R_MakeExternalPtr(ctx, nano_ContextSymbol, NANO_PROT(socket)));
R_RegisterCFinalizerEx(context, context_finalizer, TRUE);
UNPROTECT(1);
return context;
fail:
free(ctx);
failmem:
ERROR_OUT(xc);
}
SEXP rnng_ctx_close(SEXP context) {
if (NANO_PTR_CHECK(context, nano_ContextSymbol))
Rf_error("`context` is not a valid Context");
nng_ctx *ctx = (nng_ctx *) NANO_PTR(context);
const int xc = nng_ctx_close(*ctx);
if (xc)
ERROR_RET(xc);
Rf_setAttrib(context, nano_StateSymbol, Rf_mkString("closed"));
return nano_success;
}
// dialers and listeners -------------------------------------------------------
SEXP rnng_dial(SEXP socket, SEXP url, SEXP tls, SEXP autostart, SEXP fail) {
if (NANO_PTR_CHECK(socket, nano_SocketSymbol))
Rf_error("`socket` is not a valid Socket");
const int sec = tls != R_NilValue;
if (sec && NANO_PTR_CHECK(tls, nano_TlsSymbol))
Rf_error("`tls` is not a valid TLS Configuration");
const int failmode = nano_fail_mode(fail);
nng_socket *sock = (nng_socket *) NANO_PTR(socket);
const int start = NANO_INTEGER(autostart);
const char *ur = CHAR(STRING_ELT(url, 0));
SEXP dialer, attr, newattr, xp;
nng_tls_config *cfg = NULL;
nng_url *up = NULL;
int xc;
nng_dialer *dp = malloc(sizeof(nng_dialer));
NANO_ENSURE_ALLOC(dp);
if (sec) {
cfg = (nng_tls_config *) NANO_PTR(tls);
if ((xc = nng_dialer_create(dp, *sock, ur)) ||
(xc = nng_url_parse(&up, ur)) ||
(xc = nng_tls_config_server_name(cfg, up->u_hostname)) ||
(xc = nng_dialer_set_ptr(*dp, NNG_OPT_TLS_CONFIG, cfg)))
goto fail;
nng_url_free(up);
if (start && (xc = nng_dialer_start(*dp, start == 1 ? NNG_FLAG_NONBLOCK : 0)))
goto fail;
nng_tls_config_hold(cfg);
PROTECT_INDEX pxi;
PROTECT_WITH_INDEX(xp = R_MakeExternalPtr(cfg, nano_TlsSymbol, R_NilValue), &pxi);
R_RegisterCFinalizerEx(xp, tls_finalizer, TRUE);
REPROTECT(dialer = R_MakeExternalPtr(dp, nano_DialerSymbol, xp), pxi);
} else {
if ((xc = start ? nng_dial(*sock, ur, dp, start == 1 ? NNG_FLAG_NONBLOCK : 0) : nng_dialer_create(dp, *sock, ur)))
goto fail;
PROTECT(dialer = R_MakeExternalPtr(dp, nano_DialerSymbol, R_NilValue));
}
R_RegisterCFinalizerEx(dialer, dialer_finalizer, TRUE);
NANO_CLASS2(dialer, "nanoDialer", "nano");
Rf_setAttrib(dialer, nano_IdSymbol, Rf_ScalarInteger(nng_dialer_id(*dp)));
Rf_setAttrib(dialer, nano_UrlSymbol, url);
Rf_setAttrib(dialer, nano_StateSymbol, Rf_mkString(start ? "started" : "not started"));
Rf_setAttrib(dialer, nano_SocketSymbol, Rf_ScalarInteger(nng_socket_id(*sock)));
PROTECT(attr = Rf_getAttrib(socket, nano_DialerSymbol));
R_xlen_t xlen = Rf_xlength(attr);
PROTECT(newattr = Rf_allocVector(VECSXP, xlen + 1));
for (R_xlen_t i = 0; i < xlen; i++) {
SET_VECTOR_ELT(newattr, i, VECTOR_ELT(attr, i));
}
SET_VECTOR_ELT(newattr, xlen, dialer);
Rf_setAttrib(socket, nano_DialerSymbol, newattr);
UNPROTECT(3);
return nano_success;
fail:
nng_url_free(up);
free(dp);
failmem:
if (failmode == 2) {
ERROR_OUT(xc);
} else if (failmode == 3) {
return mk_error(xc);
}
ERROR_RET(xc);
}
SEXP rnng_listen(SEXP socket, SEXP url, SEXP tls, SEXP autostart, SEXP fail) {
if (NANO_PTR_CHECK(socket, nano_SocketSymbol))
Rf_error("`socket` is not a valid Socket");
const int sec = tls != R_NilValue;
if (sec && NANO_PTR_CHECK(tls, nano_TlsSymbol))
Rf_error("`tls` is not a valid TLS Configuration");
const int failmode = nano_fail_mode(fail);
nng_socket *sock = (nng_socket *) NANO_PTR(socket);
const int start = NANO_INTEGER(autostart);
const char *ur = CHAR(STRING_ELT(url, 0));
SEXP listener, attr, newattr, xp;
nng_tls_config *cfg = NULL;
int xc;
nng_listener *lp = malloc(sizeof(nng_listener));
NANO_ENSURE_ALLOC(lp);
if (sec) {
cfg = (nng_tls_config *) NANO_PTR(tls);
if ((xc = nng_listener_create(lp, *sock, ur)) ||
(xc = nng_listener_set_ptr(*lp, NNG_OPT_TLS_CONFIG, cfg)) ||
(start && (xc = nng_listener_start(*lp, 0))))
goto fail;
nng_tls_config_hold(cfg);
PROTECT_INDEX pxi;
PROTECT_WITH_INDEX(xp = R_MakeExternalPtr(cfg, nano_TlsSymbol, R_NilValue), &pxi);
R_RegisterCFinalizerEx(xp, tls_finalizer, TRUE);
REPROTECT(listener = R_MakeExternalPtr(lp, nano_ListenerSymbol, xp), pxi);
} else {
if ((xc = start ? nng_listen(*sock, ur, lp, 0) : nng_listener_create(lp, *sock, ur)))
goto fail;
PROTECT(listener = R_MakeExternalPtr(lp, nano_ListenerSymbol, R_NilValue));
}
R_RegisterCFinalizerEx(listener, listener_finalizer, TRUE);
NANO_CLASS2(listener, "nanoListener", "nano");
Rf_setAttrib(listener, nano_IdSymbol, Rf_ScalarInteger(nng_listener_id(*lp)));
if (start) {
nng_url *up;
if (nng_url_parse(&up, ur) == 0) {
if (up->u_port != NULL && up->u_port[0] == '0' && up->u_port[1] == '\0') {
int port;
if (nng_listener_get_int(*lp, NNG_OPT_TCP_BOUND_PORT, &port) == 0)
url = nano_url_with_port(up, port);
}
nng_url_free(up);
}
}
Rf_setAttrib(listener, nano_UrlSymbol, url);
Rf_setAttrib(listener, nano_StateSymbol, Rf_mkString(start ? "started" : "not started"));
Rf_setAttrib(listener, nano_SocketSymbol, Rf_ScalarInteger(nng_socket_id(*sock)));
PROTECT(attr = Rf_getAttrib(socket, nano_ListenerSymbol));
R_xlen_t xlen = Rf_xlength(attr);
PROTECT(newattr = Rf_allocVector(VECSXP, xlen + 1));
for (R_xlen_t i = 0; i < xlen; i++) {
SET_VECTOR_ELT(newattr, i, VECTOR_ELT(attr, i));
}
SET_VECTOR_ELT(newattr, xlen, listener);
Rf_setAttrib(socket, nano_ListenerSymbol, newattr);
UNPROTECT(3);
return nano_success;
fail:
free(lp);
failmem:
if (failmode == 2) {
ERROR_OUT(xc);
} else if (failmode == 3) {
return mk_error(xc);
}
ERROR_RET(xc);
}
SEXP rnng_dialer_start(SEXP dialer, SEXP async) {
if (NANO_PTR_CHECK(dialer, nano_DialerSymbol))
Rf_error("`dialer` is not a valid Dialer");
nng_dialer *dial = (nng_dialer *) NANO_PTR(dialer);
const int flags = (NANO_INTEGER(async) == 1) * NNG_FLAG_NONBLOCK;
const int xc = nng_dialer_start(*dial, flags);
if (xc)
ERROR_RET(xc);
Rf_setAttrib(dialer, nano_StateSymbol, Rf_mkString("started"));
return nano_success;
}
SEXP rnng_listener_start(SEXP listener) {
if (NANO_PTR_CHECK(listener, nano_ListenerSymbol))
Rf_error("`listener` is not a valid Listener");
nng_listener *list = (nng_listener *) NANO_PTR(listener);
const int xc = nng_listener_start(*list, 0);
if (xc)
ERROR_RET(xc);
SEXP url = Rf_getAttrib(listener, nano_UrlSymbol);
nng_url *up;
if (nng_url_parse(&up, CHAR(STRING_ELT(url, 0))) == 0) {
if (up->u_port != NULL && up->u_port[0] == '0' && up->u_port[1] == '\0') {
int port;
if (nng_listener_get_int(*list, NNG_OPT_TCP_BOUND_PORT, &port) == 0)
Rf_setAttrib(listener, nano_UrlSymbol, nano_url_with_port(up, port));
}
nng_url_free(up);
}
Rf_setAttrib(listener, nano_StateSymbol, Rf_mkString("started"));
return nano_success;
}
SEXP rnng_dialer_close(SEXP dialer) {
if (NANO_PTR_CHECK(dialer, nano_DialerSymbol))
Rf_error("`dialer` is not a valid Dialer");
nng_dialer *dial = (nng_dialer *) NANO_PTR(dialer);
const int xc = nng_dialer_close(*dial);
if (xc)
ERROR_RET(xc);
Rf_setAttrib(dialer, nano_StateSymbol, Rf_mkString("closed"));
return nano_success;
}
SEXP rnng_listener_close(SEXP listener) {
if (NANO_PTR_CHECK(listener, nano_ListenerSymbol))
Rf_error("`listener` is not a valid Listener");
nng_listener *list = (nng_listener *) NANO_PTR(listener);
const int xc = nng_listener_close(*list);
if (xc)
ERROR_RET(xc);
Rf_setAttrib(listener, nano_StateSymbol, Rf_mkString("closed"));
return nano_success;
}
// send and recv ---------------------------------------------------------------
SEXP rnng_send(SEXP con, SEXP data, SEXP mode, SEXP block, SEXP pipe) {
const int flags = block == R_NilValue ? NNG_DURATION_DEFAULT : TYPEOF(block) == LGLSXP ? 0 : nano_integer(block);
const int raw = nano_encode_mode(mode);
nano_buf buf;
int sock, xc;
if ((sock = !NANO_PTR_CHECK(con, nano_SocketSymbol)) || !NANO_PTR_CHECK(con, nano_ContextSymbol)) {
const int pipeid = sock ? nano_integer(pipe) : 0;
if (raw) {
nano_encode(&buf, data);
} else {
nano_serialize(&buf, data, NANO_PROT(con), 0);
}
nng_msg *msgp = NULL;
if ((xc = nng_msg_alloc(&msgp, 0)))
goto fail;
nano_msg_set_body(msgp, &buf);
if (pipeid) {
nng_pipe p;
p.id = (uint32_t) pipeid;
nng_msg_set_pipe(msgp, p);
}
if (flags <= 0) {
if ((xc = sock ? nng_sendmsg(*(nng_socket *) NANO_PTR(con), msgp, flags ? NNG_FLAG_NONBLOCK : (NANO_INTEGER(block) != 1) * NNG_FLAG_NONBLOCK) :
nng_ctx_sendmsg(*(nng_ctx *) NANO_PTR(con), msgp, flags ? NNG_FLAG_NONBLOCK : (NANO_INTEGER(block) != 1) * NNG_FLAG_NONBLOCK))) {
nng_msg_free(msgp);
goto fail;
}
NANO_FREE(buf);
} else {
nng_aio *aiop = NULL;
if ((xc = nng_aio_alloc(&aiop, NULL, NULL))) {
nng_msg_free(msgp);
goto fail;
}
nng_aio_set_msg(aiop, msgp);
nng_aio_set_timeout(aiop, flags);
sock ? nng_send_aio(*(nng_socket *) NANO_PTR(con), aiop) :
nng_ctx_send(*(nng_ctx *) NANO_PTR(con), aiop);
NANO_FREE(buf);
nng_aio_wait(aiop);
if ((xc = nng_aio_result(aiop)))
nng_msg_free(nng_aio_get_msg(aiop));
nng_aio_free(aiop);
}
} else if (!NANO_PTR_CHECK(con, nano_StreamSymbol)) {
nano_encode(&buf, data);
nano_stream *nst = (nano_stream *) NANO_PTR(con);
nng_stream *sp = nst->stream;
nng_aio *aiop = NULL;
if ((xc = nng_aio_alloc(&aiop, NULL, NULL)))
goto fail;
if (nst->msgmode) {
nng_msg *msgp;
const size_t xlen = buf.cur - nst->textframes;
if ((xc = nng_msg_alloc(&msgp, xlen))) {
nng_aio_free(aiop);
goto fail;
}
memcpy(nng_msg_body(msgp), buf.buf, xlen);
nng_aio_set_msg(aiop, msgp);
} else {
nng_iov iov = {
.iov_buf = buf.buf,
.iov_len = buf.cur - nst->textframes
};
if ((xc = nng_aio_set_iov(aiop, 1u, &iov))) {
nng_aio_free(aiop);
goto fail;
}
}
nng_aio_set_timeout(aiop, flags ? flags : (NANO_INTEGER(block) != 0) * NNG_DURATION_DEFAULT);
nng_stream_send(sp, aiop);
NANO_FREE(buf);
nng_aio_wait(aiop);
if ((xc = nng_aio_result(aiop)) && nst->msgmode)
nng_msg_free(nng_aio_get_msg(aiop));
nng_aio_free(aiop);
} else {
Rf_error("`con` is not a valid Socket, Context or Stream");
}
if (xc)
return mk_error(xc);
return nano_success;
fail:
NANO_FREE(buf);
return mk_error(xc);
}
SEXP rnng_recv(SEXP con, SEXP mode, SEXP block) {
const int flags = block == R_NilValue ? NNG_DURATION_DEFAULT : TYPEOF(block) == LGLSXP ? 0 : nano_integer(block);
int xc;
unsigned char *buf = NULL;
size_t sz;
SEXP res;
if (!NANO_PTR_CHECK(con, nano_SocketSymbol)) {
const int mod = nano_matcharg(mode);
nng_socket *sock = (nng_socket *) NANO_PTR(con);
nng_msg *msgp = NULL;
if (flags <= 0) {
if ((xc = nng_recvmsg(*sock, &msgp, (flags < 0 || NANO_INTEGER(block) != 1) * NNG_FLAG_NONBLOCK)))
goto fail;
} else {
nng_aio *aiop = NULL;
if ((xc = nng_aio_alloc(&aiop, NULL, NULL)))
goto fail;
nng_aio_set_timeout(aiop, flags);
nng_recv_aio(*sock, aiop);
nng_aio_wait(aiop);
if ((xc = nng_aio_result(aiop))) {
nng_aio_free(aiop);
goto fail;
}
msgp = nng_aio_get_msg(aiop);
nng_aio_free(aiop);
}
buf = nng_msg_body(msgp);
sz = nng_msg_len(msgp);
res = nano_decode(buf, sz, mod, NANO_PROT(con));
nng_msg_free(msgp);
} else if (!NANO_PTR_CHECK(con, nano_ContextSymbol)) {
const int mod = nano_matcharg(mode);
nng_ctx *ctxp = (nng_ctx *) NANO_PTR(con);
nng_msg *msgp = NULL;
if (flags <= 0) {
if ((xc = nng_ctx_recvmsg(*ctxp, &msgp, (flags < 0 || NANO_INTEGER(block) != 1) * NNG_FLAG_NONBLOCK)))
goto fail;
buf = nng_msg_body(msgp);
sz = nng_msg_len(msgp);
res = nano_decode(buf, sz, mod, NANO_PROT(con));
nng_msg_free(msgp);
} else {
nng_aio *aiop = NULL;
if ((xc = nng_aio_alloc(&aiop, NULL, NULL)))
goto fail;
nng_aio_set_timeout(aiop, flags);
nng_ctx_recv(*ctxp, aiop);
nng_aio_wait(aiop);
if ((xc = nng_aio_result(aiop))) {
nng_aio_free(aiop);
goto fail;
}
msgp = nng_aio_get_msg(aiop);
nng_aio_free(aiop);
buf = nng_msg_body(msgp);
sz = nng_msg_len(msgp);
res = nano_decode(buf, sz, mod, NANO_PROT(con));
nng_msg_free(msgp);
}
} else if (!NANO_PTR_CHECK(con, nano_StreamSymbol)) {
const int mod = nano_matcharg(mode) == 1 ? 2 : nano_matcharg(mode);
nano_stream *nst = (nano_stream *) NANO_PTR(con);
nng_stream *sp = nst->stream;
nng_aio *aiop = NULL;
if ((xc = nng_aio_alloc(&aiop, NULL, NULL)))
goto fail;
if (nst->msgmode) {
nng_msg *msgp;
nng_aio_set_timeout(aiop, flags ? flags : (NANO_INTEGER(block) != 0) * NNG_DURATION_DEFAULT);
nng_stream_recv(sp, aiop);
nng_aio_wait(aiop);
if ((xc = nng_aio_result(aiop))) {
nng_aio_free(aiop);
goto fail;
}
msgp = nng_aio_get_msg(aiop);
nng_aio_free(aiop);
buf = nng_msg_body(msgp);
sz = nng_msg_len(msgp);
res = nano_decode(buf, sz, mod, NANO_PROT(con));
nng_msg_free(msgp);
} else {
size_t xlen = nst->bufsize;
buf = malloc(xlen);
NANO_ENSURE_ALLOC(buf);
nng_iov iov = {
.iov_buf = buf,
.iov_len = xlen
};
if ((xc = nng_aio_set_iov(aiop, 1u, &iov))) {
nng_aio_free(aiop);
goto fail;
}
nng_aio_set_timeout(aiop, flags ? flags : (NANO_INTEGER(block) != 0) * NNG_DURATION_DEFAULT);
nng_stream_recv(sp, aiop);
nng_aio_wait(aiop);
if ((xc = nng_aio_result(aiop))) {
nng_aio_free(aiop);
goto fail;
}
sz = nng_aio_count(aiop);
nng_aio_free(aiop);
res = nano_decode(buf, sz, mod, NANO_PROT(con));
free(buf);
}
} else {
Rf_error("`con` is not a valid Socket, Context or Stream");
}
return res;
fail:
free(buf);
failmem:
return mk_error(xc);
}
nanonext/src/nanonext.h 0000644 0001762 0000144 00000040266 15176112256 014714 0 ustar ligges users // nanonext - header file ------------------------------------------------------
#ifndef NANONEXT_H
#define NANONEXT_H
#include
#include
#include
#include "nng_structs.h"
#ifdef NANONEXT_PROTOCOLS
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#endif
#ifdef NANONEXT_HTTP
#include
#ifdef _WIN32
#include
#else
#include
#endif
typedef struct nano_handle_s {
nng_url *url;
nng_http_client *cli;
nng_http_req *req;
nng_http_res *res;
nng_tls_config *cfg;
} nano_handle;
#endif
#ifdef NANONEXT_IO
#include
#endif
#ifdef NANONEXT_NET
#ifdef _WIN32
#if !defined(_WIN32_WINNT) || (defined(_WIN32_WINNT) && (_WIN32_WINNT < 0x0600))
#define _WIN32_WINNT 0x0600
#endif
#include
#include
#include
#define WIN32_LEAN_AND_MEAN
#include
#else
#include
#include
#include
#include
#include
#endif
#endif
#ifdef NANONEXT_SIGNALS
#ifndef _WIN32
#include
#endif
#include
#endif
#ifdef NANONEXT_TLS
#include
#if MBEDTLS_VERSION_MAJOR < 3
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef MBEDTLS_PSA_CRYPTO_C
#include
#endif
#include
#endif
#include
#ifndef R_NO_REMAP
#define R_NO_REMAP
#endif
#ifndef STRICT_R_HEADERS
#define STRICT_R_HEADERS
#endif
#include
#include
#include
#include
#if defined(NANONEXT_SIGNALS)
#ifdef _WIN32
#include
#else
extern int R_interrupts_pending;
#endif
#endif
#define NANO_PTR(x) (void *) CAR(x)
#define NANO_PTR_CHECK(x, tag) (TAG(x) != tag || NANO_PTR(x) == NULL)
#define NANO_PROT(x) CDR(x)
#define NANO_ENCLOS(x) CDR(x)
#define NANO_SET_TAG(x, v) SET_TAG(x, v)
#define NANO_SET_PROT(x, v) SETCDR(x, v)
#define NANO_SET_ENCLOS(x, v) SETCDR(x, v)
#define NANO_DATAPTR(x) (void *) DATAPTR_RO(x)
#if R_VERSION < R_Version(4, 5, 0)
# define VECTOR_PTR_RO(x) ((const SEXP *) DATAPTR_RO(x))
#endif
#define NANO_INTEGER(x) *(const int *) DATAPTR_RO(x)
#define ERROR_OUT(xc) Rf_error("%d | %s", xc, nng_strerror(xc))
#define ERROR_RET(xc) { Rf_warning("%d | %s", xc, nng_strerror(xc)); return mk_error(xc); }
#define NANONEXT_INIT_BUFSIZE 4096
#define NANONEXT_SERIAL_VER 3
#define NANONEXT_SERIAL_THR 67108864
#define NANONEXT_CHUNK_SIZE 67108864 // must be <= INT_MAX
#define NANONEXT_STR_SIZE 40
#define NANONEXT_WAIT_DUR 1000
#define NANONEXT_SLEEP_DUR 200
#define NANO_ALLOC(x, sz) \
(x)->buf = malloc(sz); \
if ((x)->buf == NULL) Rf_error("memory allocation failed"); \
(x)->len = sz; \
(x)->cur = 0
#define NANO_INIT(x, ptr, sz) \
(x)->buf = ptr; \
(x)->len = 0; \
(x)->cur = sz
#define NANO_FREE(x) if (x.len) free(x.buf)
#define NANO_CLASS2(x, cls1, cls2) \
SEXP klass = Rf_allocVector(STRSXP, 2); \
Rf_classgets(x, klass); \
SET_STRING_ELT(klass, 0, Rf_mkChar(cls1)); \
SET_STRING_ELT(klass, 1, Rf_mkChar(cls2))
#define NANO_ENSURE_ALLOC(x) if (x == NULL) { xc = 2; goto failmem; }
#define NANO_URL_MAX 8192
typedef union nano_opt_u {
char *str;
uint64_t u;
size_t s;
nng_duration d;
int i;
bool b;
} nano_opt;
typedef struct nano_stream_s {
nng_stream *stream;
union {
nng_stream_dialer *dial;
nng_stream_listener *list;
} endpoint;
nng_tls_config *tls;
size_t bufsize;
int textframes;
int msgmode;
enum {
NANO_STREAM_DIALER,
NANO_STREAM_LISTENER
} mode;
} nano_stream;
typedef enum nano_aio_typ {
SENDAIO,
RECVAIO,
REQAIO,
IOV_SENDAIO,
IOV_RECVAIO,
HTTP_AIO,
RECVAIOS,
REQAIOS,
IOV_RECVAIOS
} nano_aio_typ;
typedef struct nano_aio_s {
nng_aio *aio;
void *data;
void *cb;
void *next;
int result;
nano_aio_typ type;
uint8_t mode;
} nano_aio;
typedef struct nano_saio_s {
nng_aio *aio;
void *disp;
void *cb;
int id;
int type;
} nano_saio;
typedef struct nano_cv_s {
int condition;
int flag;
nng_mtx *mtx;
nng_cv *cv;
} nano_cv;
typedef struct nano_monitor_s {
nano_cv *cv;
int *ids;
int size;
int updates;
} nano_monitor;
typedef struct nano_thread_aio_s {
nng_thread *thr;
nano_cv *cv;
nng_aio *aio;
} nano_thread_aio;
typedef struct nano_thread_duo_s {
nng_thread *thr;
nano_cv *cv;
nano_cv *cv2;
} nano_thread_duo;
typedef struct nano_buf_s {
unsigned char *buf;
size_t len;
size_t cur;
} nano_buf;
typedef struct nano_serial_bundle_s {
R_outpstream_t outpstream;
R_inpstream_t inpstream;
SEXP klass;
} nano_serial_bundle;
typedef enum nano_list_op {
INIT,
FINALIZE,
COMPLETE,
FREE,
SHUTDOWN
} nano_list_op;
#ifdef NANONEXT_HTTP
// Forward declarations
typedef struct nano_conn_s nano_conn;
typedef struct nano_ws_conn_s nano_ws_conn;
typedef struct nano_stream_conn_s nano_stream_conn;
typedef struct nano_http_server_s nano_http_server;
typedef struct nano_http_handler_info_s nano_http_handler_info;
typedef struct nano_http_request_s nano_http_request;
// Connection type discriminator
typedef enum {
NANO_CONN_WEBSOCKET,
NANO_CONN_HTTP_STREAM
} nano_conn_type;
// Connection state machine (prevents races between R and NNG threads)
typedef enum {
CONN_STATE_OPEN, // Connection active, can send/receive
CONN_STATE_CLOSING, // Close initiated, waiting for cleanup
CONN_STATE_CLOSED // Fully closed, safe to free
} nano_conn_state;
// Base connection structure - common fields for linked list and lifecycle
typedef struct nano_conn_s {
nng_aio *send_aio; // For async send (both types)
nano_http_handler_info *handler; // Back-reference to handler
struct nano_conn_s *next; // Linked list
struct nano_conn_s *prev; // Doubly-linked for O(1) removal
SEXP xptr; // R external pointer
nano_conn_type type; // Connection type (for dispatch)
nano_conn_state state; // Connection state
int id; // Unique connection ID (server-wide)
int onclose_scheduled; // Prevents duplicate on_close callbacks
} nano_conn;
// WebSocket connection (embeds base as first member)
typedef struct nano_ws_conn_s {
nano_conn conn; // Base - MUST be first member
nng_stream *stream; // WebSocket stream with automatic framing
nng_aio *recv_aio; // For async receive loop
} nano_ws_conn;
// HTTP streaming connection (embeds base as first member)
typedef struct nano_stream_conn_s {
nano_conn conn; // Base - MUST be first member
nng_http_conn *http; // Hijacked HTTP connection
nng_http_req *req; // Request pointer (valid because hijacked)
// Response state (set before first send)
char **resp_header_names;
char **resp_header_values;
int resp_header_count;
int resp_header_capacity;
int status_code; // Default 200
int headers_sent; // Whether HTTP headers written
} nano_stream_conn;
// HTTP handler info (links NNG handler to R callback)
typedef struct nano_http_handler_info_s {
nng_http_handler *handler; // NNG HTTP handler (NULL for WS)
SEXP callback; // HTTP callback or WS on_message or stream on_request
nano_http_server *server; // Back-reference
// Long-lived connection handler fields:
nng_stream_listener *listener; // WebSocket listener (NULL for HTTP stream)
nng_aio *accept_aio; // Accept AIO for WS handler
nano_conn *conns; // Linked list of connections (WS or HTTP stream)
SEXP on_open; // R callback: WS open or stream request
SEXP on_close; // R callback: connection close
int textframes; // WS: text frame mode
} nano_http_handler_info;
typedef struct nano_http_request_s {
nng_aio *aio; // The HTTP AIO to complete
nng_http_req *req; // The HTTP request (valid until aio finished)
SEXP callback; // R callback function
nano_http_server *server; // Back-reference to server
nano_http_request *next; // Linked list for tracking
nano_http_request *prev;
int cancelled; // Set when server stops (protected by server->mtx)
} nano_http_request;
typedef enum {
SERVER_CREATED,
SERVER_STARTED,
SERVER_STOPPED
} nano_server_state;
typedef struct nano_http_server_s {
nng_http_server *server; // NNG HTTP server
nng_tls_config *tls; // TLS configuration
nano_http_handler_info *handlers; // Array of handler info
nano_http_request *pending_reqs; // Linked list of pending HTTP requests
nng_mtx *mtx; // Mutex for thread safety
SEXP xptr; // R external pointer for this server
SEXP prot; // Pairlist for GC protection of callbacks
int handler_count; // Number of handlers
int conn_counter; // Server-wide unique connection ID counter
nano_server_state state; // Server lifecycle state
} nano_http_server;
typedef struct ws_message_s {
nano_ws_conn *conn;
nng_msg *msg;
} ws_message;
#endif
extern void (*eln2)(void (*)(void *), void *, double, int);
extern SEXP nano_AioSymbol;
extern SEXP nano_ContextSymbol;
extern SEXP nano_CvSymbol;
extern SEXP nano_DataSymbol;
extern SEXP nano_DialerSymbol;
extern SEXP nano_DotcallSymbol;
extern SEXP nano_HeadersSymbol;
extern SEXP nano_IdSymbol;
extern SEXP nano_ListenerSymbol;
extern SEXP nano_MonitorSymbol;
extern SEXP nano_ProtocolSymbol;
extern SEXP nano_ResolveSymbol;
extern SEXP nano_ResponseSymbol;
extern SEXP nano_ResultSymbol;
extern SEXP nano_SocketSymbol;
extern SEXP nano_StateSymbol;
extern SEXP nano_StatusSymbol;
extern SEXP nano_StreamSymbol;
extern SEXP nano_ThreadSymbol;
extern SEXP nano_TlsSymbol;
extern SEXP nano_UrlSymbol;
extern SEXP nano_ValueSymbol;
extern SEXP nano_HttpServerSymbol;
extern SEXP nano_ConnSymbol;
extern SEXP nano_aioFuncMsg;
extern SEXP nano_aioFuncRes;
extern SEXP nano_aioNFuncs;
extern SEXP nano_error;
extern SEXP nano_precious;
extern SEXP nano_recvAio;
extern SEXP nano_reqAio;
extern SEXP nano_sendAio;
extern SEXP nano_success;
extern SEXP nano_unresolved;
#if R_VERSION < R_Version(4, 1, 0)
static inline SEXP R_NewEnv(SEXP parent, int hash, int size) {
(void) parent;
(void) hash;
(void) size;
return Rf_allocSExp(ENVSXP);
}
#endif
#if R_VERSION < R_Version(4, 5, 0)
static inline SEXP R_mkClosure(SEXP formals, SEXP body, SEXP env) {
SEXP fun = Rf_allocSExp(CLOSXP);
SET_FORMALS(fun, formals);
SET_BODY(fun, body);
SET_CLOENV(fun, env);
return fun;
}
#endif
static inline int nano_integer(const SEXP x) {
return (TYPEOF(x) == INTSXP || TYPEOF(x) == LGLSXP) ? NANO_INTEGER(x) : Rf_asInteger(x);
}
static inline void later2(void (*fun)(void *), void *data) {
eln2(fun, data, 0, 0);
}
void dialer_finalizer(SEXP);
void listener_finalizer(SEXP);
void socket_finalizer(SEXP);
void raio_invoke_cb(void *);
void haio_invoke_cb(void *);
SEXP mk_error(const int);
SEXP mk_error_data(const int);
SEXP nano_raw_char(const unsigned char *, const size_t);
void nano_serialize(nano_buf *, const SEXP, SEXP, int);
void nano_msg_set_body(nng_msg *, nano_buf *);
SEXP nano_unserialize(unsigned char *, const size_t, SEXP);
SEXP nano_decode(unsigned char *, const size_t, const uint8_t, SEXP);
SEXP nano_url_with_port(nng_url *, int);
void nano_encode(nano_buf *, const SEXP);
int nano_encode_mode(const SEXP);
int nano_matcharg(const SEXP);
SEXP nano_aio_result(SEXP);
SEXP nano_aio_get_msg(SEXP);
SEXP nano_aio_http_status(SEXP);
void pipe_cb_signal(nng_pipe, nng_pipe_ev, void *);
void pipe_cb_monitor(nng_pipe, nng_pipe_ev, void *);
void tls_finalizer(SEXP);
void nano_load_later(void);
SEXP nano_findVarInFrame(const SEXP, const SEXP, int *);
SEXP nano_PreserveObject(const SEXP);
void nano_ReleaseObject(SEXP);
void nano_list_do(nano_list_op, nano_aio *);
void nano_thread_shutdown(void);
int dispatch_cancel_direct(void *, int);
SEXP rnng_advance_rng_state(void);
SEXP rnng_aio_call(SEXP);
SEXP rnng_aio_collect(SEXP);
SEXP rnng_aio_collect_safe(SEXP);
SEXP rnng_aio_get_msg(SEXP);
SEXP rnng_aio_http_data(SEXP);
SEXP rnng_aio_http_headers(SEXP);
SEXP rnng_aio_http_status(SEXP);
SEXP rnng_aio_result(SEXP);
SEXP rnng_aio_stop(SEXP);
SEXP rnng_clock(void);
SEXP rnng_close(SEXP);
SEXP rnng_conn_close(SEXP);
SEXP rnng_ctx_close(SEXP);
SEXP rnng_ctx_create(SEXP);
SEXP rnng_ctx_open(SEXP);
SEXP rnng_cv_alloc(void);
SEXP rnng_cv_reset(SEXP);
SEXP rnng_cv_signal(SEXP);
SEXP rnng_cv_until(SEXP, SEXP);
SEXP rnng_cv_until_safe(SEXP, SEXP);
SEXP rnng_cv_value(SEXP);
SEXP rnng_cv_wait(SEXP);
SEXP rnng_cv_wait_safe(SEXP);
SEXP rnng_dial(SEXP, SEXP, SEXP, SEXP, SEXP);
SEXP rnng_dialer_close(SEXP);
SEXP rnng_dialer_start(SEXP, SEXP);
SEXP rnng_dispatcher_capacity(SEXP);
SEXP rnng_dispatcher_gate(SEXP);
SEXP rnng_dispatcher_try_gate(SEXP);
SEXP rnng_dispatcher_info(SEXP);
SEXP rnng_dispatcher_run(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP);
SEXP rnng_dispatcher_start(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP);
SEXP rnng_dispatcher_stop(SEXP);
SEXP rnng_dispatcher_wait(SEXP, SEXP);
SEXP rnng_eval_safe(SEXP);
SEXP rnng_fini(void);
SEXP rnng_fini_priors(void);
SEXP rnng_get_opt(SEXP, SEXP);
SEXP rnng_http_server_close(SEXP);
SEXP rnng_http_server_create(SEXP, SEXP, SEXP);
SEXP rnng_http_server_start(SEXP);
SEXP rnng_http_server_stop(SEXP);
SEXP rnng_ip_addr(void);
SEXP rnng_is_error_value(SEXP);
SEXP rnng_is_nul_byte(SEXP);
SEXP rnng_listen(SEXP, SEXP, SEXP, SEXP, SEXP);
SEXP rnng_listener_close(SEXP);
SEXP rnng_listener_start(SEXP);
SEXP rnng_marker_set(SEXP);
SEXP rnng_monitor_create(SEXP, SEXP);
SEXP rnng_monitor_read(SEXP);
SEXP rnng_ncurl(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP);
SEXP rnng_ncurl_aio(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP);
SEXP rnng_ncurl_session(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP);
SEXP rnng_ncurl_session_close(SEXP);
SEXP rnng_ncurl_transact(SEXP);
SEXP rnng_pipe_notify(SEXP, SEXP, SEXP, SEXP, SEXP);
SEXP rnng_protocol_open(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP);
SEXP rnng_random(SEXP, SEXP);
SEXP rnng_read_stdin(SEXP);
SEXP rnng_reap(SEXP);
SEXP rnng_recv(SEXP, SEXP, SEXP);
SEXP rnng_recv_aio(SEXP, SEXP, SEXP, SEXP, SEXP);
SEXP rnng_request(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP);
SEXP rnng_request_stop(SEXP);
SEXP rnng_send(SEXP, SEXP, SEXP, SEXP, SEXP);
SEXP rnng_send_aio(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP);
SEXP rnng_serial_config(SEXP, SEXP, SEXP);
SEXP rnng_set_opt(SEXP, SEXP, SEXP);
SEXP rnng_set_promise_context(SEXP, SEXP);
SEXP rnng_signal_thread_create(SEXP, SEXP);
SEXP rnng_sleep(SEXP);
SEXP rnng_stats_get(SEXP, SEXP);
SEXP rnng_status_code(SEXP);
SEXP rnng_stream_close(SEXP);
SEXP rnng_stream_conn_send(SEXP, SEXP);
SEXP rnng_stream_conn_set_header(SEXP, SEXP, SEXP);
SEXP rnng_stream_conn_set_status(SEXP, SEXP);
SEXP rnng_stream_open(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP);
SEXP rnng_strerror(SEXP);
SEXP rnng_subscribe(SEXP, SEXP, SEXP);
SEXP rnng_tls_config(SEXP, SEXP, SEXP, SEXP);
SEXP rnng_traverse_precious(void);
SEXP rnng_unresolved(SEXP);
SEXP rnng_unresolved2(SEXP);
SEXP rnng_race_aio(SEXP, SEXP);
SEXP rnng_url_parse(SEXP);
SEXP rnng_version(void);
SEXP rnng_wait_thread_create(SEXP);
SEXP rnng_write_cert(SEXP, SEXP);
SEXP rnng_write_stdout(SEXP);
SEXP rnng_ws_close(SEXP);
SEXP rnng_ws_send(SEXP, SEXP);
#endif
nanonext/src/Makevars.ucrt 0000644 0001762 0000144 00000001016 15142221674 015345 0 ustar ligges users PKG_CFLAGS = -I../nano-install/include -DNNG_STATIC_LIB $(C_VISIBILITY)
PKG_LIBS = ../nano-install/lib/libnng.a ../nano-install/lib/libmbedtls.a ../nano-install/lib/libmbedx509.a ../nano-install/lib/libmbedcrypto.a -lbcrypt -liphlpapi -lws2_32
SOURCES = aio.c comms.c core.c dispatcher.c init.c ncurl.c net.c proto.c server.c sync.c thread.c tls.c utils.c
OBJECTS = $(SOURCES:.c=.o)
.PHONY: all cleanup clean
all: cleanup
cleanup: $(SHLIB)
@rm -rf ../nano-install
$(SHLIB): $(OBJECTS)
clean:
rm -f $(OBJECTS) $(SHLIB)
nanonext/src/init.c 0000644 0001762 0000144 00000022644 15174743044 014023 0 ustar ligges users // nanonext - package level registrations --------------------------------------
#include "nanonext.h"
void (*eln2)(void (*)(void *), void *, double, int) = NULL;
SEXP nano_AioSymbol;
SEXP nano_ContextSymbol;
SEXP nano_CvSymbol;
SEXP nano_DataSymbol;
SEXP nano_DialerSymbol;
SEXP nano_DotcallSymbol;
SEXP nano_HeadersSymbol;
SEXP nano_IdSymbol;
SEXP nano_ListenerSymbol;
SEXP nano_MonitorSymbol;
SEXP nano_ProtocolSymbol;
SEXP nano_ResolveSymbol;
SEXP nano_ResponseSymbol;
SEXP nano_ResultSymbol;
SEXP nano_SocketSymbol;
SEXP nano_StateSymbol;
SEXP nano_StatusSymbol;
SEXP nano_StreamSymbol;
SEXP nano_ThreadSymbol;
SEXP nano_TlsSymbol;
SEXP nano_UrlSymbol;
SEXP nano_ValueSymbol;
SEXP nano_HttpServerSymbol;
SEXP nano_ConnSymbol;
SEXP nano_aioFuncMsg;
SEXP nano_aioFuncRes;
SEXP nano_aioNFuncs;
SEXP nano_error;
SEXP nano_precious;
SEXP nano_recvAio;
SEXP nano_reqAio;
SEXP nano_sendAio;
SEXP nano_success;
SEXP nano_unresolved;
static void RegisterSymbols(void) {
nano_AioSymbol = Rf_install("aio");
nano_ConnSymbol = Rf_install("conn");
nano_ContextSymbol = Rf_install("context");
nano_CvSymbol = Rf_install("cv");
nano_DataSymbol = Rf_install("data");
nano_DialerSymbol = Rf_install("dialer");
nano_DotcallSymbol = Rf_install(".Call");
nano_HeadersSymbol = Rf_install("headers");
nano_HttpServerSymbol = Rf_install("httpServer");
nano_IdSymbol = Rf_install("id");
nano_ListenerSymbol = Rf_install("listener");
nano_MonitorSymbol = Rf_install("monitor");
nano_ProtocolSymbol = Rf_install("protocol");
nano_ResolveSymbol = Rf_install("resolve");
nano_ResponseSymbol = Rf_install("response");
nano_ResultSymbol = Rf_install("result");
nano_SocketSymbol = Rf_install("socket");
nano_StateSymbol = Rf_install("state");
nano_StatusSymbol = Rf_install("status");
nano_StreamSymbol = Rf_install("stream");
nano_ThreadSymbol = Rf_install("thread");
nano_TlsSymbol = Rf_install("tls");
nano_UrlSymbol = Rf_install("url");
nano_ValueSymbol = Rf_install("value");
}
static void PreserveObjects(void) {
R_PreserveObject(nano_aioFuncMsg = Rf_lang3(nano_DotcallSymbol, Rf_install("rnng_aio_get_msg"), nano_DataSymbol));
R_PreserveObject(nano_aioFuncRes = Rf_lang3(nano_DotcallSymbol, Rf_install("rnng_aio_result"), nano_DataSymbol));
R_PreserveObject(nano_aioNFuncs = Rf_allocVector(LISTSXP, 3));
SETCAR(nano_aioNFuncs, Rf_lang3(nano_DotcallSymbol, Rf_install("rnng_aio_http_status"), nano_DataSymbol));
SETCADR(nano_aioNFuncs, Rf_lang3(nano_DotcallSymbol, Rf_install("rnng_aio_http_headers"), nano_DataSymbol));
SETCADDR(nano_aioNFuncs, Rf_lang3(nano_DotcallSymbol, Rf_install("rnng_aio_http_data"), nano_DataSymbol));
R_PreserveObject(nano_error = Rf_allocVector(STRSXP, 2));
SET_STRING_ELT(nano_error, 0, Rf_mkChar("errorValue"));
SET_STRING_ELT(nano_error, 1, Rf_mkChar("try-error"));
R_PreserveObject(nano_precious = Rf_cons(R_NilValue, R_NilValue));
R_PreserveObject(nano_recvAio = Rf_mkString("recvAio"));
R_PreserveObject(nano_reqAio = Rf_allocVector(STRSXP, 2));
SET_STRING_ELT(nano_reqAio, 0, Rf_mkChar("mirai"));
SET_STRING_ELT(nano_reqAio, 1, Rf_mkChar("recvAio"));
R_PreserveObject(nano_sendAio = Rf_mkString("sendAio"));
R_PreserveObject(nano_success = Rf_ScalarInteger(0));
R_PreserveObject(nano_unresolved = Rf_shallow_duplicate(Rf_ScalarLogical(NA_LOGICAL)));
Rf_classgets(nano_unresolved, Rf_mkString("unresolvedValue"));
}
// # nocov start
static void ReleaseObjects(void) {
R_ReleaseObject(nano_unresolved);
R_ReleaseObject(nano_success);
R_ReleaseObject(nano_sendAio);
R_ReleaseObject(nano_reqAio);
R_ReleaseObject(nano_recvAio);
R_ReleaseObject(nano_precious);
R_ReleaseObject(nano_error);
R_ReleaseObject(nano_aioNFuncs);
R_ReleaseObject(nano_aioFuncRes);
R_ReleaseObject(nano_aioFuncMsg);
}
// # nocov end
static const R_CallMethodDef callMethods[] = {
{"rnng_advance_rng_state", (DL_FUNC) &rnng_advance_rng_state, 0},
{"rnng_aio_call", (DL_FUNC) &rnng_aio_call, 1},
{"rnng_aio_collect", (DL_FUNC) &rnng_aio_collect, 1},
{"rnng_aio_collect_safe", (DL_FUNC) &rnng_aio_collect_safe, 1},
{"rnng_aio_get_msg", (DL_FUNC) &rnng_aio_get_msg, 1},
{"rnng_aio_http_data", (DL_FUNC) &rnng_aio_http_data, 1},
{"rnng_aio_http_headers", (DL_FUNC) &rnng_aio_http_headers, 1},
{"rnng_aio_http_status", (DL_FUNC) &rnng_aio_http_status, 1},
{"rnng_aio_result", (DL_FUNC) &rnng_aio_result, 1},
{"rnng_aio_stop", (DL_FUNC) &rnng_aio_stop, 1},
{"rnng_clock", (DL_FUNC) &rnng_clock, 0},
{"rnng_close", (DL_FUNC) &rnng_close, 1},
{"rnng_conn_close", (DL_FUNC) &rnng_conn_close, 1},
{"rnng_ctx_close", (DL_FUNC) &rnng_ctx_close, 1},
{"rnng_ctx_create", (DL_FUNC) &rnng_ctx_create, 1},
{"rnng_ctx_open", (DL_FUNC) &rnng_ctx_open, 1},
{"rnng_cv_alloc", (DL_FUNC) &rnng_cv_alloc, 0},
{"rnng_cv_reset", (DL_FUNC) &rnng_cv_reset, 1},
{"rnng_cv_signal", (DL_FUNC) &rnng_cv_signal, 1},
{"rnng_cv_until", (DL_FUNC) &rnng_cv_until, 2},
{"rnng_cv_until_safe", (DL_FUNC) &rnng_cv_until_safe, 2},
{"rnng_cv_value", (DL_FUNC) &rnng_cv_value, 1},
{"rnng_cv_wait", (DL_FUNC) &rnng_cv_wait, 1},
{"rnng_cv_wait_safe", (DL_FUNC) &rnng_cv_wait_safe, 1},
{"rnng_dial", (DL_FUNC) &rnng_dial, 5},
{"rnng_dialer_close", (DL_FUNC) &rnng_dialer_close, 1},
{"rnng_dialer_start", (DL_FUNC) &rnng_dialer_start, 2},
{"rnng_dispatcher_capacity", (DL_FUNC) &rnng_dispatcher_capacity, 1},
{"rnng_dispatcher_gate", (DL_FUNC) &rnng_dispatcher_gate, 1},
{"rnng_dispatcher_try_gate", (DL_FUNC) &rnng_dispatcher_try_gate, 1},
{"rnng_dispatcher_info", (DL_FUNC) &rnng_dispatcher_info, 1},
{"rnng_dispatcher_run", (DL_FUNC) &rnng_dispatcher_run, 7},
{"rnng_dispatcher_start", (DL_FUNC) &rnng_dispatcher_start, 7},
{"rnng_dispatcher_stop", (DL_FUNC) &rnng_dispatcher_stop, 1},
{"rnng_dispatcher_wait", (DL_FUNC) &rnng_dispatcher_wait, 2},
{"rnng_eval_safe", (DL_FUNC) &rnng_eval_safe, 1},
{"rnng_fini", (DL_FUNC) &rnng_fini, 0},
{"rnng_fini_priors", (DL_FUNC) &rnng_fini_priors, 0},
{"rnng_get_opt", (DL_FUNC) &rnng_get_opt, 2},
{"rnng_http_server_close", (DL_FUNC) &rnng_http_server_close, 1},
{"rnng_http_server_create", (DL_FUNC) &rnng_http_server_create, 3},
{"rnng_http_server_start", (DL_FUNC) &rnng_http_server_start, 1},
{"rnng_http_server_stop", (DL_FUNC) &rnng_http_server_stop, 1},
{"rnng_ip_addr", (DL_FUNC) &rnng_ip_addr, 0},
{"rnng_is_error_value", (DL_FUNC) &rnng_is_error_value, 1},
{"rnng_is_nul_byte", (DL_FUNC) &rnng_is_nul_byte, 1},
{"rnng_listen", (DL_FUNC) &rnng_listen, 5},
{"rnng_listener_close", (DL_FUNC) &rnng_listener_close, 1},
{"rnng_listener_start", (DL_FUNC) &rnng_listener_start, 1},
{"rnng_marker_set", (DL_FUNC) &rnng_marker_set, 1},
{"rnng_monitor_create", (DL_FUNC) &rnng_monitor_create, 2},
{"rnng_monitor_read", (DL_FUNC) &rnng_monitor_read, 1},
{"rnng_ncurl", (DL_FUNC) &rnng_ncurl, 9},
{"rnng_ncurl_aio", (DL_FUNC) &rnng_ncurl_aio, 9},
{"rnng_ncurl_session", (DL_FUNC) &rnng_ncurl_session, 8},
{"rnng_ncurl_session_close", (DL_FUNC) &rnng_ncurl_session_close, 1},
{"rnng_ncurl_transact", (DL_FUNC) &rnng_ncurl_transact, 1},
{"rnng_pipe_notify", (DL_FUNC) &rnng_pipe_notify, 5},
{"rnng_protocol_open", (DL_FUNC) &rnng_protocol_open, 6},
{"rnng_race_aio", (DL_FUNC) &rnng_race_aio, 2},
{"rnng_random", (DL_FUNC) &rnng_random, 2},
{"rnng_read_stdin", (DL_FUNC) &rnng_read_stdin, 1},
{"rnng_reap", (DL_FUNC) &rnng_reap, 1},
{"rnng_recv", (DL_FUNC) &rnng_recv, 3},
{"rnng_recv_aio", (DL_FUNC) &rnng_recv_aio, 5},
{"rnng_request", (DL_FUNC) &rnng_request, 8},
{"rnng_request_stop", (DL_FUNC) &rnng_request_stop, 1},
{"rnng_send", (DL_FUNC) &rnng_send, 5},
{"rnng_send_aio", (DL_FUNC) &rnng_send_aio, 6},
{"rnng_serial_config", (DL_FUNC) &rnng_serial_config, 3},
{"rnng_set_opt", (DL_FUNC) &rnng_set_opt, 3},
{"rnng_set_promise_context", (DL_FUNC) &rnng_set_promise_context, 2},
{"rnng_signal_thread_create", (DL_FUNC) &rnng_signal_thread_create, 2},
{"rnng_sleep", (DL_FUNC) &rnng_sleep, 1},
{"rnng_stats_get", (DL_FUNC) &rnng_stats_get, 2},
{"rnng_status_code", (DL_FUNC) &rnng_status_code, 1},
{"rnng_stream_close", (DL_FUNC) &rnng_stream_close, 1},
{"rnng_stream_conn_send", (DL_FUNC) &rnng_stream_conn_send, 2},
{"rnng_stream_conn_set_header", (DL_FUNC) &rnng_stream_conn_set_header, 3},
{"rnng_stream_conn_set_status", (DL_FUNC) &rnng_stream_conn_set_status, 2},
{"rnng_stream_open", (DL_FUNC) &rnng_stream_open, 6},
{"rnng_strerror", (DL_FUNC) &rnng_strerror, 1},
{"rnng_subscribe", (DL_FUNC) &rnng_subscribe, 3},
{"rnng_tls_config", (DL_FUNC) &rnng_tls_config, 4},
{"rnng_traverse_precious", (DL_FUNC) &rnng_traverse_precious, 0},
{"rnng_unresolved", (DL_FUNC) &rnng_unresolved, 1},
{"rnng_unresolved2", (DL_FUNC) &rnng_unresolved2, 1},
{"rnng_url_parse", (DL_FUNC) &rnng_url_parse, 1},
{"rnng_version", (DL_FUNC) &rnng_version, 0},
{"rnng_wait_thread_create", (DL_FUNC) &rnng_wait_thread_create, 1},
{"rnng_write_cert", (DL_FUNC) &rnng_write_cert, 2},
{"rnng_write_stdout", (DL_FUNC) &rnng_write_stdout, 1},
{"rnng_ws_close", (DL_FUNC) &rnng_ws_close, 1},
{"rnng_ws_send", (DL_FUNC) &rnng_ws_send, 2},
{NULL, NULL, 0}
};
void attribute_visible R_init_nanonext(DllInfo* dll) {
RegisterSymbols();
PreserveObjects();
nano_list_do(INIT, NULL);
R_registerRoutines(dll, NULL, callMethods, NULL, NULL);
R_useDynamicSymbols(dll, FALSE);
R_forceSymbols(dll, TRUE);
}
// # nocov start
void attribute_visible R_unload_nanonext(DllInfo *info) {
nano_thread_shutdown();
nano_list_do(SHUTDOWN, NULL);
ReleaseObjects();
}
// # nocov end
nanonext/src/proto.c 0000644 0001762 0000144 00000026767 15176112256 014232 0 ustar ligges users // nanonext - C level - Socket and Stream Constructors -------------------------
#define NANONEXT_PROTOCOLS
#include "nanonext.h"
// finalizers ------------------------------------------------------------------
static void stream_finalizer(SEXP xptr) {
if (NANO_PTR(xptr) == NULL) return;
nano_stream *xp = (nano_stream *) NANO_PTR(xptr);
nng_stream_close(xp->stream);
nng_stream_free(xp->stream);
if (xp->mode == NANO_STREAM_LISTENER) {
nng_stream_listener_close(xp->endpoint.list);
nng_stream_listener_free(xp->endpoint.list);
} else {
nng_stream_dialer_close(xp->endpoint.dial);
nng_stream_dialer_free(xp->endpoint.dial);
}
if (xp->tls != NULL)
nng_tls_config_free(xp->tls);
free(xp);
}
// sockets ---------------------------------------------------------------------
SEXP rnng_protocol_open(SEXP protocol, SEXP dial, SEXP listen, SEXP tls, SEXP autostart, SEXP raw) {
const char *pro = CHAR(STRING_ELT(protocol, 0));
const int rw = NANO_INTEGER(raw);
size_t slen = strlen(pro);
const char *pname;
int xc;
SEXP socket;
nng_socket *sock = malloc(sizeof(nng_socket));
NANO_ENSURE_ALLOC(sock);
switch (slen) {
case 1:
case 2:
case 3:
if (!memcmp(pro, "bus", slen)) {
pname = "bus";
xc = rw ? nng_bus0_open_raw(sock) : nng_bus0_open(sock);
break;
}
if (slen > 2) {
if (!memcmp(pro, "pub", slen)) {
pname = "pub";
xc = rw ? nng_pub0_open_raw(sock) : nng_pub0_open(sock);
break;
}
if (!memcmp(pro, "sub", slen)) {
pname = "sub";
xc = rw ? nng_sub0_open_raw(sock) : nng_sub0_open(sock);
break;
}
if (!memcmp(pro, "req", slen)) {
pname = "req";
xc = rw ? nng_req0_open_raw(sock) : nng_req0_open(sock);
break;
}
if (!memcmp(pro, "rep", slen)) {
pname = "rep";
xc = rw ? nng_rep0_open_raw(sock) : nng_rep0_open(sock);
break;
}
}
case 4:
if (slen > 1) {
if (!memcmp(pro, "pair", slen)) {
pname = "pair";
xc = rw ? nng_pair0_open_raw(sock) : nng_pair0_open(sock);
break;
}
if (!memcmp(pro, "poly", slen)) {
pname = "poly";
xc = rw ? nng_pair1_open_raw(sock) : nng_pair1_open_poly(sock);
break;
}
if (slen > 2) {
if (!memcmp(pro, "push", slen)) {
pname = "push";
xc = rw ? nng_push0_open_raw(sock) : nng_push0_open(sock);
break;
}
if (!memcmp(pro, "pull", slen)) {
pname = "pull";
xc = rw ? nng_pull0_open_raw(sock) : nng_pull0_open(sock);
break;
}
}
}
case 5:
case 6:
case 7:
case 8:
if (slen > 2 && !memcmp(pro, "surveyor", slen)) {
pname = "surveyor";
xc = rw ? nng_surveyor0_open_raw(sock) : nng_surveyor0_open(sock);
break;
}
case 9:
case 10:
if (slen > 2 && !memcmp(pro, "respondent", slen)) {
pname = "respondent";
xc = rw ? nng_respondent0_open_raw(sock) : nng_respondent0_open(sock);
break;
}
default:
free(sock);
Rf_error("`protocol` should be one of: bus, pair, poly, push, pull, pub, sub, req, rep, surveyor, respondent");
}
if (xc)
goto failmem;
PROTECT(socket = R_MakeExternalPtr(sock, nano_SocketSymbol, R_NilValue));
R_RegisterCFinalizerEx(socket, socket_finalizer, TRUE);
NANO_CLASS2(socket, "nanoSocket", "nano");
Rf_setAttrib(socket, nano_IdSymbol, Rf_ScalarInteger(nng_socket_id(*sock)));
Rf_setAttrib(socket, nano_ProtocolSymbol, Rf_mkString(pname));
Rf_setAttrib(socket, nano_StateSymbol, Rf_mkString("opened"));
if (dial != R_NilValue) {
SEXP intd;
PROTECT(intd = Rf_ScalarInteger(2));
rnng_dial(socket, dial, tls, autostart, intd);
UNPROTECT(1);
}
if (listen != R_NilValue) {
SEXP intl;
PROTECT(intl = Rf_ScalarInteger(2));
rnng_listen(socket, listen, tls, autostart, intl);
UNPROTECT(1);
}
UNPROTECT(1);
return socket;
failmem:
free(sock);
ERROR_OUT(xc);
}
SEXP rnng_close(SEXP socket) {
if (NANO_PTR_CHECK(socket, nano_SocketSymbol))
Rf_error("`socket` is not a valid Socket");
nng_socket *sock = (nng_socket *) NANO_PTR(socket);
const int xc = nng_close(*sock);
if (xc)
ERROR_RET(xc);
Rf_setAttrib(socket, nano_StateSymbol, Rf_mkString("closed"));
return nano_success;
}
SEXP rnng_reap(SEXP con) {
int xc;
if (!NANO_PTR_CHECK(con, nano_ContextSymbol)) {
xc = nng_ctx_close(*(nng_ctx *) NANO_PTR(con));
} else if (!NANO_PTR_CHECK(con, nano_SocketSymbol)) {
xc = nng_close(*(nng_socket *) NANO_PTR(con));
} else if (!NANO_PTR_CHECK(con, nano_ListenerSymbol)) {
xc = nng_listener_close(*(nng_listener *) NANO_PTR(con));
} else if (!NANO_PTR_CHECK(con, nano_DialerSymbol)) {
xc = nng_dialer_close(*(nng_dialer *) NANO_PTR(con));
} else {
xc = 3;
}
if (xc)
return mk_error(xc);
return nano_success;
}
// streams ---------------------------------------------------------------------
static SEXP nano_stream_dial(SEXP url, SEXP textframes, SEXP headers, SEXP tls, SEXP buffer) {
const char *add = CHAR(STRING_ELT(url, 0));
const size_t bufsize = (size_t) nano_integer(buffer);
if (tls != R_NilValue && NANO_PTR_CHECK(tls, nano_TlsSymbol))
Rf_error("`tls` is not a valid TLS Configuration");
nng_url *up = NULL;
nng_aio *aiop = NULL;
int xc;
SEXP sd;
nano_stream *nst = calloc(1, sizeof(nano_stream));
NANO_ENSURE_ALLOC(nst);
nst->mode = NANO_STREAM_DIALER;
nst->bufsize = bufsize;
nst->textframes = NANO_INTEGER(textframes) != 0;
if ((xc = nng_url_parse(&up, add)) ||
(xc = nng_stream_dialer_alloc_url(&nst->endpoint.dial, up)) ||
(xc = nng_aio_alloc(&aiop, NULL, NULL)))
goto fail;
if (!strcmp(up->u_scheme, "ws") || !strcmp(up->u_scheme, "wss")) {
nst->msgmode = 1;
if ((xc = nng_stream_dialer_set_bool(nst->endpoint.dial, "ws:msgmode", 1)))
goto fail;
if (nst->textframes &&
((xc = nng_stream_dialer_set_bool(nst->endpoint.dial, "ws:recv-text", 1)) ||
(xc = nng_stream_dialer_set_bool(nst->endpoint.dial, "ws:send-text", 1))))
goto fail;
if (headers != R_NilValue && TYPEOF(headers) == STRSXP) {
const R_xlen_t hlen = XLENGTH(headers);
SEXP hnames = Rf_getAttrib(headers, R_NamesSymbol);
if (TYPEOF(hnames) == STRSXP && XLENGTH(hnames) == hlen) {
const SEXP *hnames_p = STRING_PTR_RO(hnames);
const SEXP *headers_p = STRING_PTR_RO(headers);
for (R_xlen_t i = 0; i < hlen; i++) {
const char *name = CHAR(hnames_p[i]);
char optname[256];
snprintf(optname, sizeof(optname), "%s%s", NNG_OPT_WS_REQUEST_HEADER, name);
if ((xc = nng_stream_dialer_set_string(nst->endpoint.dial, optname, CHAR(headers_p[i]))))
goto fail;
}
}
}
}
if (!strcmp(up->u_scheme, "wss")) {
if (tls == R_NilValue) {
if ((xc = nng_tls_config_alloc(&nst->tls, NNG_TLS_MODE_CLIENT)) ||
(xc = nng_tls_config_server_name(nst->tls, up->u_hostname)) ||
(xc = nng_tls_config_auth_mode(nst->tls, NNG_TLS_AUTH_MODE_NONE)) ||
(xc = nng_stream_dialer_set_ptr(nst->endpoint.dial, NNG_OPT_TLS_CONFIG, nst->tls)))
goto fail;
} else {
nst->tls = (nng_tls_config *) NANO_PTR(tls);
nng_tls_config_hold(nst->tls);
if ((xc = nng_tls_config_server_name(nst->tls, up->u_hostname)) ||
(xc = nng_stream_dialer_set_ptr(nst->endpoint.dial, NNG_OPT_TLS_CONFIG, nst->tls)))
goto fail;
}
}
nng_stream_dialer_dial(nst->endpoint.dial, aiop);
nng_aio_wait(aiop);
if ((xc = nng_aio_result(aiop)))
goto fail;
nst->stream = nng_aio_get_output(aiop, 0);
nng_aio_free(aiop);
nng_url_free(up);
PROTECT(sd = R_MakeExternalPtr(nst, nano_StreamSymbol, R_NilValue));
R_RegisterCFinalizerEx(sd, stream_finalizer, TRUE);
NANO_CLASS2(sd, "nanoStream", "nano");
Rf_setAttrib(sd, R_ModeSymbol, Rf_mkString(nst->textframes ? "dialer text frames" : "dialer"));
Rf_setAttrib(sd, nano_StateSymbol, Rf_mkString("opened"));
Rf_setAttrib(sd, nano_UrlSymbol, url);
UNPROTECT(1);
return sd;
fail:
if (nst->tls != NULL)
nng_tls_config_free(nst->tls);
nng_aio_free(aiop);
nng_stream_dialer_free(nst->endpoint.dial);
nng_url_free(up);
free(nst);
failmem:
ERROR_OUT(xc);
}
static SEXP nano_stream_listen(SEXP url, SEXP textframes, SEXP tls, SEXP buffer) {
const char *add = CHAR(STRING_ELT(url, 0));
const size_t bufsize = (size_t) nano_integer(buffer);
if (tls != R_NilValue && NANO_PTR_CHECK(tls, nano_TlsSymbol))
Rf_error("`tls` is not a valid TLS Configuration");
nng_url *up = NULL;
nng_aio *aiop = NULL;
int xc;
SEXP sl;
nano_stream *nst = calloc(1, sizeof(nano_stream));
NANO_ENSURE_ALLOC(nst);
nst->mode = NANO_STREAM_LISTENER;
nst->bufsize = bufsize;
nst->textframes = NANO_INTEGER(textframes) != 0;
if ((xc = nng_url_parse(&up, add)) ||
(xc = nng_stream_listener_alloc_url(&nst->endpoint.list, up)) ||
(xc = nng_aio_alloc(&aiop, NULL, NULL)))
goto fail;
if (!strcmp(up->u_scheme, "ws") || !strcmp(up->u_scheme, "wss")) {
nst->msgmode = 1;
if ((xc = nng_stream_listener_set_bool(nst->endpoint.list, "ws:msgmode", 1)))
goto fail;
if (nst->textframes &&
((xc = nng_stream_listener_set_bool(nst->endpoint.list, "ws:recv-text", 1)) ||
(xc = nng_stream_listener_set_bool(nst->endpoint.list, "ws:send-text", 1))))
goto fail;
}
if (!strcmp(up->u_scheme, "wss")) {
if (tls == R_NilValue) {
if ((xc = nng_tls_config_alloc(&nst->tls, NNG_TLS_MODE_SERVER)) ||
(xc = nng_tls_config_auth_mode(nst->tls, NNG_TLS_AUTH_MODE_NONE)) ||
(xc = nng_stream_listener_set_ptr(nst->endpoint.list, NNG_OPT_TLS_CONFIG, nst->tls)))
goto fail;
} else {
nst->tls = (nng_tls_config *) NANO_PTR(tls);
nng_tls_config_hold(nst->tls);
if ((xc = nng_tls_config_server_name(nst->tls, up->u_hostname)) ||
(xc = nng_stream_listener_set_ptr(nst->endpoint.list, NNG_OPT_TLS_CONFIG, nst->tls)))
goto fail;
}
}
if ((xc = nng_stream_listener_listen(nst->endpoint.list)))
goto fail;
nng_stream_listener_accept(nst->endpoint.list, aiop);
nng_aio_wait(aiop);
if ((xc = nng_aio_result(aiop)))
goto fail;
nst->stream = nng_aio_get_output(aiop, 0);
nng_aio_free(aiop);
nng_url_free(up);
PROTECT(sl = R_MakeExternalPtr(nst, nano_StreamSymbol, R_NilValue));
R_RegisterCFinalizerEx(sl, stream_finalizer, TRUE);
NANO_CLASS2(sl, "nanoStream", "nano");
Rf_setAttrib(sl, R_ModeSymbol, Rf_mkString(nst->textframes ? "listener text frames" : "listener"));
Rf_setAttrib(sl, nano_StateSymbol, Rf_mkString("opened"));
Rf_setAttrib(sl, nano_UrlSymbol, url);
UNPROTECT(1);
return sl;
fail:
if (nst->tls != NULL)
nng_tls_config_free(nst->tls);
nng_aio_free(aiop);
nng_stream_listener_free(nst->endpoint.list);
nng_url_free(up);
free(nst);
failmem:
ERROR_OUT(xc);
}
SEXP rnng_stream_open(SEXP dial, SEXP listen, SEXP textframes, SEXP headers, SEXP tls, SEXP buffer) {
if (dial != R_NilValue) {
return nano_stream_dial(dial, textframes, headers, tls, buffer);
} else if (listen != R_NilValue) {
return nano_stream_listen(listen, textframes, tls, buffer);
}
Rf_error("specify a URL for either `dial` or `listen`");
}
SEXP rnng_stream_close(SEXP stream) {
if (NANO_PTR_CHECK(stream, nano_StreamSymbol))
return mk_error(NNG_ECLOSED);
stream_finalizer(stream);
R_ClearExternalPtr(stream);
Rf_setAttrib(stream, nano_StateSymbol, Rf_mkString("closed"));
return nano_success;
}
nanonext/src/Makevars.in 0000644 0001762 0000144 00000000522 15142221674 014777 0 ustar ligges users PKG_CFLAGS = @cflags@ $(C_VISIBILITY)
PKG_LIBS = @libs@
SOURCES = aio.c comms.c core.c dispatcher.c init.c ncurl.c net.c proto.c server.c sync.c thread.c tls.c utils.c
OBJECTS = $(SOURCES:.c=.o)
.PHONY: all cleanup clean
all: cleanup
cleanup: $(SHLIB)
@rm -rf ../nano-install
$(SHLIB): $(OBJECTS)
clean:
rm -f $(OBJECTS) $(SHLIB)
nanonext/src/server.c 0000644 0001762 0000144 00000113445 15176112256 014363 0 ustar ligges users // nanonext - C level - HTTP/WebSocket Server ----------------------------------
#define NANONEXT_HTTP
#include "nanonext.h"
// internals -------------------------------------------------------------------
#define DL_PREPEND(head, node) do { \
(node)->next = (head); \
if ((head) != NULL) \
(head)->prev = (node); \
(head) = (node); \
} while (0)
#define DL_REMOVE(head, node) do { \
if ((node)->prev != NULL) \
(node)->prev->next = (node)->next; \
else if ((head) == (node)) \
(head) = (node)->next; \
if ((node)->next != NULL) \
(node)->next->prev = (node)->prev; \
(node)->next = (node)->prev = NULL; \
} while (0)
#define SEXP_TO_BUF(data, buf, len) do { \
switch (TYPEOF(data)) { \
case RAWSXP: \
(buf) = (unsigned char *) DATAPTR_RO(data); \
(len) = (size_t) XLENGTH(data); \
break; \
case STRSXP: \
(buf) = (unsigned char *) CHAR(STRING_ELT(data, 0)); \
(len) = strlen((const char *) (buf)); \
break; \
default: \
Rf_error("`data` must be raw or character"); \
} \
} while (0)
#define STREAM_CONN_CHECK(xptr, sc) do { \
if (NANO_PTR_CHECK(xptr, nano_ConnSymbol)) \
Rf_error("`conn` is not a valid connection"); \
nano_conn *conn_ = (nano_conn *) NANO_PTR(xptr); \
if (conn_->type != NANO_CONN_HTTP_STREAM) \
Rf_error("`conn` is not an HTTP stream connection"); \
(sc) = (nano_stream_conn *) conn_; \
} while (0)
static inline void prot_add(SEXP prot, SEXP obj) {
SETCDR(prot, Rf_cons(obj, CDR(prot)));
}
static inline void prot_remove(SEXP prot, SEXP obj) {
SEXP prev = prot;
for (SEXP cur = CDR(prot); cur != R_NilValue; prev = cur, cur = CDR(cur)) {
if (CAR(cur) == obj) {
SETCDR(prev, CDR(cur));
return;
}
}
}
static SEXP get_list_element(SEXP list, const char *name) {
SEXP names = Rf_getAttrib(list, R_NamesSymbol);
if (names != R_NilValue) {
for (R_xlen_t i = 0; i < Rf_xlength(list); i++) {
if (!strcmp(CHAR(STRING_ELT(names, i)), name))
return VECTOR_ELT(list, i);
}
}
return R_NilValue;
}
static void http_handler_cb(nng_aio *);
static void http_invoke_callback(void *);
static int http_request_unlink(nano_http_request *);
static void http_cancel_pending_requests(nano_http_server *);
static void ws_accept_cb(void *);
static int conn_close_locked(nano_conn *);
static void conn_close(nano_conn *);
static void ws_recv_cb(void *);
static void conn_cleanup(nano_conn *);
static void ws_invoke_onopen(void *);
static void ws_invoke_onmessage(void *);
static void conn_invoke_onclose(void *);
static void http_server_finalizer(SEXP);
static void http_server_stop(nano_http_server *);
static void http_server_cleanup(void *);
static SEXP make_request_list(nng_http_req *);
// HTTP streaming functions
static void stream_handler_cb(nng_aio *);
static void stream_invoke_onrequest(void *);
static int stream_write_headers(nano_stream_conn *);
static int stream_write_chunk(nano_stream_conn *, const unsigned char *, size_t);
static int stream_write_terminator(nano_stream_conn *);
// On NNG threads --------------------------------------------------------------
static void http_handler_cb(nng_aio *aio) {
nng_http_req *req = nng_aio_get_input(aio, 0);
nng_http_handler *h = nng_aio_get_input(aio, 1);
nano_http_handler_info *info = nng_http_handler_get_data(h);
nano_http_server *srv = info->server;
if (srv->state == SERVER_STOPPED) {
nng_aio_finish(aio, NNG_ECANCELED);
return;
}
nano_http_request *r = calloc(1, sizeof(nano_http_request));
if (r == NULL) {
nng_aio_finish(aio, NNG_ENOMEM);
return;
}
r->aio = aio;
r->req = req;
r->callback = info->callback;
r->server = srv;
nng_mtx_lock(srv->mtx);
DL_PREPEND(srv->pending_reqs, r);
nng_mtx_unlock(srv->mtx);
later2(http_invoke_callback, r);
}
static int http_request_unlink(nano_http_request *r) {
nano_http_server *srv = r->server;
nng_mtx_lock(srv->mtx);
DL_REMOVE(srv->pending_reqs, r);
const int cancelled = r->cancelled;
nng_mtx_unlock(srv->mtx);
return cancelled;
}
static void http_cancel_pending_requests(nano_http_server *srv) {
nng_mtx_lock(srv->mtx);
nano_http_request *r = srv->pending_reqs;
while (r != NULL) {
r->cancelled = 1;
nng_aio_finish(r->aio, NNG_ECANCELED);
r = r->next;
}
nng_mtx_unlock(srv->mtx);
}
static void http_server_stop(nano_http_server *srv) {
nng_http_server_stop(srv->server);
http_cancel_pending_requests(srv);
for (int i = 0; i < srv->handler_count; i++) {
if (srv->handlers[i].listener != NULL) {
nng_aio_stop(srv->handlers[i].accept_aio);
nng_stream_listener_close(srv->handlers[i].listener);
}
// Schedule on_close for all connections (WebSocket and HTTP streaming)
nng_mtx_lock(srv->mtx);
for (nano_conn *conn = srv->handlers[i].conns; conn != NULL; conn = conn->next) {
conn_close_locked(conn);
if (!conn->onclose_scheduled) {
conn->onclose_scheduled = 1;
later2(conn_invoke_onclose, conn);
}
}
nng_mtx_unlock(srv->mtx);
// Wait for WebSocket receive AIOs
for (nano_conn *conn = srv->handlers[i].conns; conn != NULL; conn = conn->next) {
if (conn->type == NANO_CONN_WEBSOCKET) {
nano_ws_conn *ws = (nano_ws_conn *) conn;
nng_aio_wait(ws->recv_aio);
}
}
}
}
// On R main thread ------------------------------------------------------------
static SEXP make_ws_request_list(nng_stream *stream) {
const char *names[] = {"uri", "headers", ""};
SEXP request;
PROTECT(request = Rf_mkNamed(VECSXP, names));
char *uri_str = NULL;
if (nng_stream_get_string(stream, NNG_OPT_WS_REQUEST_URI, &uri_str) == 0) {
SET_VECTOR_ELT(request, 0, Rf_mkString(uri_str));
nng_strfree(uri_str);
}
char *headers_str = NULL;
if (nng_stream_get_string(stream, NNG_OPT_WS_REQUEST_HEADERS, &headers_str) == 0) {
int count = 0;
for (const char *p = headers_str; *p != '\0'; ) {
const char *eol = strstr(p, "\r\n");
if (eol == NULL) break;
if (eol > p) count++;
p = eol + 2;
}
SEXP headers = Rf_allocVector(STRSXP, count);
SET_VECTOR_ELT(request, 1, headers);
SEXP hdr_names = Rf_allocVector(STRSXP, count);
Rf_namesgets(headers, hdr_names);
int i = 0;
for (const char *p = headers_str; *p != '\0' && i < count; ) {
const char *eol = strstr(p, "\r\n");
if (eol == NULL) break;
if (eol > p) {
const char *sep = strstr(p, ": ");
if (sep != NULL && sep < eol) {
SET_STRING_ELT(hdr_names, i, Rf_mkCharLen(p, (int) (sep - p)));
SET_STRING_ELT(headers, i, Rf_mkCharLen(sep + 2, (int) (eol - sep - 2)));
} else {
SET_STRING_ELT(hdr_names, i, Rf_mkCharLen(p, (int) (eol - p)));
SET_STRING_ELT(headers, i, Rf_mkChar(""));
}
i++;
}
p = eol + 2;
}
nng_strfree(headers_str);
}
UNPROTECT(1);
return request;
}
static SEXP make_request_list(nng_http_req *req) {
const char *names[] = {"method", "uri", "headers", "body", ""};
SEXP request, headers, hdr_names, body;
PROTECT(request = Rf_mkNamed(VECSXP, names));
SET_VECTOR_ELT(request, 0, Rf_mkString(nng_http_req_get_method(req)));
SET_VECTOR_ELT(request, 1, Rf_mkString(nng_http_req_get_uri(req)));
int header_count = 0;
for (nano_http_header_s *hdr = nano_http_header_first(req); hdr != NULL;
hdr = nano_http_header_next(req, hdr))
header_count++;
headers = Rf_allocVector(STRSXP, header_count);
SET_VECTOR_ELT(request, 2, headers);
hdr_names = Rf_allocVector(STRSXP, header_count);
Rf_namesgets(headers, hdr_names);
int i = 0;
for (nano_http_header_s *hdr = nano_http_header_first(req); hdr != NULL;
hdr = nano_http_header_next(req, hdr), i++) {
SET_STRING_ELT(headers, i, Rf_mkChar(hdr->value));
SET_STRING_ELT(hdr_names, i, Rf_mkChar(hdr->name));
}
void *body_data;
size_t body_len;
nng_http_req_get_data(req, &body_data, &body_len);
body = Rf_allocVector(RAWSXP, body_len);
SET_VECTOR_ELT(request, 3, body);
if (body_len)
memcpy(NANO_DATAPTR(body), body_data, body_len);
UNPROTECT(1);
return request;
}
static void http_server_cleanup(void *arg) {
nano_http_server *srv = (nano_http_server *) arg;
for (int i = 0; i < srv->handler_count; i++) {
if (srv->handlers[i].handler != NULL)
nng_http_server_del_handler(srv->server, srv->handlers[i].handler);
if (srv->handlers[i].listener != NULL) {
nng_stream_listener_free(srv->handlers[i].listener);
nng_aio_free(srv->handlers[i].accept_aio);
}
// Free any connections not cleaned up by callbacks (shouldn't normally happen)
nano_conn *conn = srv->handlers[i].conns;
while (conn != NULL) {
nano_conn *next = conn->next;
if (conn->xptr != R_NilValue)
R_ClearExternalPtr(conn->xptr);
if (conn->type == NANO_CONN_WEBSOCKET) {
nano_ws_conn *ws = (nano_ws_conn *) conn;
nng_stream_free(ws->stream);
nng_aio_free(ws->recv_aio);
} else {
nano_stream_conn *sc = (nano_stream_conn *) conn;
for (int j = 0; j < sc->resp_header_count; j++) {
free(sc->resp_header_names[j]);
free(sc->resp_header_values[j]);
}
free(sc->resp_header_names);
free(sc->resp_header_values);
}
nng_aio_free(conn->send_aio);
free(conn);
conn = next;
}
}
free(srv->handlers);
if (srv->tls != NULL)
nng_tls_config_free(srv->tls);
nng_http_server_release(srv->server);
if (srv->mtx != NULL)
nng_mtx_free(srv->mtx);
free(srv);
}
static void http_server_finalizer(SEXP xptr) {
if (NANO_PTR(xptr) == NULL) return;
nano_http_server *srv = (nano_http_server *) NANO_PTR(xptr);
if (srv->state == SERVER_STARTED) {
srv->state = SERVER_STOPPED;
http_server_stop(srv);
}
// Schedule deferred cleanup - runs after all pending later2 callbacks due to FIFO ordering
later2(http_server_cleanup, srv);
}
static void http_invoke_callback(void *arg) {
nano_http_request *r = (nano_http_request *) arg;
const int cancelled = http_request_unlink(r);
if (cancelled) {
free(r);
return;
}
SEXP req_list, call;
PROTECT(req_list = make_request_list(r->req));
PROTECT(call = Rf_lang2(r->callback, req_list));
int err;
SEXP result = R_tryEval(call, R_GlobalEnv, &err);
UNPROTECT(2);
nng_http_res *res = NULL;
if (err) {
nng_http_res_alloc_error(&res, 500);
} else {
PROTECT(result);
nng_http_res_alloc(&res);
SEXP status_elem = get_list_element(result, "status");
int status_code = TYPEOF(status_elem) == INTSXP ? NANO_INTEGER(status_elem) : 200;
nng_http_res_set_status(res, (uint16_t) status_code);
SEXP res_headers = get_list_element(result, "headers");
if (TYPEOF(res_headers) == STRSXP) {
PROTECT(res_headers);
SEXP hdr_nm = Rf_getAttrib(res_headers, R_NamesSymbol);
if (TYPEOF(hdr_nm) == STRSXP) {
const SEXP *hdr_nm_p = STRING_PTR_RO(hdr_nm);
const SEXP *res_headers_p = STRING_PTR_RO(res_headers);
for (int i = 0; i < XLENGTH(res_headers); i++) {
nng_http_res_set_header(res, CHAR(hdr_nm_p[i]), CHAR(res_headers_p[i]));
}
}
UNPROTECT(1);
}
SEXP res_body = get_list_element(result, "body");
switch (TYPEOF(res_body)) {
case RAWSXP:
nng_http_res_copy_data(res, NANO_DATAPTR(res_body), XLENGTH(res_body));
break;
case STRSXP:
if (XLENGTH(res_body) > 0) {
const char *str = CHAR(STRING_ELT(res_body, 0));
nng_http_res_copy_data(res, str, strlen(str));
if (nng_http_res_get_header(res, "Content-Type") == NULL)
nng_http_res_set_header(res, "Content-Type", "text/plain; charset=utf-8");
}
break;
default:
break;
}
UNPROTECT(1);
}
nng_aio_set_output(r->aio, 0, res);
nng_aio_finish(r->aio, 0);
free(r);
}
// WebSocket functions ---------------------------------------------------------
static void ws_accept_cb(void *arg) {
nano_http_handler_info *info = (nano_http_handler_info *) arg;
nano_http_server *srv = info->server;
const int xc = nng_aio_result(info->accept_aio);
if (xc == 0) {
nng_stream *stream = nng_aio_get_output(info->accept_aio, 0);
nano_ws_conn *ws = calloc(1, sizeof(nano_ws_conn));
if (ws == NULL) {
nng_stream_free(stream);
goto next;
}
// Initialize base fields
ws->conn.type = NANO_CONN_WEBSOCKET;
ws->conn.handler = info;
ws->conn.state = CONN_STATE_OPEN;
ws->conn.xptr = R_NilValue;
// Initialize WebSocket-specific fields
ws->stream = stream;
if (nng_aio_alloc(&ws->recv_aio, ws_recv_cb, ws) ||
nng_aio_alloc(&ws->conn.send_aio, NULL, NULL)) {
nng_aio_free(ws->recv_aio);
nng_stream_free(stream);
free(ws);
goto next;
}
// Add to handler's connection list via base pointer
nano_conn *conn = &ws->conn;
nng_mtx_lock(srv->mtx);
conn->id = ++srv->conn_counter;
DL_PREPEND(info->conns, conn);
nng_mtx_unlock(srv->mtx);
later2(ws_invoke_onopen, ws);
}
next:
if (srv->state != SERVER_STOPPED) {
nng_stream_listener_accept(info->listener, info->accept_aio);
}
}
static inline int conn_is_open(nano_conn *conn) {
nano_http_server *srv = conn->handler->server;
nng_mtx_lock(srv->mtx);
const int is_open = conn->state == CONN_STATE_OPEN;
nng_mtx_unlock(srv->mtx);
return is_open;
}
// Caller must hold srv->mtx
static int conn_close_locked(nano_conn *conn) {
if (conn->state != CONN_STATE_OPEN)
return 0;
conn->state = CONN_STATE_CLOSING;
// Cancel common AIO
nng_aio_cancel(conn->send_aio);
// Type-specific close
if (conn->type == NANO_CONN_WEBSOCKET) {
nano_ws_conn *ws = (nano_ws_conn *) conn;
nng_aio_cancel(ws->recv_aio);
nng_stream_close(ws->stream);
} else {
nano_stream_conn *sc = (nano_stream_conn *) conn;
nng_http_conn_close(sc->http);
}
return 1;
}
static void conn_close(nano_conn *conn) {
nano_http_server *srv = conn->handler->server;
nng_mtx_lock(srv->mtx);
conn_close_locked(conn);
const int scheduled = conn->onclose_scheduled;
conn->onclose_scheduled = 1;
nng_mtx_unlock(srv->mtx);
if (!scheduled)
later2(conn_invoke_onclose, conn);
}
static void ws_recv_cb(void *arg) {
nano_ws_conn *ws = (nano_ws_conn *) arg;
const int xc = nng_aio_result(ws->recv_aio);
if (xc == 0) {
nng_msg *msgp = nng_aio_get_msg(ws->recv_aio);
ws_message *msg = malloc(sizeof(ws_message));
if (msg != NULL) {
msg->conn = ws;
msg->msg = msgp;
later2(ws_invoke_onmessage, msg);
} else {
nng_msg_free(msgp);
}
nano_http_server *srv = ws->conn.handler->server;
nng_mtx_lock(srv->mtx);
if (ws->conn.state == CONN_STATE_OPEN)
nng_stream_recv(ws->stream, ws->recv_aio);
nng_mtx_unlock(srv->mtx);
} else {
conn_close(&ws->conn);
}
}
static void conn_cleanup(nano_conn *conn) {
nano_http_handler_info *info = conn->handler;
nano_http_server *srv = info->server;
nng_mtx_lock(srv->mtx);
DL_REMOVE(info->conns, conn);
nng_mtx_unlock(srv->mtx);
conn->state = CONN_STATE_CLOSED;
if (conn->xptr != R_NilValue) {
prot_remove(srv->prot, conn->xptr);
R_ClearExternalPtr(conn->xptr);
conn->xptr = R_NilValue;
}
// Type-specific cleanup
if (conn->type == NANO_CONN_WEBSOCKET) {
nano_ws_conn *ws = (nano_ws_conn *) conn;
nng_stream_free(ws->stream);
nng_aio_free(ws->recv_aio);
} else {
nano_stream_conn *sc = (nano_stream_conn *) conn;
for (int i = 0; i < sc->resp_header_count; i++) {
free(sc->resp_header_names[i]);
free(sc->resp_header_values[i]);
}
free(sc->resp_header_names);
free(sc->resp_header_values);
}
nng_aio_free(conn->send_aio);
free(conn);
}
static void ws_invoke_onopen(void *arg) {
nano_ws_conn *ws = (nano_ws_conn *) arg;
if (!conn_is_open(&ws->conn))
return;
nano_http_handler_info *info = ws->conn.handler;
SEXP xptr;
PROTECT(xptr = R_MakeExternalPtr(ws, nano_ConnSymbol, R_NilValue));
NANO_CLASS2(xptr, "nanoWsConn", "nano");
Rf_setAttrib(xptr, nano_IdSymbol, Rf_ScalarInteger(ws->conn.id));
prot_add(info->server->prot, xptr);
ws->conn.xptr = xptr;
UNPROTECT(1);
if (info->on_open != R_NilValue) {
SEXP request, call;
PROTECT(request = make_ws_request_list(ws->stream));
PROTECT(call = Rf_lang3(info->on_open, ws->conn.xptr, request));
R_tryEval(call, R_GlobalEnv, NULL);
UNPROTECT(2);
}
// on_open callback may have closed connection e.g. if not authorized
if (NANO_PTR(ws->conn.xptr) == NULL)
return;
nano_http_server *srv = info->server;
nng_mtx_lock(srv->mtx);
if (ws->conn.state == CONN_STATE_OPEN)
nng_stream_recv(ws->stream, ws->recv_aio);
nng_mtx_unlock(srv->mtx);
}
static void ws_invoke_onmessage(void *arg) {
ws_message *wsmsg = (ws_message *) arg;
nano_ws_conn *ws = wsmsg->conn;
if (!conn_is_open(&ws->conn)) {
nng_msg_free(wsmsg->msg);
free(wsmsg);
return;
}
nano_http_handler_info *info = ws->conn.handler;
const size_t len = nng_msg_len(wsmsg->msg);
unsigned char *buf = nng_msg_body(wsmsg->msg);
SEXP data;
if (info->textframes) {
PROTECT(data = Rf_ScalarString(Rf_mkCharLenCE((char *) buf, len, CE_UTF8)));
} else {
PROTECT(data = Rf_allocVector(RAWSXP, len));
memcpy(NANO_DATAPTR(data), buf, len);
}
if (info->callback != R_NilValue) {
SEXP call;
PROTECT(call = Rf_lang3(info->callback, ws->conn.xptr, data));
R_tryEval(call, R_GlobalEnv, NULL);
UNPROTECT(1);
}
UNPROTECT(1);
nng_msg_free(wsmsg->msg);
free(wsmsg);
}
static void conn_invoke_onclose(void *arg) {
nano_conn *conn = (nano_conn *) arg;
nano_http_handler_info *info = conn->handler;
if (info->on_close != R_NilValue && conn->xptr != R_NilValue) {
SEXP call;
PROTECT(call = Rf_lang2(info->on_close, conn->xptr));
R_tryEval(call, R_GlobalEnv, NULL);
UNPROTECT(1);
}
conn_cleanup(conn);
}
// R-callable functions --------------------------------------------------------
SEXP rnng_http_server_create(SEXP url, SEXP handlers, SEXP tls) {
if (tls != R_NilValue && NANO_PTR_CHECK(tls, nano_TlsSymbol))
Rf_error("`tls` is not a valid TLS Configuration");
if (eln2 == NULL)
nano_load_later();
const char *ur = CHAR(STRING_ELT(url, 0));
nng_url *up = NULL;
nano_http_handler_info *hinfo = NULL;
int xc;
int handlers_added = 0;
nano_http_server *srv = calloc(1, sizeof(nano_http_server));
if (srv == NULL) {
xc = NNG_ENOMEM;
goto fail;
}
srv->xptr = R_NilValue;
srv->prot = R_NilValue;
if ((xc = nng_mtx_alloc(&srv->mtx)))
goto fail;
SEXP prot;
PROTECT(prot = Rf_cons(R_NilValue, R_NilValue));
if ((xc = nng_url_parse(&up, ur)))
goto fail;
if ((xc = nng_http_server_hold(&srv->server, up)))
goto fail;
if (tls != R_NilValue) {
srv->tls = (nng_tls_config *) NANO_PTR(tls);
nng_tls_config_hold(srv->tls);
prot_add(prot, tls);
if ((xc = nng_http_server_set_tls(srv->server, srv->tls)))
goto fail;
}
const R_xlen_t handler_count = Rf_xlength(handlers);
srv->handler_count = handler_count;
if (handler_count > 0) {
hinfo = calloc(handler_count, sizeof(nano_http_handler_info));
if (hinfo == NULL) {
xc = NNG_ENOMEM;
goto fail;
}
srv->handlers = hinfo;
for (R_xlen_t i = 0; i < handler_count; i++) {
srv->handlers[i].callback = R_NilValue;
srv->handlers[i].on_open = R_NilValue;
srv->handlers[i].on_close = R_NilValue;
}
for (R_xlen_t i = 0; i < handler_count; i++) {
SEXP h = VECTOR_ELT(handlers, i);
const int type = NANO_INTEGER(get_list_element(h, "type"));
SEXP path_elem = get_list_element(h, "path");
const char *path = CHAR(STRING_ELT(path_elem, 0));
SEXP tree_elem = get_list_element(h, "prefix");
const int tree = (tree_elem != R_NilValue) ? NANO_INTEGER(tree_elem) : 0;
switch (type) {
case 1: {
// Dynamic callback handler
SEXP method_elem = get_list_element(h, "method");
const char *method = CHAR(STRING_ELT(method_elem, 0));
if (method[0] == '*' && method[1] == '\0')
method = NULL;
if ((xc = nng_http_handler_alloc(&srv->handlers[i].handler, path, http_handler_cb)))
goto fail;
if ((xc = nng_http_handler_set_method(srv->handlers[i].handler, method)))
goto fail;
if (tree && (xc = nng_http_handler_set_tree(srv->handlers[i].handler)))
goto fail;
SEXP callback = get_list_element(h, "callback");
prot_add(prot, callback);
srv->handlers[i].callback = callback;
srv->handlers[i].server = srv;
nng_http_handler_set_data(srv->handlers[i].handler, &srv->handlers[i], NULL);
if ((xc = nng_http_server_add_handler(srv->server, srv->handlers[i].handler)))
goto fail;
handlers_added++;
break;
}
case 2: {
// WebSocket handler
SEXP textframes_elem = get_list_element(h, "textframes");
const int textframes = textframes_elem != R_NilValue ? NANO_INTEGER(textframes_elem) : 0;
const char *scheme = strcmp(up->u_scheme, "https") == 0 ? "wss" : "ws";
const size_t ws_url_len = strlen(scheme) + strlen(up->u_hostname) +
strlen(up->u_port) + strlen(path) + 5;
if (ws_url_len > NANO_URL_MAX) {
xc = NNG_EINVAL;
goto fail;
}
char ws_url[ws_url_len];
snprintf(ws_url, ws_url_len, "%s://%s:%s%s", scheme, up->u_hostname, up->u_port, path);
if ((xc = nng_stream_listener_alloc(&srv->handlers[i].listener, ws_url)) ||
(xc = nng_stream_listener_set_bool(srv->handlers[i].listener, "ws:msgmode", true)))
goto fail;
if (textframes) {
if ((xc = nng_stream_listener_set_bool(srv->handlers[i].listener, "ws:recv-text", true)) ||
(xc = nng_stream_listener_set_bool(srv->handlers[i].listener, "ws:send-text", true)))
goto fail;
srv->handlers[i].textframes = 1;
}
if (srv->tls != NULL) {
nng_stream_listener_set_ptr(srv->handlers[i].listener, NNG_OPT_TLS_CONFIG, srv->tls);
}
SEXP on_message = get_list_element(h, "on_message");
prot_add(prot, on_message);
srv->handlers[i].callback = on_message;
SEXP on_open = get_list_element(h, "on_open");
if (on_open != R_NilValue) {
prot_add(prot, on_open);
srv->handlers[i].on_open = on_open;
}
SEXP on_close = get_list_element(h, "on_close");
if (on_close != R_NilValue) {
prot_add(prot, on_close);
srv->handlers[i].on_close = on_close;
}
srv->handlers[i].server = srv;
if ((xc = nng_aio_alloc(&srv->handlers[i].accept_aio, ws_accept_cb, &srv->handlers[i])))
goto fail;
handlers_added++;
break;
}
case 3: {
// Static file handler
SEXP file_elem = get_list_element(h, "file");
const char *file = CHAR(STRING_ELT(file_elem, 0));
if ((xc = nng_http_handler_alloc_file(&srv->handlers[i].handler, path, file)))
goto fail;
if (tree && (xc = nng_http_handler_set_tree(srv->handlers[i].handler)))
goto fail;
if ((xc = nng_http_server_add_handler(srv->server, srv->handlers[i].handler)))
goto fail;
handlers_added++;
break;
}
case 4: {
// Directory handler (tree is implicit)
SEXP dir_elem = get_list_element(h, "directory");
const char *dir = CHAR(STRING_ELT(dir_elem, 0));
if ((xc = nng_http_handler_alloc_directory(&srv->handlers[i].handler, path, dir)))
goto fail;
if ((xc = nng_http_server_add_handler(srv->server, srv->handlers[i].handler)))
goto fail;
handlers_added++;
break;
}
case 5: {
// Inline static content handler
SEXP ct_elem = get_list_element(h, "content_type");
const char *content_type = TYPEOF(ct_elem) == STRSXP ?
CHAR(STRING_ELT(ct_elem, 0)) : "application/octet-stream";
SEXP data_elem = get_list_element(h, "data");
const unsigned char *data;
size_t size;
switch (TYPEOF(data_elem)) {
case STRSXP:
data = (const unsigned char *) CHAR(STRING_ELT(data_elem, 0));
size = strlen((const char *) data);
break;
default:
data = NANO_DATAPTR(data_elem);
size = (size_t) XLENGTH(data_elem);
}
if ((xc = nng_http_handler_alloc_static(&srv->handlers[i].handler,
path, data, size, content_type)))
goto fail;
if (tree && (xc = nng_http_handler_set_tree(srv->handlers[i].handler)))
goto fail;
if ((xc = nng_http_server_add_handler(srv->server, srv->handlers[i].handler)))
goto fail;
handlers_added++;
break;
}
case 6: {
// Redirect handler
SEXP loc_elem = get_list_element(h, "location");
const char *location = CHAR(STRING_ELT(loc_elem, 0));
SEXP status_elem = get_list_element(h, "status");
const int status_int = NANO_INTEGER(status_elem);
if ((xc = nng_http_handler_alloc_redirect(&srv->handlers[i].handler,
path,
(uint16_t) status_int,
location)))
goto fail;
if (tree && (xc = nng_http_handler_set_tree(srv->handlers[i].handler)))
goto fail;
if ((xc = nng_http_server_add_handler(srv->server, srv->handlers[i].handler)))
goto fail;
handlers_added++;
break;
}
case 7: {
// HTTP streaming handler
SEXP on_request = get_list_element(h, "on_request");
SEXP on_close = get_list_element(h, "on_close");
SEXP method_elem = get_list_element(h, "method");
const char *method = CHAR(STRING_ELT(method_elem, 0));
if (method[0] == '*' && method[1] == '\0')
method = NULL;
if ((xc = nng_http_handler_alloc(&srv->handlers[i].handler, path, stream_handler_cb)))
goto fail;
if ((xc = nng_http_handler_set_method(srv->handlers[i].handler, method)))
goto fail;
if (tree && (xc = nng_http_handler_set_tree(srv->handlers[i].handler)))
goto fail;
srv->handlers[i].callback = on_request;
prot_add(prot, on_request);
if (on_close != R_NilValue) {
srv->handlers[i].on_close = on_close;
prot_add(prot, on_close);
}
srv->handlers[i].server = srv;
nng_http_handler_set_data(srv->handlers[i].handler, &srv->handlers[i], NULL);
if ((xc = nng_http_server_add_handler(srv->server, srv->handlers[i].handler)))
goto fail;
handlers_added++;
break;
}
default:
xc = NNG_EINVAL;
goto fail;
}
}
}
nng_url_free(up);
SEXP xptr = PROTECT(R_MakeExternalPtr(srv, nano_HttpServerSymbol, prot));
R_RegisterCFinalizerEx(xptr, http_server_finalizer, TRUE);
srv->xptr = xptr;
srv->prot = prot;
NANO_CLASS2(xptr, "nanoServer", "nano");
Rf_setAttrib(xptr, nano_UrlSymbol, url);
Rf_setAttrib(xptr, nano_StateSymbol, Rf_mkString("not started"));
UNPROTECT(2);
return xptr;
fail:
if (srv->tls != NULL)
nng_tls_config_free(srv->tls);
for (int i = 0; i < handlers_added; i++) {
if (srv->handlers[i].handler != NULL)
nng_http_server_del_handler(srv->server, srv->handlers[i].handler);
if (srv->handlers[i].listener != NULL)
nng_stream_listener_free(srv->handlers[i].listener);
if (srv->handlers[i].accept_aio != NULL)
nng_aio_free(srv->handlers[i].accept_aio);
}
if (hinfo != NULL && handlers_added < handler_count) {
if (srv->handlers[handlers_added].handler != NULL)
nng_http_handler_free(srv->handlers[handlers_added].handler);
if (srv->handlers[handlers_added].listener != NULL)
nng_stream_listener_free(srv->handlers[handlers_added].listener);
if (srv->handlers[handlers_added].accept_aio != NULL)
nng_aio_free(srv->handlers[handlers_added].accept_aio);
}
if (srv->server != NULL) nng_http_server_release(srv->server);
if (srv->mtx != NULL) nng_mtx_free(srv->mtx);
nng_url_free(up);
free(hinfo);
free(srv);
ERROR_OUT(xc);
}
SEXP rnng_http_server_start(SEXP xptr) {
if (NANO_PTR_CHECK(xptr, nano_HttpServerSymbol))
Rf_error("`server` is not a valid HTTP Server");
nano_http_server *srv = (nano_http_server *) NANO_PTR(xptr);
if (srv->state == SERVER_STARTED)
return nano_success;
if (srv->state == SERVER_STOPPED)
Rf_error("server has been stopped");
int xc;
if ((xc = nng_http_server_start(srv->server)))
ERROR_OUT(xc);
for (int i = 0; i < srv->handler_count; i++) {
if (srv->handlers[i].listener != NULL) {
if ((xc = nng_stream_listener_listen(srv->handlers[i].listener)))
goto fail;
nng_stream_listener_accept(srv->handlers[i].listener, srv->handlers[i].accept_aio);
}
}
srv->state = SERVER_STARTED;
Rf_setAttrib(xptr, nano_StateSymbol, Rf_mkString("started"));
SEXP url = Rf_getAttrib(xptr, nano_UrlSymbol);
nng_url *up;
if (nng_url_parse(&up, CHAR(STRING_ELT(url, 0))) == 0) {
if (up->u_port != NULL && up->u_port[0] == '0' && up->u_port[1] == '\0') {
nng_sockaddr sa;
if (nng_http_server_get_addr(srv->server, &sa) == 0 &&
(sa.s_family == NNG_AF_INET || sa.s_family == NNG_AF_INET6)) {
int port = (int) ntohs(sa.s_family == NNG_AF_INET ? sa.s_in.sa_port : sa.s_in6.sa_port);
Rf_setAttrib(xptr, nano_UrlSymbol, nano_url_with_port(up, port));
}
}
nng_url_free(up);
}
return nano_success;
fail:
for (int j = 0; j < srv->handler_count; j++) {
if (srv->handlers[j].listener != NULL)
nng_stream_listener_close(srv->handlers[j].listener);
}
nng_http_server_stop(srv->server);
ERROR_OUT(xc);
}
SEXP rnng_http_server_close(SEXP xptr) {
if (NANO_PTR_CHECK(xptr, nano_HttpServerSymbol))
Rf_error("`server` is not a valid HTTP Server");
http_server_finalizer(xptr);
R_ClearExternalPtr(xptr);
Rf_setAttrib(xptr, nano_StateSymbol, Rf_mkString("closed"));
return nano_success;
}
SEXP rnng_ws_send(SEXP xptr, SEXP data) {
if (NANO_PTR_CHECK(xptr, nano_ConnSymbol))
return mk_error(NNG_ECLOSED);
nano_ws_conn *ws = (nano_ws_conn *) NANO_PTR(xptr);
unsigned char *buf;
size_t len;
SEXP_TO_BUF(data, buf, len);
nng_msg *msgp = NULL;
int xc;
if ((xc = nng_msg_alloc(&msgp, len)))
return mk_error(xc);
memcpy(nng_msg_body(msgp), buf, len);
nng_aio_set_msg(ws->conn.send_aio, msgp);
nng_stream_send(ws->stream, ws->conn.send_aio);
nng_aio_wait(ws->conn.send_aio);
if ((xc = nng_aio_result(ws->conn.send_aio))) {
nng_msg_free(nng_aio_get_msg(ws->conn.send_aio));
return mk_error(xc);
}
return nano_success;
}
SEXP rnng_ws_close(SEXP xptr) {
if (NANO_PTR_CHECK(xptr, nano_ConnSymbol))
return mk_error(NNG_ECLOSED);
nano_ws_conn *ws = (nano_ws_conn *) NANO_PTR(xptr);
conn_close(&ws->conn);
return nano_success;
}
// HTTP Streaming functions ----------------------------------------------------
static void stream_handler_cb(nng_aio *aio) {
nng_http_req *req = nng_aio_get_input(aio, 0);
nng_http_handler *h = nng_aio_get_input(aio, 1);
nng_http_conn *http_conn = nng_aio_get_input(aio, 2);
nano_http_handler_info *info = nng_http_handler_get_data(h);
nano_http_server *srv = info->server;
if (srv->state == SERVER_STOPPED) {
nng_aio_finish(aio, NNG_ECANCELED);
return;
}
// Hijack the connection - this means we own it now
nng_http_hijack(http_conn);
nano_stream_conn *sc = calloc(1, sizeof(nano_stream_conn));
if (sc == NULL) {
nng_http_conn_close(http_conn);
nng_aio_finish(aio, 0);
return;
}
sc->conn.type = NANO_CONN_HTTP_STREAM;
sc->conn.handler = info;
sc->conn.state = CONN_STATE_OPEN;
sc->conn.xptr = R_NilValue;
sc->http = http_conn;
sc->req = req; // Store pointer - valid because we hijacked
sc->status_code = 200;
if (nng_aio_alloc(&sc->conn.send_aio, NULL, NULL)) {
nng_http_conn_close(http_conn);
free(sc);
nng_aio_finish(aio, 0);
return;
}
nano_conn *conn = &sc->conn;
nng_mtx_lock(srv->mtx);
conn->id = ++srv->conn_counter;
DL_PREPEND(info->conns, conn);
nng_mtx_unlock(srv->mtx);
later2(stream_invoke_onrequest, conn);
nng_aio_finish(aio, 0);
}
static void stream_invoke_onrequest(void *arg) {
nano_conn *conn = (nano_conn *) arg;
nano_stream_conn *sc = (nano_stream_conn *) conn;
nano_http_handler_info *info = conn->handler;
nano_http_server *srv = info->server;
if (!conn_is_open(conn)) {
conn_cleanup(conn);
return;
}
SEXP xptr;
PROTECT(xptr = R_MakeExternalPtr(conn, nano_ConnSymbol, R_NilValue));
NANO_CLASS2(xptr, "nanoStreamConn", "nano");
Rf_setAttrib(xptr, nano_IdSymbol, Rf_ScalarInteger(conn->id));
prot_add(srv->prot, xptr);
conn->xptr = xptr;
UNPROTECT(1);
if (info->callback != R_NilValue) {
SEXP request, call;
PROTECT(request = make_request_list(sc->req));
PROTECT(call = Rf_lang3(info->callback, conn->xptr, request));
R_tryEval(call, R_GlobalEnv, NULL);
UNPROTECT(2);
}
}
static int stream_write_headers(nano_stream_conn *sc) {
nng_http_res *res;
int xc;
if ((xc = nng_http_res_alloc(&res)))
return xc;
if ((xc = nng_http_res_set_status(res, (uint16_t) sc->status_code)))
goto cleanup;
if ((xc = nng_http_res_set_header(res, "Transfer-Encoding", "chunked")))
goto cleanup;
for (int i = 0; i < sc->resp_header_count; i++) {
if ((xc = nng_http_res_set_header(res, sc->resp_header_names[i],
sc->resp_header_values[i])))
goto cleanup;
}
nng_http_conn_write_res(sc->http, res, sc->conn.send_aio);
nng_aio_wait(sc->conn.send_aio);
xc = nng_aio_result(sc->conn.send_aio);
cleanup:
nng_http_res_free(res);
return xc;
}
static int stream_write_chunk(nano_stream_conn *sc, const unsigned char *data,
size_t len) {
// Format: hex_size + CRLF + data + CRLF
char header[24];
int header_len = snprintf(header, sizeof(header), "%" PRIxPTR "\r\n", (uintptr_t) len);
size_t chunk_len = (size_t) header_len + len + 2;
unsigned char *chunk = malloc(chunk_len);
if (chunk == NULL)
return NNG_ENOMEM;
memcpy(chunk, header, (size_t) header_len);
if (len > 0)
memcpy(chunk + header_len, data, len);
memcpy(chunk + header_len + len, "\r\n", 2);
nng_iov iov = {
.iov_buf = chunk,
.iov_len = chunk_len
};
int xc;
if ((xc = nng_aio_set_iov(sc->conn.send_aio, 1, &iov))) {
free(chunk);
return xc;
}
nng_http_conn_write_all(sc->http, sc->conn.send_aio);
nng_aio_wait(sc->conn.send_aio);
xc = nng_aio_result(sc->conn.send_aio);
free(chunk);
return xc;
}
static int stream_write_terminator(nano_stream_conn *sc) {
static const char terminator[] = "0\r\n\r\n";
nng_iov iov = {
.iov_buf = (void *) terminator,
.iov_len = 5
};
int xc;
if ((xc = nng_aio_set_iov(sc->conn.send_aio, 1, &iov)))
return xc;
nng_http_conn_write_all(sc->http, sc->conn.send_aio);
nng_aio_wait(sc->conn.send_aio);
return nng_aio_result(sc->conn.send_aio);
}
SEXP rnng_stream_conn_send(SEXP xptr, SEXP data) {
nano_stream_conn *sc;
STREAM_CONN_CHECK(xptr, sc);
unsigned char *buf;
size_t len;
SEXP_TO_BUF(data, buf, len);
int xc;
if (!sc->headers_sent) {
if ((xc = stream_write_headers(sc)))
return mk_error(xc);
sc->headers_sent = 1;
}
if ((xc = stream_write_chunk(sc, buf, len)))
return mk_error(xc);
return nano_success;
}
SEXP rnng_stream_conn_set_status(SEXP xptr, SEXP code) {
nano_stream_conn *sc;
STREAM_CONN_CHECK(xptr, sc);
if (sc->headers_sent)
Rf_error("cannot set status after headers have been sent");
sc->status_code = nano_integer(code);
return nano_success;
}
SEXP rnng_stream_conn_set_header(SEXP xptr, SEXP name, SEXP value) {
nano_stream_conn *sc;
STREAM_CONN_CHECK(xptr, sc);
if (sc->headers_sent)
Rf_error("cannot set header after headers have been sent");
const char *hdr_name = CHAR(STRING_ELT(name, 0));
const char *hdr_value = CHAR(STRING_ELT(value, 0));
if (sc->resp_header_count >= sc->resp_header_capacity) {
int new_cap = sc->resp_header_capacity == 0 ? 8 : sc->resp_header_capacity * 2;
char **new_names = realloc(sc->resp_header_names, new_cap * sizeof(char *));
char **new_values = realloc(sc->resp_header_values, new_cap * sizeof(char *));
if (new_names == NULL || new_values == NULL) {
free(new_names);
free(new_values);
Rf_error("memory allocation failed");
}
sc->resp_header_names = new_names;
sc->resp_header_values = new_values;
sc->resp_header_capacity = new_cap;
}
sc->resp_header_names[sc->resp_header_count] = strdup(hdr_name);
sc->resp_header_values[sc->resp_header_count] = strdup(hdr_value);
if (sc->resp_header_names[sc->resp_header_count] == NULL ||
sc->resp_header_values[sc->resp_header_count] == NULL)
Rf_error("memory allocation failed");
sc->resp_header_count++;
return nano_success;
}
SEXP rnng_conn_close(SEXP xptr) {
if (NANO_PTR_CHECK(xptr, nano_ConnSymbol))
return mk_error(NNG_ECLOSED);
nano_conn *conn = (nano_conn *) NANO_PTR(xptr);
if (conn->type == NANO_CONN_HTTP_STREAM) {
nano_stream_conn *sc = (nano_stream_conn *) conn;
if (conn_is_open(conn) && sc->headers_sent)
stream_write_terminator(sc);
}
conn_close(conn);
return nano_success;
}
SEXP rnng_http_server_stop(SEXP xptr) {
if (NANO_PTR_CHECK(xptr, nano_HttpServerSymbol))
Rf_error("`server` is not a valid HTTP Server");
nano_http_server *srv = (nano_http_server *) NANO_PTR(xptr);
if (srv->state == SERVER_STOPPED)
return nano_success;
if (srv->state == SERVER_STARTED) {
srv->state = SERVER_STOPPED;
http_server_stop(srv);
}
Rf_setAttrib(xptr, nano_StateSymbol, Rf_mkString("stopped"));
return nano_success;
}
nanonext/src/tls.c 0000644 0001762 0000144 00000025054 15176112256 013655 0 ustar ligges users // nanonext - C level - Mbed TLS Functions -------------------------------------
#define NANONEXT_TLS
#include "nanonext.h"
// internals -------------------------------------------------------------------
static SEXP nano_hash_char(unsigned char *buf, const size_t sz) {
static const char hex[] = "0123456789abcdef";
SEXP out;
char cbuf[sz + sz + 1];
for (size_t i = 0; i < sz; i++) {
cbuf[2 * i] = hex[buf[i] >> 4];
cbuf[2 * i + 1] = hex[buf[i] & 0x0f];
}
cbuf[sz + sz] = '\0';
PROTECT(out = Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(out, 0, Rf_mkCharLenCE(cbuf, (int) (sz + sz), CE_NATIVE));
UNPROTECT(1);
return out;
}
#if MBEDTLS_VERSION_MAJOR == 3 && MBEDTLS_VERSION_MINOR >= 4 || MBEDTLS_VERSION_MAJOR >= 4
static int parse_serial_decimal_format(unsigned char *obuf, size_t obufmax,
const char *ibuf, size_t *len) {
unsigned long long dec;
unsigned int remaining_bytes = sizeof(dec);
unsigned char *p = obuf;
unsigned char val;
char *end_ptr = NULL;
errno = 0;
dec = strtoull(ibuf, &end_ptr, 10);
if ((errno != 0) || (end_ptr == ibuf))
return -1;
*len = 0;
while (remaining_bytes > 0) {
if (obufmax < (*len + 1))
return -1;
val = (dec >> ((remaining_bytes - 1) * 8)) & 0xFF;
if ((val != 0) || (*len != 0)) {
*p = val;
(*len)++;
p++;
}
remaining_bytes--;
}
return 0;
}
#endif
// Mbed TLS Random Data Generator ----------------------------------------------
SEXP rnng_random(SEXP n, SEXP convert) {
#ifdef MBEDTLS_PSA_CRYPTO_C
psa_crypto_init();
#endif
int sz, xc;
switch (TYPEOF(n)) {
case INTSXP:
case LGLSXP:
sz = NANO_INTEGER(n);
if (sz > 0 && sz <= 1024) break;
case REALSXP:
sz = Rf_asInteger(n);
if (sz > 0 && sz <= 1024) break;
default:
Rf_error("`n` must be an integer value between 1 and 1024");
}
SEXP out;
unsigned char buf[sz];
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
const char *pers = "r-nanonext-rng";
mbedtls_entropy_init(&entropy);
mbedtls_ctr_drbg_init(&ctr_drbg);
xc = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const unsigned char *) pers, strlen(pers));
if (xc == 0) {
xc = mbedtls_ctr_drbg_random(&ctr_drbg, buf, sz);
}
mbedtls_ctr_drbg_free(&ctr_drbg);
mbedtls_entropy_free(&entropy);
if (xc)
Rf_error("error generating random bytes");
if (NANO_INTEGER(convert)) {
out = nano_hash_char(buf, sz);
} else {
out = Rf_allocVector(RAWSXP, sz);
if (sz)
memcpy(NANO_DATAPTR(out), buf, sz);
}
return out;
}
// nanonext - Key Generation and Certificates ----------------------------------
SEXP rnng_write_cert(SEXP cn, SEXP valid) {
#ifdef MBEDTLS_PSA_CRYPTO_C
psa_crypto_init();
#endif
const char *common = CHAR(STRING_ELT(cn, 0));
const char *not_after = CHAR(STRING_ELT(valid, 0)); /* validity period not after */
mbedtls_entropy_context entropy = {0};
mbedtls_ctr_drbg_context ctr_drbg = {0};
mbedtls_pk_context key = {0};
const char *pers = "r-nanonext-key";
unsigned char key_buf[16000] = {0};
mbedtls_entropy_init(&entropy);
mbedtls_ctr_drbg_init(&ctr_drbg);
mbedtls_pk_init(&key);
const char *serialvalue = "1"; /* serial number string (decimal) */
const char *not_before = "20010101000000"; /* validity period not before */
const int is_ca = 1; /* is a CA certificate */
const int max_pathlen = 0; /* maximum CA path length */
const int version = 2; /* CRT version */
const mbedtls_md_type_t md = MBEDTLS_MD_SHA256; /* Hash used for signing */
size_t clen = strlen(common) + 20;
char issuer_name[clen]; /* issuer name for certificate */
snprintf(issuer_name, clen, "CN=%s,O=Nanonext,C=JP", common);
int xc, error = 1;
mbedtls_x509_crt issuer_crt = {0};
mbedtls_pk_context loaded_issuer_key = {0};
mbedtls_pk_context *issuer_key = &loaded_issuer_key;
char buf[1024] = {0};
mbedtls_x509_csr csr; // #if defined(MBEDTLS_X509_CSR_PARSE_C)
mbedtls_x509write_cert crt;
const char *persn = "certificate";
mbedtls_x509write_crt_init(&crt);
mbedtls_pk_init(&loaded_issuer_key);
mbedtls_x509_csr_init(&csr); // #if defined(MBEDTLS_X509_CSR_PARSE_C)
mbedtls_x509_crt_init(&issuer_crt);
unsigned char output_buf[4096] = {0};
#if MBEDTLS_VERSION_MAJOR == 3 && MBEDTLS_VERSION_MINOR >= 4 || MBEDTLS_VERSION_MAJOR >= 4
unsigned char serial[MBEDTLS_X509_RFC5280_MAX_SERIAL_LEN] = {0};
size_t serial_len;
#else
mbedtls_mpi serial = {0};
mbedtls_mpi_init(&serial);
#endif
if ((xc = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const unsigned char *) pers, strlen(pers))) ||
(xc = mbedtls_pk_setup(&key, mbedtls_pk_info_from_type((mbedtls_pk_type_t) MBEDTLS_PK_RSA))) ||
(xc = mbedtls_rsa_gen_key(mbedtls_pk_rsa(key), mbedtls_ctr_drbg_random, &ctr_drbg, 4096, 65537)) ||
(xc = mbedtls_pk_write_key_pem(&key, key_buf, 16000)))
goto cleanup;
size_t klen = strlen((char *) key_buf);
if ((xc = mbedtls_ctr_drbg_reseed(&ctr_drbg, (const unsigned char *) persn, strlen(persn))) ||
#if MBEDTLS_VERSION_MAJOR == 3 && MBEDTLS_VERSION_MINOR >= 4 || MBEDTLS_VERSION_MAJOR >= 4
(xc = parse_serial_decimal_format(serial, sizeof(serial), serialvalue, &serial_len)) ||
#else
(xc = mbedtls_mpi_read_string(&serial, 10, serialvalue)) ||
#endif
#if MBEDTLS_VERSION_MAJOR >= 3
(xc = mbedtls_pk_parse_key(&loaded_issuer_key, key_buf, klen + 1, NULL, 0, mbedtls_ctr_drbg_random, &ctr_drbg)))
#else
(xc = mbedtls_pk_parse_key(&loaded_issuer_key, key_buf, klen + 1, NULL, 0)))
#endif
goto cleanup;
mbedtls_x509write_crt_set_subject_key(&crt, issuer_key);
mbedtls_x509write_crt_set_issuer_key(&crt, issuer_key);
if ((xc = mbedtls_x509write_crt_set_subject_name(&crt, issuer_name)) ||
(xc = mbedtls_x509write_crt_set_issuer_name(&crt, issuer_name)))
goto cleanup;
mbedtls_x509write_crt_set_version(&crt, version);
mbedtls_x509write_crt_set_md_alg(&crt, md);
#if MBEDTLS_VERSION_MAJOR == 3 && MBEDTLS_VERSION_MINOR >= 4 || MBEDTLS_VERSION_MAJOR >= 4
if ((xc = mbedtls_x509write_crt_set_serial_raw(&crt, serial, serial_len)) ||
#else
if ((xc = mbedtls_x509write_crt_set_serial(&crt, &serial)) ||
#endif
(xc = mbedtls_x509write_crt_set_validity(&crt, not_before, not_after)) ||
(xc = mbedtls_x509write_crt_set_basic_constraints(&crt, is_ca, max_pathlen)) ||
(xc = mbedtls_x509write_crt_set_subject_key_identifier(&crt)) ||
(xc = mbedtls_x509write_crt_set_authority_key_identifier(&crt)) ||
(xc = mbedtls_x509write_crt_pem(&crt, output_buf, 4096, mbedtls_ctr_drbg_random, &ctr_drbg)))
goto cleanup;
SEXP vec, kcstr, cstr;
const char *names[] = {"server", "client", ""};
PROTECT(vec = Rf_mkNamed(VECSXP, names));
kcstr = Rf_allocVector(STRSXP, 2);
SET_VECTOR_ELT(vec, 0, kcstr);
SET_STRING_ELT(kcstr, 0, Rf_mkChar((char *) &output_buf));
SET_STRING_ELT(kcstr, 1, Rf_mkChar((char *) key_buf));
cstr = Rf_allocVector(STRSXP, 2);
SET_VECTOR_ELT(vec, 1, cstr);
SET_STRING_ELT(cstr, 0, Rf_mkChar((char *) &output_buf));
SET_STRING_ELT(cstr, 1, R_BlankString);
error = 0;
cleanup:
mbedtls_x509_csr_free(&csr); // #if defined(MBEDTLS_X509_CSR_PARSE_C)
mbedtls_x509_crt_free(&issuer_crt);
mbedtls_x509write_crt_free(&crt);
mbedtls_pk_free(&loaded_issuer_key);
#if MBEDTLS_VERSION_MAJOR == 3 && MBEDTLS_VERSION_MINOR < 4 || MBEDTLS_VERSION_MAJOR < 3
mbedtls_mpi_free(&serial);
#endif
mbedtls_pk_free(&key);
mbedtls_ctr_drbg_free(&ctr_drbg);
mbedtls_entropy_free(&entropy);
if (error) {
mbedtls_strerror(xc, buf, sizeof(buf));
Rf_error("%d | %s", xc, buf);
}
UNPROTECT(1);
return vec;
}
// TLS Config ------------------------------------------------------------------
SEXP rnng_tls_config(SEXP client, SEXP server, SEXP pass, SEXP auth) {
const nng_tls_auth_mode mod = NANO_INTEGER(auth) ? NNG_TLS_AUTH_MODE_REQUIRED : NNG_TLS_AUTH_MODE_OPTIONAL;
R_xlen_t usefile;
nng_tls_config *cfg = NULL;
int xc;
const char *crl, *file, *key, *pss;
SEXP xp;
if (client != R_NilValue) {
file = CHAR(STRING_ELT(client, 0));
usefile = XLENGTH(client);
if ((xc = nng_tls_config_alloc(&cfg, NNG_TLS_MODE_CLIENT)) ||
(xc = nng_tls_config_auth_mode(cfg, mod)))
goto fail;
if (usefile > 1) {
crl = CHAR(STRING_ELT(client, 1));
if ((xc = nng_tls_config_ca_chain(cfg, file, strncmp(crl, "", 1) ? crl : NULL)))
goto fail;
} else {
file = R_ExpandFileName(file);
if ((xc = nng_tls_config_ca_file(cfg, file)))
goto fail;
}
} else if (server != R_NilValue) {
file = CHAR(STRING_ELT(server, 0));
usefile = XLENGTH(server);
pss = pass != R_NilValue ? CHAR(STRING_ELT(pass, 0)) : NULL;
if ((xc = nng_tls_config_alloc(&cfg, NNG_TLS_MODE_SERVER)) ||
(xc = nng_tls_config_auth_mode(cfg, mod)))
goto fail;
if (usefile > 1) {
key = CHAR(STRING_ELT(server, 1));
if ((xc = nng_tls_config_own_cert(cfg, file, key, pss)))
goto fail;
} else {
file = R_ExpandFileName(file);
if ((xc = nng_tls_config_cert_key_file(cfg, file, pss)))
goto fail;
}
} else {
if ((xc = nng_tls_config_alloc(&cfg, NNG_TLS_MODE_CLIENT)) ||
(xc = nng_tls_config_auth_mode(cfg, NNG_TLS_AUTH_MODE_NONE)))
goto fail;
}
PROTECT(xp = R_MakeExternalPtr(cfg, nano_TlsSymbol, R_NilValue));
R_RegisterCFinalizerEx(xp, tls_finalizer, TRUE);
Rf_classgets(xp, Rf_mkString("tlsConfig"));
if (client != R_NilValue) {
Rf_setAttrib(xp, R_SpecSymbol, Rf_mkString("client"));
Rf_setAttrib(xp, R_ModeSymbol, Rf_mkString(mod == NNG_TLS_AUTH_MODE_REQUIRED ? "required" : "optional"));
} else if (server != R_NilValue) {
Rf_setAttrib(xp, R_SpecSymbol, Rf_mkString("server"));
Rf_setAttrib(xp, R_ModeSymbol, Rf_mkString(mod == NNG_TLS_AUTH_MODE_REQUIRED ? "required" : "optional"));
} else {
Rf_setAttrib(xp, R_SpecSymbol, Rf_mkString("client"));
Rf_setAttrib(xp, R_ModeSymbol, Rf_mkString("none"));
}
UNPROTECT(1);
return xp;
fail:
if (cfg != NULL)
nng_tls_config_free(cfg);
ERROR_OUT(xc);
}
// utils -----------------------------------------------------------------------
SEXP rnng_version(void) {
char mbed_version_string[18];
mbedtls_version_get_string_full(mbed_version_string);
SEXP version;
PROTECT(version = Rf_allocVector(STRSXP, 2));
SET_STRING_ELT(version, 0, Rf_mkChar(nng_version()));
SET_STRING_ELT(version, 1, Rf_mkChar(mbed_version_string));
UNPROTECT(1);
return version;
}
nanonext/src/nng_structs.h 0000644 0001762 0000144 00000006276 15166504164 015440 0 ustar ligges users // nanonext - NNG internal structure mirrors ------------------------------------
// Mirror structures for NNG HTTP header iteration (matches NNG v1.6+ internals)
// Layout must match: src/nng/src/core/list.h and src/nng/src/supplemental/http/http_msg.c
// Copyright 2017 Garrett D'Amore
// Copyright 2019 Staysail Systems, Inc.
// Copyright 2018 Capitar IT Group BV
//
// This software is supplied under the terms of the MIT License, a
// copy of which should be located in the distribution where this
// file was obtained (LICENSE.txt). A copy of the license may also be
// found online at https://opensource.org/licenses/MIT.
#ifndef NANONEXT_NNG_STRUCTS_H
#define NANONEXT_NNG_STRUCTS_H
#include
#include
typedef struct nano_list_node_s {
struct nano_list_node_s *next;
struct nano_list_node_s *prev;
} nano_list_node_s;
typedef struct nano_list_s {
nano_list_node_s head;
size_t offset;
} nano_list_s;
typedef struct nano_http_header_s {
char *name;
char *value;
nano_list_node_s node;
} nano_http_header_s;
// Mirror of nng_http_req/nng_http_res - only hdrs field needed (first field in both)
typedef struct nano_http_msg_s {
nano_list_s hdrs;
} nano_http_msg_s;
// Generic header iteration - works for both nng_http_req and nng_http_res
// since both have hdrs as their first field with identical structure
static inline nano_http_header_s *nano_http_header_first(void *msg) {
nano_http_msg_s *m = (nano_http_msg_s *) msg;
nano_list_node_s *node = m->hdrs.head.next;
if (node == &m->hdrs.head)
return NULL;
return (nano_http_header_s *) ((char *) node - m->hdrs.offset);
}
static inline nano_http_header_s *nano_http_header_next(void *msg,
nano_http_header_s *h) {
nano_http_msg_s *m = (nano_http_msg_s *) msg;
nano_list_node_s *node = h->node.next;
if (node == &m->hdrs.head)
return NULL;
return (nano_http_header_s *) ((char *) node - m->hdrs.offset);
}
// Mirror of NNG nng_msg body layout for zero-copy buffer transfer.
// Used by nano_msg_set_body() to install a realloc'd serialization buffer
// directly as the message body, avoiding a redundant allocation + memcpy.
//
// Assumed NNG internal layout (stable since NNG 1.6, required >= 1.11.0):
//
// struct nng_msg { // src/nng/src/core/message.c
// uint32_t m_header_buf[16]; // NNI_MAX_MAX_TTL (15) + 1
// size_t m_header_len;
// nni_chunk m_body; // <-- we access this
// uint32_t m_pipe;
// nni_atomic_int m_refcnt;
// };
//
// typedef struct { // nni_chunk
// size_t ch_cap;
// size_t ch_len;
// uint8_t *ch_buf;
// uint8_t *ch_ptr;
// } nni_chunk;
//
// nni_free() is free() on all platforms, so body.buf may be a malloc'd buffer.
typedef struct nano_nng_chunk_s {
size_t cap;
size_t len;
uint8_t *buf;
uint8_t *ptr;
} nano_nng_chunk;
typedef struct nano_nng_msg_s {
uint32_t header_buf[16];
size_t header_len;
nano_nng_chunk body;
} nano_nng_msg;
#endif // NANONEXT_NNG_STRUCTS_H
nanonext/src/core.c 0000644 0001762 0000144 00000037552 15176112256 014011 0 ustar ligges users // nanonext - C level - Core Functions -----------------------------------------
#include "nanonext.h"
// internals -------------------------------------------------------------------
static int special_marker = 0;
static nano_serial_bundle nano_bundle;
static SEXP nano_eval_res;
static SEXP nano_eval_prot (void *call) {
return Rf_eval((SEXP) call, R_GlobalEnv);
}
static void nano_cleanup(void *data, Rboolean jump) {
if (jump)
free(((nano_buf *) data)->buf);
}
static void nano_eval_safe (void *call) {
nano_eval_res = Rf_eval((SEXP) call, R_GlobalEnv);
}
static void nano_write_bytes(R_outpstream_t stream, void *src, int len) {
nano_buf *buf = (nano_buf *) stream->data;
size_t req = buf->cur + (size_t) len;
if (req > buf->len) {
if (req > R_XLEN_T_MAX) {
if (buf->len) free(buf->buf);
Rf_error("serialization exceeds max length of raw vector");
}
do {
buf->len += buf->len > NANONEXT_SERIAL_THR ? NANONEXT_SERIAL_THR : buf->len;
} while (buf->len < req);
unsigned char *nbuf = realloc(buf->buf, buf->len);
if (nbuf == NULL) {
free(buf->buf);
Rf_error("memory allocation failed");
}
buf->buf = nbuf;
}
memcpy(buf->buf + buf->cur, src, len);
buf->cur += len;
}
static void nano_read_bytes(R_inpstream_t stream, void *dst, int len) {
nano_buf *buf = (nano_buf *) stream->data;
if (buf->cur + len > buf->len) Rf_error("unserialization error");
memcpy(dst, buf->buf + buf->cur, len);
buf->cur += len;
}
static int nano_read_char(R_inpstream_t stream) {
nano_buf *buf = (nano_buf *) stream->data;
if (buf->cur >= buf->len) Rf_error("unserialization error");
return buf->buf[buf->cur++];
}
// Serialization Hooks - this section only subject to copyright notice: --------
/*
* MIT License
*
* Copyright (c) 2025 sakura authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
static SEXP nano_serialize_hook(SEXP x, SEXP hook_func) {
SEXP klass = nano_bundle.klass;
int len = (int) XLENGTH(klass), match = 0, i;
const SEXP *klass_p = STRING_PTR_RO(klass);
for (i = 0; i < len; i++) {
if (Rf_inherits(x, CHAR(klass_p[i]))) {
match = 1;
break;
}
}
if (!match)
return R_NilValue;
R_outpstream_t stream = nano_bundle.outpstream;
void (*OutBytes)(R_outpstream_t, void *, int) = stream->OutBytes;
SEXP out, call;
PROTECT(call = Rf_lcons(VECTOR_PTR_RO(hook_func)[i], Rf_cons(x, R_NilValue)));
out = R_UnwindProtect(nano_eval_prot, call, nano_cleanup, stream->data, NULL);
UNPROTECT(1);
if (TYPEOF(out) != RAWSXP) {
free(((nano_buf *) stream->data)->buf);
Rf_error("Serialization function for `%s` did not return a raw vector", CHAR(klass_p[i]));
}
uint64_t size = XLENGTH(out);
char size_string[21];
snprintf(size_string, sizeof(size_string), "%020" PRIu64, size);
static int int_0 = 0;
static int int_1 = 1;
static int int_20 = 20;
static int int_charsxp = CHARSXP;
static int int_persistsxp = 247;
OutBytes(stream, &int_persistsxp, sizeof(int)); // 4
OutBytes(stream, &int_0, sizeof(int)); // 8
OutBytes(stream, &int_1, sizeof(int)); // 12
OutBytes(stream, &int_charsxp, sizeof(int)); // 16
OutBytes(stream, &int_20, sizeof(int)); // 20
OutBytes(stream, &size_string[0], 20); // 40
unsigned char *src = (unsigned char *) DATAPTR_RO(out);
while (size > NANONEXT_CHUNK_SIZE) {
OutBytes(stream, src, NANONEXT_CHUNK_SIZE);
src += NANONEXT_CHUNK_SIZE;
size -= NANONEXT_CHUNK_SIZE;
}
OutBytes(stream, src, (int) size);
OutBytes(stream, &i, sizeof(int)); // 4
return R_BlankScalarString;
}
static SEXP nano_unserialize_hook(SEXP x, SEXP hook_func) {
R_inpstream_t stream = nano_bundle.inpstream;
void (*InBytes)(R_inpstream_t, void *, int) = stream->InBytes;
const char *size_string = CHAR(STRING_ELT(x, 0));
uint64_t size = strtoull(size_string, NULL, 10);
SEXP raw, call, out;
PROTECT(raw = Rf_allocVector(RAWSXP, size));
unsigned char *dest = (unsigned char *) DATAPTR_RO(raw);
while (size > NANONEXT_CHUNK_SIZE) {
InBytes(stream, dest, NANONEXT_CHUNK_SIZE);
dest += NANONEXT_CHUNK_SIZE;
size -= NANONEXT_CHUNK_SIZE;
}
InBytes(stream, dest, (int) size);
int i;
InBytes(stream, &i, 4);
char buf[20];
InBytes(stream, buf, 20);
PROTECT(call = Rf_lcons(VECTOR_PTR_RO(hook_func)[i], Rf_cons(raw, R_NilValue)));
out = Rf_eval(call, R_GlobalEnv);
UNPROTECT(2);
return out;
}
// functions with forward definitions in nanonext.h ----------------------------
void dialer_finalizer(SEXP xptr) {
if (NANO_PTR(xptr) == NULL) return;
nng_dialer *xp = (nng_dialer *) NANO_PTR(xptr);
nng_dialer_close(*xp);
free(xp);
}
void listener_finalizer(SEXP xptr) {
if (NANO_PTR(xptr) == NULL) return;
nng_listener *xp = (nng_listener *) NANO_PTR(xptr);
nng_listener_close(*xp);
free(xp);
}
void socket_finalizer(SEXP xptr) {
if (NANO_PTR(xptr) == NULL) return;
nng_socket *xp = (nng_socket *) NANO_PTR(xptr);
nng_close(*xp);
free(xp);
}
void tls_finalizer(SEXP xptr) {
if (NANO_PTR(xptr) == NULL) return;
nng_tls_config *xp = (nng_tls_config *) NANO_PTR(xptr);
nng_tls_config_free(xp);
}
SEXP mk_error(const int xc) {
SEXP err = Rf_ScalarInteger(xc);
Rf_classgets(err, nano_error);
return err;
}
SEXP mk_error_data(const int xc) {
SEXP env, err;
PROTECT(env = R_NewEnv(R_NilValue, 0, 0));
Rf_classgets(env, xc < 0 ? nano_sendAio : nano_recvAio);
PROTECT(err = Rf_ScalarInteger(abs(xc)));
Rf_classgets(err, nano_error);
Rf_defineVar(nano_ValueSymbol, err, env);
Rf_defineVar(xc < 0 ? nano_ResultSymbol : nano_DataSymbol, err, env);
UNPROTECT(2);
return env;
}
SEXP nano_raw_char(const unsigned char *buf, const size_t sz) {
SEXP out;
int i;
for (i = 0; i < sz; i++) if (!buf[i]) break;
if (sz - i > 1) {
Rf_warningcall_immediate(R_NilValue, "data could not be converted to a character string");
out = Rf_allocVector(RAWSXP, sz);
memcpy(NANO_DATAPTR(out), buf, sz);
return out;
}
PROTECT(out = Rf_allocVector(STRSXP, 1));
SET_STRING_ELT(out, 0, Rf_mkCharLenCE((const char *) buf, i, CE_NATIVE));
UNPROTECT(1);
return out;
}
SEXP nano_findVarInFrame(const SEXP env, const SEXP sym, int *found) {
SEXP frame = CAR(env); // FRAME
while (frame != R_NilValue) {
if (TAG(frame) == sym) {
if (found != NULL) *found = 1;
return CAR(frame); // BINDING_VALUE
}
frame = CDR(frame);
}
if (found != NULL) *found = 0;
return R_NilValue;
}
SEXP nano_url_with_port(nng_url *up, int port) {
char port_s[8];
const int port_len = snprintf(port_s, sizeof(port_s), "%d", port);
const int ipv6 = strchr(up->u_hostname, ':') != NULL;
size_t len = strlen(up->u_scheme) + 3 + strlen(up->u_hostname) + (ipv6 ? 2 : 0) +
1 + (size_t) port_len;
if (up->u_path != NULL) len += strlen(up->u_path);
if (up->u_query != NULL && up->u_query[0] != '\0') len += 1 + strlen(up->u_query);
if (up->u_fragment != NULL && up->u_fragment[0] != '\0') len += 1 + strlen(up->u_fragment);
char *s = R_alloc(len + 1, 1);
char *p = s;
size_t r = len + 1;
int n;
n = snprintf(p, r, "%s://", up->u_scheme); p += n; r -= (size_t) n;
n = snprintf(p, r, ipv6 ? "[%s]" : "%s", up->u_hostname); p += n; r -= (size_t) n;
n = snprintf(p, r, ":%s", port_s); p += n; r -= (size_t) n;
if (up->u_path != NULL) { n = snprintf(p, r, "%s", up->u_path); p += n; r -= (size_t) n; }
if (up->u_query != NULL && up->u_query[0] != '\0') { n = snprintf(p, r, "?%s", up->u_query); p += n; r -= (size_t) n; }
if (up->u_fragment != NULL && up->u_fragment[0] != '\0') snprintf(p, r, "#%s", up->u_fragment);
return Rf_mkString(s);
}
void nano_serialize(nano_buf *buf, SEXP object, SEXP hook, int header) {
NANO_ALLOC(buf, NANONEXT_INIT_BUFSIZE);
struct R_outpstream_st output_stream;
if (header || special_marker) {
memset(buf->buf, 0, 8);
buf->buf[0] = 0x7;
buf->buf[3] = (uint8_t) special_marker;
if (header)
memcpy(buf->buf + 4, &header, sizeof(int));
buf->cur += 8;
}
if (hook != R_NilValue) {
nano_bundle.klass = VECTOR_PTR_RO(hook)[0];
nano_bundle.outpstream = &output_stream;
}
R_InitOutPStream(
&output_stream,
(R_pstream_data_t) buf,
R_pstream_binary_format,
NANONEXT_SERIAL_VER,
NULL,
nano_write_bytes,
hook != R_NilValue ? nano_serialize_hook : NULL,
hook != R_NilValue ? VECTOR_PTR_RO(hook)[1] : R_NilValue
);
R_Serialize(object, &output_stream);
}
void nano_msg_set_body(nng_msg *msg, nano_buf *buf) {
if (buf->len) {
nano_nng_msg *m = (nano_nng_msg *) msg;
free(m->body.buf);
m->body.buf = buf->buf;
m->body.ptr = buf->buf;
m->body.len = buf->cur;
m->body.cap = buf->len;
buf->len = 0;
} else {
nng_msg_append(msg, buf->buf, buf->cur);
}
}
SEXP nano_unserialize(unsigned char *buf, size_t sz, SEXP hook) {
int match = 0;
size_t cur;
if (sz > 12) {
switch (buf[0]) {
case 0x41:
case 0x42:
case 0x58:
cur = 0;
match = 1;
break;
case 0x7:
cur = 8;
match = 1;
break;
}
}
if (!match) {
Rf_warningcall_immediate(R_NilValue, "received data could not be unserialized");
return nano_decode(buf, sz, 8, R_NilValue);
}
nano_buf nbuf = {.buf = buf, .len = sz, .cur = cur};
struct R_inpstream_st input_stream;
if (hook != R_NilValue) {
nano_bundle.inpstream = &input_stream;
}
R_InitInPStream(
&input_stream,
(R_pstream_data_t) &nbuf,
R_pstream_any_format,
nano_read_char,
nano_read_bytes,
hook != R_NilValue ? nano_unserialize_hook : NULL,
hook != R_NilValue ? VECTOR_PTR_RO(hook)[2] : R_NilValue
);
return R_Unserialize(&input_stream);
}
SEXP nano_decode(unsigned char *buf, const size_t sz, const uint8_t mod, SEXP hook) {
SEXP data;
size_t size;
switch (mod) {
case 2:
size = sz / 2 + 1;
PROTECT(data = Rf_allocVector(STRSXP, size));
R_xlen_t i, m, nbytes = sz, np = 0;
for (i = 0, m = 0; i < size; i++) {
unsigned char *p;
R_xlen_t j;
SEXP res;
for (j = np, p = buf + np; j < nbytes; p++, j++)
if (*p == '\0') break;
res = Rf_mkCharLenCE((const char *) (buf + np), (int) (j - np), CE_NATIVE);
if (res == R_NilValue) break;
SET_STRING_ELT(data, i, res);
if (XLENGTH(res) > 0) m = i;
np = j < nbytes ? j + 1 : nbytes;
}
if (i)
data = Rf_xlengthgets(data, m + 1);
UNPROTECT(1);
return data;
case 3:
size = 2 * sizeof(double);
if (sz % size) {
Rf_warningcall_immediate(R_NilValue, "received data could not be converted to complex");
data = Rf_allocVector(RAWSXP, sz);
} else {
data = Rf_allocVector(CPLXSXP, sz / size);
}
break;
case 4:
size = sizeof(double);
if (sz % size) {
Rf_warningcall_immediate(R_NilValue, "received data could not be converted to double");
data = Rf_allocVector(RAWSXP, sz);
} else {
data = Rf_allocVector(REALSXP, sz / size);
}
break;
case 5:
size = sizeof(int);
if (sz % size) {
Rf_warningcall_immediate(R_NilValue, "received data could not be converted to integer");
data = Rf_allocVector(RAWSXP, sz);
} else {
data = Rf_allocVector(INTSXP, sz / size);
}
break;
case 6:
size = sizeof(int);
if (sz % size) {
Rf_warningcall_immediate(R_NilValue, "received data could not be converted to logical");
data = Rf_allocVector(RAWSXP, sz);
} else {
data = Rf_allocVector(LGLSXP, sz / size);
}
break;
case 7:
size = sizeof(double);
if (sz % size) {
Rf_warningcall_immediate(R_NilValue, "received data could not be converted to numeric");
data = Rf_allocVector(RAWSXP, sz);
} else {
data = Rf_allocVector(REALSXP, sz / size);
}
break;
case 8:
data = Rf_allocVector(RAWSXP, sz);
break;
case 9:
return nano_raw_char(buf, sz);
default:
return nano_unserialize(buf, sz, hook);
}
if (sz)
memcpy(NANO_DATAPTR(data), buf, sz);
return data;
}
void nano_encode(nano_buf *enc, const SEXP object) {
switch (TYPEOF(object)) {
case STRSXP: {
const char *s;
R_xlen_t xlen = XLENGTH(object);
if (xlen == 1) {
s = CHAR(STRING_ELT(object, 0));
NANO_INIT(enc, (unsigned char *) s, strlen(s) + 1);
break;
}
R_xlen_t i;
size_t slen, outlen = 0;
const SEXP *object_p = STRING_PTR_RO(object);
for (i = 0; i < xlen; i++)
outlen += strlen(CHAR(object_p[i])) + 1;
NANO_ALLOC(enc, outlen);
for (i = 0; i < xlen; i++) {
s = CHAR(object_p[i]);
slen = strlen(s) + 1;
memcpy(enc->buf + enc->cur, s, slen);
enc->cur += slen;
}
break;
}
case REALSXP:
NANO_INIT(enc, (unsigned char *) DATAPTR_RO(object), XLENGTH(object) * sizeof(double));
break;
case INTSXP:
case LGLSXP:
NANO_INIT(enc, (unsigned char *) DATAPTR_RO(object), XLENGTH(object) * sizeof(int));
break;
case CPLXSXP:
NANO_INIT(enc, (unsigned char *) DATAPTR_RO(object), XLENGTH(object) * 2 * sizeof(double));
break;
case RAWSXP:
NANO_INIT(enc, (unsigned char *) DATAPTR_RO(object), XLENGTH(object));
break;
case NILSXP:
NANO_INIT(enc, NULL, 0);
break;
default:
Rf_error("`data` must be an atomic vector type or NULL to send in mode 'raw'");
}
}
int nano_encode_mode(const SEXP mode) {
if (TYPEOF(mode) == INTSXP)
return NANO_INTEGER(mode) == 2;
const char *mod = CHAR(STRING_ELT(mode, 0));
const size_t slen = strlen(mod);
switch (slen) {
case 3:
if (!memcmp(mod, "raw", slen)) return 1;
break;
case 6:
if (!memcmp(mod, "serial", slen)) return 0;
break;
}
Rf_error("`mode` should be one of: serial, raw");
}
int nano_matcharg(const SEXP mode) {
if (TYPEOF(mode) == INTSXP)
return NANO_INTEGER(mode);
const char *mod = CHAR(STRING_ELT(mode, 0));
size_t slen = strlen(mod);
int i;
switch (slen) {
case 3:
if (!memcmp(mod, "raw", slen)) { i = 8; break; }
goto fail;
case 6:
if (!memcmp(mod, "serial", slen)) { i = 1; break; }
if (!memcmp(mod, "double", slen)) { i = 4; break; }
if (!memcmp(mod, "string", slen)) { i = 9; break; }
goto fail;
case 7:
if (!memcmp(mod, "integer", slen)) { i = 5; break; }
if (!memcmp(mod, "numeric", slen)) { i = 7; break; }
if (!memcmp(mod, "logical", slen)) { i = 6; break; }
if (!memcmp(mod, "complex", slen)) { i = 3; break; }
goto fail;
case 9:
if (!memcmp(mod, "character", slen)) { i = 2; break; }
goto fail;
default:
goto fail;
}
return i;
fail:
Rf_error("`mode` should be one of: serial, character, complex, double, integer, logical, numeric, raw, string");
}
SEXP rnng_eval_safe(SEXP arg) {
return R_ToplevelExec(nano_eval_safe, arg) ? nano_eval_res : Rf_allocVector(RAWSXP, 1);
}
// specials --------------------------------------------------------------------
SEXP rnng_marker_set(SEXP x) {
special_marker = NANO_INTEGER(x);
return x;
}
nanonext/src/sync.c 0000644 0001762 0000144 00000037660 15166504164 014037 0 ustar ligges users // nanonext - C level - Synchronization Primitives and Signals -----------------
#define NANONEXT_SIGNALS
#include "nanonext.h"
// internals -------------------------------------------------------------------
void nano_load_later(void) {
SEXP str, call;
PROTECT(str = Rf_mkString("later"));
PROTECT(call = Rf_lang2(Rf_install("loadNamespace"), str));
Rf_eval(call, R_BaseEnv);
UNPROTECT(2);
eln2 = (void (*)(void (*)(void *), void *, double, int)) R_GetCCallable("later", "execLaterNative2");
}
SEXP nano_PreserveObject(const SEXP x) {
SEXP tail = CDR(nano_precious);
SEXP node = Rf_cons(nano_precious, tail);
SETCDR(nano_precious, node);
if (tail != R_NilValue)
SETCAR(tail, node);
SET_TAG(node, x);
return node;
}
void nano_ReleaseObject(SEXP node) {
SET_TAG(node, R_NilValue);
SEXP head = CAR(node);
SEXP tail = CDR(node);
SETCDR(head, tail);
if (tail != R_NilValue)
SETCAR(tail, head);
}
// aio completion callbacks ----------------------------------------------------
void raio_invoke_cb(void *arg) {
SEXP call, node = (SEXP) arg, x = TAG(node);
PROTECT(call = Rf_lcons(nano_ResolveSymbol, Rf_cons(nano_aio_get_msg(x), R_NilValue)));
Rf_eval(call, NANO_ENCLOS(x));
UNPROTECT(1);
nano_ReleaseObject(node);
}
void haio_invoke_cb(void *arg) {
SEXP call, out, node = (SEXP) arg, x = TAG(node);
const char *names[] = {"status", "headers", "data", ""};
PROTECT(out = Rf_mkNamed(VECSXP, names));
SET_VECTOR_ELT(out, 0, nano_aio_http_status(x));
SET_VECTOR_ELT(out, 1, nano_findVarInFrame(x, nano_ProtocolSymbol, NULL));
SET_VECTOR_ELT(out, 2, nano_findVarInFrame(x, nano_ValueSymbol, NULL));
PROTECT(call = Rf_lcons(nano_ResolveSymbol, Rf_cons(out, R_NilValue)));
Rf_eval(call, NANO_ENCLOS(x));
UNPROTECT(2);
nano_ReleaseObject(node);
}
static void sendaio_complete(void *arg) {
nng_aio *aio = ((nano_saio *) arg)->aio;
if (nng_aio_result(aio))
nng_msg_free(nng_aio_get_msg(aio));
}
static void request_complete(void *arg) {
nano_aio *raio = (nano_aio *) arg;
nano_saio *saio = (nano_saio *) raio->cb;
int res = nng_aio_result(raio->aio);
if (res == 0) {
nng_msg *msg = nng_aio_get_msg(raio->aio);
raio->data = msg;
nng_pipe p = nng_msg_get_pipe(msg);
res = - (int) p.id;
if (saio->id == 0) {
unsigned char *buf = (unsigned char *) nng_msg_body(msg);
if (buf[0] == 0x7 && buf[3] == 0x1)
nng_pipe_close(p);
}
} else if (res == 5 && saio->id > 0) {
if (saio->disp != NULL) {
if (saio->type) {
nng_msg *msg = NULL;
if (nng_msg_alloc(&msg, 0) ||
nng_msg_append_u32(msg, 0) ||
nng_msg_append(msg, &saio->id, sizeof(int)) ||
nng_ctx_sendmsg(*(nng_ctx *) saio->disp, msg, NNG_FLAG_NONBLOCK)) {
nng_msg_free(msg);
}
} else {
dispatch_cancel_direct(saio->disp, saio->id);
}
}
}
if (raio->next != NULL) {
nano_cv *ncv = (nano_cv *) raio->next;
nng_cv *cv = ncv->cv;
nng_mtx *mtx = ncv->mtx;
nng_mtx_lock(mtx);
raio->result = res;
ncv->condition++;
nng_cv_wake(cv);
nng_mtx_unlock(mtx);
} else {
raio->result = res;
}
if (saio->cb != NULL)
later2(raio_invoke_cb, saio->cb);
}
void delayed_sigterm(void *arg) {
(void) arg;
nng_msleep(NANONEXT_SLEEP_DUR);
#ifdef _WIN32
raise(SIGTERM);
#else
kill(getpid(), SIGTERM);
#endif
}
void pipe_cb_signal(nng_pipe p, nng_pipe_ev ev, void *arg) {
int sig;
nano_cv *ncv = (nano_cv *) arg;
nng_cv *cv = ncv->cv;
nng_mtx *mtx = ncv->mtx;
nng_mtx_lock(mtx);
sig = ncv->flag;
if (sig > 0) ncv->flag = -1;
ncv->condition++;
nng_cv_wake(cv);
nng_mtx_unlock(mtx);
if (sig > 1) {
if (sig == SIGTERM) {
nng_thread *thr;
nng_thread_create(&thr, delayed_sigterm, NULL);
} else {
#ifdef _WIN32
if (sig == SIGINT)
UserBreak = 1;
raise(sig);
#else
if (sig == SIGINT)
R_interrupts_pending = 1;
kill(getpid(), sig);
#endif
}
}
}
void pipe_cb_monitor(nng_pipe p, nng_pipe_ev ev, void *arg) {
nano_monitor *monitor = (nano_monitor *) arg;
nano_cv *ncv = monitor->cv;
nng_cv *cv = ncv->cv;
nng_mtx *mtx = ncv->mtx;
const int id = (int) p.id;
if (!id)
return;
nng_mtx_lock(mtx);
if (monitor->updates >= monitor->size) {
monitor->size += 8;
int *ids = realloc(monitor->ids, monitor->size * sizeof(int));
if (ids == NULL) {
monitor->size -= 8;
nng_mtx_unlock(mtx);
return;
}
monitor->ids = ids;
}
monitor->ids[monitor->updates] = ev == NNG_PIPE_EV_ADD_POST ? id : -id;
monitor->updates++;
ncv->condition++;
nng_cv_wake(cv);
nng_mtx_unlock(mtx);
}
// finalizers ------------------------------------------------------------------
static void cv_finalizer(SEXP xptr) {
if (NANO_PTR(xptr) == NULL) return;
nano_cv *xp = (nano_cv *) NANO_PTR(xptr);
nng_cv_free(xp->cv);
nng_mtx_free(xp->mtx);
free(xp);
}
static void request_finalizer(SEXP xptr) {
if (NANO_PTR(xptr) == NULL) return;
nano_aio *xp = (nano_aio *) NANO_PTR(xptr);
nano_saio *saio = (nano_saio *) xp->cb;
nng_aio_free(saio->aio);
nng_aio_free(xp->aio);
if (xp->data != NULL)
nng_msg_free((nng_msg *) xp->data);
free(saio);
free(xp);
}
static void monitor_finalizer(SEXP xptr) {
if (NANO_PTR(xptr) == NULL) return;
nano_monitor *xp = (nano_monitor *) NANO_PTR(xptr);
free(xp->ids);
free(xp);
}
// synchronization primitives --------------------------------------------------
SEXP rnng_cv_alloc(void) {
SEXP xp;
int xc;
nano_cv *cvp = calloc(1, sizeof(nano_cv));
NANO_ENSURE_ALLOC(cvp);
if ((xc = nng_mtx_alloc(&cvp->mtx)))
goto fail;
if ((xc = nng_cv_alloc(&cvp->cv, cvp->mtx)))
goto fail;
PROTECT(xp = R_MakeExternalPtr(cvp, nano_CvSymbol, R_NilValue));
R_RegisterCFinalizerEx(xp, cv_finalizer, TRUE);
Rf_classgets(xp, Rf_mkString("conditionVariable"));
UNPROTECT(1);
return xp;
fail:
nng_mtx_free(cvp->mtx);
free(cvp);
failmem:
ERROR_OUT(xc);
}
SEXP rnng_cv_wait(SEXP cvar) {
if (NANO_PTR_CHECK(cvar, nano_CvSymbol))
Rf_error("`cv` is not a valid Condition Variable");
nano_cv *ncv = (nano_cv *) NANO_PTR(cvar);
nng_cv *cv = ncv->cv;
nng_mtx *mtx = ncv->mtx;
int flag;
nng_mtx_lock(mtx);
while (ncv->condition == 0)
nng_cv_wait(cv);
ncv->condition--;
flag = ncv->flag;
nng_mtx_unlock(mtx);
return Rf_ScalarLogical(flag >= 0);
}
SEXP rnng_cv_until(SEXP cvar, SEXP msec) {
if (NANO_PTR_CHECK(cvar, nano_CvSymbol))
Rf_error("`cv` is not a valid Condition Variable");
nano_cv *ncv = (nano_cv *) NANO_PTR(cvar);
nng_cv *cv = ncv->cv;
nng_mtx *mtx = ncv->mtx;
int signalled = 1;
nng_time time = nng_clock();
switch (TYPEOF(msec)) {
case INTSXP:
time = time + (nng_time) NANO_INTEGER(msec);
break;
case REALSXP:
time = time + (nng_time) Rf_asInteger(msec);
break;
}
nng_mtx_lock(mtx);
while (ncv->condition == 0) {
if (nng_cv_until(cv, time) == NNG_ETIMEDOUT) {
signalled = 0;
break;
}
}
if (signalled) {
ncv->condition--;
nng_mtx_unlock(mtx);
} else {
nng_mtx_unlock(mtx);
R_CheckUserInterrupt();
}
return Rf_ScalarLogical(signalled);
}
SEXP rnng_cv_wait_safe(SEXP cvar) {
if (NANO_PTR_CHECK(cvar, nano_CvSymbol))
Rf_error("`cv` is not a valid Condition Variable");
nano_cv *ncv = (nano_cv *) NANO_PTR(cvar);
nng_cv *cv = ncv->cv;
nng_mtx *mtx = ncv->mtx;
int signalled;
int flag;
nng_time time = nng_clock();
while (1) {
time = time + 400;
signalled = 1;
nng_mtx_lock(mtx);
while (ncv->condition == 0) {
if (nng_cv_until(cv, time) == NNG_ETIMEDOUT) {
signalled = 0;
break;
}
}
if (signalled) break;
nng_mtx_unlock(mtx);
R_CheckUserInterrupt();
}
ncv->condition--;
flag = ncv->flag;
nng_mtx_unlock(mtx);
return Rf_ScalarLogical(flag >= 0);
}
SEXP rnng_cv_until_safe(SEXP cvar, SEXP msec) {
if (NANO_PTR_CHECK(cvar, nano_CvSymbol))
Rf_error("`cv` is not a valid Condition Variable");
nano_cv *ncv = (nano_cv *) NANO_PTR(cvar);
nng_cv *cv = ncv->cv;
nng_mtx *mtx = ncv->mtx;
int signalled;
nng_time time, period, now;
switch (TYPEOF(msec)) {
case INTSXP:
period = (nng_time) NANO_INTEGER(msec);
break;
case REALSXP:
period = (nng_time) Rf_asInteger(msec);
break;
default:
period = 0;
}
now = nng_clock();
do {
time = period > 400 ? now + 400 : now + period;
period = period > 400 ? period - 400 : 0;
signalled = 1;
nng_mtx_lock(mtx);
while (ncv->condition == 0) {
if (nng_cv_until(cv, time) == NNG_ETIMEDOUT) {
signalled = 0;
break;
}
}
if (signalled) {
ncv->condition--;
nng_mtx_unlock(mtx);
break;
}
nng_mtx_unlock(mtx);
R_CheckUserInterrupt();
now += 400;
} while (period > 0);
return Rf_ScalarLogical(signalled);
}
SEXP rnng_cv_reset(SEXP cvar) {
if (NANO_PTR_CHECK(cvar, nano_CvSymbol))
Rf_error("`cv` is not a valid Condition Variable");
nano_cv *ncv = (nano_cv *) NANO_PTR(cvar);
nng_mtx *mtx = ncv->mtx;
nng_mtx_lock(mtx);
ncv->flag = ncv->flag < 0;
ncv->condition = 0;
nng_mtx_unlock(mtx);
return nano_success;
}
SEXP rnng_cv_value(SEXP cvar) {
if (NANO_PTR_CHECK(cvar, nano_CvSymbol))
Rf_error("`cv` is not a valid Condition Variable");
nano_cv *ncv = (nano_cv *) NANO_PTR(cvar);
nng_mtx *mtx = ncv->mtx;
int cond;
nng_mtx_lock(mtx);
cond = ncv->condition;
nng_mtx_unlock(mtx);
return Rf_ScalarInteger(cond);
}
SEXP rnng_cv_signal(SEXP cvar) {
if (NANO_PTR_CHECK(cvar, nano_CvSymbol))
Rf_error("`cv` is not a valid Condition Variable");
nano_cv *ncv = (nano_cv *) NANO_PTR(cvar);
nng_cv *cv = ncv->cv;
nng_mtx *mtx = ncv->mtx;
nng_mtx_lock(mtx);
ncv->condition++;
nng_cv_wake(cv);
nng_mtx_unlock(mtx);
return nano_success;
}
// request ---------------------------------------------------------------------
SEXP rnng_request(SEXP con, SEXP data, SEXP sendmode, SEXP recvmode, SEXP timeout, SEXP cvar, SEXP msgid, SEXP clo) {
if (NANO_PTR_CHECK(con, nano_ContextSymbol))
Rf_error("`con` is not a valid Context");
nng_ctx *ctx = (nng_ctx *) NANO_PTR(con);
const nng_duration dur = timeout == R_NilValue ? NNG_DURATION_DEFAULT : (nng_duration) nano_integer(timeout);
const uint8_t mod = (uint8_t) nano_matcharg(recvmode);
const int raw = nano_encode_mode(sendmode);
const int id = nng_ctx_id(*ctx);
const int signal = cvar != R_NilValue && !NANO_PTR_CHECK(cvar, nano_CvSymbol);
int xc;
nano_cv *ncv = signal ? (nano_cv *) NANO_PTR(cvar) : NULL;
nano_saio *saio = NULL;
nano_aio *raio = NULL;
nng_msg *msg = NULL;
SEXP aio, env, fun;
nano_buf buf;
if (raw) {
nano_encode(&buf, data);
} else {
nano_serialize(&buf, data, NANO_PROT(con), id);
}
saio = calloc(1, sizeof(nano_saio));
NANO_ENSURE_ALLOC(saio);
raio = calloc(1, sizeof(nano_aio));
NANO_ENSURE_ALLOC(raio);
if (TYPEOF(msgid) == EXTPTRSXP) {
saio->disp = NANO_PTR(msgid);
} else if (msgid != R_NilValue) {
saio->disp = ctx;
saio->type = 1;
} else {
saio->disp = NULL;
}
saio->id = msgid != R_NilValue ? id : mod != 1 ? -id : 0;
if ((xc = nng_msg_alloc(&msg, 0)) ||
(xc = nng_aio_alloc(&saio->aio, sendaio_complete, saio))) {
nng_msg_free(msg);
goto fail;
}
nano_msg_set_body(msg, &buf);
nng_aio_set_msg(saio->aio, msg);
nng_ctx_send(*ctx, saio->aio);
raio->type = signal ? REQAIOS : REQAIO;
raio->mode = mod;
raio->cb = saio;
raio->next = ncv;
if ((xc = nng_aio_alloc(&raio->aio, request_complete, raio)))
goto fail;
nng_aio_set_timeout(raio->aio, dur);
nng_ctx_recv(*ctx, raio->aio);
NANO_FREE(buf);
PROTECT(aio = R_MakeExternalPtr(raio, nano_AioSymbol, NANO_PROT(con)));
R_RegisterCFinalizerEx(aio, request_finalizer, TRUE);
Rf_setAttrib(aio, nano_ContextSymbol, con);
PROTECT(env = R_NewEnv(R_NilValue, 0, 0));
Rf_classgets(env, nano_reqAio);
Rf_setAttrib(env, nano_IdSymbol, Rf_ScalarInteger(id));
Rf_defineVar(nano_AioSymbol, aio, env);
PROTECT(fun = R_mkClosure(R_NilValue, nano_aioFuncMsg, clo));
R_MakeActiveBinding(nano_DataSymbol, fun, env);
UNPROTECT(3);
return env;
fail:
nng_aio_free(saio->aio);
failmem:
free(raio);
free(saio);
NANO_FREE(buf);
return mk_error_data(xc);
}
SEXP rnng_set_promise_context(SEXP x, SEXP ctx) {
if (TYPEOF(x) != ENVSXP)
return R_NilValue;
const SEXP aio = nano_findVarInFrame(x, nano_AioSymbol, NULL);
if (NANO_PTR_CHECK(aio, nano_AioSymbol))
return R_NilValue;
nano_aio *raio = (nano_aio *) NANO_PTR(aio);
if (eln2 == NULL)
nano_load_later();
switch (raio->type) {
case REQAIO:
case REQAIOS:
NANO_SET_ENCLOS(x, ctx);
nano_saio *saio = (nano_saio *) raio->cb;
saio->cb = nano_PreserveObject(x);
break;
case RECVAIO:
case RECVAIOS:
case IOV_RECVAIO:
case IOV_RECVAIOS:
case HTTP_AIO:
NANO_SET_ENCLOS(x, ctx);
raio->cb = nano_PreserveObject(x);
break;
case SENDAIO:
case IOV_SENDAIO:
break;
}
return R_NilValue;
}
// pipes -----------------------------------------------------------------------
SEXP rnng_pipe_notify(SEXP socket, SEXP cv, SEXP add, SEXP remove, SEXP flag) {
if (NANO_PTR_CHECK(socket, nano_SocketSymbol))
Rf_error("`socket` is not a valid Socket");
int xc;
nng_socket *sock;
if (cv == R_NilValue) {
sock = (nng_socket *) NANO_PTR(socket);
if (NANO_INTEGER(add) && (xc = nng_pipe_notify(*sock, NNG_PIPE_EV_ADD_POST, NULL, NULL)))
ERROR_OUT(xc);
if (NANO_INTEGER(remove) && (xc = nng_pipe_notify(*sock, NNG_PIPE_EV_REM_POST, NULL, NULL)))
ERROR_OUT(xc);
return nano_success;
} else if (NANO_PTR_CHECK(cv, nano_CvSymbol)) {
Rf_error("`cv` is not a valid Condition Variable");
}
sock = (nng_socket *) NANO_PTR(socket);
nano_cv *cvp = (nano_cv *) NANO_PTR(cv);
const int flg = nano_integer(flag);
cvp->flag = flg < 0 ? 1 : flg;
if (NANO_INTEGER(add) && (xc = nng_pipe_notify(*sock, NNG_PIPE_EV_ADD_POST, pipe_cb_signal, cvp)))
ERROR_OUT(xc);
if (NANO_INTEGER(remove) && (xc = nng_pipe_notify(*sock, NNG_PIPE_EV_REM_POST, pipe_cb_signal, cvp)))
ERROR_OUT(xc);
R_MakeWeakRef(socket, cv, R_NilValue, FALSE);
return nano_success;
}
// monitors --------------------------------------------------------------------
SEXP rnng_monitor_create(SEXP socket, SEXP cv) {
if (NANO_PTR_CHECK(socket, nano_SocketSymbol))
Rf_error("`socket` is not a valid Socket");
if (NANO_PTR_CHECK(cv, nano_CvSymbol))
Rf_error("`cv` is not a valid Condition Variable");
const int n = 8;
SEXP xptr;
int xc;
nano_monitor *monitor = calloc(1, sizeof(nano_monitor));
NANO_ENSURE_ALLOC(monitor);
monitor->ids = calloc(n, sizeof(int));
NANO_ENSURE_ALLOC(monitor->ids);
monitor->size = n;
monitor->cv = (nano_cv *) NANO_PTR(cv);
nng_socket *sock = (nng_socket *) NANO_PTR(socket);
if ((xc = nng_pipe_notify(*sock, NNG_PIPE_EV_ADD_POST, pipe_cb_monitor, monitor)))
goto failmem;
if ((xc = nng_pipe_notify(*sock, NNG_PIPE_EV_REM_POST, pipe_cb_monitor, monitor)))
goto failmem;
PROTECT(xptr = R_MakeExternalPtr(monitor, nano_MonitorSymbol, R_NilValue));
R_RegisterCFinalizerEx(xptr, monitor_finalizer, TRUE);
NANO_CLASS2(xptr, "nanoMonitor", "nano");
Rf_setAttrib(xptr, nano_SocketSymbol, Rf_ScalarInteger(nng_socket_id(*sock)));
UNPROTECT(1);
return xptr;
failmem:
if (monitor != NULL)
free(monitor->ids);
free(monitor);
ERROR_OUT(xc);
}
SEXP rnng_monitor_read(SEXP x) {
if (NANO_PTR_CHECK(x, nano_MonitorSymbol))
Rf_error("`x` is not a valid Monitor");
nano_monitor *monitor = (nano_monitor *) NANO_PTR(x);
nano_cv *ncv = monitor->cv;
nng_mtx *mtx = ncv->mtx;
SEXP out;
nng_mtx_lock(mtx);
const int updates = monitor->updates;
if (updates) {
out = Rf_allocVector(INTSXP, updates);
memcpy(NANO_DATAPTR(out), monitor->ids, updates * sizeof(int));
monitor->updates = 0;
}
nng_mtx_unlock(mtx);
if (!updates)
out = R_NilValue;
return out;
}
nanonext/NAMESPACE 0000644 0001762 0000144 00000005602 15176112266 013335 0 ustar ligges users # Generated by roxygen2: do not edit by hand
S3method("$",nano)
S3method("$",nanoStreamConn)
S3method("$",nanoWsConn)
S3method("$<-",nano)
S3method("$<-",nanoObject)
S3method("$<-",recvAio)
S3method("$<-",sendAio)
S3method("[",nano)
S3method("[",recvAio)
S3method("[",sendAio)
S3method("[[",nano)
S3method(close,nanoContext)
S3method(close,nanoDialer)
S3method(close,nanoListener)
S3method(close,nanoServer)
S3method(close,nanoSocket)
S3method(close,nanoStream)
S3method(close,ncurlSession)
S3method(print,conditionVariable)
S3method(print,errorValue)
S3method(print,nanoContext)
S3method(print,nanoDialer)
S3method(print,nanoListener)
S3method(print,nanoMonitor)
S3method(print,nanoObject)
S3method(print,nanoServer)
S3method(print,nanoSocket)
S3method(print,nanoStream)
S3method(print,nanoStreamConn)
S3method(print,nanoWsConn)
S3method(print,ncurlAio)
S3method(print,ncurlSession)
S3method(print,recvAio)
S3method(print,sendAio)
S3method(print,tlsConfig)
S3method(print,unresolvedValue)
S3method(promises::as.promise,ncurlAio)
S3method(promises::as.promise,recvAio)
S3method(promises::is.promising,ncurlAio)
S3method(promises::is.promising,recvAio)
S3method(stats::start,nanoDialer)
S3method(stats::start,nanoListener)
S3method(utils::.DollarNames,nano)
S3method(utils::.DollarNames,ncurlAio)
S3method(utils::.DollarNames,recvAio)
S3method(utils::.DollarNames,sendAio)
export("%~>%")
export("opt<-")
export(.advance)
export(.context)
export(.dispatcher)
export(.dispatcher_capacity)
export(.dispatcher_gate)
export(.dispatcher_info)
export(.dispatcher_start)
export(.dispatcher_stop)
export(.dispatcher_try_gate)
export(.dispatcher_wait)
export(.keep)
export(.mark)
export(.unresolved)
export(call_aio)
export(call_aio_)
export(collect_aio)
export(collect_aio_)
export(context)
export(cv)
export(cv_reset)
export(cv_signal)
export(cv_value)
export(dial)
export(format_sse)
export(handler)
export(handler_directory)
export(handler_file)
export(handler_inline)
export(handler_redirect)
export(handler_stream)
export(handler_ws)
export(http_server)
export(ip_addr)
export(is_aio)
export(is_error_value)
export(is_nano)
export(is_ncurl_session)
export(is_nul_byte)
export(listen)
export(mclock)
export(monitor)
export(msleep)
export(nano)
export(ncurl)
export(ncurl_aio)
export(ncurl_session)
export(nng_error)
export(nng_version)
export(opt)
export(parse_url)
export(pipe_id)
export(pipe_notify)
export(race_aio)
export(random)
export(read_monitor)
export(read_stdin)
export(reap)
export(recv)
export(recv_aio)
export(reply)
export(request)
export(send)
export(send_aio)
export(serial_config)
export(socket)
export(stat)
export(status_code)
export(stop_aio)
export(stop_request)
export(stream)
export(subscribe)
export(survey_time)
export(tls_config)
export(transact)
export(unresolved)
export(unsubscribe)
export(until)
export(until_)
export(wait)
export(wait_)
export(write_cert)
export(write_stdout)
useDynLib(nanonext, .registration = TRUE)
nanonext/LICENSE 0000644 0001762 0000144 00000000056 15077362607 013127 0 ustar ligges users YEAR: 2025
COPYRIGHT HOLDER: nanonext authors
nanonext/NEWS.md 0000644 0001762 0000144 00000140371 15176112764 013222 0 ustar ligges users # nanonext 1.9.0
#### Performance
* Serialized send operations now transfer the buffer directly into the NNG message, eliminating a redundant copy and halving peak memory usage (#219).
* Speeds up hex string conversion in `random(convert = TRUE)`.
#### Updates
* Fixes sending and receiving of messages larger than `INT_MAX` bytes over TCP and IPC transports on macOS and Windows (#266).
* Fixes unserialization of custom refhook payloads larger than `ULONG_MAX` on 64-bit Windows.
* Adds common MIME types to the HTTP server content map (SVG, JSON, MP4, WebP, WASM, and web fonts) (#275).
* Removes `messenger()` as a non-core function (#268).
* Removes the deprecated `n` argument from `recv()` and `recv_aio()`.
* Minimum required NNG version raised to 1.11 (bundled: 1.11.1-pre).
# nanonext 1.8.2
#### Updates
* Adds `$serve()` method for running an HTTP server created by `http_server()` in synchronous (blocking) mode.
* `stream()` gains a `buffer` argument for setting the receive buffer size for non-websocket byte streams.
* The `n` argument of `recv()` and `recv_aio()` is deprecated in favour of the new `buffer` argument of `stream()`.
# nanonext 1.8.1
#### Updates
* `handler_ws()` `on_open` callback now receives a second argument `req`, a list containing `uri` and `headers` from the HTTP upgrade request (this is technically a breaking change).
* `stream()` gains a `headers` argument for setting custom request headers on WebSocket connections.
# nanonext 1.8.0
#### New Features
* Adds `http_server()` for creating HTTP and WebSocket servers with TLS/SSL support.
* Adds `handler()` for dynamic HTTP request handling with custom callbacks.
* Adds `handler_ws()` for WebSocket connections with `on_message`, `on_open`, and `on_close` callbacks.
* Adds `handler_stream()` for HTTP streaming using chunked transfer encoding, supporting Server-Sent Events (SSE), NDJSON, and custom streaming formats.
* Adds `format_sse()` helper for formatting Server-Sent Events messages.
* Adds static content handlers: `handler_file()` and `handler_directory()` for serving files, `handler_inline()` for in-memory content, and `handler_redirect()` for HTTP redirects.
* `ncurl()` and variants now accept `response = TRUE` to return all response headers.
* Listeners now automatically resolve port `0` to the actual port assigned by the OS. The listener's `url` attribute is updated when the listener starts.
* Adds `race_aio()` to wait for and return the index of the first resolved Aio in a list of Aios.
#### Updates
* Closing an already closed stream now returns 'errorValue' 7 | Object closed rather than error.
* `random()` now only accepts 'n' between 1 and 1024. Supplying 0 will error (#238).
* Fixes a potential crash when `random()` or `write_cert()` is called in a fresh session before any other TLS-related functions have been called, and nanonext has been compiled against a system Mbed TLS with PSA crypto enabled. (#242).
* Fixes a potential crash when a serialization hook errors (#225).
* Performance improvements for serialization, streaming, and async sends.
* Bundled 'libmbedtls' updated to latest 3.6.5 LTS branch release (#234).
* Building from source no longer requires `xz`.
# nanonext 1.7.2
#### Updates
* `pipe_notify(flag = tools::SIGTERM)` will now raise the signal with a 200ms grace period to allow a process to exit normally (#212).
* `parse_url()` drops 'rawurl', 'host' and 'requri' from the output vector as these can be derived from the other parts (#209).
# nanonext 1.7.1
#### Updates
* `stop_aio()` now resets the R interrupt flags (#194).
* The `cv` arguments at `recv_aio()` and `request()` have been simplified to accept just a 'conditionVariable' or NULL.
# nanonext 1.7.0
#### Behavioural Changes
* The promises method for `ncurlAio` has been updated for ease of use (#176):
+ Returns a list of 'status', 'headers' and 'data'.
+ Rejects only if an NNG error is returned (all HTTP status codes are resolved).
* Improved behaviour when using `serial_config()` configurations applied to a socket.
If the serialization hook function errors or otherwise fails to return a raw vector, this will error out rather than be silently ignored (#173).
#### New Features
* A 'recvAio' returned by `request()` now has an attribute `id`, which is the integer ID of the context passed to it.
#### Updates
* `opt()` for a boolean NNG option now correctly returns a logical value instead of an integer (#186).
* `as.promise()` method for `recvAio` and `ncurlAio` objects made robust for high-throughput cases (#171).
# nanonext 1.6.2
#### Updates
* Fixes extremely rare cases of `unresolvedValue` being returned by fulfilled promises (#163).
# nanonext 1.6.1
#### Updates
* `ncurl()` and variants now accept request data supplied as a raw vector (thanks @thomasp85, #158).
* `cv_reset()` now correctly resets the flag in the case a flag event has already been registered with `pipe_notify()`.
* The previous `listen()` and `dial()` argument `error`, removed in v1.6.0, is now defunct.
* The previous `serial_config()` argument `vec`, unutilised since v1.6.0, is removed.
* Fixes package installation with 'libmbedtls' in a non-standard filesystem location, even if known to the compiler (thanks @tdhock, #150).
* Bundled 'libnng' updated to 1.11.0 release.
# nanonext 1.6.0
#### New Features
* `serial_config()` now accepts vector arguments to register multiple custom serialization configurations.
* Adds `ip_addr()` for returning the local network IPv4 address of all network adapters, named by interface.
* Adds `pipe_id()` for returning the integer pipe ID for a resolved 'recvAio'.
* Adds `write_stdout()` which performs a non-buffered write to `stdout`, to avoid interleaved messages when used concurrently by different processes.
* Adds `read_stdin()` which performs a read from `stdin` on a background thread, relayed via an 'inproc' socket so that it may be consumed via `recv()` or `recv_aio()`.
* `request()` gains integer argument `id`. This may be specified to have a special payload sent asynchronously upon timeout (to communicate with the connected party).
#### Updates
* `listen()` and `dial()` argument `error` is replaced with `fail` to specify the failure mode - 'warn', 'error', or 'none' to just return an 'errorValue'.
+ Any existing usage of `error = TRUE` will work only until the next release.
* Partial matching is no longer enabled for the `mode` argument to send/receive functions.
* `send_aio()` without keeping a reference to the return value no longer potentially drops sends (thanks @wch, #129).
* `pipe_notify()` no longer requires any particular sequencing of closing the socket and garbage collection of the socket / 'conditionVariable' (#143).
* More robust interruption on non-Windows platforms when `tools::SIGINT` is supplied to the `flag` argument of `pipe_notify()` (thanks @LennardLux, #97).
* Installation from source specifying 'INCLUDE_DIR' and 'LIB_DIR' environment variables works again, correcting a regression in v1.5.2 (#104).
* Windows bi-arch source builds for R <= 4.1 using rtools40 and earlier work again (regression since v1.5.1) (thanks @daroczig, #107).
* Bundled 'libnng' 1.10.2 pre-release updated with latest patches.
* Package is re-licensed under the MIT license.
# nanonext 1.5.2
#### Updates
* `write_cert()` argument 'cn' now defaults to '127.0.0.1' instead of 'localhost'.
* `messenger()` now exits cleanly, correcting a regression in nanonext 1.5.0 (#87).
* Promises created from 'recvAio' and 'ncurlAio' now reject in exactly the same way whether or not they were resolved at time of creation (#89).
* Bundled 'libnng' updated to 1.10.2 pre-release.
+ With this library version, a 'req' socket with option 'req:resend-time' set as 0 now frees the message as soon as the send has completed without waiting for the reply.
# nanonext 1.5.1
#### Updates
* `pipe_notify()` drops argument 'cv2' for signalling 2 condition variables on one pipe event. Use signal forwarders `%~>%` instead.
* The abillity to `lock()` and `unlock()` sockets is removed.
* Renders it safe to serialize 'nano' and 'aio' objects - they will be inactive when unserialized.
* Unified Windows build system now compiles 'libmbedtls' and 'libnng' from source even on R <= 4.1 using Rtools40 or earlier.
* Minimum supported 'libnng' version increased to 1.9.0.
# nanonext 1.5.0
#### Library Updates
* Bundled 'libnng' updated to latest 1.10.1 release.
* Bundled 'libmbedtls' updated to latest 3.6.2 LTS branch release.
#### Updates
* `nano()` updated with the 'poly' protocol, with 'pipe' argument enabled for the send methods.
* `write_cert()` no longer displays a status message when interactive (thanks @wlandau, #74).
* Removes partial matching when using `$`, `[[` or `[` on an object inheriting from class 'nano'.
* Fixes a rare hang on socket close that was possible on Windows platforms for IPC connections (#76).
# nanonext 1.4.0
#### New Features
* New interface to Pipes moves to using integer pipe IDs rather than Pipe (external pointer) objects:
+ `send()` and `send_aio()` gain the argument 'pipe' which accepts an integer pipe ID for directed sends (currently only supported by Sockets using the 'poly' protocol).
+ A 'recvAio' now records the integer pipe ID, where successful, at `$aio` upon resolution.
+ Pipe objects (of class 'nanoPipe') are obsoleted.
* Adds `monitor()` and `read_monitor()` for easy monitoring of connection changes (pipe additons and removals) at a Socket.
#### Updates
* `collect_pipe()` is removed given the pipe interface changes.
# nanonext 1.3.2
#### Updates
* Hotfix for CRAN (updates to tests only).
# nanonext 1.3.1
#### Updates
* Performs interruptible 'aio' waits using a single dedicated thread, rather than launching new threads, for higher performance and efficiency.
* Performance enhancements for 'ncurlAio' and 'recvAio' promises methods.
* Updates bundled 'libnng' to v1.9.0 stable release.
* The package has a shiny new hex logo.
# nanonext 1.3.0
#### New Features
* Adds support for threaded dispatcher in `mirai`.
* Adds 'recvAio' method for `promises::as.promise()` and `promises::is.promising()` to enable 'recvAio' promises.
#### Updates
* `serial_config()` now validates all arguments and returns them as a list. Full validation is also performed when the option is set for additional safety.
* Warning messages for unserialization or conversion failures of received data are now suppressable.
* Upgrades `reply()` to always return even when there is an evaluation error. This allows it to be used safely in a loop without exiting early, for example.
* Removes deprecated and defunct `next_config()`.
* Internal performance enhancements.
* Updates bundled 'libnng' v1.8.0 with latest patches.
# nanonext 1.2.1
#### Updates
* Re-optimizes custom serialization (whilst addressing CRAN clang-UBSAN checks).
# nanonext 1.2.0
#### New Features
* Adds `serial_config()` to create configurations that can be set on Sockets to make use of custom serialization and unserialization functions for reference objects (plugs into the 'refhook' system of native R serialization).
* `'opt<-'()` now accepts the special option 'serial' for Sockets, which takes a configuration returned from `serial_config()`.
* Adds the 'poly' protocol for one-to-one of many socket connections (NNG's pair v1 polyamorous mode).
* Adds `is_ncurl_session()` as a validation function.
* Adds `collect_pipe()` for obtaining the underlying Pipe from a 'recvAio'. This affords more granular control of connections, with the ability to close individual pipes.
* `send_aio()` now accept a Pipe to direct messages to a specific peer for supported protocols such as 'poly'.
#### Updates
* Send mode 'next' is folded into the default 'serial', with custom serialization functions applying automatically if they have been registered.
* The session-wide `next_config()` is now deprecated and defunct, in favour of the new `serial_config()`.
* `ncurl_session()` now returns 'errorValue' 7 (Object closed) when attempting to transact over a closed session or closing a closed session, rather than throwing an error.
* `collect_aio()` and `collect_aio_()` no longer append empty names when acting on lists of Aios where there were none in the first place.
* Removes hard dependency on `stats` and `utils` base packages.
* Requires R >= 3.6.
# nanonext 1.1.1
#### New Features
* Adds 'ncurlAio' method for `promises::as.promise()` and `promises::is.promising()` to enable 'ncurlAio' promises.
* Adds `x[]` as a new method for an Aio `x` equivalent to `collect_aio_(x)`, which waits for and collects the data.
#### Updates
* `request()` specifying argument 'cv' other than NULL or a 'conditionVariable' will cause the pipe connection to be dropped when the reply is (asynchronously) completed.
* Removes deprecated functions `strcat()`, `recv_aio_signal()` and `request_signal()`.
* Drops `base64enc()` and `base64dec()` in favour of those from the {secretbase} package.
* `msleep()` now ignores negative values rather than taking the absolute value.
* `later` is now relaxed to a soft 'suggests' dependency (only required if using promises).
* `promises` is added as a soft 'enhances' dependency.
# nanonext 1.1.0
#### New Features
* Adds `collect_aio()` and `collect_aio_()` to wait for and collect the data of an Aio or list of Aios.
* `unresolved()`, `call_aio()`, `call_aio()_` and `stop_aio()` now all accept a list of Aios.
* `pipe_notify()` gains the ability to specify 'cv' as NULL to cancel previously-set signals.
* `ncurl_aio()` modified internally to support conversion of 'ncurlAio' to event-driven promises.
#### Updates
* `recv_aio()` and `request()` add argument 'cv' allowing optional signalling of a condition variable. The separate functions `recv_aio_signal()` and `request_signal()` are deprecated.
* `strcat()` is deprecated as considered non-core - it is recommended to replace usage with `sprintf()`.
* `status_code()` now returns the status code combined with the explanation as a character string.
* Performance enhancements for `unresolved()`, `call_aio()` and `call_aio_()`.
* Updates bundled 'libnng' v1.8.0 with latest patches.
# nanonext 1.0.0
#### New Features
* Integrates with the `later` package to provide the foundation for truly event-driven (non-polling) promises (thanks @jcheng5 for the initial prototype in #28), where side-effects are enacted asynchronously upon aio completion.
+ `request()` and `request_signal()` modified internally to support conversion of 'recvAio' to event-driven promises.
+ `later` dependency ensures asynchronous R code is always run on the main R thread.
+ `later` is lazily loaded the first time a promise is used, and hence does not impact the load time of `nanonext` or dependent packages.
#### Updates
* `stop_aio()` now causes the 'aio' to resolve to an 'errorValue' of 20 (Operation canceled) if successfully stopped.
* `nng_error()` now returns the error code combined with the message as a character string.
* Integer file descriptors are no longer appended to 'nanoSocket' attributes.
* Adds 'xz' to SystemRequirements (as was the case previously but not explicitly specified) (thanks @gaborcsardi).
* Re-aligns bundled 'libmbedtls' to v3.5.2 and optimises bundle size.
* Updates minimum 'libnng' version requirement to v1.6.0.
* Upgrades bundled 'libnng' to v1.8.0.
# nanonext 0.13.5.2
#### Updates
* Safer and more efficient memory reads for 'next' serialization corrects for CRAN UBSAN-clang check errors.
# nanonext 0.13.5.1
#### New Features
* `next_config()` gains argument 'class' and 'vec', enabling custom serialization for all reference object types supported by R serialization.
* An integer file descriptor is appended to 'nanoSockets' as the attribute 'fd' - see updated documentation for `socket()`.
#### Updates
* Removes SHA-2 cryptographic hash functions (please use the streaming implementation in the secretbase package).
# nanonext 0.13.2
#### Updates
* Fixes cases of 'built for newer macOS version than being linked' installation warnings on MacOS.
* Upgrades bundled 'libnng' to v1.7.2.
# nanonext 0.13.0
#### Updates
*Please note the following potentially breaking changes, and only update when ready:*
* Default behaviour of `send()` and `recv()` aligned to non-blocking for both Sockets and Contexts (facilitated by synchronous context sends in NNG since v1.6.0).
* `ncurl()`, `ncurl_aio()` and `ncurl_session()` now restrict 'header' and 'response' arguments to character vectors only, no longer accepting lists (for safety and performance).
* Unserialization / decoding errors where the received message cannot be translated to the specified mode will output a message to stderr, but no longer generate a warning.
* SHA functions now skip serialization headers for serialized R objects (ensuring portability as these contain R version and encoding information). This means that, for serialized objects, hashes will be different to those obtained using prior package versions.
* `sha1()` is removed as a hash option.
*Other changes:*
* `messenger()` specifying 'auth' now works reliably on endpoints using different R versions/platforms due to the above hashing portability fix.
* Internal memory-efficiency and performance enhancements.
* Upgrades bundled 'libmbedtls' to v3.5.2.
# nanonext 0.12.0
*This is a major performance and stability release bundling the 'libnng' v1.7.0 source code.*
#### New Features
* `pipe_notify()` argument 'flag' allows supplying a signal to be raised on a flag being set upon a pipe event.
#### Updates
* More compact print methods for 'recvAio', 'sendAio', 'ncurlAio', 'ncurlSession' and 'tlsConfig' objects.
* `random()` now explicitly limits argument 'n' to values between 0 and 1024.
* `next_config()` now returns a pairlist (of the registered serialization functions) rather than a list (for efficiency).
* Using mode 'next', serialization functions with incorrect signatures are now simply ignored rather than raise errors.
* 'nanoStream' objects simplified internally with updated attributes 'mode' and 'state'.
* Deprecated function `.until()` is removed.
* Eliminates potential memory leaks along certain error paths.
* Fixes bug which prevented much higher TLS performance when using the bundled 'libnng' source.
* Upgrades bundled 'libnng' to v1.7.0 release.
# nanonext 0.11.0
*This is a major stability release bundling the 'libnng' v1.6.0 source code.*
#### New Features
* Introduces `call_aio_()`, a user-interruptible version of `call_aio()` suitable for interactive use.
* Introduces `wait_()` and `until_()` user-interruptible versions of `wait()` and `until()` suitable for interactive use.
* Implements `%~>%` signal forwarder from one 'conditionVariable' to another.
#### Updates
* `next_config()` replaces `nextmode()` with the following improvements:
+ simplified 'refhook' argument takes a pair of serialization and unserialization functions as a list.
+ registered 'refhook' functions apply to external pointer type objects only.
+ no longer returns invisibly for easier confimation that the correct functions have been registered.
* `until()` updated to be identical to `.until()`, returning FALSE instead of TRUE if the timeout has been reached.
* `reap()` updated to no longer warn in cases it returns an 'errorValue'.
* `pipe_notify()` arguments 'add', 'remove' and 'flag' now default to FALSE instead of TRUE for easier selective specification of the events to signal.
* Fixes regression in release 0.10.4 that caused a potential segfault using `ncurl()` with 'follow' set to TRUE when the server returns a missing or invalid relocation address.
* The weak references interface is removed as 'non-core'.
* Upgrades bundled 'libnng' to v1.6.0 release.
* Upgrades bundled 'libmbedtls' to v3.5.1.
# nanonext 0.10.4
#### New Features
* `nextmode()` configures settings for send mode 'next'. Registers hook functions for custom serialization and unserialization of reference objects (such as those accessed via an external pointer).
* `.until()` contains revised behaviour for this synchronisation primitive, returning FALSE instead of TRUE if the timeout has been reached. This function will replace `until()` in a future package version.
#### Updates
* `lock()` supplying 'cv' has improved behaviour which locks the socket whilst allowing for both initial connections and re-connections (when the 'cv' is registered for both add and remove pipe events).
* Improves listener / dialer logic for TLS connections, allowing *inter alia* synchronous dials.
* `request()` argument 'ack' removed due to stability considerations.
* Fixes memory leaks detected with valgrind.
* Upgrades bundled 'libmbedtls' to v3.5.0.
# nanonext 0.10.2
#### Updates
* Addresses one case of memory access error identified by CRAN.
# nanonext 0.10.1
#### New Features
* `request()` adds logical argument 'ack', which sends an ack(nowledgement) back to the rep node upon a successful async message receive.
* `reap()` implemented as a faster alternative to `close()` for Sockets, Contexts, Listeners and Dialers - avoiding S3 method dispatch, hence works for unclassed external pointers created by `.context()`.
* `random()` updated to use the Mbed TLS library to generate random bytes. Adds a 'convert' argument for specifying whether to return a raw vector or character string.
* Adds 'next' as a mode for send functions, as a 100% compatible R serialisation format (may be received using mode 'serial').
#### Updates
* `write_cert()` has been optimised for higher efficiency and faster operation.
* `send()` and `recv()` over contexts now use more efficient synchronous methods where available.
* Fixes package installation failures where the R default C compiler command contains additional flags (thanks @potash #16).
* Performance improvements due to simplification of the internal structure of 'aio' objects.
* Rolls forward bundled 'libnng' to v1.6.0 alpha (a54820f).
# nanonext 0.10.0
#### New Features
* `ncurl_aio()` has been separated into a dedicated function for async http requests.
* Receive functions add `mode = 'string'` as a faster alternative to 'character' when receiving a scalar value.
#### Updates
*Please review the following potentially breaking changes, and only update when ready:*
* `ncurl()` argument 'async' is retired. Please use `ncurl_aio()` for asynchronous requests.
* `ncurl()` now always returns the response message body at `$data` whether convert is TRUE or FALSE.
* The argument 'keep.raw' for all receive functions (previously-deprecated) is removed.
* `cv_reset()` and `cv_signal()` now both return invisible zero rather than NULL.
* Function `device()` is removed partially due to its non-interruptible blocking behaviour.
*Other changes:*
* Improvements to recv (mode = 'serial') and `ncurl()`:
+ Failure to unserialize, or convert to character, automatically saves the data as a raw vector for recovery, generating a warning instead of an error (as was the case prior to v0.9.2).
* Improvements to vector send/recv (mode = 'raw'):
+ Higher performance sending of vector data.
+ Permits sending of NULL, in which case an empty vector of the corresponding mode is received.
+ Character vectors containing empty characters in the middle are now received correctly.
+ For character vectors, respects original encoding and no longer performs automatic conversion to UTF8.
* Base64 and SHA hash functions now always use big-endian representation for serialization (where this is performed) to ensure consistency across all systems (fixes #14, a regression in nanonext 0.9.2).
* Package installation now succeeds in certain environments where 'cmake' failed to make 'libmbedtls' detectable after building (thanks @kendonB #13).
* Source bundles for 'libmbedtls' and 'libnng' slimmed down for smaller package and installed sizes.
* Configures bundled 'libmbedtls' v3.4.0 for higher performance.
* Supported 'libmbedtls' version increased to >= 2.5.
# nanonext 0.9.2
*This version contains performance enhancements which have resulted in potentially breaking changes; please review carefully and only update when ready.*
#### New Features
* `base64dec()` argument 'convert' now accepts NA as an input, which unserializes back to the original object.
#### Updates
* The argument 'keep.raw' for all receive functions is deprecated. This is as raw vectors are no longer created as part of unserialisation or data conversion.
* Higher performance send and receive of serialized R objects.
+ For receive functions, attempting to unserialise a non-serialised message will now error with 'unknown input format' rather than fall back to a raw message vector.
* `ncurl()` etc. gain higher performance raw to character conversion, resulting in the following changes:
+ Attempting to convert non-text data with embedded nuls will now error instead of silently returning NULL.
+ For efficiency, when 'convert' = TRUE, a raw vector is no longer stored at `$raw`.
* Higher performance cryptographic hash and base64 conversion functions.
+ Attributes are now taken into account for scalar strings and raw vectors to ensure unique hashes.
* Experimental threaded function `timed_signal()` removed.
* Requires R >= 3.5 to ensure R serialization version 3.
# nanonext 0.9.1
#### New Features
* Enables secure TLS transports `tls+tcp://` and `wss://` for scalability protocols.
+ `listen()` and `dial()` gain the argument 'tls' for supplying a TLS configuration object
+ `write_cert()` generates 4096 bit RSA keys and self-signed X.509 certificates for use with `tls_config()`.
* `weakref()`, `weakref_key()` and `weakref_value()` implement an interface to R's weak reference system. These may be used for synchronising the lifetimes of objects with reference objects such as Sockets or Aios, or creating read-only objects accessible by the weakref value alone.
* `strcat()` provides a simple, fast utility to concatenate two strings.
#### Updates
* `tls_config()` now accepts a relative path if filenames are supplied for the 'client' or 'server' arguments.
* 'tlsConfig' objects no longer have a 'source' attribute.
* Fix cases where `base64enc()` failed for objects exceeding a certain size.
* `stream()` has been updated internally for additional robustness.
* Updates bundled 'libmbedtls' v3.4.0 source configuration for threading support.
* Updates bundled 'libnng' to v1.6.0 alpha (c5e9d8a) again, having resolved previous issues.
# nanonext 0.9.0
*The package is now compatible (again) with currently released 'libnng' versions. It will attempt to use system 'libnng' versions >= 1.5 where detected, and only compile the bundled library where necessary.*
#### New Features
* Implements `tls_config()` to create re-usable TLS configurations from certificate / key files (or provided directly as text).
#### Updates
* 'pem' argument of `ncurl()`, `ncurl_session()` and `stream()` retired in favour of 'tls' which takes a TLS Configuration object created by `tls_config()` rather than a PEM certificate directly.
* Removes `nanonext_version()` in favour of the existing `nng_version()`, along with `utils::packageVersion()` if required, for greater flexibility.
* Removes `...` argument for `context()` - retained for compatibility with the 'verify' argument, which was removed in the previous release.
* Package widens compatibility to support system 'libnng' versions >= 1.5.0.
* Bundled 'libnng' source rolled back to v1.6.0 pre-release (8e1836f) for stability.
# nanonext 0.8.3
#### New Features
* Implements `cv_signal()` and `timed_signal()` for signalling a condition variable, the latter after a specified time (from a newly-created thread).
* Implements `.context()`, a performance alternative to `context()` that does not create the full object.
* Adds utility `nanonext_version()` for providing the package version, NNG and mbed TLS library versions in a single string.
* `ncurl()` gains a 'timeout' argument.
#### Updates
* Removes 'verify' argument of `context()` (changed to '...' for compatibility) as `request()` and `request_signal()` have been rendered safe internally for use with timeouts.
* The name of the single argument to `msleep()` has been changed to 'time' from 'msec'.
* Functions `pipe_notify()`, `lock()` and `unlock()` now error if unsuccessful rather than returning with a warning.
* For compiling bundled 'libmbedtls' and 'libnng' libraries from source, R's configured C compiler is now chosen over the system default where this is different.
* Bundled 'libnng' source updated to v1.6.0 alpha (c5e9d8a).
* Bundled 'libmbedtls' source updated to v3.4.0.
# nanonext 0.8.2
#### New Features
* `lock()` and `unlock()` implemented to prevent further pipe connections from being established at a socket, optionally tied to the value of a condition variable.
#### Updates
* `context()` gains the argument 'verify' with a default of TRUE. This adds additional protection to notably the `request()` and `request_signal()` functions when using timeouts, as these require a connection to be present.
* Sending and hashing of language objects and symbols is now possible after fixes to serialisation.
* `until()` now works as intended.
* Removes recently-introduced `msg_pipe()` and `'weakref<-'()` to maintain simplicity of user interface.
* Internal performance enhancements.
# nanonext 0.8.1
#### New Features
* Implements synchronisation primitives from the NNG library. Condition variables allow the R execution thread to wait until it is signalled by an incoming message or pipe event.
+ adds core functions `cv()`, `wait()`, `until()`, `cv_value()`, and `cv_reset()`.
+ adds signalling receive functions `recv_aio_signal()` and `request_signal()`.
+ `pipe_notify()` signals up to 2 condition variables whenever pipes are added or removed at a socket.
* Adds `msg_pipe()` to return the pipe connection associated with a 'recvAio' message.
* Exposes the `sha1()` cryptographic hash and HMAC generation function from the 'Mbed TLS' library (for secure applications, use one of the SHA-2 algorithms instead).
* Utility function `'weakref<-'()` exposes `R_MakeWeakRef` from R's C API. Useful for keeping objects alive for as long as required by a dependent object.
#### Updates
* `ncurl_session()` gains a 'timeout' argument, and returns an 'errorValue' with warning upon error.
* `listen()` and `dial()` gain the new logical argument 'error' to govern the function behaviour upon error.
* Internal performance enhancements.
# nanonext 0.8.0
#### New Features
* Implements `stat()`, an interface to the NNG statistics framework. Can be used to return the number of currently connected pipes for a socket, connection attempts for a listener/dialer etc.
* Implements `parse_url()`, which parses a URL as per NNG. Provides a fast and standardised method for obtaining parts of a URL string.
#### Updates
*Please review the following potentially breaking changes, and only update when ready:*
* Using `socket()` specifying either 'dial' or 'listen', a failure to either dial or listen (due to an invalid URL for example) will now error rather than return a socket with a warning. This is safer behaviour that should make it easier to detect bugs in user code.
* `opt()` and `'opt<-'()` have been implemented as more ergonomic options getter and setter functions to replace `getopt()` and `setopt()`. These will error if the option does not exist / input value is invalid etc.
* `subscribe()`, `unsubscribe()` and `survey_time()` now return the Socket or Context invisibly rather than an exit code, and will error upon invalid input etc.
* `survey_time()` argument name is now 'value', with a default of 1000L.
* nano Object methods `$opt`, `$listener_opt`, and `$dialer_opt` re-implemented to either get or set values depending on whether the 'value' parameter has been supplied.
*Other changes:*
* Bundled 'libnng' source updated to v1.6.0 pre-release (8e1836f).
* Supported R version amended to >= 2.12, when person() adopted the current format used for package description.
* Internal performance enhancements.
# nanonext 0.7.3
#### New Features
* Implements `ncurl_session()` and `transact()` providing high-performance, re-usable http(s) connections.
#### Updates
* For dialers, the 'autostart' argument to `dial()`, `socket()` and `nano()` now accepts NA for starting the dialer synchronously - this is less resilient if a connection is not immediately possible, but avoids subtle errors from attempting to use the socket before an asynchronous dial has completed.
* Closing a stream now renders it inactive safely, without the need to strip all attributes on the object (as was the case previously).
* `messenger()` is faster to connect and exits gracefully in case of a connection error.
* Removes defunct function `nano_init()`.
* Bundled 'libnng' source updated to v1.6.0 pre-release (539e559).
* Fixes CRAN 'additional issue' (clang-UBSAN).
# nanonext 0.7.2
#### Updates
* For raw to character hash conversion, uses snprintf instead of sprintf for CRAN compliance.
# nanonext 0.7.1
#### New Features
* Implements `getopt()`, the counterpart to `setopt()` for retrieving the value of options on objects.
#### Updates
* The `setopt()` interface is simplified, with the type now inferred from the value supplied.
* `ncurl()` now returns redirect addresses as the response header 'Location'. This is so that HTTP data can also be returned at `$data` where this is provided.
* Eliminates CRAN 'additional issue' (clang/gcc-UBSAN).
* Internal performance optimisations.
# nanonext 0.7.0
#### New Features
* `status_code()` utility returns a translation of HTTP response status codes.
#### Updates
*Please review the following potentially breaking changes, and only update when ready:*
* The API has been re-engineered to ensure stability of return types:
+ `socket()`, `context()` and `stream()` will now error rather than return an 'errorValue'. The error value is included in the error message.
+ `send_aio()` and `recv_aio()` now always return an integer 'errorValue' at `$result` and `$data` respectively.
+ `recv()` and `recv_aio()` now return an integer 'errorValue' at each of `$raw` and `$data` when 'keep.raw' is set to TRUE.
+ `ncurl()` now returns an integer 'errorValue' at each of `$status`, `$headers`, `$raw` and `$data` for both sync and async. Where redirects are not followed, the address is now returned as a character string at `$data`.
* For functions that send and receive messages i.e. `send()`, `send_aio()`, `recv()`, `recv_aio()` and `ncurl()`, 'errorValues' are now returned silently without an accompanying warning. Use `is_error_value()` to explicitly check for errors.
* `nano_init()` is deprecated due to the above change in behaviour.
* `send()` no longer has a '...' argument. This has had no effect since 0.6.0, but will now error if additional arguments are provided (please check and remove previous uses of the argument 'echo'). Also no longer returns invisibly for consistency with `recv()`.
* `listen()` and `dial()` now only take a socket as argument; for nano objects, the `$listen()` and `$dial()` methods must be used instead.
* `nano()` now creates a nano object with method `$context_open()` for applicable protocols. Opening a context will attach a context at `$context` and a `$context_close()` method. When a context is active, all object methods apply to the context instead of the socket. Method `$socket_setopt()` renamed to `$setopt()` as it can be used on the socket or active context as applicable.
* Non-logical values supplied to logical arguments will now error: this is documented for each function where this is applicable.
*Other changes:*
* Integer `send()`/`recv()` arguments for 'mode' implemented in 0.5.3 are now documented and considered part of the API. This is a performance feature that skips matching the character argument value.
* Fixes bug introduced in 0.6.0 where Aios returning 'errorValues' are not cached with the class, returning only integer values when accessed subsequently.
* Fixes potential crash when `base64dec()` encounters invalid input data. Error messages have been revised to be more accurate.
* Fixes the `$` method for 'recvAio' objects for when the object has been stopped using `stop_aio()`.
* Using the `$listen()` or `$dial()` methods of a nano object specifying 'autostart = FALSE' now attaches the `$listener_start()` or `$dialer_start()` method for the most recently added listener/dialer.
* `device()` no longer prompts for confirmation in interactive environments - as device creation is only successful when binding 2 raw mode sockets, there is little scope for accidental use.
* Print method for 'errorValue' now also provides the human translation of the error code.
* Bundled 'libnng' source updated to v1.6.0 pre-release (5385b78).
* Internal performance enhancements.
# nanonext 0.6.0
#### New Features
* Implements `base64enc()` and `base64dec()` base64 encoding and decoding using the 'Mbed TLS' library.
* `sha224()`, `sha256()`, `sha384()` and `sha512()` functions gain an argument 'convert' to control whether to return a raw vector or character string.
* `ncurl()` gains the argument 'follow' (default FALSE) to control whether redirects are automatically followed.
#### Updates
*Please review the following potentially breaking changes, and only update when ready:*
* `send()` now returns an integer exit code in all cases. The 'echo' argument has been replaced by '...', and specifying 'echo' no longer has any effect.
* `recv()`, `recv_aio()` and `request()` now default to 'keep.raw' = FALSE to return only the sent object.
* `ncurl()` argument 'request' renamed to 'response' for specifying response headers to return (to avoid confusion); new argument 'follow' (placed between 'convert' and 'method') controls whether redirects are followed, and there is no longer a user prompt in interactive environments.
* `sha224()`, `sha256()`, `sha384()` and `sha512()` functions no longer return 'nanoHash' objects, but a raw vector or character string depending on the new argument 'convert'.
*Other changes:*
* `socket()` and `nano()` now accept non-missing NULL 'listen' and 'dial' arguments, allowing easier programmatic use.
* Functions `send()`, `recv()`, `send_aio()`, `recv_aio()`, `setopt()`, `subscribe()`, `unsubscribe()` and `survey_time()` are no longer S3 generics for enhanced performance.
* `messenger()` uses longer SHA-512 hash for authentication; fixes errors creating a connnection not being shown.
* The source code of 'libnng' v1.6.0 pre-release (722bf46) and 'libmbedtls' v3.2.1 now comes bundled rather than downloaded - this is much more efficient as unused portions have been stripped out.
* Detects and uses system installations of 'libnng' >= 1.6.0 pre-release 722bf46 and 'libmbedtls' >= 2 where available, only compiling from source when required.
* R >= 4.2 on Windows now performs source compilation of the bundled 'libnng' and 'libmbedtls' using the rtools42 toolchain. Installation falls back to pre-compiled libraries for older R releases.
* Supported R version amended to >= 2.5, when the current `new.env()` interface was implemented.
* Internal performance enhancements.
# nanonext 0.5.5
#### Updates
* Installation succeeds under Linux where library path uses 'lib64' instead of 'lib', and fails gracefully if 'cmake' is not found.
# nanonext 0.5.4
#### New Features
* Implements `sha224()`, `sha256()`, `sha384()` and `sha512()` series of fast, optimised cryptographic hash and HMAC generation functions using the 'Mbed TLS' library.
* `ncurl()` and `stream()` gain the argmument 'pem' for optionally specifying a certificate authority certificate chain PEM file for authenticating secure sites.
* `ncurl()` gains the argument 'request' for specifying response headers to return.
* `ncurl()` now returns additional `$status` (response status code) and `$headers` (response headers) fields.
* `messenger()` gains the argument 'auth' for authenticating communications based on a pre-shared key.
* `random()` gains the argument 'n' for generating a vector of random numbers.
#### Updates
* 'libmbedtls' is now built from source upon install so the package always has TLS support and uses the latest v3.2.1 release. Windows binaries also updated to include TLS support.
* `nng_version()` now returns the 'Mbed TLS' library version number.
* `device()` gains a confirmation prompt when running interactively for more safety.
* Fixes issue with `ncurl()` that caused a 26 cryptography error with certain secure sites using SNI.
# nanonext 0.5.3
#### Updates
* Configure script provides more information by default.
* Allows integer send/recv 'mode' arguments (note: this is an undocumented performance feature with no future guarantees).
* Aio 'timeout' arguments now default to NULL for applying the socket default, although non-breaking as -2L will also work.
* `msleep()` made safe (does not block) in case of non-numeric input.
* Internal performance optimisations.
# nanonext 0.5.2
#### New Features
* Adds `mclock()`, `msleep()` and `random()` utilities exposing the library functions for timing and cryptographic RNG respectively.
* `socket()` gains the ability to open 'raw' mode sockets. Please note: this is not for general use - do not set this argument unless you have a specific need, such as for use with `device()` (refer to NNG documentation).
* Implements `device()` which creates a socket forwarder or proxy. Warning: use this in a separate process as this function blocks with no ability to interrupt.
#### Updates
* Internal performance optimisations.
# nanonext 0.5.1
#### Updates
* Upgrades NNG library to 1.6.0 pre-release (locked to version 722bf46). This version incorporates a feature simplifying the aio implementation in nanonext.
* Configure script updated to always download and build 'libnng' from source (except on Windows where pre-built libraries are downloaded). The script still attempts to detect a system 'libmbedtls' library to link against.
* Environment variable 'NANONEXT_SYS' introduced to permit use of a system 'libnng' install in `/usr/local`. Note that this is a manual setting allowing for custom NNG builds, and requires a version of NNG at least as recent as 722bf46.
* Fixes a bug involving the `unresolvedValue` returned by Aios (thanks @lionel- #3).
# nanonext 0.5.0
#### New Features
* `$context()` method added for creating new contexts from nano Objects using supported protocols (i.e. req, rep, sub, surveyor, respondent) - this replaces the `context()` function for nano Objects.
* `subscribe()` and `unsubscribe()` now accept a topic of any atomic type (not just character), allowing pub/sub to be used with integer, double, logical, complex, or raw vectors.
* Sending via the "pub" protocol, the topic no longer needs to be separated from the rest of the message, allowing character scalars to be sent as well as vectors.
* Added convenience auxiliary functions `is_nano()` and `is_aio()`.
#### Updates
* Protocol-specific helpers `subscribe()`, `unsubscribe()`, and `survey_time()` gain nanoContext methods.
* Default protocol is now 'bus' when opening a new Socket or nano Object - the choices are ordered more logically.
* Closing a stream now strips all attributes on the object rendering it a nil external pointer - this is for safety, eliminating a potential crash if attempting to re-use a closed stream.
* For receives, if an error occurs in unserialisation or data conversion (e.g. mode was incorrectly specified), the received raw vector is now available at both `$raw` and `$data` if `keep.raw = TRUE`.
* Setting 'NANONEXT_TLS=1' now allows the downloaded NNG library to be built against a system mbedtls installation.
* Setting 'NANONEXT_ARM' is no longer required on platforms such as Raspberry Pi - the package configure script now detects platforms requiring the libatomic linker flag automatically.
* Deprecated `send_ctx()`, `recv_ctx()` and logging removed.
* All-round internal performance optimisations.
# nanonext 0.4.0
#### New Features
* New `stream()` interface exposes low-level byte stream functionality in the NNG library, intended for communicating with non-NNG endpoints, including but not limited to websocket servers.
* `ncurl()` adds an 'async' option to perform HTTP requests asynchronously, returning immediately with a 'recvAio'. Adds explicit arguments for HTTP method, headers (which takes a named list or character vector) and request data, as well as to specify if conversion from raw bytes is required.
* New `messenger()` function implements a multi-threaded console-based messaging system using NNG's scalability protocols (currently as proof of concept).
* New `nano_init()` function intended to be called immediately after package load to set global options.
#### Updates
* Behavioural change: messages have been upgraded to warnings across the package to allow for enhanced reporting of the originating call e.g. via `warnings()` and flexibility in handling via setting `options()`.
* Returned NNG error codes are now all classed as 'errorValue' across the package.
* Unified `send()` and `recv()` functions, and their asynchronous counterparts `send_aio()` and `recv_aio()`, are now S3 generics and can be used across Sockets, Contexts and Streams.
* Revised 'block' argument for `send()` and `recv()` now allows an integer value for setting a timeout.
* `send_ctx()` and `recv_ctx()` are deprecated and will be removed in a future package version - the methods for `send()` and `recv()` should be used instead.
* To allow for more flexible practices, logging is now deprecated and will be removed entirely in the next package version. Logging can still be enabled via 'NANONEXT_LOG' prior to package load but `logging()` is now defunct.
* Internal performance optimisations.
# nanonext 0.3.0
#### New Features
* Aio values `$result`, `$data` and `$raw` now resolve automatically without requiring `call_aio()`. Access directly and an 'unresolved' logical NA value will be returned if the Aio operation is yet to complete.
* `unresolved()` added as an auxiliary function to query whether an Aio is unresolved, for use in control flow statements.
* Integer error values generated by receive functions are now classed 'errorValue'. `is_error_value()` helper function included.
* `is_nul_byte()` added as a helper function for request/reply setups.
* `survey_time()` added as a convenience function for surveyor/respondent patterns.
* `logging()` function to specify a global package logging level - 'error' or 'info'. Automatically checks the environment variable 'NANONEXT_LOG' on package load and then each time `logging(level = "check")` is called.
* `ncurl()` adds a '...' argument. Support for HTTP methods other than GET.
#### Updates
* `listen()` and `dial()` now return (invisible) zero rather than NULL upon success for consistency with other functions.
* Options documentation entry renamed to `opts` to avoid clash with base R 'options'.
* Common format for NNG errors and informational events now starts with a timestamp for easier logging.
* Allows setting the environment variable 'NANONEXT_ARM' prior to package installation
+ Fixes installation issues on certain ARM architectures
* Package installation using a system 'libnng' now automatically detects 'libmbedtls', removing the need to manually set 'NANONEXT_TLS' in this case.
* More streamlined NNG build process eliminating unused options.
* Removes experimental `nng_timer()` utility as a non-essential function.
* Deprecated functions 'send_vec' and 'recv_vec' removed.
# nanonext 0.2.0
#### New Features
* Implements full async I/O capabilities
+ `send_aio()` and `recv_aio()` now return Aio objects, for which the results may be called using `call_aio()`.
* New `request()` and `reply()` functions implement the full logic of an RPC client/server, allowing processes to run concurrently on the client and server.
+ Designed to be run in separate processes, the reply server will await data and apply a function before returning a result.
+ The request client performs an async request to the server and returns immediately with an Aio.
* New `ncurl()` minimalistic http(s) client.
* New `nng_timer()` utility as a demonstration of NNG's multithreading capabilities.
* Allows setting the environment variable 'NANONEXT_TLS' prior to package installation
+ Enables TLS where the system NNG library has been built with TLS support (using Mbed TLS).
#### Updates
* Dialer/listener starts and close operations no longer print a message to stderr when successful for less verbosity by default.
* All send and receive functions, e.g. `send()`/`recv()`, gain a revised 'mode' argument.
+ This now permits R serialization as an option, consolidating the functionality of the '_vec' series of functions.
* Functions 'send_vec' and 'recv_vec' are deprecated and will be removed in a future release.
* Functions 'ctx_send' and 'ctx_recv' have been renamed `send_ctx()` and `recv_ctx()` for consistency.
* The `$socket_close()` method of nano objects has been renamed `$close()` to better align with the functional API.
# nanonext 0.1.0
* Initial release to CRAN, rOpenSci R-universe and Github.
nanonext/inst/ 0000755 0001762 0000144 00000000000 15176113105 013061 5 ustar ligges users nanonext/inst/doc/ 0000755 0001762 0000144 00000000000 15176113105 013626 5 ustar ligges users nanonext/inst/doc/nanonext.Rmd 0000644 0001762 0000144 00000022407 15142221674 016135 0 ustar ligges users ---
title: "nanonext - Quick Reference"
vignette: >
%\VignetteIndexEntry{nanonext - Quick Reference}
%\VignetteEngine{litedown::vignette}
%\VignetteEncoding{UTF-8}
---
## Core Concepts
**nanonext** provides bindings to NNG (Nanomsg Next Gen), a high-performance messaging library for building distributed systems.
This is a cheatsheet. Refer to the other vignettes for detailed introductions:
- [Messaging and Async I/O](v01-messaging.html) - cross-language exchange, async operations, synchronisation
- [Scalability Protocols](v02-protocols.html) - req/rep, pub/sub, surveyor/respondent
- [Configuration and Security](v03-configuration.html) - TLS, options, serialization, statistics
- [Web Toolkit](v04-web.html) - HTTP client/server, WebSocket, streaming
## Key Takeaways
- **Sockets** connect via URLs using scalability protocols (req/rep, pub/sub, etc.)
- **Transports**: `inproc://` (in-process), `ipc://` (inter-process), `tcp://`, `ws://`, `wss://`, `tls+tcp://`
- **Async I/O**: `send_aio()` / `recv_aio()` return immediately; access results via `$data` or `$result`
- **Modes**: `"serial"` (R objects), `"raw"` (bytes), `"double"`, `"integer"`, `"character"`, etc.
- **Condition variables**: `cv()` for zero-latency event synchronisation
## 1. Sockets and Connections
### Create Sockets
```r
library(nanonext)
# Functional interface
s <- socket("pair")
listen(s, "tcp://127.0.0.1:5555")
dial(s, "tcp://127.0.0.1:5555")
# Object-oriented interface
n <- nano("pair", listen = "tcp://127.0.0.1:5555")
n$dial("tcp://127.0.0.1:5556")
# Close when done
close(s)
n$close()
```
### Protocols
| Protocol | Description | Socket Types |
|----------|-------------|--------------|
| **Pair** | 1-to-1 bidirectional | `"pair"` |
| **Poly** | Polyamorous pair | `"poly"` |
| **Pipeline** | One-way data flow | `"push"`, `"pull"` |
| **Req/Rep** | RPC pattern | `"req"`, `"rep"` |
| **Pub/Sub** | Broadcast/subscribe | `"pub"`, `"sub"` |
| **Survey** | Query all peers | `"surveyor"`, `"respondent"` |
| **Bus** | Many-to-many mesh | `"bus"` |
### Transports
| URL Scheme | Description |
|------------|-------------|
| `inproc://name` | In-process (fastest, same process) |
| `ipc:///path` | Inter-process (Unix socket / named pipe) |
| `tcp://host:port` | TCP/IP network |
| `ws://host:port/path` | WebSocket |
| `wss://host:port/path` | WebSocket over TLS |
| `tls+tcp://host:port` | TLS encrypted TCP |
## 2. Send and Receive
### Synchronous
```r
# Send R object (serialized)
send(s, data.frame(a = 1, b = 2))
# Receive R object
recv(s)
# Send raw bytes (for cross-language exchange)
send(s, c(1.1, 2.2, 3.3), mode = "raw")
# Receive as specific type
recv(s, mode = "double")
recv(s, mode = "character")
recv(s, mode = "raw")
```
### Receive Modes
| Mode | Description |
|------|-------------|
| `"serial"` / `1` | R serialization (default) |
| `"character"` / `2` | Coerce to character |
| `"complex"` / `3` | Coerce to complex |
| `"double"` / `4` | Coerce to double |
| `"integer"` / `5` | Coerce to integer |
| `"logical"` / `6` | Coerce to logical |
| `"numeric"` / `7` | Coerce to numeric |
| `"raw"` / `8` | Raw bytes |
| `"string"` / `9` | Fast option for length-1 character |
## 3. Async I/O
### Basic Async
```r
# Async send - returns immediately
res <- send_aio(s, data)
res$result # 0 = success, error code otherwise
# Async receive - returns immediately
msg <- recv_aio(s)
msg$data # Value when resolved, 'unresolved' NA otherwise
# Check if resolved
unresolved(msg) # TRUE while pending
# Wait for resolution
call_aio(msg) # Blocks, returns Aio object
collect_aio(msg) # Blocks, returns value directly
msg[] # Blocks (user-interruptible), returns value
```
### Non-blocking Patterns
```r
# Poll while doing other work
while (unresolved(msg)) {
# do other tasks
}
result <- msg$data
# Multiple async operations
msg1 <- recv_aio(s1)
msg2 <- recv_aio(s2)
# Both run concurrently
```
## 4. Condition Variables
### Basics
```r
# Create condition variable
cv <- cv()
# Check/signal
cv_value(cv) # Get counter value
cv_signal(cv) # Increment counter
cv_reset(cv) # Reset to zero
# Wait (blocks until counter > 0, then decrements)
wait(cv)
# Wait with timeout (ms), returns FALSE on timeout
until(cv, 1000)
```
### Pipe Notifications
```r
# Signal on connection/disconnection
pipe_notify(socket, cv = cv, add = TRUE, remove = TRUE)
# Distinguish message vs disconnect with flag
pipe_notify(socket, cv = cv, remove = TRUE, flag = TRUE)
r <- recv_aio(socket, cv = cv)
wait(cv) || stop("disconnected") # FALSE = pipe event
```
### Async with CV
```r
cv <- cv()
msg <- recv_aio(s, cv = cv)
wait(cv) # Wake on receive completion
msg$data
```
## 5. Request/Reply (RPC)
### Server
```r
rep <- socket("rep", listen = "tcp://127.0.0.1:5555")
ctx <- context(rep)
# reply() blocks, waiting for request
reply(ctx, execute = my_function, send_mode = "raw")
close(rep)
```
### Client
```r
req <- socket("req", dial = "tcp://127.0.0.1:5555")
ctx <- context(req)
# request() returns immediately
aio <- request(ctx, data = args, recv_mode = "double")
# Do other work while server processes...
# Get result when needed
result <- aio[]
close(req)
```
## 6. Pub/Sub
```r
pub <- socket("pub", listen = "inproc://pubsub")
sub <- socket("sub", dial = "inproc://pubsub")
# Subscribe to topic (prefix matching)
subscribe(sub, topic = "news")
subscribe(sub, topic = NULL) # All topics
# Unsubscribe
unsubscribe(sub, topic = "news")
# Publish (topic is message prefix)
send(pub, c("news", "headline"), mode = "raw")
# Receive (includes topic)
recv(sub, mode = "character")
close(pub)
close(sub)
```
## 7. Surveyor/Respondent
```r
sur <- socket("surveyor", listen = "inproc://survey")
res1 <- socket("respondent", dial = "inproc://survey")
res2 <- socket("respondent", dial = "inproc://survey")
# Set survey timeout (ms)
survey_time(sur, 500)
# Broadcast survey
send(sur, "ping")
# Collect responses (async)
aio1 <- recv_aio(sur)
aio2 <- recv_aio(sur)
# Respondents reply
recv(res1)
send(res1, "pong1")
# Late/missing responses timeout (errorValue 5)
msleep(500)
aio2$data # errorValue if no response
close(sur)
close(res1)
close(res2)
```
## 8. TLS Secure Connections
### Self-signed Certificates
```r
# Generate certificate (cn must match URL host exactly)
cert <- write_cert(cn = "127.0.0.1")
# Create TLS configs
server_tls <- tls_config(server = cert$server)
client_tls <- tls_config(client = cert$client)
# Use with tls+tcp:// or wss://
s1 <- socket(listen = "tls+tcp://127.0.0.1:5555", tls = server_tls)
s2 <- socket(dial = "tls+tcp://127.0.0.1:5555", tls = client_tls)
```
### CA Certificates
```r
# Client with CA cert file
client_tls <- tls_config(client = "/path/to/ca-cert.pem")
# Server with cert + key
server_tls <- tls_config(server = c("/path/to/cert.pem", "/path/to/key.pem"))
```
## 9. Options and Statistics
### Get/Set Options
```r
# Delayed start for configuration
s <- socket(listen = "tcp://127.0.0.1:5555", autostart = FALSE)
# Get option
opt(s$listener[[1]], "recv-size-max")
# Set option
opt(s$listener[[1]], "recv-size-max") <- 8192L
# Start after configuration
start(s$listener[[1]])
```
### Common Options
| Option | Description |
|--------|-------------|
| `"recv-size-max"` | Max message size (0 = unlimited) |
| `"send-timeout"` | Send timeout (ms) |
| `"recv-timeout"` | Receive timeout (ms) |
| `"reconnect-time-min"` | Min reconnect interval (ms) |
| `"reconnect-time-max"` | Max reconnect interval (ms) |
| `"req:resend-time"` | Request retry interval |
| `"sub:prefnew"` | Prefer newer messages |
### Custom Serialization
```r
# Register custom serializer for a class
serial <- serial_config(
"class_name",
function(x) serialize(x, NULL), # serialize
unserialize # unserialize
)
opt(socket, "serial") <- serial
```
### Statistics
```r
stat(socket, "pipes") # Active connections
stat(listener, "accept") # Connection attempts
stat(dialer, "reject") # Rejected connections
```
## 10. Contexts
Contexts enable concurrent operations on a single socket (for req/rep, surveyor/respondent).
```r
s <- socket("req", dial = "tcp://127.0.0.1:5555")
# Create independent contexts
ctx1 <- context(s)
ctx2 <- context(s)
# Concurrent requests
aio1 <- request(ctx1, data1)
aio2 <- request(ctx2, data2)
# Close contexts (or they close with socket)
close(ctx1)
close(ctx2)
close(s)
```
## 11. Cross-language Exchange
### R to Python (NumPy)
```r
# R: send raw doubles
n <- nano("pair", dial = "ipc:///tmp/nanonext")
n$send(c(1.1, 2.2, 3.3), mode = "raw")
result <- n$recv(mode = "double")
```
```python
# Python: receive as NumPy array
import numpy as np
import pynng
socket = pynng.Pair0(listen="ipc:///tmp/nanonext")
array = np.frombuffer(socket.recv())
socket.send(array.tobytes())
```
## 12. Error Handling
```r
# Errors return as 'errorValue' class
result <- recv(s, block = FALSE)
# Check for errors
is_error_value(result)
# Error codes
# 5 = Timed out
# 6 = Connection refused
# 8 = Try again (non-blocking, no message)
# Get error message
nng_error(5) # "Timed out"
```
## 13. Utilities
```r
# Sleep (uninterruptible, ms)
msleep(100)
# Random bytes
random(8) # 8 random bytes as hex string
random(8, convert = FALSE) # As raw vector
# Parse URL
parse_url("tcp://127.0.0.1:5555")
```
nanonext/inst/doc/v01-messaging.Rmd 0000644 0001762 0000144 00000012723 15142221674 016664 0 ustar ligges users ---
title: "nanonext - Messaging and Async I/O"
vignette: >
%\VignetteIndexEntry{nanonext - Messaging and Async I/O}
%\VignetteEngine{litedown::vignette}
%\VignetteEncoding{UTF-8}
---
### 1. Cross-language Exchange
`nanonext` provides a fast, reliable data interface between different programming languages where NNG has an implementation, including C, C++, Java, Python, Go, and Rust.
This messaging interface is lightweight, robust, and has limited points of failure. It enables:
- Communication between processes in the same or different languages
- Distributed computing across networks or on the same machine
- Real-time data pipelines where computation times exceed data frequency
- Modular software design following Unix philosophy
This example demonstrates numerical data exchange between R and Python (NumPy).
Create socket in Python using the NNG binding 'pynng':
``` python
import numpy as np
import pynng
socket = pynng.Pair0(listen="ipc:///tmp/nanonext.socket")
```
Create nano object in R using `nanonext`, then send a vector of 'doubles', specifying mode as 'raw':
``` r
library(nanonext)
n <- nano("pair", dial = "ipc:///tmp/nanonext.socket")
n$send(c(1.1, 2.2, 3.3, 4.4, 5.5), mode = "raw")
#> [1] 0
```
Receive in Python as a NumPy array of 'floats', and send back to R:
``` python
raw = socket.recv()
array = np.frombuffer(raw)
print(array)
#> [1.1 2.2 3.3 4.4 5.5]
msg = array.tobytes()
socket.send(msg)
socket.close()
```
Receive in R, specifying the receive mode as 'double':
``` r
n$recv(mode = "double")
#> [1] 1.1 2.2 3.3 4.4 5.5
n$close()
```
### 2. Async and Concurrency
`nanonext` implements true async send and receive, leveraging NNG as a massively-scalable concurrency framework.
``` r
s1 <- socket("pair", listen = "inproc://nano")
s2 <- socket("pair", dial = "inproc://nano")
```
`send_aio()` and `recv_aio()` return immediately with an 'Aio' object that performs operations asynchronously. Aio objects return an unresolved value while the operation is ongoing, then automatically resolve once complete.
``` r
# async receive requested, but no messages waiting yet
msg <- recv_aio(s2)
msg
#> < recvAio | $data >
msg$data
#> 'unresolved' logi NA
```
For 'sendAio' objects, the result is stored at `$result`. For 'recvAio' objects, the message is stored at `$data`.
``` r
res <- send_aio(s1, data.frame(a = 1, b = 2))
res
#> < sendAio | $result >
res$result
#> [1] 0
```
> 0 indicates successful send - the message has been accepted by the socket for sending but may still be buffered within the system.
``` r
# once a message is sent, the recvAio resolves automatically
msg$data
#> a b
#> 1 1 2
```
Use `unresolved()` in control flow to perform actions before or after Aio resolution without blocking.
``` r
msg <- recv_aio(s2)
# unresolved() checks resolution status
while (unresolved(msg)) {
# perform other tasks while waiting
send_aio(s1, "resolved")
cat("unresolved")
}
#> unresolved
# access resolved value
msg$data
#> [1] "resolved"
```
Explicitly wait for completion with `call_aio()` (blocking).
``` r
# wait for completion and return resolved Aio
call_aio(msg)
# access resolved value (waiting if required):
call_aio(msg)$data
#> [1] "resolved"
# or directly:
collect_aio(msg)
#> [1] "resolved"
# or user-interruptible:
msg[]
#> [1] "resolved"
close(s1)
close(s2)
```
### 3. Synchronisation Primitives
`nanonext` implements cross-platform synchronisation primitives from the NNG library, enabling synchronisation between NNG events and the main R execution thread.
Condition variables can signal events such as asynchronous receive completions and pipe events (connections established or dropped). Each condition variable has a value (counter) and flag (boolean). Signals increment the value; successful `wait()` or `until()` calls decrement it. A non-zero value allows waiting threads to continue.
This approach is more efficient than polling - consuming no resources while waiting and synchronising with zero latency.
**Example 1: Wait for connection**
``` r
sock <- socket("pair", listen = "inproc://nanopipe")
cv <- cv()
cv_value(cv)
#> [1] 0
pipe_notify(sock, cv = cv, add = TRUE, remove = TRUE)
# wait(cv) would block until connection established
# for illustration:
sock2 <- socket("pair", dial = "inproc://nanopipe")
cv_value(cv) # incremented when pipe created
#> [1] 1
wait(cv) # does not block as cv value is non-zero
cv_value(cv) # decremented by wait()
#> [1] 0
close(sock2)
cv_value(cv) # incremented when pipe destroyed
#> [1] 1
close(sock)
```
**Example 2: Wait for message or disconnection**
``` r
sock <- socket("pair", listen = "inproc://nanosignal")
sock2 <- socket("pair", dial = "inproc://nanosignal")
cv <- cv()
cv_value(cv)
#> [1] 0
pipe_notify(sock, cv = cv, add = FALSE, remove = TRUE, flag = TRUE)
send(sock2, "this message will wake waiting thread")
#> [1] 0
r <- recv_aio(sock, cv = cv)
# wakes when async receive completes
wait(cv) || stop("peer disconnected")
#> [1] TRUE
r$data
#> [1] "this message will wake waiting thread"
close(sock)
close(sock2)
```
When `flag = TRUE` is set for pipe notifications, `wait()` returns FALSE for pipe events (rather than TRUE for message events). This distinguishes between disconnections and successful receives, something not possible using `call_aio()` alone.
This mechanism enables waiting simultaneously on multiple events while distinguishing between them. `pipe_notify()` can signal up to two condition variables per event for additional flexibility in concurrent applications.
nanonext/inst/doc/v04-web.Rmd 0000644 0001762 0000144 00000041644 15163546346 015504 0 ustar ligges users ---
title: "nanonext - Web Toolkit"
vignette: >
%\VignetteIndexEntry{nanonext - Web Toolkit}
%\VignetteEngine{litedown::vignette}
%\VignetteEncoding{UTF-8}
---
``` r
library(nanonext)
```
nanonext provides high-performance HTTP/WebSocket client and server capabilities built on NNG's networking stack with Mbed TLS for secure connections.
### 1. HTTP Client
#### ncurl: Basic Requests
`ncurl()` is a minimalist HTTP(S) client. Basic usage requires only a URL.
``` r
ncurl("https://postman-echo.com/get")
#> $status
#> [1] 200
#>
#> $headers
#> NULL
#>
#> $data
#> [1] "{\"args\":{},\"headers\":{\"host\":\"postman-echo.com\",\"accept-encoding\":\"gzip, br\",\"x-forwarded-proto\":\"https\"},\"url\":\"https://postman-echo.com/get\"}"
```
Advanced usage supports all HTTP methods (POST, PUT, DELETE, etc.), custom headers, and request bodies.
``` r
ncurl("https://postman-echo.com/post",
method = "POST",
headers = c(`Content-Type` = "application/json", Authorization = "Bearer APIKEY"),
data = '{"key": "value"}',
response = "date")
#> $status
#> [1] 200
#>
#> $headers
#> $headers$date
#> [1] "Mon, 30 Mar 2026 22:16:56 GMT"
#>
#>
#> $data
#> [1] "{\"args\":{},\"data\":{\"key\":\"value\"},\"files\":{},\"form\":{},\"headers\":{\"host\":\"postman-echo.com\",\"accept-encoding\":\"gzip, br\",\"content-length\":\"16\",\"authorization\":\"Bearer APIKEY\",\"content-type\":\"application/json\",\"x-forwarded-proto\":\"https\"},\"json\":{\"key\":\"value\"},\"url\":\"https://postman-echo.com/post\"}"
```
Specify `response = TRUE` to return all response headers.
``` r
ncurl("https://postman-echo.com/get",
response = TRUE)
#> $status
#> [1] 200
#>
#> $headers
#> $headers$Date
#> [1] "Mon, 30 Mar 2026 22:16:56 GMT"
#>
#> $headers$`Content-Type`
#> [1] "application/json; charset=utf-8"
#>
#> $headers$`Content-Length`
#> [1] "143"
#>
#> $headers$Connection
#> [1] "close"
#>
#> $headers$etag
#> [1] "W/\"8f-7zN8nSad8A9WlFJjKQZB04z5nHE\""
#>
#> $headers$vary
#> [1] "Accept-Encoding"
#>
#> $headers$`x-envoy-upstream-service-time`
#> [1] "5"
#>
#> $headers$`cf-cache-status`
#> [1] "DYNAMIC"
#>
#> $headers$`Set-Cookie`
#> [1] "sails.sid=s%3AtfbN2TazlwshVuIc_Y-l_CKCt7WScK3s.5Z30lz615FemZ1kCseuVIUD4G%2BEAGJfIiYaJhfFDiGU; Path=/; HttpOnly, __cf_bm=mX8EtQ27DKZ2X6uAi8_krT8AN00Bf9rEnI4aauRi_.Q-1774909016.466189-1.0.1.1-Wtzs3qUkHi8sLuARpkSeFbJ8vXysEL7bXJdqKa3EqeWEanTLfzrcxvOudXHbpT8moKuZq5KuQXmZr0dSvqRdXUuc_yS8Ms47TY9OL3jb0muL_3gxejNDW7cDvtDcmLKh; HttpOnly; Secure; Path=/; Domain=postman-echo.com; Expires=Mon, 30 Mar 2026 22:46:56 GMT, _cfuvid=PgOu7XWt97qqSBedwhgeqXxkMVIYi18WzlBWroe865k-1774909016.466189-1.0.1.1-z9Y3X8DXI.zgMtblcU7939qcoMyvq2dQUGtA9Mbi59o; HttpOnly; SameSite=None; Secure; Path=/; Domain=postman-echo.com"
#>
#> $headers$Server
#> [1] "cloudflare"
#>
#> $headers$`CF-RAY`
#> [1] "9e4a7b48ed31cd3a-LHR"
#>
#>
#> $data
#> [1] "{\"args\":{},\"headers\":{\"host\":\"postman-echo.com\",\"accept-encoding\":\"gzip, br\",\"x-forwarded-proto\":\"https\"},\"url\":\"https://postman-echo.com/get\"}"
```
#### ncurl_aio: Async Requests
`ncurl_aio()` performs asynchronous requests, returning immediately with an 'ncurlAio' object that resolves when the response arrives.
``` r
res <- ncurl_aio("https://postman-echo.com/post",
method = "POST",
headers = c(`Content-Type` = "application/json"),
data = '{"async": true}',
response = "date")
res
#> < ncurlAio | $status $headers $data >
call_aio(res)$headers
#> $date
#> [1] "Mon, 30 Mar 2026 22:16:56 GMT"
res$status
#> [1] 200
res$data
#> [1] "{\"args\":{},\"data\":{\"async\":true},\"files\":{},\"form\":{},\"headers\":{\"host\":\"postman-echo.com\",\"accept-encoding\":\"gzip, br\",\"content-type\":\"application/json\",\"content-length\":\"15\",\"x-forwarded-proto\":\"https\"},\"json\":{\"async\":true},\"url\":\"https://postman-echo.com/post\"}"
```
##### Promises Integration
'ncurlAio' objects work anywhere that accepts a 'promise' from the promises package, including Shiny ExtendedTask.
``` r
library(promises)
p <- ncurl_aio("https://postman-echo.com/get") |> then(\(x) cat(x$data))
is.promise(p)
#> [1] TRUE
```
#### ncurl_session: Persistent Connections
`ncurl_session()` creates a reusable connection for efficient repeated requests to an API endpoint. Use `transact()` to send requests over the session.
``` r
sess <- ncurl_session("https://postman-echo.com/get",
convert = FALSE,
headers = c(`Content-Type` = "application/json"),
response = c("Date", "Content-Type"))
sess
#> < ncurlSession > - transact() to return data
transact(sess)
#> $status
#> [1] 200
#>
#> $headers
#> $headers$Date
#> [1] "Mon, 30 Mar 2026 22:16:57 GMT"
#>
#> $headers$`Content-Type`
#> [1] "application/json; charset=utf-8"
#>
#>
#> $data
#> [1] 7b 22 61 72 67 73 22 3a 7b 7d 2c 22 68 65 61 64 65 72 73 22 3a 7b 22 68 6f
#> [26] 73 74 22 3a 22 70 6f 73 74 6d 61 6e 2d 65 63 68 6f 2e 63 6f 6d 22 2c 22 63
#> [51] 6f 6e 74 65 6e 74 2d 74 79 70 65 22 3a 22 61 70 70 6c 69 63 61 74 69 6f 6e
#> [76] 2f 6a 73 6f 6e 22 2c 22 78 2d 66 6f 72 77 61 72 64 65 64 2d 70 72 6f 74 6f
#> [101] 22 3a 22 68 74 74 70 73 22 2c 22 61 63 63 65 70 74 2d 65 6e 63 6f 64 69 6e
#> [126] 67 22 3a 22 67 7a 69 70 2c 20 62 72 22 7d 2c 22 75 72 6c 22 3a 22 68 74 74
#> [151] 70 73 3a 2f 2f 70 6f 73 74 6d 61 6e 2d 65 63 68 6f 2e 63 6f 6d 2f 67 65 74
#> [176] 22 7d
close(sess)
```
### 2. WebSocket Client
`stream()` provides a low-level byte stream interface for communicating with WebSocket servers and other non-NNG endpoints.
Use `textframes = TRUE` for servers that expect text frames (most WebSocket servers).
``` r
s <- stream(dial = "wss://echo.websocket.org/", textframes = TRUE)
s
#> < nanoStream >
#> - mode: dialer text frames
#> - state: opened
#> - url: wss://echo.websocket.org/
```
`send()` and `recv()`, along with their async counterparts `send_aio()` and `recv_aio()`, work on Streams just like Sockets.
``` r
s |> recv()
#> [1] "Request served by 4d896d95b55478"
s |> send("hello websocket")
#> [1] 0
s |> recv()
#> [1] "hello websocket"
s |> recv_aio() -> r
s |> send("async message")
#> [1] 0
r[]
#> [1] "async message"
close(s)
```
### 3. Unified HTTP/WebSocket Server
`http_server()` creates a single server that can handle HTTP requests, WebSocket connections, and HTTP streaming, all on the same port.
A single call to `http_server()` sets up one NNG server instance with a list of handlers. HTTP routes, WebSocket endpoints, streaming endpoints, and static file handlers all share the same underlying server -- there is no need to run separate processes or bind additional ports. WebSocket clients connect via the standard HTTP upgrade mechanism, so a browser can load a page over HTTP and open a WebSocket connection to the same origin without any cross-origin configuration.
``` r
server <- http_server(
url = "http://127.0.0.1:8080",
handlers = list(
handler("/", function(req) {
list(status = 200L, body = "Hello from nanonext!")
}),
handler("/api/data", function(req) {
list(
status = 200L,
headers = c("Content-Type" = "application/json"),
body = '{"value": 42}'
)
}, method = "GET")
)
)
server$serve()
```
`$serve()` is a blocking call that starts the server, runs the event loop to process requests, and automatically closes the server on interrupt (Ctrl+C). For non-blocking use, call `$start()` and `$close()` separately, with `repeat later::run_now(Inf)` to run the event loop manually.
Specifying port `0` in the URL lets the OS assign an available port. The actual port is reflected in `server$url` after `$start()` or `$serve()`, making it easy to set up test servers without port conflicts.
#### Handler Types
All handler types can be freely mixed in a single server's handler list:
| Handler | Purpose |
|:--------|:--------|
| `handler()` | HTTP request/response with R callback |
| `handler_ws()` | WebSocket with `on_message`, `on_open`, `on_close` callbacks |
| `handler_stream()` | Chunked HTTP streaming (SSE, NDJSON, custom) |
| `handler_file()` | Serve a single static file |
| `handler_directory()` | Serve a directory tree with automatic MIME types |
| `handler_inline()` | Serve in-memory content |
| `handler_redirect()` | HTTP redirect |
#### HTTP Request Handlers
`handler()` creates HTTP route handlers. The callback receives a request list with `method`, `uri`, `headers`, and `body`, and returns a response list with `status`, optional `headers`, and `body`.
``` r
# GET endpoint
h1 <- handler("/hello", function(req) {
list(status = 200L, body = "Hello!")
})
# POST endpoint echoing the request body
h2 <- handler("/echo", function(req) {
list(status = 200L, body = req$body)
}, method = "POST")
# Catch-all for any method under a path prefix
h3 <- handler("/api", function(req) {
list(
status = 200L,
headers = c("Content-Type" = "application/json"),
body = sprintf('{"method":"%s","uri":"%s"}', req$method, req$uri)
)
}, method = "*", prefix = TRUE)
```
#### Static Content Handlers
``` r
# Serve a single file
h_file <- handler_file("/favicon.ico", "path/to/favicon.ico")
# Serve a directory tree (automatic MIME type detection)
h_dir <- handler_directory("/static", "www/assets")
# Serve inline content
h_inline <- handler_inline("/robots.txt", "User-agent: *\nDisallow:",
content_type = "text/plain")
# Redirect requests
h_redirect <- handler_redirect("/old-page", "/new-page", status = 301L)
```
#### WebSocket Handlers
WebSockets provide full bidirectional communication -- the server can push messages to the client, and the client can send messages back.
`handler_ws()` creates WebSocket endpoints. NNG handles the HTTP upgrade handshake and all WebSocket framing (RFC 6455) automatically. Because WebSocket handlers share the same server as HTTP handlers, the browser can load a page and open a WebSocket to the same host and port with no additional setup.
``` r
clients <- list()
server <- http_server(
url = "http://127.0.0.1:8080",
handlers = list(
handler_ws(
"/chat",
on_message = function(ws, data) {
# Broadcast to all connected clients
for (client in clients) client$send(data)
},
on_open = function(ws, req) {
clients[[as.character(ws$id)]] <<- ws
},
on_close = function(ws) {
clients[[as.character(ws$id)]] <<- NULL
},
textframes = TRUE
)
)
)
server$serve()
```
The `ws` connection object provides:
- `ws$send(data)` - Send a message to the client
- `ws$close()` - Close the connection
- `ws$id` - Unique integer connection identifier
Multiple WebSocket endpoints can coexist on the same server, each with independent callbacks and connection tracking. Connection IDs are unique across the entire server, so they are safe to use as keys in a shared data structure spanning multiple handlers.
#### HTTP Streaming Handlers
When you only need to push data in one direction -- server to client -- streaming is a lighter-weight alternative to WebSockets. It works over plain HTTP, so any client that speaks HTTP can consume the stream without needing a WebSocket library.
`handler_stream()` enables HTTP streaming using chunked transfer encoding, supporting Server-Sent Events (SSE), newline-delimited JSON (NDJSON), and custom streaming formats. Like WebSocket handlers, streaming endpoints share the same server as all other handlers.
``` r
conns <- list()
server <- http_server(
url = "http://127.0.0.1:8080",
handlers = list(
# SSE endpoint
handler_stream("/events",
on_request = function(conn, req) {
conn$set_header("Content-Type", "text/event-stream")
conn$set_header("Cache-Control", "no-cache")
conns[[as.character(conn$id)]] <<- conn
conn$send(format_sse(data = "connected", id = "1"))
},
on_close = function(conn) {
conns[[as.character(conn$id)]] <<- NULL
}
),
# Trigger broadcast via POST
handler("/broadcast", function(req) {
msg <- format_sse(data = rawToChar(req$body), event = "message")
lapply(conns, function(c) c$send(msg))
list(status = 200L, body = "sent")
}, method = "POST")
)
)
server$serve()
```
#### Server-Sent Events
`format_sse()` formats messages according to the SSE specification for browser `EventSource` clients.
``` r
format_sse(data = "Hello")
#> [1] "data: Hello\n\n"
format_sse(data = "Update available", event = "notification", id = "42")
#> [1] "event: notification\nid: 42\ndata: Update available\n\n"
format_sse(data = "Line 1\nLine 2")
#> [1] "data: Line 1\ndata: Line 2\n\n"
```
The streaming connection object provides:
- `conn$send(data)` - Send a data chunk
- `conn$close()` - Close the connection
- `conn$set_status(code)` - Set HTTP status (before first send)
- `conn$set_header(name, value)` - Set response header (before first send)
- `conn$id` - Unique connection identifier
### 4. Secure Connections (TLS)
All web functions support TLS for secure HTTPS/WSS connections via `tls_config()`.
#### Public Internet HTTPS
When making HTTPS requests over the public internet, you should supply a TLS configuration to validate server certificates.
Root CA certificates in PEM format may be found at:
- Linux: `/etc/ssl/certs/ca-certificates.crt` or `/etc/pki/tls/certs/ca-bundle.crt`
- macOS: `/etc/ssl/cert.pem`
- Windows: download from the [Common CA Database](https://www.ccadb.org/resources) site run by Mozilla (select the Server Authentication SSL/TLS certificates text file). *This link is not endorsed; use at your own risk.*
``` r
tls <- tls_config(client = "/etc/ssl/cert.pem")
ncurl("https://www.google.com", tls = tls)
```
#### Self-Signed Certificates
For internal services or testing, generate self-signed certificates using `write_cert()`.
``` r
# Generate self-signed certificate for testing
cert <- write_cert(cn = "127.0.0.1")
# Server TLS configuration
ser <- tls_config(server = cert$server)
# Client TLS configuration
cli <- tls_config(client = cert$client)
```
Use the configurations with servers and clients:
``` r
# HTTPS server
server <- http_server(
url = "https://127.0.0.1:0",
handlers = list(
handler("/", function(req) list(status = 200L, body = "Secure!"))
),
tls = ser
)
server$start()
server
#> < nanoServer >
#> - url: https://127.0.0.1:55055
#> - state: started
# HTTPS client request
aio <- ncurl_aio(paste0(server$url, "/"), tls = cli)
while (unresolved(aio)) later::run_now(1)
#> {"args":{},"headers":{"host":"postman-echo.com","accept-encoding":"gzip, br","x-forwarded-proto":"https"},"url":"https://postman-echo.com/get"}
aio$status
#> [1] 200
aio$data
#> [1] "Secure!"
server$close()
```
### 5. Client Example: Shiny ExtendedTask
This example demonstrates using `ncurl_aio()` with Shiny's ExtendedTask for non-blocking HTTP requests.
If your Shiny app calls an external API, a slow or unresponsive endpoint will block the R process and freeze the app for *all* users, not just the one who triggered the request.
`ncurl_aio()` avoids this -- it performs the HTTP call on a background thread and returns a promise, so the R process stays free to serve other sessions.
It works anywhere that accepts a promise, including Shiny's ExtendedTask:
``` r
library(shiny)
library(bslib)
library(nanonext)
ui <- page_fluid(
p("The time is ", textOutput("current_time", inline = TRUE)),
hr(),
input_task_button("btn", "Fetch data"),
verbatimTextOutput("result")
)
server <- function(input, output, session) {
output$current_time <- renderText({
invalidateLater(1000)
format(Sys.time(), "%H:%M:%S %p")
})
task <- ExtendedTask$new(
function() ncurl_aio("https://postman-echo.com/get", response = TRUE)
) |> bind_task_button("btn")
observeEvent(input$btn, task$invoke())
output$result <- renderPrint(task$result()$headers)
}
shinyApp(ui, server)
```
### 6. Server Example: Quarto Site with Dynamic API
This example shows how the unified server architecture makes it straightforward to combine HTTP or WebSocket handlers to serve different content over the same port.
If you've rendered a Quarto website and want to serve it locally -- but also expose a dynamic API endpoint alongside it, that's possible with a single `http_server()` call:
``` r
library(nanonext)
server <- http_server(
url = "http://127.0.0.1:0",
handlers = list(
# Serve your rendered Quarto site
handler_directory("/", "_site"),
# Add a prediction API endpoint
handler("/api/predict", function(req) {
input <- secretbase::jsondec(req$body)
pred <- predict(model, newdata = input)
list(
status = 200L,
headers = c("Content-Type" = "application/json"),
body = secretbase::jsonenc(list(prediction = pred))
)
}, method = "POST")
)
)
server$start()
server$url
# Browse to the URL to see your Quarto site with a live API behind it
```
Static pages are served at native speed by NNG while the prediction endpoint is handled by R -- no separate processes or ports required. Adding TLS is a single argument.
nanonext/inst/doc/v03-configuration.Rmd 0000644 0001762 0000144 00000006664 15142221674 017567 0 ustar ligges users ---
title: "nanonext - Configuration and Security"
vignette: >
%\VignetteIndexEntry{nanonext - Configuration and Security}
%\VignetteEngine{litedown::vignette}
%\VignetteEncoding{UTF-8}
---
``` r
library(nanonext)
```
### 1. TLS Secure Connections
Secure connections use NNG and Mbed TLS libraries. Enable them by:
1. Specifying a secure `tls+tcp://` or `wss://` URL
2. Passing a TLS configuration object to the 'tls' argument of `listen()` or `dial()`
Create TLS configurations with `tls_config()`:
- Client configuration: requires PEM-encoded CA certificate to verify server identity
- Server configuration: requires certificate and private key
Certificates may be supplied as files or character vectors. Valid X.509 certificates from Certificate Authorities are supported.
The convenience function `write_cert()` generates a 4096-bit RSA key pair and self-signed X.509 certificate. The 'cn' argument must match exactly the hostname/IP address of the URL (e.g., use '127.0.0.1' throughout, or 'localhost' throughout, not mixed).
``` r
cert <- write_cert(cn = "127.0.0.1")
str(cert)
#> List of 2
#> $ server: chr [1:2] "-----BEGIN CERTIFICATE-----\nMIIFOTCCAyGgAwIBAgIBATANBgkqhkiG9w0BAQsFADA0MRIwEAYDVQQDDAkxMjcu\nMC4wLjExETAPBgNV"| __truncated__ "-----BEGIN RSA PRIVATE KEY-----\nMIIJKAIBAAKCAgEA7bh7hshxv3wfY81Gkct1ffRlFB4XJj3vAH+wiM1l8Q9WAllX\nIfyEVwGdC665"| __truncated__
#> $ client: chr [1:2] "-----BEGIN CERTIFICATE-----\nMIIFOTCCAyGgAwIBAgIBATANBgkqhkiG9w0BAQsFADA0MRIwEAYDVQQDDAkxMjcu\nMC4wLjExETAPBgNV"| __truncated__ ""
ser <- tls_config(server = cert$server)
ser
#> < TLS server config | auth mode: optional >
cli <- tls_config(client = cert$client)
cli
#> < TLS client config | auth mode: required >
s <- socket(listen = "tls+tcp://127.0.0.1:5558", tls = ser)
s1 <- socket(dial = "tls+tcp://127.0.0.1:5558", tls = cli)
# secure TLS connection established
close(s1)
close(s)
```
### 2. Options
Use `opt()` and `'opt<-'()` to get and set options on Sockets, Contexts, Streams, Listeners, or Dialers. See function documentation for available options.
To configure dialers or listeners after creation, specify `autostart = FALSE` (configuration cannot be changed after starting).
``` r
s <- socket(listen = "inproc://options", autostart = FALSE)
# no maximum message size
opt(s$listener[[1]], "recv-size-max")
#> [1] 0
# enforce maximum message size to protect against denial-of-service attacks
opt(s$listener[[1]], "recv-size-max") <- 8192L
opt(s$listener[[1]], "recv-size-max")
#> [1] 8192
start(s$listener[[1]])
```
### 3. Custom Serialization
The special write-only option 'serial' sets a serialization configuration via `serial_config()`. This registers custom functions for serializing/unserializing reference objects using R's 'refhook' system, enabling transparent send/receive with mode 'serial'. Configurations apply to the Socket and all Contexts created from it.
``` r
serial <- serial_config("obj_class", function(x) serialize(x, NULL), unserialize)
opt(s, "serial") <- serial
close(s)
```
### 4. Statistics
Use `stat()` to access NNG's statistics framework. Query Sockets, Listeners, or Dialers for statistics such as connection attempts and current connections. See function documentation for available statistics.
``` r
s <- socket(listen = "inproc://stat")
# no active connections (pipes)
stat(s, "pipes")
#> [1] 0
s1 <- socket(dial = "inproc://stat")
# one now that the dialer has connected
stat(s, "pipes")
#> [1] 1
close(s)
```
nanonext/inst/doc/v02-protocols.Rmd 0000644 0001762 0000144 00000012044 15142221674 016730 0 ustar ligges users ---
title: "nanonext - Scalability Protocols"
vignette: >
%\VignetteIndexEntry{nanonext - Scalability Protocols}
%\VignetteEngine{litedown::vignette}
%\VignetteEncoding{UTF-8}
---
``` r
library(nanonext)
```
### 1. Request Reply Protocol
`nanonext` implements remote procedure calls (RPC) using NNG's req/rep protocol for distributed computing. Use this for computationally-expensive calculations or I/O-bound operations in separate server processes.
**[S] Server process:** `reply()` waits for a message, applies a function, and sends back the result. Started in a background 'mirai' process.
``` r
m <- mirai::mirai({
library(nanonext)
rep <- socket("rep", listen = "tcp://127.0.0.1:6556")
reply(context(rep), execute = rnorm, send_mode = "raw")
Sys.sleep(2) # linger period to flush system socket send
})
```
**[C] Client process:** `request()` performs async send/receive, returning immediately with a `recvAio` object.
``` r
req <- socket("req", dial = "tcp://127.0.0.1:6556")
aio <- request(context(req), data = 1e8, recv_mode = "double")
```
The client can now run additional code while the server processes the request.
``` r
# do more...
```
When the result is needed, call the recvAio using `call_aio()` to retrieve the value at `$data`.
``` r
call_aio(aio)$data |> str()
#> num [1:100000000] -0.63 0.883 1.134 -0.474 -0.237 ...
```
Since `call_aio()` blocks, alternatively query `aio$data` directly, which returns 'unresolved' (logical NA) if incomplete.
For server-side operations (e.g., writing to disk), calling or querying the value confirms completion and provides the function's return value (typically NULL or an exit code).
The [`mirai`](https://doi.org/10.5281/zenodo.7912722) package () uses `nanonext` as the back-end to provide asynchronous execution of arbitrary R code using the RPC model.
### 2. Publisher Subscriber Protocol
`nanonext` implements NNG's pub/sub protocol. Subscribers can subscribe to one or multiple topics broadcast by a publisher.
``` r
pub <- socket("pub", listen = "inproc://nanobroadcast")
sub <- socket("sub", dial = "inproc://nanobroadcast")
sub |> subscribe(topic = "examples")
pub |> send(c("examples", "this is an example"), mode = "raw")
#> [1] 0
sub |> recv(mode = "character")
#> [1] "examples" "this is an example"
pub |> send("examples at the start of a single text message", mode = "raw")
#> [1] 0
sub |> recv(mode = "character")
#> [1] "examples at the start of a single text message"
pub |> send(c("other", "this other topic will not be received"), mode = "raw")
#> [1] 0
sub |> recv(mode = "character")
#> 'errorValue' int 8 | Try again
# specify NULL to subscribe to ALL topics
sub |> subscribe(topic = NULL)
pub |> send(c("newTopic", "this is a new topic"), mode = "raw")
#> [1] 0
sub |> recv("character")
#> [1] "newTopic" "this is a new topic"
sub |> unsubscribe(topic = NULL)
pub |> send(c("newTopic", "this topic will now not be received"), mode = "raw")
#> [1] 0
sub |> recv("character")
#> 'errorValue' int 8 | Try again
# however the topics explicitly subscribed to are still received
pub |> send(c("examples will still be received"), mode = "raw")
#> [1] 0
sub |> recv(mode = "character")
#> [1] "examples will still be received"
```
The subscribed topic can be of any atomic type (not just character), allowing integer, double, logical, complex and raw vectors to be sent and received.
``` r
sub |> subscribe(topic = 1)
pub |> send(c(1, 10, 10, 20), mode = "raw")
#> [1] 0
sub |> recv(mode = "double")
#> [1] 1 10 10 20
pub |> send(c(2, 10, 10, 20), mode = "raw")
#> [1] 0
sub |> recv(mode = "double")
#> 'errorValue' int 8 | Try again
close(pub)
close(sub)
```
### 3. Surveyor Respondent Protocol
Useful for service discovery and similar applications. A surveyor broadcasts a survey to all respondents, who may reply within a timeout period. Late responses are discarded.
``` r
sur <- socket("surveyor", listen = "inproc://nanoservice")
res1 <- socket("respondent", dial = "inproc://nanoservice")
res2 <- socket("respondent", dial = "inproc://nanoservice")
# sur sets a survey timeout, applying to this and subsequent surveys
sur |> survey_time(value = 500)
# sur sends a message and then requests 2 async receives
sur |> send("service check")
#> [1] 0
aio1 <- sur |> recv_aio()
aio2 <- sur |> recv_aio()
# res1 receives the message and replies using an aio send function
res1 |> recv()
#> [1] "service check"
res1 |> send_aio("res1")
# res2 receives the message but fails to reply
res2 |> recv()
#> [1] "service check"
# checking the aio - only the first will have resolved
aio1$data
#> [1] "res1"
aio2$data
#> 'unresolved' logi NA
# after the survey expires, the second resolves into a timeout error
msleep(500)
aio2$data
#> 'errorValue' int 5 | Timed out
close(sur)
close(res1)
close(res2)
```
`msleep()` is an uninterruptible sleep function (using NNG) that takes a time in milliseconds.
The final value resolves to a timeout error (integer 5 classed as 'errorValue'). All error codes are classed as 'errorValue' for easy distinction from integer message values.
nanonext/README.md 0000644 0001762 0000144 00000012460 15176112256 013374 0 ustar ligges users
# nanonext
[](https://CRAN.R-project.org/package=nanonext)
[](https://r-lib.r-universe.dev/nanonext)
[](https://github.com/r-lib/nanonext/actions/workflows/R-CMD-check.yaml)
[](https://app.codecov.io/gh/r-lib/nanonext)
Fast, lightweight toolkit for messaging, concurrency, and the web in R.
Built on [NNG (Nanomsg Next Gen)](https://nng.nanomsg.org/) and
implemented almost entirely in C.
- **Scalability protocols** - pub/sub, req/rep, push/pull,
surveyor/respondent, bus, pair
- **Multiple transports** - TCP, IPC, WebSocket, TLS, in-process
- **Async I/O** - non-blocking operations with auto-resolving ‘aio’
objects
- **Cross-language** - exchange data with Python, C++, Go, Rust
- **Web toolkit** - unified HTTP, WebSocket, and streaming (SSE, NDJSON)
on a single port
[](https://deepwiki.com/r-lib/nanonext)
### Quick Start
``` r
library(nanonext)
# Open sockets
s1 <- socket("req", listen = "ipc:///tmp/nanonext")
s2 <- socket("rep", dial = "ipc:///tmp/nanonext")
# Send
s1 |> send("hello world")
#> [1] 0
# Receive on the other
s2 |> recv()
#> [1] "hello world"
close(s1)
close(s2)
```
### Async I/O
Non-blocking operations that resolve automatically:
``` r
s1 <- socket("rep", listen = "tcp://127.0.0.1:5556")
s2 <- socket("req", dial = "tcp://127.0.0.1:5556")
# Sender
s2 |> send_aio("async request")
# Async operations return immediately
aio <- recv_aio(s1)
aio
#> < recvAio | $data >
# Retrieve result when ready
aio$data
#> [1] "async request"
close(s1)
close(s2)
```
### Web Toolkit
One server, one port – HTTP endpoints, WebSocket connections, and
streaming all coexist. Mbed TLS built in for HTTPS/WSS.
``` r
# Generate self-signed certificates
cert <- write_cert(cn = "127.0.0.1")
# HTTPS server (port 0 = auto-assign a free port)
server <- http_server(
url = "https://127.0.0.1:0",
handlers = list(
handler("/", \(req) list(status = 200L, body = '{"status":"ok"}'))
),
tls = tls_config(server = cert$server)
)
server$start()
# Async HTTPS client
aio <- ncurl_aio(server$url, tls = tls_config(client = cert$client))
while (unresolved(aio)) later::run_now(1)
aio$data
#> [1] "{\"status\":\"ok\"}"
server$close()
```
### Documentation
| Guide | Topics |
|:---|:---|
| [Quick Reference](https://nanonext.r-lib.org/articles/nanonext.html) | At-a-glance API overview |
| [Messaging](https://nanonext.r-lib.org/articles/v01-messaging.html) | Cross-language, async, synchronisation |
| [Protocols](https://nanonext.r-lib.org/articles/v02-protocols.html) | req/rep, pub/sub, surveyor/respondent |
| [Configuration](https://nanonext.r-lib.org/articles/v03-configuration.html) | TLS, options, serialization |
| [Web Toolkit](https://nanonext.r-lib.org/articles/v04-web.html) | HTTP client/server, WebSocket, streaming |
### Installation
``` r
# CRAN
install.packages("nanonext")
# Development version
install.packages("nanonext", repos = "https://r-lib.r-universe.dev")
```
### Building from Source
#### Linux / Mac / Solaris
Requires ‘libnng’ \>= v1.11.0 and ‘libmbedtls’ \>= 2.5.0, or ‘cmake’ to
compile bundled libraries (libnng v1.11.1-pre, libmbedtls v3.6.5).
Recommended: Let the package compile bundled libraries for optimal
performance:
``` r
Sys.setenv(NANONEXT_LIBS = 1)
install.packages("nanonext")
```
System packages: libnng-dev / nng-devel, libmbedtls-dev /
libmbedtls-devel. Set `INCLUDE_DIR` and `LIB_DIR` for custom locations.
#### Windows
Requires Rtools. For R \>= 4.2, cmake is included. Earlier versions need
cmake installed separately and added to PATH.
### Links
[Documentation](https://nanonext.r-lib.org/) \|
[NNG](https://nng.nanomsg.org/) \| [Mbed
TLS](https://www.trustedfirmware.org/projects/mbed-tls/) \| [CRAN HPC
Task View](https://cran.r-project.org/view=HighPerformanceComputing) \|
[CRAN Web Technologies](https://cran.r-project.org/view=WebTechnologies)
### Acknowledgements
- [Garrett D’Amore](https://github.com/gdamore) (NNG author) for advice
and implementing features for nanonext
- [R Consortium](https://r-consortium.org/) for funding TLS development,
with support from [Henrik
Bengtsson](https://github.com/HenrikBengtsson) and [Will
Landau](https://github.com/wlandau/)
- [Joe Cheng](https://github.com/jcheng5/) for prototyping event-driven
promises integration
- [Luke Tierney](https://github.com/ltierney/) and [Mike
Cheng](https://github.com/coolbutuseless) for R serialization
documentation
- [Travers Ching](https://github.com/traversc) for novel ideas on custom
serialization
- [Jeroen Ooms](https://github.com/jeroen) for the Anticonf configure
script
–
Please note that this project is released with a [Contributor Code of
Conduct](https://nanonext.r-lib.org/CODE_OF_CONDUCT.html). By
participating in this project you agree to abide by its terms.
nanonext/build/ 0000755 0001762 0000144 00000000000 15176113105 013203 5 ustar ligges users nanonext/build/vignette.rds 0000644 0001762 0000144 00000000542 15176113105 015543 0 ustar ligges users ‹ •RMO1-° $*‘ƒ§ÆÄã"~ü#!1ƈ@¢×n·`CiI·,ró›ˆãÚ’.ÔÃÎÇ{ov¦Ó>×BETÀ– ,5Á@‚öá+£ UÁ×%‘J²WÓÌb‹¤ópÆ’„L¸œä‰‹p®•QT‰Ä#ŽÒÎeH•óÉBÕôȽ´s.Y”AùîÇ®;ñã‚Ó)°1ÓLRf'žâÞ„‰Œñu²’ßž=X%ö”CJ‰¸àf…ûn^«;õt7þÈÙ_‡Œ.4”YqË?±”Sn¶ŽÑØ,ñÅÌ„ó[Ì3þ=¦õs[w‹Ì°üèWP3Óס?øêƒ"˜‚íW–ÎnÉŠƒîbË#n6I©ßíÙ°0°ÁN—Í™ŒÝµíÞ±ÕRiÈóªZ-Û®YãëÅ¿Y¯×ïÛQA7‘k11¤=ÖPÙÇ'‘4›3 nanonext/configure 0000755 0001762 0000144 00000010331 15176113106 014012 0 ustar ligges users #!/bin/sh
# Initialise
PKG_CFLAGS=""
PKG_LIBS=""
# Helper function: detect cmake
detect_cmake() {
if which cmake > /dev/null 2>&1; then
return 0
fi
export PATH=$PATH:/Applications/CMake.app/Contents/bin
if which cmake > /dev/null 2>&1; then
return 0
fi
echo "Required 'cmake' not found"
exit 1
}
# Helper function: find library paths
# Usage: find_lib_paths
# Sets: LIB_CFLAGS, LIB_LIBS
find_lib_paths() {
lib_name=$1
default_libs=$2
LIB_CFLAGS=""
LIB_LIBS="$default_libs"
if [ "$INCLUDE_DIR" ] || [ "$LIB_DIR" ]; then
LIB_CFLAGS="-I$INCLUDE_DIR"
LIB_LIBS="-L$LIB_DIR $LIB_LIBS"
echo "Found INCLUDE_DIR $INCLUDE_DIR"
echo "Found LIB_DIR $LIB_DIR"
elif [ -d "/usr/local/include/$lib_name" ]; then
LIB_CFLAGS="-I/usr/local/include"
LIB_LIBS="-L/usr/local/lib $LIB_LIBS"
elif [ -d "/usr/include/$lib_name" ]; then
LIB_CFLAGS="-I/usr/include"
LIB_LIBS="-L/usr/lib $LIB_LIBS"
elif [ -d "/usr/local/opt/$lib_name" ]; then
LIB_CFLAGS="-I/usr/local/opt/$lib_name/include"
LIB_LIBS="-L/usr/local/opt/$lib_name/lib $LIB_LIBS"
fi
}
# Find compiler and export flags
CC=`"${R_HOME}/bin/R" CMD config CC`
CFLAGS=`"${R_HOME}/bin/R" CMD config CFLAGS`
LDFLAGS=`"${R_HOME}/bin/R" CMD config LDFLAGS`
export CC CFLAGS LDFLAGS
if [ -z "$MACOSX_DEPLOYMENT_TARGET" ]; then
MACOSX_DEPLOYMENT_TARGET=`echo $CC | sed -En 's/.*-version-min=([0-9][0-9.]*).*/\1/p'`
[ -n "$MACOSX_DEPLOYMENT_TARGET" ] && export MACOSX_DEPLOYMENT_TARGET
fi
# Detect -latomic linker flag for ARM architectures (Raspberry Pi etc.)
echo "#include
uint64_t v;
int main() {
return (int)__atomic_load_n(&v, __ATOMIC_ACQUIRE);
}" | ${CC} -xc - -o /dev/null > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo "Adding -latomic linker flag ..."
PKG_LIBS="$PKG_LIBS -latomic"
fi
# Determine whether to compile bundled libraries
compile_mbedtls=0
compile_nng=0
if [ -n "$NANONEXT_LIBS" ]; then
echo "NANONEXT_LIBS is set... building from source"
compile_mbedtls=1
compile_nng=1
else
# Find MbedTLS
find_lib_paths "mbedtls" "-lmbedtls -lmbedx509 -lmbedcrypto"
MBEDTLS_CFLAGS="$LIB_CFLAGS"
MBEDTLS_LIBS="$LIB_LIBS"
echo "#include
int main() {
#if MBEDTLS_VERSION_MAJOR < 2 || MBEDTLS_VERSION_MAJOR == 2 && MBEDTLS_VERSION_MINOR < 5
*(void *) 0 = 0;
#endif
}" | ${CC} ${MBEDTLS_CFLAGS} -xc - -o /dev/null > /dev/null 2>&1
if [ $? -ne 0 ]; then
compile_mbedtls=1
else
echo "Found 'libmbedtls' $MBEDTLS_CFLAGS"
PKG_CFLAGS="$MBEDTLS_CFLAGS $PKG_CFLAGS"
PKG_LIBS="$MBEDTLS_LIBS $PKG_LIBS"
fi
# Find NNG
find_lib_paths "nng" "-lnng"
NNG_CFLAGS="$LIB_CFLAGS"
NNG_LIBS="$LIB_LIBS"
echo "#include
int main() {
#if NNG_MAJOR_VERSION < 1 || NNG_MAJOR_VERSION == 1 && NNG_MINOR_VERSION < 11
*(void *) 0 = 0;
#endif
}" | ${CC} ${NNG_CFLAGS} -xc - -o /dev/null > /dev/null 2>&1
if [ $? -ne 0 ]; then
compile_nng=1
else
echo "Found 'libnng' $NNG_CFLAGS"
PKG_CFLAGS="$NNG_CFLAGS $PKG_CFLAGS"
PKG_LIBS="$NNG_LIBS $PKG_LIBS"
fi
fi
# Compile MbedTLS if needed
if [ $compile_mbedtls -eq 1 ]; then
echo "Existing 'libmbedtls' >= 2.5.0 not found"
detect_cmake
echo "Compiling 'libmbedtls' from source ..."
cmake -S src/mbedtls -B nano-build -DCMAKE_INSTALL_PREFIX=nano-install -DCMAKE_INSTALL_LIBDIR=lib
cmake --build nano-build --target install
rm -rf nano-build
fi
# Compile NNG if needed
if [ $compile_nng -eq 1 ]; then
echo "Existing 'libnng' >= 1.11.0 not found"
detect_cmake
echo "Compiling 'libnng' from source ..."
cmake -S src/nng -B nano-build -DCMAKE_INSTALL_PREFIX=nano-install -DCMAKE_INSTALL_LIBDIR=lib
cmake --build nano-build --target install
rm -rf nano-build
fi
# Set flags for bundled libraries
if [ -d "nano-install/lib" ]; then
PKG_CFLAGS="-I../nano-install/include -DNNG_STATIC_LIB $PKG_CFLAGS"
if [ -f "nano-install/lib/libmbedtls.a" ]; then
PKG_LIBS="../nano-install/lib/libnng.a ../nano-install/lib/libmbedtls.a ../nano-install/lib/libmbedx509.a ../nano-install/lib/libmbedcrypto.a $PKG_LIBS"
else
PKG_LIBS="../nano-install/lib/libnng.a $PKG_LIBS"
fi
fi
# Write to Makevars
sed -e "s|@cflags@|$PKG_CFLAGS|" -e "s|@libs@|$PKG_LIBS|" src/Makevars.in > src/Makevars
# Success
exit 0
nanonext/man/ 0000755 0001762 0000144 00000000000 15176112256 012665 5 ustar ligges users nanonext/man/is_aio.Rd 0000644 0001762 0000144 00000002026 15176112267 014421 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/utils.R
\name{is_aio}
\alias{is_aio}
\alias{is_nano}
\alias{is_ncurl_session}
\title{Validators}
\usage{
is_aio(x)
is_nano(x)
is_ncurl_session(x)
}
\arguments{
\item{x}{an object.}
}
\value{
Logical value TRUE or FALSE.
}
\description{
Validator functions for object types created by \pkg{nanonext}.
}
\details{
Is the object an Aio (inheriting from class 'sendAio' or 'recvAio').
Is the object an object inheriting from class 'nano' i.e. a nanoSocket,
nanoContext, nanoStream, nanoListener, nanoDialer, nanoMonitor or nano
Object.
Is the object an ncurlSession (object of class 'ncurlSession').
Is the object a Condition Variable (object of class 'conditionVariable').
}
\examples{
nc <- call_aio(ncurl_aio("https://postman-echo.com/get", timeout = 1000L))
is_aio(nc)
s <- socket()
is_nano(s)
n <- nano()
is_nano(n)
close(s)
n$close()
s <- ncurl_session("https://postman-echo.com/get", timeout = 1000L)
is_ncurl_session(s)
if (is_ncurl_session(s)) close(s)
}
nanonext/man/dot-dispatcher.Rd 0000644 0001762 0000144 00000001501 15176112267 016065 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/dispatcher.R
\name{.dispatcher}
\alias{.dispatcher}
\title{Dispatcher}
\usage{
.dispatcher(sock, psock, monitor, reset, serial, envir, next_stream)
}
\arguments{
\item{sock}{REP socket for host communication.}
\item{psock}{POLY socket for daemon communication.}
\item{monitor}{Monitor object for pipe events (its CV is used for signaling).}
\item{reset}{Pre-serialized connection reset error (raw vector).}
\item{serial}{Serialization configuration (list or NULL).}
\item{envir}{Environment containing RNG stream state.}
\item{next_stream}{Function to get next RNG stream, called as next_stream(envir).}
}
\value{
Integer status code (0 = normal exit).
}
\description{
Run the dispatcher event loop for mirai task distribution.
}
\keyword{internal}
nanonext/man/dot-keep.Rd 0000644 0001762 0000144 00000000713 15176112267 014667 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/aio.R
\name{.keep}
\alias{.keep}
\title{Keep Promise}
\usage{
.keep(x, ctx)
}
\arguments{
\item{x}{a 'recvAio' or 'ncurlAio' object.}
\item{ctx}{the return value of \code{environment()}.}
}
\value{
NULL.
}
\description{
Internal package function.
}
\details{
If successful, both \code{x} and \code{ctx} are preserved and accessible from the
promise callback.
}
\keyword{internal}
nanonext/man/subscribe.Rd 0000644 0001762 0000144 00000003214 15176112267 015137 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/opts.R
\name{subscribe}
\alias{subscribe}
\alias{unsubscribe}
\title{Subscribe / Unsubscribe Topic}
\usage{
subscribe(con, topic = NULL)
unsubscribe(con, topic = NULL)
}
\arguments{
\item{con}{a Socket or Context using the 'sub' protocol.}
\item{topic}{[default NULL] an atomic type or \code{NULL}. The default \code{NULL}
subscribes to all topics / unsubscribes from all topics (if all topics were
previously subscribed).}
}
\value{
Invisibly, the passed Socket or Context.
}
\description{
For a socket or context using the sub protocol in a publisher/subscriber
pattern. Set a topic to subscribe to, or remove a topic from the subscription
list.
}
\details{
To use pub/sub the publisher must:
\itemize{
\item specify \code{mode = 'raw'} when sending.
\item ensure the sent vector starts with the topic.
}
The subscriber should then receive specifying the correct mode.
}
\examples{
pub <- socket("pub", listen = "inproc://nanonext")
sub <- socket("sub", dial = "inproc://nanonext")
subscribe(sub, "examples")
send(pub, c("examples", "this is an example"), mode = "raw")
recv(sub, "character")
send(pub, "examples will also be received", mode = "raw")
recv(sub, "character")
send(pub, c("other", "this other topic will not be received"), mode = "raw")
recv(sub, "character")
unsubscribe(sub, "examples")
send(pub, c("examples", "this example is no longer received"), mode = "raw")
recv(sub, "character")
subscribe(sub, 2)
send(pub, c(2, 10, 10, 20), mode = "raw")
recv(sub, "double")
unsubscribe(sub, 2)
send(pub, c(2, 10, 10, 20), mode = "raw")
recv(sub, "double")
close(pub)
close(sub)
}
nanonext/man/send.Rd 0000644 0001762 0000144 00000006004 15176112267 014107 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/sendrecv.R
\name{send}
\alias{send}
\title{Send}
\usage{
send(con, data, mode = c("serial", "raw"), block = NULL, pipe = 0L)
}
\arguments{
\item{con}{a Socket, Context or Stream.}
\item{data}{an object (a vector, if \code{mode = "raw"}).}
\item{mode}{[default 'serial'] character value or integer equivalent -
either \code{"serial"} (1L) to send serialised R objects, or \code{"raw"} (2L) to
send atomic vectors of any type as a raw byte vector. For Streams, \code{"raw"}
is the only option and this argument is ignored.}
\item{block}{[default NULL] which applies the connection default (see
section 'Blocking' below). Specify logical \code{TRUE} to block until successful
or \code{FALSE} to return immediately even if unsuccessful (e.g. if no
connection is available), or else an integer value specifying the maximum
time to block in milliseconds, after which the operation will time out.}
\item{pipe}{[default 0L] only applicable to Sockets using the 'poly'
protocol, an integer pipe ID if directing the send via a specific pipe.}
}
\value{
An integer exit code (zero on success).
}
\description{
Send data over a connection (Socket, Context or Stream).
}
\section{Blocking}{
For Sockets and Contexts: the default behaviour is non-blocking with
\code{block = FALSE}. This will return immediately with an error if the message
could not be queued for sending. Certain protocol / transport combinations
may limit the number of messages that can be queued if they have yet to be
received.
For Streams: the default behaviour is blocking with \code{block = TRUE}. This will
wait until the send has completed. Set a timeout to ensure that the function
returns under all scenarios. As the underlying implementation uses an
asynchronous send with a wait, it is recommended to set a small positive
value for \code{block} rather than \code{FALSE}.
}
\section{Send Modes}{
The default mode \code{"serial"} sends serialised R objects to ensure perfect
reproducibility within R. When receiving, the corresponding mode \code{"serial"}
should be used. Custom serialization and unserialization functions for
reference objects may be enabled by the function \code{\link[=serial_config]{serial_config()}}.
Mode \code{"raw"} sends atomic vectors of any type as a raw byte vector, and must
be used when interfacing with external applications or raw system sockets,
where R serialization is not in use. When receiving, the mode corresponding
to the vector sent should be used.
}
\examples{
pub <- socket("pub", dial = "inproc://nanonext")
send(pub, data.frame(a = 1, b = 2))
send(pub, c(10.1, 20.2, 30.3), mode = "raw", block = 100)
close(pub)
req <- socket("req", listen = "inproc://nanonext")
rep <- socket("rep", dial = "inproc://nanonext")
ctx <- context(req)
send(ctx, data.frame(a = 1, b = 2), block = 100)
msg <- recv_aio(rep, timeout = 100)
send(ctx, c(1.1, 2.2, 3.3), mode = "raw", block = 100)
close(req)
close(rep)
}
\seealso{
\code{\link[=send_aio]{send_aio()}} for asynchronous send.
}
nanonext/man/handler_file.Rd 0000644 0001762 0000144 00000001354 15176112267 015575 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/server.R
\name{handler_file}
\alias{handler_file}
\title{Create Static File Handler}
\usage{
handler_file(path, file, prefix = FALSE)
}
\arguments{
\item{path}{URI path to match (e.g., "/favicon.ico").}
\item{file}{Path to the file to serve.}
\item{prefix}{[default FALSE] Logical, if TRUE matches path as a prefix.}
}
\value{
A handler object for use with \code{\link[=http_server]{http_server()}}.
}
\description{
Creates an HTTP handler that serves a single file. NNG handles MIME type
detection automatically.
}
\examples{
\dontshow{if (interactive()) withAutoprint(\{ # examplesIf}
h <- handler_file("/favicon.ico", "~/favicon.ico")
\dontshow{\}) # examplesIf}
}
nanonext/man/pipe_id.Rd 0000644 0001762 0000144 00000000606 15176112267 014571 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/aio.R
\name{pipe_id}
\alias{pipe_id}
\title{Get the Pipe ID of a recvAio}
\usage{
pipe_id(x)
}
\arguments{
\item{x}{a resolved 'recvAio'.}
}
\value{
Integer pipe ID.
}
\description{
Caution: must only be used on an already-resolved 'recvAio'. This function
does not perform validation of these pre-conditions.
}
nanonext/man/dial.Rd 0000644 0001762 0000144 00000006162 15176112267 014074 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/listdial.R
\name{dial}
\alias{dial}
\title{Dial an Address from a Socket}
\usage{
dial(
socket,
url = "inproc://nanonext",
tls = NULL,
autostart = TRUE,
fail = c("warn", "error", "none")
)
}
\arguments{
\item{socket}{a Socket.}
\item{url}{[default 'inproc://nanonext'] a URL to dial, specifying the
transport and address as a character string e.g. 'inproc://anyvalue' or
'tcp://127.0.0.1:5555' (see \link{transports}).}
\item{tls}{[default NULL] for secure tls+tcp:// or wss:// connections only,
provide a TLS configuration object created by \code{\link[=tls_config]{tls_config()}}.}
\item{autostart}{[default TRUE] whether to start the dialer (by default
asynchronously). Set to NA to start synchronously - this is less resilient
if a connection is not immediately possible, but avoids subtle errors from
attempting to use the socket before an asynchronous dial has completed. Set
to FALSE if setting configuration options on the dialer as it is not
generally possible to change these once started.}
\item{fail}{[default 'warn'] failure mode - a character value or integer
equivalent, whether to warn (1L), error (2L), or for none (3L) just return
an 'errorValue' without any corresponding warning.}
}
\value{
Invisibly, an integer exit code (zero on success). A new Dialer
(object of class 'nanoDialer' and 'nano') is created and bound to the
Socket if successful.
}
\description{
Creates a new Dialer and binds it to a Socket.
}
\details{
To view all Dialers bound to a socket use \verb{$dialer} on the socket, which
returns a list of Dialer objects. To access any individual Dialer (e.g. to
set options on it), index into the list e.g. \verb{$dialer[[1]]} to return the
first Dialer.
A Dialer is an external pointer to a dialer object, which creates a single
outgoing connection at a time. If the connection is broken, or fails, the
dialer object will automatically attempt to reconnect, and will keep doing so
until the dialer or socket is destroyed.
}
\section{Further details}{
Dialers and Listeners are always associated with a single socket. A given
socket may have multiple Listeners and/or multiple Dialers.
The client/server relationship described by dialer/listener is completely
orthogonal to any similar relationship in the protocols. For example, a rep
socket may use a dialer to connect to a listener on an req socket. This
orthogonality can lead to innovative solutions to otherwise challenging
communications problems.
Any configuration options on the dialer/listener should be set by \code{\link[=opt<-]{opt<-()}}
before starting the dialer/listener with \code{\link[=start]{start()}}.
Dialers/Listeners may be destroyed by \code{\link[=close]{close()}}. They are also closed when
their associated socket is closed.
}
\examples{
socket <- socket("rep")
dial(socket, url = "inproc://nanodial", autostart = FALSE)
socket$dialer
start(socket$dialer[[1]])
socket$dialer
close(socket$dialer[[1]])
close(socket)
nano <- nano("bus")
nano$dial(url = "inproc://nanodial", autostart = FALSE)
nano$dialer
nano$dialer_start()
nano$dialer
close(nano$dialer[[1]])
nano$close()
}
nanonext/man/socket.Rd 0000644 0001762 0000144 00000007531 15176112267 014454 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/socket.R
\name{socket}
\alias{socket}
\title{Open Socket}
\usage{
socket(
protocol = c("bus", "pair", "poly", "push", "pull", "pub", "sub", "req", "rep",
"surveyor", "respondent"),
dial = NULL,
listen = NULL,
tls = NULL,
autostart = TRUE,
raw = FALSE
)
}
\arguments{
\item{protocol}{[default 'bus'] choose protocol - \code{"bus"}, \code{"pair"},
\code{"poly"}, \code{"push"}, \code{"pull"}, \code{"pub"}, \code{"sub"}, \code{"req"}, \code{"rep"},
\code{"surveyor"}, or \code{"respondent"} - see \link{protocols}.}
\item{dial}{(optional) a URL to dial, specifying the transport and address as
a character string e.g. 'inproc://anyvalue' or 'tcp://127.0.0.1:5555' (see
\link{transports}).}
\item{listen}{(optional) a URL to listen at, specifying the transport and
address as a character string e.g. 'inproc://anyvalue' or
'tcp://127.0.0.1:5555' (see \link{transports}).}
\item{tls}{[default NULL] for secure tls+tcp:// or wss:// connections only,
provide a TLS configuration object created by \code{\link[=tls_config]{tls_config()}}.}
\item{autostart}{[default TRUE] whether to start the dialer/listener. Set
to FALSE if setting configuration options on the dialer/listener as it is
not generally possible to change these once started. For dialers only: set
to NA to start synchronously - this is less resilient if a connection is
not immediately possible, but avoids subtle errors from attempting to use
the socket before an asynchronous dial has completed.}
\item{raw}{[default FALSE] whether to open raw mode sockets. Note: not for
general use - do not enable unless you have a specific need (refer to NNG
documentation).}
}
\value{
A Socket (object of class 'nanoSocket' and 'nano').
}
\description{
Open a Socket implementing \code{protocol}, and optionally dial (establish an
outgoing connection) or listen (accept an incoming connection) at an address.
}
\details{
NNG presents a socket view of networking. The sockets are constructed using
protocol-specific functions, as a given socket implements precisely one
protocol.
Each socket may be used to send and receive messages (if the protocol
supports it, and implements the appropriate protocol semantics). For example,
sub sockets automatically filter incoming messages to discard those for
topics that have not been subscribed.
This function (optionally) binds a single Dialer and/or Listener to a Socket.
More complex network topologies may be created by binding further Dialers /
Listeners to the Socket as required using \code{\link[=dial]{dial()}} and \code{\link[=listen]{listen()}}.
New contexts may also be created using \code{\link[=context]{context()}} if the protocol supports
it.
}
\section{Protocols}{
The following Scalability Protocols (communication patterns) are implemented:
\itemize{
\item Bus (mesh networks) - protocol: 'bus'
\item Pair (two-way radio) - protocol: 'pair'
\item Poly (one-to-one of many) - protocol: 'poly'
\item Pipeline (one-way pipe) - protocol: 'push', 'pull'
\item Publisher/Subscriber (topics & broadcast) - protocol: 'pub', 'sub'
\item Request/Reply (RPC) - protocol: 'req', 'rep'
\item Survey (voting & service discovery) - protocol: 'surveyor',
'respondent'
}
Please see \link{protocols} for further documentation.
}
\section{Transports}{
The following communications transports may be used:
\itemize{
\item Inproc (in-process) - url: 'inproc://'
\item IPC (inter-process communications) - url: 'ipc://' (or 'abstract://'
on Linux)
\item TCP and TLS over TCP - url: 'tcp://' and 'tls+tcp://'
\item WebSocket and TLS over WebSocket - url: 'ws://' and 'wss://'
}
Please see \link{transports} for further documentation.
}
\examples{
s <- socket(protocol = "req", listen = "inproc://nanosocket")
s
s1 <- socket(protocol = "rep", dial = "inproc://nanosocket")
s1
send(s, "hello world!")
recv(s1)
close(s1)
close(s)
}
nanonext/man/recv_aio.Rd 0000644 0001762 0000144 00000006363 15176112267 014755 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/aio.R
\name{recv_aio}
\alias{recv_aio}
\title{Receive Async}
\usage{
recv_aio(
con,
mode = c("serial", "character", "complex", "double", "integer", "logical", "numeric",
"raw", "string"),
timeout = NULL,
cv = NULL
)
}
\arguments{
\item{con}{a Socket, Context or Stream.}
\item{mode}{[default 'serial'] character value or integer equivalent - one
of \code{"serial"} (1L), \code{"character"} (2L), \code{"complex"} (3L), \code{"double"} (4L),
\code{"integer"} (5L), \code{"logical"} (6L), \code{"numeric"} (7L), \code{"raw"} (8L), or
\code{"string"} (9L). The default \code{"serial"} means a serialised R object; for
the other modes, received bytes are converted into the respective mode.
\code{"string"} is a faster option for length one character vectors. For
Streams, \code{"serial"} will default to \code{"character"}.}
\item{timeout}{[default NULL] integer value in milliseconds or NULL, which
applies a socket-specific default, usually the same as no timeout.}
\item{cv}{(optional) a 'conditionVariable' to signal when the async receive
is complete.}
}
\value{
A 'recvAio' (object of class 'recvAio') (invisibly).
}
\description{
Receive data asynchronously over a connection (Socket, Context or Stream).
}
\details{
Async receive is always non-blocking and returns a 'recvAio' immediately.
For a 'recvAio', the received message is available at \verb{$data}. An
'unresolved' logical NA is returned if the async operation is yet to
complete.
To wait for the async operation to complete and retrieve the received
message, use \code{\link[=call_aio]{call_aio()}} on the returned 'recvAio' object.
Alternatively, to stop the async operation, use \code{\link[=stop_aio]{stop_aio()}}.
In case of an error, an integer 'errorValue' is returned (to be
distiguishable from an integer message value). This can be checked using
\code{\link[=is_error_value]{is_error_value()}}.
If an error occurred in unserialization or conversion of the message data to
the specified mode, a raw vector will be returned instead to allow recovery
(accompanied by a warning).
}
\section{Signalling}{
By supplying a 'conditionVariable', when the receive is complete, the
'conditionVariable' is signalled by incrementing its value by 1. This
happens asynchronously and independently of the R execution thread.
}
\examples{
s1 <- socket("pair", listen = "inproc://nanonext")
s2 <- socket("pair", dial = "inproc://nanonext")
res <- send_aio(s1, data.frame(a = 1, b = 2), timeout = 100)
msg <- recv_aio(s2, timeout = 100)
msg
msg$data
res <- send_aio(s1, c(1.1, 2.2, 3.3), mode = "raw", timeout = 100)
msg <- recv_aio(s2, mode = "double", timeout = 100)
msg
msg$data
res <- send_aio(s1, "example message", mode = "raw", timeout = 100)
msg <- recv_aio(s2, mode = "character", timeout = 100)
call_aio(msg)
msg$data
close(s1)
close(s2)
# Signalling a condition variable
s1 <- socket("pair", listen = "inproc://cv-example")
cv <- cv()
msg <- recv_aio(s1, timeout = 100, cv = cv)
until(cv, 10L)
msg$data
close(s1)
# in another process in parallel
s2 <- socket("pair", dial = "inproc://cv-example")
res <- send_aio(s2, c(1.1, 2.2, 3.3), mode = "raw", timeout = 100)
close(s2)
}
\seealso{
\code{\link[=recv]{recv()}} for synchronous receive.
}
nanonext/man/stat.Rd 0000644 0001762 0000144 00000005022 15176112267 014130 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/stats.R
\name{stat}
\alias{stat}
\title{Get Statistic for a Socket, Listener or Dialer}
\usage{
stat(object, name)
}
\arguments{
\item{object}{a Socket, Listener or Dialer.}
\item{name}{character name of statistic to return.}
}
\value{
The value of the statistic (character or double depending on the type
of statistic requested) if available, or else NULL.
}
\description{
Obtain value of a statistic for a Socket, Listener or Dialer. This function
exposes the stats interface of NNG.
}
\details{
Note: the values of individual statistics are guaranteed to be atomic, but
due to the way statistics are collected there may be discrepancies between
them at times. For example, statistics counting bytes and messages received
may not reflect the same number of messages, depending on when the snapshot
is taken. This potential inconsistency arises as a result of optimisations to
minimise the impact of statistics on actual operations.
}
\section{Stats}{
The following stats may be requested for a Socket:
\itemize{
\item 'id' - numeric id of the socket.
\item 'name' - character socket name.
\item 'protocol' - character protocol type e.g. 'bus'.
\item 'pipes' - numeric number of pipes (active connections).
\item 'dialers' - numeric number of listeners attached to the socket.
\item 'listeners' - numeric number of dialers attached to the socket.
}
The following stats may be requested for a Listener / Dialer:
\itemize{
\item 'id' - numeric id of the listener / dialer.
\item 'socket' - numeric id of the socket of the listener / dialer.
\item 'url' - character URL address.
\item 'pipes' - numeric number of pipes (active connections).
}
The following additional stats may be requested for a Listener:
\itemize{
\item 'accept' - numeric total number of connection attempts, whether
successful or not.
\item 'reject' - numeric total number of rejected connection attempts e.g.
due to incompatible protocols.
}
The following additional stats may be requested for a Dialer:
\itemize{
\item 'connect' - numeric total number of connection attempts, whether
successful or not.
\item 'reject' - numeric total number of rejected connection attempts e.g.
due to incompatible protocols.
\item 'refused' - numeric total number of refused connections e.g. when
starting synchronously with no listener on the other side.
}
}
\examples{
s <- socket("bus", listen = "inproc://stats")
stat(s, "pipes")
s1 <- socket("bus", dial = "inproc://stats")
stat(s, "pipes")
close(s1)
stat(s, "pipes")
close(s)
}
nanonext/man/tls_config.Rd 0000644 0001762 0000144 00000005723 15176112267 015314 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/tls.R
\name{tls_config}
\alias{tls_config}
\title{Create TLS Configuration}
\usage{
tls_config(client = NULL, server = NULL, pass = NULL, auth = is.null(server))
}
\arguments{
\item{client}{\strong{either} the character path to a file containing X.509
certificate(s) in PEM format, comprising the certificate authority
certificate chain (and revocation list if present), used to validate
certificates presented by peers,\cr
\strong{or} a length 2 character vector comprising [i] the certificate
authority certificate chain and [ii] the certificate revocation list, or
empty string \code{""} if not applicable.}
\item{server}{\strong{either} the character path to a file containing the
PEM-encoded TLS certificate and associated private key (may contain
additional certificates leading to a validation chain, with the leaf
certificate first),\cr
\strong{or} a length 2 character vector comprising [i] the TLS certificate
(optionally certificate chain) and [ii] the associated private key.}
\item{pass}{(optional) required only if the secret key supplied to \code{server}
is encrypted with a password. For security, consider providing through a
function that returns this value, rather than directly.}
\item{auth}{logical value whether to require authentication - by default TRUE
for client and FALSE for server configurations. If TRUE, the session is
only allowed to proceed if the peer has presented a certificate and it has
been validated. If FALSE, authentication is optional, whereby a certificate
is validated if presented by the peer, but the session allowed to proceed
otherwise. If neither \code{client} nor \code{server} are supplied, then no
authentication is performed and this argument has no effect.}
}
\value{
A 'tlsConfig' object.
}
\description{
Create a TLS configuration object to be used for secure connections. Specify
\code{client} to create a client configuration or \code{server} to create a server
configuration.
}
\details{
Specify one of \code{client} or \code{server} only, or neither (in which case an empty
client configuration is created), as a configuration can only be of one type.
}
\section{Public Internet HTTPS}{
When making HTTPS requests over the public internet, you should supply a TLS
configuration to validate server certificates.
Root CA certificates in PEM format may be found at:
\itemize{
\item Linux: \file{/etc/ssl/certs/ca-certificates.crt} or
\file{/etc/pki/tls/certs/ca-bundle.crt}
\item macOS: \file{/etc/ssl/cert.pem}
\item Windows: download from the Common CA Database site run by Mozilla:
\url{https://www.ccadb.org/resources} (select the Server Authentication SSL/TLS
certificates text file). \emph{This link is not endorsed; use at your own risk.}
}
}
\examples{
tls <- tls_config()
tls
ncurl("https://postman-echo.com/get", timeout = 1000L, tls = tls)
# client TLS configuration for public internet HTTPS on Linux
# tls <- tls_config(client = "/etc/ssl/certs/ca-certificates.crt")
}
nanonext/man/protocols.Rd 0000644 0001762 0000144 00000012174 15176112267 015207 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/docs.R
\name{protocols}
\alias{protocols}
\title{Protocols (Documentation)}
\description{
Protocols implemented by \pkg{nanonext}.
For an authoritative guide please refer to the online documentation for the
NNG library at \url{https://nng.nanomsg.org/man/}.
}
\section{Bus (mesh networks)}{
\strong{[protocol, bus]} The bus protocol is useful for routing applications or
for building mesh networks where every peer is connected to every other peer.
In this protocol, each message sent by a node is sent to every one of its
directly-connected peers. This protocol may be used to send and receive
messages. Sending messages will attempt to deliver to each directly connected
peer. Indirectly-connected peers will not receive messages. When using this
protocol to build mesh networks, it is therefore important that a
fully-connected mesh network be constructed.
All message delivery in this pattern is best-effort, which means that peers
may not receive messages. Furthermore, delivery may occur to some, all, or
none of the directly connected peers (messages are not delivered when peer
nodes are unable to receive). Hence, send operations will never block;
instead if the message cannot be delivered for any reason it is discarded.
}
\section{Pair (two-way radio)}{
\strong{[protocol, pair]} This is NNG's pair v0. The pair protocol implements a
peer-to-peer pattern, where relationships between peers are one-to-one. Only
one peer may be connected to another peer at a time, but both may send and
receive messages freely.
Normally, this pattern will block when attempting to send a message if no
peer is able to receive the message.
}
\section{Poly (one-to-one of many)}{
\strong{[protocol, poly]} This is NNG's pair v1 polyamorous mode. It allows a
socket to communicate with multiple directly-connected peers.
If no remote peer is specified by the sender, then the protocol willselect
any available connected peer.
If the peer on the given pipe is not able to receive (or the pipe is no
longer available, such as if the peer has disconnected), then the message
will be discarded with no notification to the sender.
}
\section{Push/Pull (one-way pipeline)}{
In the pipeline pattern, pushers distribute messages to pullers, hence useful
for solving producer/consumer problems.
If multiple peers are connected, the pattern attempts to distribute fairly.
Each message sent by a pusher will be sent to one of its peer pullers, chosen
in a round-robin fashion. This property makes this pattern useful in
load-balancing scenarios.
\strong{[protocol, push]} The push protocol is one half of a pipeline pattern.
The other side is the pull protocol.
\strong{[protocol, pull]} The pull protocol is one half of a pipeline pattern.
The other half is the push protocol.
}
\section{Publisher/Subscriber (topics & broadcast)}{
In a publisher/subscriber pattern, a publisher sends data, which is broadcast
to all subscribers. The subscriber only see the data to which they have
subscribed.
\strong{[protocol, pub]} The pub protocol is one half of a publisher/subscriber
pattern. This protocol may be used to send messages, but is unable to receive
them.
\strong{[protocol, sub]} The sub protocol is one half of a publisher/subscriber
pattern. This protocol may be used to receive messages, but is unable to send
them.
}
\section{Request/Reply (RPC)}{
In a request/reply pattern, a requester sends a message to one replier, who
is expected to reply with a single answer. This is used for synchronous
communications, for example remote procedure calls (RPCs).
The request is resent automatically if no reply arrives, until a reply is
received or the request times out.
\strong{[protocol, req]} The req protocol is one half of a request/reply
pattern. This socket may be used to send messages (requests), and then to
receive replies. Generally a reply can only be received after sending a
request.
\strong{[protocol, rep]} The rep protocol is one half of a request/reply
pattern. This socket may be used to receive messages (requests), and then to
send replies. Generally a reply can only be sent after receiving a request.
}
\section{Surveyor/Respondent (voting & service discovery)}{
In a survey pattern, a surveyor sends a survey, which is broadcast to all
peer respondents. The respondents then have a chance to reply (but are not
obliged). The survey itself is a timed event, so that responses received
after the survey has finished are discarded.
\strong{[protocol, surveyor]} The surveyor protocol is one half of a survey
pattern. This protocol may be used to send messages (surveys), and then to
receive replies. A reply can only be received after sending a survey. A
surveyor can normally expect to receive at most one reply from each responder
(messages may be duplicated in some topologies, so there is no guarantee of
this).
\strong{[protocol, respondent]} The respondent protocol is one half of a survey
pattern. This protocol may be used to receive messages, and then to send
replies. A reply can only be sent after receiving a survey, and generally the
reply will be sent to the surveyor from which the last survey was received.
}
nanonext/man/dot-dispatcher_capacity.Rd 0000644 0001762 0000144 00000001376 15176112267 017754 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/dispatcher.R
\name{.dispatcher_capacity}
\alias{.dispatcher_capacity}
\title{Dispatcher Capacity}
\usage{
.dispatcher_capacity(disp)
}
\arguments{
\item{disp}{External pointer to dispatcher handle.}
}
\value{
Named numeric vector of length 3: \strong{used} (current) and \strong{peak}
(high-watermark) usage, and \strong{capacity} (the \code{capacity} set on
\code{\link[=.dispatcher_start]{.dispatcher_start()}}, \code{NA_real_} if unset/unbounded), all in MB.
\code{NA_real_} in each slot if \code{disp} is invalid.
}
\description{
Read current and peak queued task payload usage at dispatcher, plus the
configured capacity, in MB (metric, 1 MB = 1,000,000 bytes).
}
\keyword{internal}
nanonext/man/nng_version.Rd 0000644 0001762 0000144 00000000534 15176112267 015507 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/utils.R
\name{nng_version}
\alias{nng_version}
\title{NNG Library Version}
\usage{
nng_version()
}
\value{
A character vector of length 2.
}
\description{
Returns the versions of the 'libnng' and 'libmbedtls' libraries used by the
package.
}
\examples{
nng_version()
}
nanonext/man/stop_aio.Rd 0000644 0001762 0000144 00000001471 15176112267 014776 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/aio.R
\name{stop_aio}
\alias{stop_aio}
\title{Stop Asynchronous Aio Operation}
\usage{
stop_aio(x)
}
\arguments{
\item{x}{an Aio or list of Aios (objects of class 'sendAio', 'recvAio' or
'ncurlAio').}
}
\value{
Invisible NULL.
}
\description{
Stop an asynchronous Aio operation, or a list of Aio operations.
}
\details{
Stops the asynchronous I/O operation associated with Aio \code{x} by aborting, and
then waits for it to complete or to be completely aborted, and for the
callback associated with the Aio to have completed executing. If successful,
the Aio will resolve to an 'errorValue' 20 (Operation canceled).
Note this function operates silently and does not error even if \code{x} is not an
active Aio, always returning invisible NULL.
}
nanonext/man/reap.Rd 0000644 0001762 0000144 00000001417 15176112267 014110 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/socket.R
\name{reap}
\alias{reap}
\title{Reap}
\usage{
reap(con)
}
\arguments{
\item{con}{a Socket, Context, Listener or Dialer.}
}
\value{
An integer exit code (zero on success).
}
\description{
An alternative to \code{close} for Sockets, Contexts, Listeners, and Dialers
avoiding S3 method dispatch.
}
\details{
May be used on unclassed external pointers e.g. those created by
\code{\link[=.context]{.context()}}. Returns silently and does not warn or error, nor does it update
the state of object attributes.
}
\examples{
s <- socket("req")
listen(s)
dial(s)
ctx <- .context(s)
reap(ctx)
reap(s[["dialer"]][[1]])
reap(s[["listener"]][[1]])
reap(s)
reap(s)
}
\seealso{
\code{\link[=close]{close()}}
}
nanonext/man/dot-dispatcher_try_gate.Rd 0000644 0001762 0000144 00000001272 15176112267 017770 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/dispatcher.R
\name{.dispatcher_try_gate}
\alias{.dispatcher_try_gate}
\title{Dispatcher Try Gate}
\usage{
.dispatcher_try_gate(disp)
}
\arguments{
\item{disp}{External pointer to dispatcher handle.}
}
\value{
Logical \code{TRUE} if submission would not block (queued bytes below
the memory budget set on \code{.dispatcher_start()}, or unbounded), \code{FALSE}
if at or over the budget. \code{NULL} if \code{disp} is invalid.
}
\description{
Snapshot read of dispatcher capacity. Returns immediately, never blocks.
The non-blocking counterpart to \code{\link[=.dispatcher_gate]{.dispatcher_gate()}}.
}
\keyword{internal}
nanonext/man/dot-dispatcher_info.Rd 0000644 0001762 0000144 00000000671 15176112267 017107 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/dispatcher.R
\name{.dispatcher_info}
\alias{.dispatcher_info}
\title{Dispatcher Info}
\usage{
.dispatcher_info(disp)
}
\arguments{
\item{disp}{External pointer to dispatcher handle.}
}
\value{
Integer vector of length 5: connections, cumulative, awaiting,
executing, completed.
}
\description{
Read dispatcher statistics directly under lock.
}
\keyword{internal}
nanonext/man/listen.Rd 0000644 0001762 0000144 00000005655 15176112267 014467 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/listdial.R
\name{listen}
\alias{listen}
\title{Listen to an Address from a Socket}
\usage{
listen(
socket,
url = "inproc://nanonext",
tls = NULL,
autostart = TRUE,
fail = c("warn", "error", "none")
)
}
\arguments{
\item{socket}{a Socket.}
\item{url}{[default 'inproc://nanonext'] a URL to dial, specifying the
transport and address as a character string e.g. 'inproc://anyvalue' or
'tcp://127.0.0.1:5555' (see \link{transports}).}
\item{tls}{[default NULL] for secure tls+tcp:// or wss:// connections only,
provide a TLS configuration object created by \code{\link[=tls_config]{tls_config()}}.}
\item{autostart}{[default TRUE] whether to start the listener. Set to FALSE
if setting configuration options on the listener as it is not generally
possible to change these once started.}
\item{fail}{[default 'warn'] failure mode - a character value or integer
equivalent, whether to warn (1L), error (2L), or for none (3L) just return
an 'errorValue' without any corresponding warning.}
}
\value{
Invisibly, an integer exit code (zero on success). A new Listener
(object of class 'nanoListener' and 'nano') is created and bound to the
Socket if successful.
}
\description{
Creates a new Listener and binds it to a Socket.
}
\details{
To view all Listeners bound to a socket use \verb{$listener} on the socket,
which returns a list of Listener objects. To access any individual Listener
(e.g. to set options on it), index into the list e.g. \verb{$listener[[1]]}
to return the first Listener.
A listener is an external pointer to a listener object, which accepts
incoming connections. A given listener object may have many connections at
the same time, much like an HTTP server can have many connections to multiple
clients simultaneously.
}
\section{Further details}{
Dialers and Listeners are always associated with a single socket. A given
socket may have multiple Listeners and/or multiple Dialers.
The client/server relationship described by dialer/listener is completely
orthogonal to any similar relationship in the protocols. For example, a rep
socket may use a dialer to connect to a listener on an req socket. This
orthogonality can lead to innovative solutions to otherwise challenging
communications problems.
Any configuration options on the dialer/listener should be set by \code{\link[=opt<-]{opt<-()}}
before starting the dialer/listener with \code{\link[=start]{start()}}.
Dialers/Listeners may be destroyed by \code{\link[=close]{close()}}. They are also closed when
their associated socket is closed.
}
\examples{
socket <- socket("req")
listen(socket, url = "inproc://nanolisten", autostart = FALSE)
socket$listener
start(socket$listener[[1]])
socket$listener
close(socket$listener[[1]])
close(socket)
nano <- nano("bus")
nano$listen(url = "inproc://nanolisten", autostart = FALSE)
nano$listener
nano$listener_start()
nano$listener
close(nano$listener[[1]])
nano$close()
}
nanonext/man/as.promise.ncurlAio.Rd 0000644 0001762 0000144 00000001450 15176112267 017011 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/ncurl.R
\name{as.promise.ncurlAio}
\alias{as.promise.ncurlAio}
\title{Make ncurlAio Promise}
\usage{
\method{as.promise}{ncurlAio}(x)
}
\arguments{
\item{x}{an object of class 'ncurlAio'.}
}
\value{
A 'promise' object.
}
\description{
Creates a 'promise' from an 'ncurlAio' object.
}
\details{
This function is an S3 method for the generic \code{as.promise} for class
'ncurlAio'.
Requires the \pkg{promises} package.
Allows an 'ncurlAio' to be used with functions such as \code{promises::then()},
which schedules a function to run upon resolution of the Aio.
The promise is resolved with a list of 'status', 'headers' and 'data' or
rejected with the error translation if an NNG error is returned e.g. for an
invalid address.
}
nanonext/man/random.Rd 0000644 0001762 0000144 00000002664 15176112267 014446 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/utils.R
\name{random}
\alias{random}
\title{Random Data Generation}
\usage{
random(n = 1L, convert = TRUE)
}
\arguments{
\item{n}{[default 1L] integer random bytes to generate (from 0 to 1024),
coerced to integer if required. If a vector, the first element is taken.}
\item{convert}{[default TRUE] logical \code{FALSE} to return a raw vector, or
\code{TRUE} to return the hex representation of the bytes as a character string.}
}
\value{
A length \code{n} raw vector, or length one vector of \verb{2n} random
characters, depending on the value of \code{convert} supplied.
}
\description{
Strictly not for use in statistical analysis. Non-reproducible and with
unknown statistical properties. Provides an alternative source of randomness
from the Mbed TLS library for purposes such as cryptographic key generation.
Mbed TLS uses a block-cipher in counter mode operation, as defined in
NIST SP800-90A: \emph{Recommendation for Random Number Generation Using
Deterministic Random Bit Generators}. The implementation uses AES-256 as the
underlying block cipher, with a derivation function, and an entropy collector
combining entropy from multiple sources including at least one strong entropy
source.
}
\note{
Results obtained are independent of and do not alter the state of R's
own pseudo-random number generators.
}
\examples{
random()
random(8L)
random(n = 8L, convert = FALSE)
}
nanonext/man/mclock.Rd 0000644 0001762 0000144 00000001270 15176112267 014426 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/utils.R
\name{mclock}
\alias{mclock}
\title{Clock Utility}
\usage{
mclock()
}
\value{
A double.
}
\description{
Provides the number of elapsed milliseconds since an arbitrary reference time
in the past. The reference time will be the same for a given session, but may
differ between sessions.
}
\details{
A convenience function for building concurrent applications. The resolution
of the clock depends on the underlying system timing facilities and may not
be particularly fine-grained. This utility should however be faster than
using \code{Sys.time()}.
}
\examples{
time <- mclock(); msleep(100); mclock() - time
}
nanonext/man/pipe_notify.Rd 0000644 0001762 0000144 00000003714 15176112267 015510 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/sync.R
\name{pipe_notify}
\alias{pipe_notify}
\title{Pipe Notify}
\usage{
pipe_notify(socket, cv, add = FALSE, remove = FALSE, flag = FALSE)
}
\arguments{
\item{socket}{a Socket.}
\item{cv}{a 'conditionVariable' to signal, or NULL to cancel a previously set
signal.}
\item{add}{[default FALSE] logical value whether to signal (or cancel
signal) when a pipe is added.}
\item{remove}{[default FALSE] logical value whether to signal (or cancel
signal) when a pipe is removed.}
\item{flag}{[default FALSE] logical value whether to also set a flag in the
'conditionVariable'. This can help distinguish between different types of
signal, and causes any subsequent \code{\link[=wait]{wait()}} to return FALSE instead of TRUE.
If a signal from the \pkg{tools} package, e.g. \code{tools::SIGINT}, or an
equivalent integer value is supplied, this sets a flag and additionally
raises this signal upon the flag being set. For \code{tools::SIGTERM}, the
signal is raised with a 200ms grace period to allow a process to exit
normally.}
}
\value{
Invisibly, zero on success (will otherwise error).
}
\description{
Signals a 'conditionVariable' whenever pipes (individual connections) are
added or removed at a socket.
}
\details{
For add: this event occurs after the pipe is fully added to the socket. Prior
to this time, it is not possible to communicate over the pipe with the
socket.
For remove: this event occurs after the pipe has been removed from the
socket. The underlying transport may be closed at this point, and it is not
possible to communicate using this pipe.
}
\examples{
s <- socket(listen = "inproc://nanopipe")
cv <- cv()
pipe_notify(s, cv, add = TRUE, remove = TRUE, flag = TRUE)
cv_value(cv)
s1 <- socket(dial = "inproc://nanopipe")
cv_value(cv)
reap(s1)
cv_value(cv)
pipe_notify(s, NULL, add = TRUE, remove = TRUE)
s1 <- socket(dial = "inproc://nanopipe")
cv_value(cv)
reap(s1)
(wait(cv))
close(s)
}
nanonext/man/handler.Rd 0000644 0001762 0000144 00000004076 15176112267 014602 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/server.R
\name{handler}
\alias{handler}
\title{Create HTTP Handler}
\usage{
handler(path, callback, method = "GET", prefix = FALSE)
}
\arguments{
\item{path}{URI path to match (e.g., "/api/data", "/users").}
\item{callback}{Function to handle requests. Receives a list with:
\itemize{
\item \code{method} - HTTP method (character)
\item \code{uri} - Request URI (character)
\item \code{headers} - Named character vector of headers
\item \code{body} - Request body (raw vector)
}
Should return a list with:
\itemize{
\item \code{status} - HTTP status code (integer, default 200)
\item \code{headers} - Response headers as a named character vector, e.g.
\code{c("Content-Type" = "application/json")} (optional)
\item \code{body} - Response body (character or raw)
}}
\item{method}{[default "GET"] HTTP method to match (e.g., "GET", "POST",
"PUT", "DELETE"). Use \code{"*"} to match any method.}
\item{prefix}{[default FALSE] Logical, if TRUE matches path as a prefix
(e.g., "/api" will match "/api/users", "/api/items", etc.).}
}
\value{
A handler object for use with \code{\link[=http_server]{http_server()}}.
}
\description{
Creates an HTTP route handler for use with \code{\link[=http_server]{http_server()}}.
}
\details{
If the callback throws an error, a 500 Internal Server Error
response is returned to the client.
}
\examples{
# Simple GET handler
h1 <- handler("/hello", function(req) {
list(status = 200L, body = "Hello!")
})
# POST handler that echoes the request body
h2 <- handler("/echo", function(req) {
list(status = 200L, body = req$body)
}, method = "POST")
# Catch-all handler for a path prefix
h3 <- handler("/static", function(req) {
# Serve static files under /static/*
}, method = "*", prefix = TRUE)
}
\seealso{
\code{\link[=handler_ws]{handler_ws()}} for WebSocket handlers. Static handlers:
\code{\link[=handler_file]{handler_file()}}, \code{\link[=handler_directory]{handler_directory()}}, \code{\link[=handler_inline]{handler_inline()}},
\code{\link[=handler_redirect]{handler_redirect()}}.
}
nanonext/man/handler_redirect.Rd 0000644 0001762 0000144 00000002031 15176112267 016450 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/server.R
\name{handler_redirect}
\alias{handler_redirect}
\title{Create HTTP Redirect Handler}
\usage{
handler_redirect(path, location, status = 302L, prefix = FALSE)
}
\arguments{
\item{path}{URI path to match (e.g., "/old-page").}
\item{location}{URL to redirect to. Can be relative (e.g., "/new-page") or
absolute (e.g., "https://example.com/page").}
\item{status}{HTTP redirect status code. Must be one of:
\itemize{
\item 301 - Moved Permanently
\item 302 - Found (default)
\item 303 - See Other
\item 307 - Temporary Redirect
\item 308 - Permanent Redirect
}}
\item{prefix}{[default FALSE] Logical, if TRUE matches path as a prefix.}
}
\value{
A handler object for use with \code{\link[=http_server]{http_server()}}.
}
\description{
Creates an HTTP handler that returns a redirect response.
}
\examples{
# Permanent redirect
h1 <- handler_redirect("/old", "/new", status = 301L)
# Redirect bare path to trailing slash
h2 <- handler_redirect("/app", "/app/")
}
nanonext/man/handler_directory.Rd 0000644 0001762 0000144 00000002451 15176112267 016661 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/server.R
\name{handler_directory}
\alias{handler_directory}
\title{Create Static Directory Handler}
\usage{
handler_directory(path, directory)
}
\arguments{
\item{path}{URI path prefix (e.g., "/static"). Requests to "/static/foo.js"
will serve "directory/foo.js".}
\item{directory}{Path to the directory to serve.}
}
\value{
A handler object for use with \code{\link[=http_server]{http_server()}}.
}
\description{
Creates an HTTP handler that serves files from a directory tree. NNG handles
MIME type detection automatically.
}
\details{
Directory handlers automatically match all paths under the prefix (prefix
matching is implicit). The URI path is mapped to the filesystem:
\itemize{
\item Request to "/static/css/style.css" serves "directory/css/style.css"
\item Request to "/static/" serves "directory/index.html" if it exists
}
Note: The trailing slash behavior depends on how clients make requests.
A request to "/static" (no trailing slash) will not automatically redirect
to "/static/". Consider using \code{\link[=handler_redirect]{handler_redirect()}} if you need this behavior.
}
\examples{
\dontshow{if (interactive()) withAutoprint(\{ # examplesIf}
h <- handler_directory("/static", "www/assets")
\dontshow{\}) # examplesIf}
}
nanonext/man/http_server.Rd 0000644 0001762 0000144 00000006267 15176112267 015536 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/server.R
\name{http_server}
\alias{http_server}
\title{Create HTTP/WebSocket Server}
\usage{
http_server(url, handlers = list(), tls = NULL)
}
\arguments{
\item{url}{URL to listen on (e.g., "http://127.0.0.1:8080").}
\item{handlers}{A handler or list of handlers created with \code{\link[=handler]{handler()}}, \code{\link[=handler_ws]{handler_ws()}}, etc.}
\item{tls}{TLS configuration for HTTPS/WSS, created via \code{\link[=tls_config]{tls_config()}}.}
}
\value{
A nanoServer object with methods:
\itemize{
\item \verb{$start()} - Start accepting connections
\item \verb{$close()} - Stop and release all resources
\item \verb{$serve()} - Start and block, processing requests via the \pkg{later}
event loop until interrupted (e.g., Ctrl+C). The server is automatically
closed on exit
\item \verb{$url} - The server URL
}
}
\description{
Creates a server that can handle HTTP requests and WebSocket connections.
}
\details{
This function leverages NNG's shared HTTP server architecture. When both
HTTP handlers and WebSocket handlers are provided, they share the same
underlying server and port. WebSocket handlers automatically handle
the HTTP upgrade handshake and all WebSocket framing (RFC 6455).
WebSocket and streaming callbacks are executed on R's main thread via the
\pkg{later} package. To process callbacks, you must run the event loop
(e.g., using \code{later::run_now()} in a loop), or use \verb{$serve()} which
handles this automatically.
Requires the \pkg{later} package.
}
\examples{
\dontshow{if (interactive() && requireNamespace("later", quietly = TRUE)) withAutoprint(\{ # examplesIf}
# Simple HTTP server
server <- http_server(
url = "http://127.0.0.1:8080",
handlers = list(
handler("/", function(req) {
list(status = 200L, body = "Hello, World!")
}),
handler("/api/data", function(req) {
list(
status = 200L,
headers = c("Content-Type" = "application/json"),
body = '{"value": 42}'
)
})
)
)
server$start()
# Run event loop: repeat later::run_now(Inf)
server$close()
# HTTP + WebSocket server
server <- http_server(
url = "http://127.0.0.1:8080",
handlers = list(
handler("/", function(req) {
list(status = 200L, body = "...")
}),
handler_ws("/ws", function(ws, data) {
ws$send(data) # Echo
}, textframes = TRUE)
)
)
# Multiple WebSocket endpoints
server <- http_server(
url = "http://127.0.0.1:8080",
handlers = list(
handler_ws("/echo", function(ws, data) ws$send(data)),
handler_ws("/upper", function(ws, data) ws$send(toupper(data)), textframes = TRUE)
)
)
# HTTPS server with self-signed certificate
cert <- write_cert(cn = "127.0.0.1")
cfg <- tls_config(server = cert$server)
server <- http_server(
url = "https://127.0.0.1:8443",
handlers = list(
handler("/", function(req) list(status = 200L, body = "Secure!"))
),
tls = cfg
)
server$start()
# Send async request and run event loop
aio <- ncurl_aio(
"https://127.0.0.1:8443/",
tls = tls_config(client = cert$client),
timeout = 2000
)
while (unresolved(aio)) later::run_now(0.1)
aio$status
aio$data
server$close()
\dontshow{\}) # examplesIf}
}
nanonext/man/dot-unresolved.Rd 0000644 0001762 0000144 00000002000 15176112267 016120 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/aio.R
\name{.unresolved}
\alias{.unresolved}
\title{Technical Utility: Query if an Aio is Unresolved}
\usage{
.unresolved(x)
}
\arguments{
\item{x}{an Aio or list of Aios (objects of class 'sendAio', 'recvAio' or
'ncurlAio').}
}
\value{
Logical \code{TRUE} if \code{x} is an unresolved Aio or else \code{FALSE}, or if \code{x}
is a list, the integer number of unresolved Aios in the list.
}
\description{
Query whether an Aio or list of Aios remains unresolved. This is an
experimental technical utility version of \code{\link[=unresolved]{unresolved()}} not intended for
ordinary use. Provides a method of querying the busy status of an Aio without
altering its state in any way i.e. not attempting to retrieve the result or
message.
}
\details{
\code{.unresolved()} is not intended to be used for 'recvAio' returned by a
signalling function, in which case \code{\link[=unresolved]{unresolved()}} must be used in all cases.
}
\keyword{internal}
nanonext/man/close.Rd 0000644 0001762 0000144 00000003533 15176112267 014267 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/context.R, R/listdial.R, R/ncurl.R,
% R/server.R, R/socket.R, R/stream.R
\name{close.nanoContext}
\alias{close.nanoContext}
\alias{close.nanoDialer}
\alias{close.nanoListener}
\alias{close.ncurlSession}
\alias{close.nanoServer}
\alias{close}
\alias{close.nanoSocket}
\alias{close.nanoStream}
\title{Close Connection}
\usage{
\method{close}{nanoContext}(con, ...)
\method{close}{nanoDialer}(con, ...)
\method{close}{nanoListener}(con, ...)
\method{close}{ncurlSession}(con, ...)
\method{close}{nanoServer}(con, ...)
\method{close}{nanoSocket}(con, ...)
\method{close}{nanoStream}(con, ...)
}
\arguments{
\item{con}{a Socket, Context, Dialer, Listener, Stream, or 'ncurlSession'.}
\item{...}{not used.}
}
\value{
Invisibly, an integer exit code (zero on success).
}
\description{
Close Connection on a Socket, Context, Dialer, Listener, Stream, Pipe, or
ncurl Session.
}
\details{
Closing an object explicitly frees its resources. An object can also be
removed directly in which case its resources are freed when the object is
garbage collected.
Closing a Socket associated with a Context also closes the Context.
Dialers and Listeners are implicitly closed when the Socket they are
associated with is closed.
Closing a Socket or a Context: messages that have been submitted for sending
may be flushed or delivered, depending upon the transport. Closing the Socket
while data is in transmission will likely lead to loss of that data. There is
no automatic linger or flush to ensure that the Socket send buffers have
completely transmitted.
Closing a Stream: if any send or receive operations are pending, they will be
terminated and any new operations will fail after the connection is closed.
Closing an 'ncurlSession' closes the http(s) connection.
}
\seealso{
\code{\link[=reap]{reap()}}
}
nanonext/man/ncurl.Rd 0000644 0001762 0000144 00000007167 15176112267 014314 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/ncurl.R
\name{ncurl}
\alias{ncurl}
\title{ncurl}
\usage{
ncurl(
url,
convert = TRUE,
follow = FALSE,
method = NULL,
headers = NULL,
data = NULL,
response = NULL,
timeout = NULL,
tls = NULL
)
}
\arguments{
\item{url}{the URL address.}
\item{convert}{[default TRUE] logical value whether to attempt conversion
of the received raw bytes to a character vector. Set to \code{FALSE} if
downloading non-text data.}
\item{follow}{[default FALSE] logical value whether to automatically follow
redirects (not applicable for async requests). If \code{FALSE}, the redirect
address is returned as response header 'Location'.}
\item{method}{(optional) the HTTP method as a character string. Defaults to
'GET' if not specified, and could also be 'POST', 'PUT' etc.}
\item{headers}{(optional) a named character vector specifying the HTTP
request headers, for example: \cr
\code{c(Authorization = "Bearer APIKEY", "Content-Type" = "text/plain")} \cr
A non-character or non-named vector will be ignored.}
\item{data}{(optional) request data to be submitted. Must be a character
string or raw vector, and other objects are ignored. If a character vector,
only the first element is taken. When supplying binary data, the
appropriate 'Content-Type' header should be set to specify the binary
format.}
\item{response}{(optional) a character vector specifying the response headers
to return e.g. \code{c("date", "server")}. These are case-insensitive and
will return NULL if not present. Specify \code{TRUE} to return all response
headers. A non-character vector will be ignored (other than \code{TRUE}).}
\item{timeout}{(optional) integer value in milliseconds after which the
transaction times out if not yet complete.}
\item{tls}{(optional) applicable to secure HTTPS sites only, a client TLS
Configuration object created by \code{\link[=tls_config]{tls_config()}}. If missing or NULL,
certificates are not validated.}
}
\value{
Named list of 3 elements:
\itemize{
\item \verb{$status} - integer HTTP repsonse status code (200 - OK). Use
\code{\link[=status_code]{status_code()}} for a translation of the meaning.
\item \verb{$headers} - named list of response headers (all headers if
\code{response = TRUE}, or those specified in \code{response}, or NULL otherwise).
If the status code is within the 300 range, i.e. a redirect, the response
header 'Location' is automatically appended to return the redirect address.
\item \verb{$data} - the response body, as a character string if \code{convert = TRUE} (may
be further parsed as html, json, xml etc. as required), or a raw byte
vector if FALSE (use \code{\link[=writeBin]{writeBin()}} to save as a file).
}
}
\description{
nano cURL - a minimalist http(s) client.
}
\section{Public Internet HTTPS}{
When making HTTPS requests over the public internet, you should supply a TLS
configuration to validate server certificates. See \code{\link[=tls_config]{tls_config()}} for
details.
}
\examples{
ncurl(
"https://postman-echo.com/get",
convert = FALSE,
response = c("date", "content-type"),
timeout = 1200L
)
ncurl(
"https://postman-echo.com/get",
response = TRUE,
timeout = 1200L
)
ncurl(
"https://postman-echo.com/put",
method = "PUT",
headers = c(Authorization = "Bearer APIKEY"),
data = "hello world",
timeout = 1500L
)
ncurl(
"https://postman-echo.com/post",
method = "POST",
headers = c(`Content-Type` = "application/json"),
data = '{"key":"value"}',
timeout = 1500L
)
}
\seealso{
\code{\link[=ncurl_aio]{ncurl_aio()}} for asynchronous http requests; \code{\link[=ncurl_session]{ncurl_session()}} for
persistent connections.
}
nanonext/man/ncurl_aio.Rd 0000644 0001762 0000144 00000007535 15176112267 015143 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/ncurl.R
\name{ncurl_aio}
\alias{ncurl_aio}
\title{ncurl Async}
\usage{
ncurl_aio(
url,
convert = TRUE,
method = NULL,
headers = NULL,
data = NULL,
response = NULL,
timeout = NULL,
tls = NULL
)
}
\arguments{
\item{url}{the URL address.}
\item{convert}{[default TRUE] logical value whether to attempt conversion
of the received raw bytes to a character vector. Set to \code{FALSE} if
downloading non-text data.}
\item{method}{(optional) the HTTP method as a character string. Defaults to
'GET' if not specified, and could also be 'POST', 'PUT' etc.}
\item{headers}{(optional) a named character vector specifying the HTTP
request headers, for example: \cr
\code{c(Authorization = "Bearer APIKEY", "Content-Type" = "text/plain")} \cr
A non-character or non-named vector will be ignored.}
\item{data}{(optional) request data to be submitted. Must be a character
string or raw vector, and other objects are ignored. If a character vector,
only the first element is taken. When supplying binary data, the
appropriate 'Content-Type' header should be set to specify the binary
format.}
\item{response}{(optional) a character vector specifying the response headers
to return e.g. \code{c("date", "server")}. These are case-insensitive and
will return NULL if not present. Specify \code{TRUE} to return all response
headers. A non-character vector will be ignored (other than \code{TRUE}).}
\item{timeout}{(optional) integer value in milliseconds after which the
transaction times out if not yet complete.}
\item{tls}{(optional) applicable to secure HTTPS sites only, a client TLS
Configuration object created by \code{\link[=tls_config]{tls_config()}}. If missing or NULL,
certificates are not validated.}
}
\value{
An 'ncurlAio' (object of class 'ncurlAio' and 'recvAio') (invisibly).
The following elements may be accessed:
\itemize{
\item \verb{$status} - integer HTTP repsonse status code (200 - OK). Use
\code{\link[=status_code]{status_code()}} for a translation of the meaning.
\item \verb{$headers} - named list of response headers (all headers if
\code{response = TRUE}, or those specified in \code{response}, or NULL otherwise).
If the status code is within the 300 range, i.e. a redirect, the response
header 'Location' is automatically appended to return the redirect address.
\item \verb{$data} - the response body, as a character string if \code{convert = TRUE} (may
be further parsed as html, json, xml etc. as required), or a raw byte
vector if FALSE (use \code{\link[=writeBin]{writeBin()}} to save as a file).
}
}
\description{
nano cURL - a minimalist http(s) client - async edition.
}
\section{Promises}{
'ncurlAio' may be used anywhere that accepts a 'promise' from the
\CRANpkg{promises} package through the included \code{as.promise} method.
The promises created are completely event-driven and non-polling.
If a status code of 200 (OK) is returned then the promise is resolved with
the reponse body, otherwise it is rejected with a translation of the status
code or 'errorValue' as the case may be.
}
\section{Public Internet HTTPS}{
When making HTTPS requests over the public internet, you should supply a TLS
configuration to validate server certificates. See \code{\link[=tls_config]{tls_config()}} for
details.
}
\examples{
nc <- ncurl_aio(
"https://postman-echo.com/get",
response = c("date", "server"),
timeout = 2000L
)
call_aio(nc)
nc$status
nc$headers
nc$data
\dontshow{if (interactive() && requireNamespace("promises", quietly = TRUE)) withAutoprint(\{ # examplesIf}
library(promises)
p <- as.promise(nc)
print(p)
p2 <- ncurl_aio("https://postman-echo.com/get") \%>\%
then(function(x) cat(x$data))
is.promise(p2)
\dontshow{\}) # examplesIf}
}
\seealso{
\code{\link[=ncurl]{ncurl()}} for synchronous http requests; \code{\link[=ncurl_session]{ncurl_session()}} for
persistent connections.
}
nanonext/man/parse_url.Rd 0000644 0001762 0000144 00000002135 15176112267 015153 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/utils.R
\name{parse_url}
\alias{parse_url}
\title{Parse URL}
\usage{
parse_url(url)
}
\arguments{
\item{url}{character string containing a URL.}
}
\value{
A named character vector of length 7, comprising:
\itemize{
\item \code{scheme} - the URL scheme, such as "http" or "inproc" (always lower
case).
\item \code{userinfo} - the username and password (if supplied in the URL
string).
\item \code{hostname} - the name of the host.
\item \code{port} - the port (if not specified, the default port if defined by
the scheme).
\item \code{path} - the path, typically used with HTTP or WebSocket.
\item \code{query} - the query info (typically following ? in the URL).
\item \code{fragment} - used for specifying an anchor, the part after # in a
URL.
}
Values that cannot be determined are represented by an empty string \code{""}.
}
\description{
Parses a character string containing an RFC 3986 compliant URL as per NNG.
}
\examples{
parse_url("https://user:password@w3.org:8080/type/path?q=info#intro")
parse_url("tcp://192.168.0.2:5555")
}
nanonext/man/ip_addr.Rd 0000644 0001762 0000144 00000001026 15176112267 014557 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/utils.R
\name{ip_addr}
\alias{ip_addr}
\title{IP Address}
\usage{
ip_addr()
}
\value{
A named character string.
}
\description{
Returns a character string comprising the local network IPv4 address, or
vector if there are multiple addresses from multiple network adapters, or
an empty character string if unavailable.
}
\details{
The IP addresses will be named by interface (adapter friendly name on
Windows) e.g. 'eth0' or 'en0'.
}
\examples{
ip_addr()
}
nanonext/man/stop_request.Rd 0000644 0001762 0000144 00000001274 15176112267 015717 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/aio.R
\name{stop_request}
\alias{stop_request}
\title{Stop Request Operation}
\usage{
stop_request(x)
}
\arguments{
\item{x}{an Aio or list of Aios (objects of class 'recvAio' returned by
\code{\link[=request]{request()}}).}
}
\value{
Invisibly, a logical vector.
}
\description{
Stop an asynchronous Aio operation, or a list of Aio operations, created by
\code{\link[=request]{request()}}. This is an augmented version of \code{\link[=stop_aio]{stop_aio()}} that additionally
requests cancellation by sending an integer zero followed by the context ID
over the context, and waiting for the response.
}
\keyword{internal}
nanonext/man/serial_config.Rd 0000644 0001762 0000144 00000003174 15176112267 015767 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/utils.R
\name{serial_config}
\alias{serial_config}
\title{Create Serialization Configuration}
\usage{
serial_config(class, sfunc, ufunc)
}
\arguments{
\item{class}{a character string (or vector) of the class of object custom
serialization functions are applied to, e.g. \code{'ArrowTabular'} or
\code{c('torch_tensor', 'ArrowTabular')}.}
\item{sfunc}{a function (or list of functions) that accepts a reference
object inheriting from \code{class} and returns a raw vector.}
\item{ufunc}{a function (or list of functions) that accepts a raw vector and
returns a reference object.}
}
\value{
A list comprising the configuration. This should be set on a Socket
using \code{\link[=opt<-]{opt<-()}} with option name \code{"serial"}.
}
\description{
Returns a serialization configuration, which may be set on a Socket for
custom serialization and unserialization of non-system reference objects,
allowing these to be sent and received between different R sessions. Once
set, the functions apply to all send and receive operations performed in mode
'serial' over the Socket, or Context created from the Socket.
}
\details{
This feature utilises the 'refhook' system of R native serialization.
}
\examples{
cfg <- serial_config("test_cls", function(x) serialize(x, NULL), unserialize)
cfg
cfg <- serial_config(
c("class_one", "class_two"),
list(function(x) serialize(x, NULL), function(x) serialize(x, NULL)),
list(unserialize, unserialize)
)
cfg
s <- socket()
opt(s, "serial") <- cfg
# provide an empty list to remove registered functions
opt(s, "serial") <- list()
close(s)
}
nanonext/man/handler_stream.Rd 0000644 0001762 0000144 00000007410 15176112267 016150 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/server.R
\name{handler_stream}
\alias{handler_stream}
\title{Create HTTP Streaming Handler}
\usage{
handler_stream(path, on_request, on_close = NULL, method = "*", prefix = FALSE)
}
\arguments{
\item{path}{URI path to match (e.g., "/stream").}
\item{on_request}{Function called when a request arrives. Signature:
\verb{function(conn, req)} where \code{conn} is the connection object and \code{req}
is a list with \code{method}, \code{uri}, \code{headers}, \code{body}.}
\item{on_close}{[default NULL] Function called when the connection closes.
Signature: \verb{function(conn)}}
\item{method}{[default "*"] HTTP method to match (e.g., "GET", "POST").
Use \code{"*"} to match any method.}
\item{prefix}{[default FALSE] Logical, if TRUE matches path as a prefix.}
}
\value{
A handler object for use with \code{\link[=http_server]{http_server()}}.
}
\description{
Creates an HTTP streaming handler using chunked transfer encoding. Supports
any streaming HTTP protocol including Server-Sent Events (SSE), newline-
delimited JSON (NDJSON), and custom streaming formats.
}
\details{
HTTP streaming uses chunked transfer encoding (RFC 9112). The first
\verb{$send()} triggers writing of HTTP headers with \code{Transfer-Encoding: chunked}.
Headers cannot be modified after the first send.
Set an appropriate \code{Content-Type} header for your streaming format:
\itemize{
\item NDJSON: \code{application/x-ndjson}
\item JSON stream: \code{application/stream+json}
\item SSE: \code{text/event-stream} (see \code{\link[=format_sse]{format_sse()}})
\item Plain text: \code{text/plain}
}
\strong{SSE Reconnection:} When an SSE client reconnects after a disconnect, it
sends a \code{Last-Event-ID} header containing the last event ID it received.
Access this via \code{req$headers["Last-Event-ID"]} in \code{on_request} to resume
the event stream from the correct position.
To broadcast to multiple clients, store connection objects in a list and
iterate over them (e.g., \code{lapply(conns, function(c) c$send(data))}).
}
\section{Connection Object}{
The \code{conn} object passed to callbacks has these methods:
\itemize{
\item \code{conn$send(data)}: Send data chunk to client.
\item \code{conn$close()}: Close the connection (sends terminal chunk).
\item \code{conn$set_status(code)}: Set HTTP status code (before first send).
\item \code{conn$set_header(name, value)}: Set response header (before first send).
\item \code{conn$id}: Unique connection identifier.
}
}
\examples{
# NDJSON streaming endpoint
h <- handler_stream("/stream", function(conn, req) {
conn$set_header("Content-Type", "application/x-ndjson")
conn$send('{"status":"connected"}\n')
})
# SSE endpoint with reconnection support
h <- handler_stream("/events", function(conn, req) {
conn$set_header("Content-Type", "text/event-stream")
conn$set_header("Cache-Control", "no-cache")
last_id <- req$headers["Last-Event-ID"]
# Resume from last_id if client is reconnecting
conn$send(format_sse(data = "connected", id = "1"))
})
# Long-lived streaming with broadcast triggered by POST
conns <- list()
handlers <- list(
handler_stream("/stream",
on_request = function(conn, req) {
conn$set_header("Content-Type", "application/x-ndjson")
conns[[as.character(conn$id)]] <<- conn
conn$send('{"status":"connected"}\n')
},
on_close = function(conn) {
conns[[as.character(conn$id)]] <<- NULL
}
),
# POST endpoint triggers broadcast to all streaming clients
handler("/broadcast", function(req) {
msg <- paste0('{"msg":"', rawToChar(req$body), '"}\n')
lapply(conns, function(c) c$send(msg))
list(status = 200L, body = "sent")
}, method = "POST")
)
}
\seealso{
\code{\link[=format_sse]{format_sse()}} for formatting Server-Sent Events.
}
nanonext/man/msleep.Rd 0000644 0001762 0000144 00000001401 15176112267 014437 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/utils.R
\name{msleep}
\alias{msleep}
\title{Sleep Utility}
\usage{
msleep(time)
}
\arguments{
\item{time}{integer number of milliseconds to block the caller.}
}
\value{
Invisible NULL.
}
\description{
Sleep function. May block for longer than requested, with the actual wait
time determined by the capabilities of the underlying system.
}
\details{
Non-integer values for \code{time} are coerced to integer. Negative, logical and
other non-numeric values are ignored, causing the function to return
immediately.
Note that unlike \code{Sys.sleep()}, this function is not user-interruptible by
sending SIGINT e.g. with ctrl + c.
}
\examples{
time <- mclock(); msleep(100); mclock() - time
}
nanonext/man/as.promise.recvAio.Rd 0000644 0001762 0000144 00000001155 15176112267 016627 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/aio.R
\name{as.promise.recvAio}
\alias{as.promise.recvAio}
\title{Make recvAio Promise}
\usage{
\method{as.promise}{recvAio}(x)
}
\arguments{
\item{x}{an object of class 'recvAio'.}
}
\value{
A 'promise' object.
}
\description{
Creates a 'promise' from an 'recvAio' object.
}
\details{
This function is an S3 method for the generic \code{as.promise} for class
'recvAio'.
Requires the \pkg{promises} package.
Allows a 'recvAio' to be used with the promise pipe \verb{\%...>\%}, which schedules
a function to run upon resolution of the Aio.
}
nanonext/man/dot-dispatcher_wait.Rd 0000644 0001762 0000144 00000000641 15176112267 017115 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/dispatcher.R
\name{.dispatcher_wait}
\alias{.dispatcher_wait}
\title{Wait for N Daemon Connections}
\usage{
.dispatcher_wait(disp, n)
}
\arguments{
\item{disp}{External pointer to dispatcher handle.}
\item{n}{Number of connections to wait for.}
}
\value{
Invisible NULL.
}
\description{
Wait for N Daemon Connections
}
\keyword{internal}
nanonext/man/reply.Rd 0000644 0001762 0000144 00000007213 15176112267 014314 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/context.R
\name{reply}
\alias{reply}
\title{Reply over Context (RPC Server for Req/Rep Protocol)}
\usage{
reply(
context,
execute,
recv_mode = c("serial", "character", "complex", "double", "integer", "logical",
"numeric", "raw", "string"),
send_mode = c("serial", "raw"),
timeout = NULL,
...
)
}
\arguments{
\item{context}{a Context.}
\item{execute}{a function which takes the received (converted) data as its
first argument. Can be an anonymous function of the form
\code{function(x) do(x)}. Additional arguments can also be passed in through
\code{...}.}
\item{recv_mode}{[default 'serial'] character value or integer equivalent -
one of \code{"serial"} (1L), \code{"character"} (2L), \code{"complex"} (3L), \code{"double"}
(4L), \code{"integer"} (5L), \code{"logical"} (6L), \code{"numeric"} (7L), \code{"raw"} (8L),
or \code{"string"} (9L). The default \code{"serial"} means a serialised R object; for
the other modes, received bytes are converted into the respective mode.
\code{"string"} is a faster option for length one character vectors.}
\item{send_mode}{[default 'serial'] character value or integer equivalent -
either \code{"serial"} (1L) to send serialised R objects, or \code{"raw"} (2L) to
send atomic vectors of any type as a raw byte vector.}
\item{timeout}{[default NULL] integer value in milliseconds or NULL, which
applies a socket-specific default, usually the same as no timeout. Note
that this applies to receiving the request. The total elapsed time would
also include performing 'execute' on the received data. The timeout then
also applies to sending the result (in the event that the requestor has
become unavailable since sending the request).}
\item{...}{additional arguments passed to the function specified by
'execute'.}
}
\value{
Integer exit code (zero on success).
}
\description{
Implements an executor/server for the rep node of the req/rep protocol.
Awaits data, applies an arbitrary specified function, and returns the result
to the caller/client.
}
\details{
Receive will block while awaiting a message to arrive and is usually the
desired behaviour. Set a timeout to allow the function to return if no data
is forthcoming.
In the event of an error in either processing the messages or in evaluation
of the function with respect to the data, a nul byte \code{00} (or serialized
nul byte) will be sent in reply to the client to signal an error. This is to
be distinguishable from a possible return value. \code{\link[=is_nul_byte]{is_nul_byte()}} can be used
to test for a nul byte.
}
\section{Send Modes}{
The default mode \code{"serial"} sends serialised R objects to ensure perfect
reproducibility within R. When receiving, the corresponding mode \code{"serial"}
should be used. Custom serialization and unserialization functions for
reference objects may be enabled by the function \code{\link[=serial_config]{serial_config()}}.
Mode \code{"raw"} sends atomic vectors of any type as a raw byte vector, and must
be used when interfacing with external applications or raw system sockets,
where R serialization is not in use. When receiving, the mode corresponding
to the vector sent should be used.
}
\examples{
req <- socket("req", listen = "inproc://req-example")
rep <- socket("rep", dial = "inproc://req-example")
ctxq <- context(req)
ctxp <- context(rep)
send(ctxq, 2022, block = 100)
reply(ctxp, execute = function(x) x + 1, send_mode = "raw", timeout = 100)
recv(ctxq, mode = "double", block = 100)
send(ctxq, 100, mode = "raw", block = 100)
reply(ctxp, recv_mode = "double", execute = log, base = 10, timeout = 100)
recv(ctxq, block = 100)
close(req)
close(rep)
}
nanonext/man/is_error_value.Rd 0000644 0001762 0000144 00000001633 15176112267 016201 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/utils.R
\name{is_error_value}
\alias{is_error_value}
\alias{is_nul_byte}
\title{Error Validators}
\usage{
is_error_value(x)
is_nul_byte(x)
}
\arguments{
\item{x}{an object.}
}
\value{
Logical value TRUE or FALSE.
}
\description{
Validator functions for error value types created by \pkg{nanonext}.
}
\details{
Is the object an error value generated by the package. All non-success
integer return values are classed 'errorValue' to be distinguishable from
integer message values. Includes error values returned after a timeout etc.
Is the object a nul byte.
}
\examples{
s <- socket()
r <- recv_aio(s, timeout = 10)
call_aio(r)$data
close(s)
r$data == 5L
is_error_value(r$data)
is_error_value(5L)
is_nul_byte(as.raw(0L))
is_nul_byte(raw(length = 1L))
is_nul_byte(writeBin("", con = raw()))
is_nul_byte(0L)
is_nul_byte(NULL)
is_nul_byte(NA)
}
nanonext/man/recv.Rd 0000644 0001762 0000144 00000006102 15176112267 014114 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/sendrecv.R
\name{recv}
\alias{recv}
\title{Receive}
\usage{
recv(
con,
mode = c("serial", "character", "complex", "double", "integer", "logical", "numeric",
"raw", "string"),
block = NULL
)
}
\arguments{
\item{con}{a Socket, Context or Stream.}
\item{mode}{[default 'serial'] character value or integer equivalent - one
of \code{"serial"} (1L), \code{"character"} (2L), \code{"complex"} (3L), \code{"double"} (4L),
\code{"integer"} (5L), \code{"logical"} (6L), \code{"numeric"} (7L), \code{"raw"} (8L), or
\code{"string"} (9L). The default \code{"serial"} means a serialised R object; for
the other modes, received bytes are converted into the respective mode.
\code{"string"} is a faster option for length one character vectors. For
Streams, \code{"serial"} will default to \code{"character"}.}
\item{block}{[default NULL] which applies the connection default (see
section 'Blocking' below). Specify logical \code{TRUE} to block until successful
or \code{FALSE} to return immediately even if unsuccessful (e.g. if no
connection is available), or else an integer value specifying the maximum
time to block in milliseconds, after which the operation will time out.}
}
\value{
The received data in the \code{mode} specified.
}
\description{
Receive data over a connection (Socket, Context or Stream).
}
\section{Errors}{
In case of an error, an integer 'errorValue' is returned (to be
distiguishable from an integer message value). This can be verified using
\code{\link[=is_error_value]{is_error_value()}}.
If an error occurred in unserialization or conversion of the message data to
the specified mode, a raw vector will be returned instead to allow recovery
(accompanied by a warning).
}
\section{Blocking}{
For Sockets and Contexts: the default behaviour is non-blocking with
\code{block = FALSE}. This will return immediately with an error if no messages
are available.
For Streams: the default behaviour is blocking with \code{block = TRUE}. This will
wait until a message is received. Set a timeout to ensure that the function
returns under all scenarios. As the underlying implementation uses an
asynchronous receive with a wait, it is recommended to set a small positive
value for \code{block} rather than \code{FALSE}.
}
\examples{
s1 <- socket("pair", listen = "inproc://nanonext")
s2 <- socket("pair", dial = "inproc://nanonext")
send(s1, data.frame(a = 1, b = 2))
res <- recv(s2)
res
send(s1, data.frame(a = 1, b = 2))
recv(s2)
send(s1, c(1.1, 2.2, 3.3), mode = "raw")
res <- recv(s2, mode = "double", block = 100)
res
send(s1, "example message", mode = "raw")
recv(s2, mode = "character")
close(s1)
close(s2)
req <- socket("req", listen = "inproc://nanonext")
rep <- socket("rep", dial = "inproc://nanonext")
ctxq <- context(req)
ctxp <- context(rep)
send(ctxq, data.frame(a = 1, b = 2), block = 100)
recv(ctxp, block = 100)
send(ctxq, c(1.1, 2.2, 3.3), mode = "raw", block = 100)
recv(ctxp, mode = "double", block = 100)
close(req)
close(rep)
}
\seealso{
\code{\link[=recv_aio]{recv_aio()}} for asynchronous receive.
}
nanonext/man/handler_inline.Rd 0000644 0001762 0000144 00000002026 15176112267 016131 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/server.R
\name{handler_inline}
\alias{handler_inline}
\title{Create Inline Static Content Handler}
\usage{
handler_inline(path, data, content_type = NULL, prefix = FALSE)
}
\arguments{
\item{path}{URI path to match (e.g., "/robots.txt").}
\item{data}{Content to serve. Character data is converted to raw bytes.}
\item{content_type}{MIME type (e.g., "text/plain", "application/json").
Defaults to "application/octet-stream" if NULL.}
\item{prefix}{[default FALSE] Logical, if TRUE matches path as a prefix.}
}
\value{
A handler object for use with \code{\link[=http_server]{http_server()}}.
}
\description{
Creates an HTTP handler that serves in-memory static content. Useful for
small files like robots.txt or inline JSON/HTML.
}
\examples{
h1 <- handler_inline("/robots.txt", "User-agent: *\nDisallow:",
content_type = "text/plain")
h2 <- handler_inline("/health", '{"status":"ok"}',
content_type = "application/json")
}
nanonext/man/survey_time.Rd 0000644 0001762 0000144 00000002461 15176112267 015534 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/opts.R
\name{survey_time}
\alias{survey_time}
\title{Set Survey Time}
\usage{
survey_time(con, value = 1000L)
}
\arguments{
\item{con}{a Socket or Context using the 'surveyor' protocol.}
\item{value}{[default 1000L] integer survey timeout in milliseconds.}
}
\value{
Invisibly, the passed Socket or Context.
}
\description{
For a socket or context using the surveyor protocol in a surveyor/respondent
pattern. Set the survey timeout in milliseconds (remains valid for all
subsequent surveys). Messages received by the surveyor after the timer has
ended are discarded.
}
\details{
After using this function, to start a new survey, the surveyor must:
\itemize{
\item send a message.
\item switch to receiving responses.
}
To respond to a survey, the respondent must:
\itemize{
\item receive the survey message.
\item send a reply using \code{\link[=send_aio]{send_aio()}} before the survey has timed out (a
reply can only be sent after receiving a survey).
}
}
\examples{
sur <- socket("surveyor", listen = "inproc://nanonext")
res <- socket("respondent", dial = "inproc://nanonext")
survey_time(sur, 1000)
send(sur, "reply to this survey")
aio <- recv_aio(sur)
recv(res)
s <- send_aio(res, "replied")
call_aio(aio)$data
close(sur)
close(res)
}
nanonext/man/nano.Rd 0000644 0001762 0000144 00000010224 15176112267 014110 0 ustar ligges users % Generated by roxygen2: do not edit by hand
% Please edit documentation in R/nano.R
\name{nano}
\alias{nano}
\title{Create Nano Object}
\usage{
nano(
protocol = c("bus", "pair", "poly", "push", "pull", "pub", "sub", "req", "rep",
"surveyor", "respondent"),
dial = NULL,
listen = NULL,
tls = NULL,
autostart = TRUE
)
}
\arguments{
\item{protocol}{[default 'bus'] choose protocol - \code{"bus"}, \code{"pair"},
\code{"poly"}, \code{"push"}, \code{"pull"}, \code{"pub"}, \code{"sub"}, \code{"req"}, \code{"rep"},
\code{"surveyor"}, or \code{"respondent"} - see \link{protocols}.}
\item{dial}{(optional) a URL to dial, specifying the transport and address as
a character string e.g. 'inproc://anyvalue' or 'tcp://127.0.0.1:5555' (see
\link{transports}).}
\item{listen}{(optional) a URL to listen at, specifying the transport and
address as a character string e.g. 'inproc://anyvalue' or
'tcp://127.0.0.1:5555' (see \link{transports}).}
\item{tls}{[default NULL] for secure tls+tcp:// or wss:// connections only,
provide a TLS configuration object created by \code{\link[=tls_config]{tls_config()}}.}
\item{autostart}{[default TRUE] whether to start the dialer/listener. Set
to FALSE if setting configuration options on the dialer/listener as it is
not generally possible to change these once started. For dialers only: set
to NA to start synchronously - this is less resilient if a connection is
not immediately possible, but avoids subtle errors from attempting to use
the socket before an asynchronous dial has completed.}
}
\value{
A nano object of class 'nanoObject'.
}
\description{
Create a nano object, encapsulating a Socket, Dialers/Listeners and
associated methods.
}
\details{
This function encapsulates a Socket, Dialer and/or Listener, and its
associated methods.
The Socket may be accessed by \verb{$socket}, and the Dialer or Listener by
\verb{$dialer[[1]]} or \verb{$listener[[1]]} respectively.
The object's methods may be accessed by \code{$} e.g. \verb{$send()} or \verb{$recv()}.
These methods mirror their functional equivalents, with the same arguments
and defaults, apart from that the first argument of the functional equivalent
is mapped to the object's encapsulated socket (or context, if active) and
does not need to be supplied.
More complex network topologies may be created by binding further dialers or
listeners using the object's \verb{$dial()} and \verb{$listen()} methods. The new
dialer/listener will be attached to the object e.g. if the object already has
a dialer, then at \verb{$dialer[[2]]} etc.
Note that \verb{$dialer_opt()} and \verb{$listener_opt()} methods will be available
once dialers/listeners are attached to the object. These methods get or apply
settings for all dialers or listeners equally. To get or apply settings for
individual dialers/listeners, access them directly via \verb{$dialer[[2]]} or
\verb{$listener[[2]]} etc.
The methods \verb{$opt()}, and also \verb{$dialer_opt()} or \verb{$listener_opt()} as may be
applicable, will get the requested option if a single argument \code{name} is
provided, and will set the value for the option if both arguments \code{name} and
\code{value} are provided.
For Dialers or Listeners not automatically started, the \verb{$dialer_start()} or
\verb{$listener_start()} methods will be available. These act on the most recently
created Dialer or Listener respectively.
For applicable protocols, new contexts may be created by using the
\verb{$context_open()} method. This will attach a new context at \verb{$context} as
well as a \verb{$context_close()} method. While a context is active, all object
methods use the context rather than the socket. A new context may be created
by calling \verb{$context_open()}, which will replace any existing context. It is
only necessary to use \verb{$context_close()} to close the existing context and
revert to using the socket.
}
\examples{
nano <- nano("bus", listen = "inproc://nanonext")
nano
nano$socket
nano$listener[[1]]
nano$opt("send-timeout", 1500)
nano$opt("send-timeout")
nano$listen(url = "inproc://nanonextgen")
nano$listener
nano1 <- nano("bus", dial = "inproc://nanonext")
nano$send("example test", mode = "raw")
nano1$recv("character")
nano$close()
nano1$close()
}
nanonext/man/figures/ 0000755 0001762 0000144 00000000000 15077362607 014340 5 ustar ligges users nanonext/man/figures/logo.png 0000644 0001762 0000144 00000034467 15077362607 016024 0 ustar ligges users ‰PNG
IHDR X .eÞ ýPLTE (UH ¾N¢À (UEŸ½K¡¿P£ÁFŸ¾Bž½@¼J¡¿M¢ÀR¤ÁT¥Â<œ»>œ»V¦ÃV¥Ã:›ºÿÿÿY§Ä7šº\¨ÅZ§Ä[¨Å3™¹9›º6š¹`ªÆ^©Æ A~(T5š¹c¬Èa«Çf®É1™¹/˜¸äíòýþþðõøºÓß«ÊÙŠ¹Îùûü†·ÍÔkéðôÜèË-˜¸y1cò»8ÕãëM’p®Çîšßuëòõ3gò½=óµ-ç…½XÛqÙæíi«ÅK9q-])V¬Mã~ÛtÑgÊbÁ×â
7nðžæ€àêðÑàéò¸2ò³(î–áxØmÍcöùûÎßèÂÔv°É/`Á^ËÝæ¯ÌÛ ÄÕ
@}ñ°%ïž
§IÂZe©Ä¥F|³Ê
(Tê…òöù¾ÕᔽÑóÇQ²R˜?¶SôøúY¥ÁÍg?ºU¯Mñ©ñ§ð¤’:œBì‹íóöÄØãðÊt®S§N‹5ãza¨ÄíÄlñ®¡DÇÚäæµiñÈdóÄIñ«ð¢£ÆÖšÁÓòÆZߥYñ¬"^¦Âç´bÓ˜Qï½O•CîÁcÝ©cî¸Hò¿E×~ §T„0Þz]–ïÈmÁ–Tê¨>»_
×rè‚ê¿kêºdÕ¢_êµXî½WÃ’NÀ‹Ĕ9ð³6ì¨3Æjµc߀ã¬`Ë›X˜¿Ñ
S”ä DÏv[2£Å&m˜×Uâ£P®H¨:Ù‹4ê 0ä”/ºu*á‹$Íp e—ç¬VÛ’=£{4ì—(Tí²B¬‡BË|+Û„%½e—ÀÓÆ¡`וJ¢‰GÃ4ã‘)ÅpÐoŸPÆe²Xv'³˜XÞšFÑŽC¯j#œ`ë˜æ‘&l˜µ‘N¶<—~; l%>ca¬ tRNS Yd$$h 5çIDATxÚìÔÁ
@ ÑÅÜœ´"¡
*Њ«hàÏ«a2%©x”¢Ñ!`éh†°ƒt4CHB:þ¥¼B2š!¤!`éø´Ï›!áËqÓ²ž†âfÏŽq£‚(K¯¦êUwu÷2ïÄXH 'FrâìÌ7 !ä†NàFìú;ô'Õ
~Uò–¡Ûu^5Ißfÿe覴ãét½53ûþ{v°sËУB¢o§Ó§f9²˜a×–‘ç‡X«AÖM®¥š¦Èx™!ìÖ2ôYU¬K?–h€Š³ØŸ§Â.-C?D
tŠ’
³^²w³ƒýY†~ÖF2©‰‹YÑ ná‚z?CØ™eäãÄB¤7µ32 MË!Ó2BD¾ÌvdúÕ«‹Ú‰"«y .2ʡЙ¼éë×ÙÁN,C/Œ°µH#¥8èž(ÖP-uO©ƒ©”Ûùö`zú‹hÑXk?JV‹”a[g€”›f¾§±¦ÃÍáâ-C·H`´Nk°‚*^³¶”¸½KÒ¸R¤¨=Î.Ú2tßLR±¶m½Fñ
í¶öjk#
/ðì0¬§Z{€Ï³ƒ‹µ=F°¼MJCíkcËJñ0nD×Ü4%”\Å’ú‘²U
²Î úRçäל-§¬Õ±‰¶†³n½{NzÈo¤TTÏW¯J Ë®s‚þÇΙ¤XSDQÏåö÷F“É{–Ù#.C¹·áÄ¡›t nÑ"">»B-!¿QÎÎ ‚œÿG5Ǹ[V&s0sñ¾5ªu*°ü]NH·‘Ó‘$8™¿º.ÂÿŒ:yN6›œBUC(™½ÝT)=ÊwZKÌ›¼Ÿ²„!UÔ¤ÁŒÏ?¼îÁÿˆß¾_x¸Ó%ÑI9±(…I`M&²y…ÀÔkl5c#IåD w«}ýþ7üæI}óeFXÜlÄ¡¶f±·Þ…jÁß?B©’Å™M §d•äBxU)‘È¥ ÿüö1}Ð[)ù_°›Sº3Û,JI}g‰+6.#›DL¹Sê¬ 2vÊþ½ôëçNæ‚°RºÏaƒ°‡9'·Ž¡M>tÇïÕ„´‡4ª“Ð](®®ºúå×μ!î@©E±FðZ©) ÄêÒv9ÊY·²›Ñ`ÉAe)’$îV%&lu)è×ÌožÍwŸ7TU˜A”8VæšçœAEj~?#l“v½GÏà )$DjåPqfIÅ’AºúåWˬ$NCÌħ-ä3æâ7H×]g¬{èÀÑb¸=9†·fa<‘œo™ºØ‘laBF\—‚~•<"µX£¢î@x0Ø<¬`ò3-Bõ1MUqÆæÇºÕ¹™ìl8OR7É*ÇÕ/¿:ÞúíæøûÏrQÓCÏ)Æì6ÍImµEÔ=ªY™',â“^ÏìJÙÐa}Hßœá÷‰LFÆ!ô¥ _†HO„Sq#ˆúœÆgãYd7ÎŧW®$R×n± ‘"CA0QÐ{Û`0Òƒa2€:ìød×5qz=ÚBu͹º†"¦ÃÖR?§§ó¤s¥RRûŒKAÿ<ÖÉ2(ƒÀª·ÛmŸÄN3ÎQö%Z“ÄÉ\osQó›ç̳ɳˆï3g81›’’„ó·3®‰Ó¿ÌcL`⻓¨ì›9ˆ…ÖýŽ¢›ª{ZíͦàùÎáîÇ@Ñ!~ºjƒƒXTŒ}y˜Ÿ÷<|E¤Ò`ô0ãûÏ®W6_ΡH2¤(§5tñ¡½G°¡Zœ@*¥ª‚(³nAÄJËÐl¹¬©‡y_Œç7I6gÖ @R7ãRÐ/ååC$²¡fÉÆ
"fÌnf§‘—j«@[î–J)‘Aã=i/iDÎæF±(í™ÏSxžlÔ©“í3®~ùE¼\'‹){F°•îŽ`&“¨‰0÷.ÝMü¼Æ-áÜ zöì±µÖÈ´X“ A¼¡õætŸÎT™áf&ãRÐ/à¥C$P bµ‚EÖ˜;6…ÛzvgÙãqœ4ÙbE^%fi=Þn”Ãg‘M‘’pƬ¢Î?™qMœ^À‹t²Õ´0|yüôg–)W³»ef½)#Hâý!‰^~ëÝç\ïÈ;ïh‡³˜³ó@%†q@DÌíÏg\§ð‚w--PÆ¥B‚`°Îõ‡¤Í9‰R+†n2+„õ &¢†
ñŽ0Zù
s$HÄ܃vMgþk×ÄéÿœN˜
KÄÌÜR´‹æ÷L Ü]h¨ë“AÛˆpÔ‚V4”|ånTƒ3ᘱx÷Œ0÷©M=ãRÐøgt2t·ÌyBÜX‰¥iÉœ‘°2k-t×p&Ê‚¹ZEˆšÐx{ÄÝlð;›¸¿ËI…ùéä¿•q)èü:y:ˆz$ˆ¢÷i& …OãÌÉÉáf–Ù
hgi%%µØ†Ý„w¶Yw›®ç¨¹Ü@ Îù·3.ý{ç‘+EDAôR髲ڨ{†‘°`Á–SpR‘0ÂÛá=ÛYÄffS¡—ñúsý:ya¦0ªguˆÁ0DV"ÄÕÕð$JE&œ>‹ u6'›—¹JH±Íßv¦~[Œm⸊¸„q(èׯ“±{À÷iR'ãh7ɘݳMSƒ/ÀX÷=ÀUŠªì‘Ÿ:‹WƪÂNêyÛaÒ;•¬´Õ{¤ê¸ŠKÇÄéšu2nÃcõ¸ŠÔ´fMM·Ì´`b¬ÑxÄk2)q+$ON%
wʶ DÜ–€§†òIºƒyŸ ¿q(èëÔÉ#
æqåL-ÕŒFÀ,Yù–Þ
¶ˆÇë£aZcIÆ´»Ò4¸h–œrƒHê¶ÚHé Ò»„‰× ßÄ8&N×u×òùˆ];GØ=ÄI,l9I¤qpô
#
P¦NÓ~ÅLêR¹Â–Ü´¥RÖ+jçSYh“’ý
º!OJõ%ØÜý÷1}-C$Ži°
G›LË"‚»
wÐvsóA]±uq~4æ9u¾+v²«°íA)!JÎ Šèëð‰ßÊxqLœ~ÿ]K&rÎÕÓ 5›n‹4,& …h{×Ja¤“lÒ”w³ÝN]tgíd2ß¿÷èìTMáTí·3ý{uòË=fDo¼#ÌÖá"¸Í ¡ÖˆäƒV
œATÚ„lÄÚ{ƒ¶&½«PœNËÄÁè±F¯ J×Á8ôïû<‹àTiÄœ#·nG;‹E4ÓN%ù¤•‚l¢ºU¹Ùz)qѬÂuÇRÚiQö¹xeE¬t]ŒCAÿ&ÌñxHË&¸HÆÕæÌÁýf™ƒ”?o¥t¼Ù¬UZó‚7ÿ]ž59u[êdy×<%%Æ52ý[2‰îafäŸÅí9ž0Ó2‹ªPûb+…z©ƒ–ŽjXXÏ)¥”Ë,¤§¥«€„šæ53}ñ]KÜÚQo9Cƒ¹Í‚”'6Î.,Mõ«(‘’RSJw»m›¤ çx>iÑiN»~Æ¡ /;vÍWáC˜²€áŽðÆÜ{¬ÃêHÁ7[)Y¥dL[.•&’&ÔOÔúƒ’åÎC²?Ã8ô/ëd¦Õ1ܽ