Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ tests/random-test-server
tests/proxy-test-client
tests/proxy-test-server
tests/unit-test-client
tests/unit-test-proxy-router
tests/unit-test-server
tests/version
tests/stamp-h2
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ Reply:
Proxy:

- [modbus_proxy](modbus_proxy.md)
- [modbus_proxy_router](modbus_proxy_router.md)

## Advanced functions

Expand Down
60 changes: 60 additions & 0 deletions docs/modbus_proxy_router.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# modbus_proxy_router

## Name

modbus_proxy_router - route a request to the backend serving its unit identifier

## Synopsis

```c
typedef modbus_t *(*modbus_backend_resolver_t)(int slave, void *user);

int modbus_proxy_router(modbus_t *frontend_ctx, const uint8_t *req, int req_length, modbus_backend_resolver_t resolve, void *user);
```

## Description

The *modbus_proxy_router()* function shall forward the request *req* of length
*req_length* received on *frontend_ctx* to the backend context returned by the
*resolve* callback for the addressed unit identifier, and relay the response
back. It is a convenience over [modbus_proxy](modbus_proxy.md) for a gateway that
bridges to several downstream links, for example one serial port per group of
slaves.

The *resolve* callback receives the unit identifier and the opaque *user*
pointer, and shall return the backend *modbus_t* serving that slave, or NULL if
the slave is not routable. When it returns NULL, a gateway path exception
(MODBUS_EXCEPTION_GATEWAY_PATH) is sent to the frontend. Backend failures, such
as a downstream timeout, are reported by *modbus_proxy()* as a gateway target
exception.

## Return value

The *modbus_proxy_router()* function shall return the length of the response
relayed to the frontend if successful. Otherwise it shall return -1 and set
errno. When *resolve* returns NULL, errno is set to EMBXGPATH and a gateway path
exception has been sent to the frontend.

## Example

```c
static modbus_t *route(int slave, void *user)
{
modbus_t **backends = user; /* indexed by unit id */
return backends[slave];
}

for (;;) {
uint8_t req[MODBUS_TCP_MAX_ADU_LENGTH];
int rc = modbus_receive(frontend, req);
if (rc > 0) {
modbus_proxy_router(frontend, req, rc, route, backends);
}
}
```

## See also

- [modbus_proxy](modbus_proxy.md)
- [modbus_get_request_slave](modbus_get_request_slave.md)
- [modbus_reply_router](modbus_reply_router.md)
38 changes: 38 additions & 0 deletions src/modbus.c
Original file line number Diff line number Diff line change
Expand Up @@ -1444,6 +1444,44 @@ int modbus_proxy(modbus_t *frontend_ctx,
return send_msg(frontend_ctx, frontend_rsp, frontend_rsp_length);
}

/* Forward a request to the backend context serving its unit identifier and relay
the response, using `resolve` to pick the backend. A convenience over
modbus_proxy() for a gateway that bridges to several downstream links. When
`resolve` returns NULL, a gateway path exception is sent so the client learns
the unit is unroutable; backend failures are reported by modbus_proxy(). */
int modbus_proxy_router(modbus_t *frontend_ctx,
const uint8_t *req,
int req_length,
modbus_backend_resolver_t resolve,
void *user)
{
modbus_t *backend_ctx;

if (frontend_ctx == NULL || req == NULL || resolve == NULL) {
errno = EINVAL;
return -1;
}

if (req_length < (int) (frontend_ctx->backend->header_length + 1)) {
errno = EMBBADDATA;
return -1;
}

backend_ctx = resolve(req[frontend_ctx->backend->header_length - 1], user);
if (backend_ctx == NULL) {
/* Tell the client the unit is unroutable, then the caller. A failure to
send the exception is more specific, so it keeps its own errno. */
if (modbus_reply_exception(frontend_ctx, req, MODBUS_EXCEPTION_GATEWAY_PATH) ==
-1) {
return -1;
}
errno = EMBXGPATH;
return -1;
}

return modbus_proxy(frontend_ctx, backend_ctx, req, req_length);
}

/* Reads IO status */
static int read_io_status(modbus_t *ctx, int function, int addr, int nb, uint8_t *dest)
{
Expand Down
8 changes: 8 additions & 0 deletions src/modbus.h
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,14 @@ MODBUS_API int modbus_proxy(modbus_t *frontend_ctx,
const uint8_t *req,
int req_length);

/* Resolve the backend context serving a unit identifier, or NULL if none. */
typedef modbus_t *(*modbus_backend_resolver_t)(int slave, void *user);
MODBUS_API int modbus_proxy_router(modbus_t *frontend_ctx,
const uint8_t *req,
int req_length,
modbus_backend_resolver_t resolve,
void *user);

MODBUS_API int modbus_enable_quirks(modbus_t *ctx, unsigned int quirks_mask);
MODBUS_API int modbus_disable_quirks(modbus_t *ctx, unsigned int quirks_mask);

Expand Down
6 changes: 5 additions & 1 deletion tests/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ noinst_PROGRAMS = \
random-test-client \
unit-test-server \
unit-test-client \
unit-test-proxy-router \
proxy-test-server \
proxy-test-client \
version
Expand Down Expand Up @@ -36,6 +37,9 @@ unit_test_server_LDADD = $(common_ldflags)
unit_test_client_SOURCES = unit-test-client.c unit-test.h
unit_test_client_LDADD = $(common_ldflags)

unit_test_proxy_router_SOURCES = unit-test-proxy-router.c
unit_test_proxy_router_LDADD = $(common_ldflags)

proxy_test_server_SOURCES = proxy-test-server.c
proxy_test_server_LDADD = $(common_ldflags)

Expand All @@ -57,4 +61,4 @@ AM_CFLAGS = $(LIBMODBUSCFLAGS) $(WARNING_CFLAGS)
CLEANFILES = *~ *.log

noinst_SCRIPTS=unit-tests.sh
TESTS=./unit-tests.sh
TESTS=./unit-tests.sh unit-test-proxy-router
Loading