Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gcoap: Add file server #14397

Closed
wants to merge 13 commits into from
Closed

Conversation

chrysn
Copy link
Member

@chrysn chrysn commented Jun 30, 2020

Contribution description

This ties in the Gcoap module with the VFS module in the style of a static web server.

This is quite minimal (no write support, no blockwise, no ETag), but already fully documented. The example commit is marked WIP as it's not sure yet where to best put the example.

Testing procedure

Currently: Run the gcoap example, and GET /vfs/const/ or GET /vfs/const/hello-world, which should show a directory listing or the file content, respectively.

@chrysn chrysn added State: WIP State: The PR is still work-in-progress and its code is not in its final presentable form yet Type: new feature The issue requests / The PR implemements a new feature for RIOT Area: CoAP Area: Constrained Application Protocol implementations labels Jun 30, 2020
@chrysn chrysn self-assigned this Jun 30, 2020
@chrysn chrysn marked this pull request as draft June 30, 2020 13:13
@chrysn
Copy link
Member Author

chrysn commented Jun 30, 2020

There is now support for ETag and block-wise transfer. (Not on directory listings yet, though).

Example-wise, I'll abandon piggy-backing on gcoap and instead add a few lines (sic: 2 lines header, 17 lines static structs, 2 lines code to main) to the filesystem example, conditionally included. Creating good example directories and mouting/formatting is way too much hassle otherwise.

@cgundogan
Copy link
Member

There is now support for ETag

👍

I'd like to see the ETag mechanism done in gcoap, so it can be reused for different scenarios (not only serving files). We can generalize it later, if you want. I'll take a deeper look at the provided implementation here.

@benpicco
Copy link
Contributor

Is this still a draft?

@benpicco benpicco closed this Jul 27, 2020
@benpicco benpicco reopened this Jul 27, 2020
@benpicco
Copy link
Contributor

(Sorry, wrong button)

@chrysn
Copy link
Member Author

chrysn commented Jul 28, 2020 via email

@benpicco
Copy link
Contributor

it struggles with the different file systems's directory listing responses of one returning "/entry" and the other "entry".

That sounds like a bug in the FS abstraction. What file systems are affected?

@chrysn
Copy link
Member Author

chrysn commented Jul 28, 2020 via email

This is starting point for a file server. It works but does not do
block-wise (truncating large files or directory listings), ETag or write
support, and fails to handle read errors (after a successful open)
properly.
The note on partially cached versions is not applicable yet (for lack of
block-wise support), but will become relevant.
The filesystem example is ideal to demonstrate the fileserver as it has
a usable default set of demo files, and different file systems can be
mounted for experimentation.

The impact on the filesystem example outside the coapfileserver context
is minimal: Only a few lines of includes (2), static configuration (17
including comments and whitespace) and startup code (2) are added under
ifdef guards that clearly mark them as optional and Gcoap related. They
are only enabled if NETWORK=1 is set.
With the "resolved agains the anchor" reading obsolete, link targets can
easily be expressed relatively.
Copy link
Contributor

@leandrolanzieri leandrolanzieri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice contribution! I made some initial comments, I'll be testing this shortly.


/* Normalizing fields whose value can change without affecting the ETag */
stattag->stat.st_nlink = 0;
memset(&stattag->stat.st_atim, 0, sizeof(stattag->stat.st_atim));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm getting errors on this member access when compiling for cortex.

Suggested change
memset(&stattag->stat.st_atim, 0, sizeof(stattag->stat.st_atim));
memset(&stattag->stat.st_atime, 0, sizeof(stattag->stat.st_atime));

#include <net/gcoap.h>
#include <vfs.h>
#include <fcntl.h>
#include <error.h>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also getting errors with this inclusion, is it needed?

#include <fcntl.h>
#include <error.h>

#define ENABLE_DEBUG 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#define ENABLE_DEBUG 1
#define ENABLE_DEBUG 0

Comment on lines +2 to +4

SRC = coapfileserver.c

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed, the file should be included automatically:

Suggested change
SRC = coapfileserver.c

#include "debug.h"

/** Maximum length of an expressible path, including the trailing 0 character. */
#define COAPFILESERVER_PATH_MAX (64)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this value be configured?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be made, but I'm hoping to get rid of the need to assemble any paths by using something openat-like (issue or PR TBD).

Comment on lines +19 to +21
* As usual, GET operations are used to read files<!-- WRITESUPPORT, and PUT writes to files.
* In the current implementation, PUTs are expressed as random-access, meaning
* that files are not updated atomically -->.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are there some parts commented out?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because I had write support planned to go in from the start, had the documentation already written in the first step, and don't want to have to write it all again later on. It'll better if I move them over to a nascent write-support-for-fileserver branch instead.

* ```
*
* The allowed methods dictate whether it's read-only (``COAP_GET``) or (in the
* future<!-- WRITESUPPORT -->) read-write (``1COAP_GET | COAP_PUT | COAP_DELETE``).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* future<!-- WRITESUPPORT -->) read-write (``1COAP_GET | COAP_PUT | COAP_DELETE``).
* future<!-- WRITESUPPORT -->) read-write (``COAP_GET | COAP_PUT | COAP_DELETE``).

* # About
*
* This maps files in the local file system onto a resources in CoAP. In that,
* is is what is called a static web server in the unconstrained web.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* is is what is called a static web server in the unconstrained web.
* it is what is called a static web server in the unconstrained web.

Comment on lines +319 to +332
static const struct coapfileserver_entry vfs = { "", 1 };

/* CoAP resources. Must be sorted by path (ASCII order). */
static const coap_resource_t _resources[] = {
{ "/vfs", COAP_GET | COAP_MATCH_SUBTREE, coapfileserver_handler, (void*)&vfs },
};

static gcoap_listener_t _listener = {
&_resources[0],
ARRAY_SIZE(_resources),
NULL,
NULL
};

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to move this to an auto-init function? Is there a reason why this needs to be registered from the application?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only if it were to be made configurable as to the file system entry point and to the coap entry point -- we really shouldn't pick resource names on users' CoAP servers. (And there'd still need to allow manual management for things like ACLs, or when different mount points need different treatment on the CoAP side, eg. w/rt writability).

AFAIK we currently don't do any automatically inserted CoAP resources (other than /.well-known/core). Doing that efficiently would mean putting sorted entries into an array, AFAICT that can't be done even with XFA (#15002) and'd thus need pre-sorting by wherethroughever the configured paths are set. (For example, if coapfileserver did this and so did a hypothetical resourcedirectory, they'd need to be put as ["/.well-known/core", "/coapfileserver", "/rd"] with the one set of names but as ["/.well-known/core", "/cord-server", "/files"] with others).

I'll give it a try, but it may result in an "I'd rather do this as a next step".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only if it were to be made configurable as to the file system entry point and to the coap entry point -- we really shouldn't pick resource names on users' CoAP servers. (And there'd still need to allow manual management for things like ACLs, or when different mount points need different treatment on the CoAP side, eg. w/rt writability).

Agreed, I was thinking as well on making the entry point configurable.

Doing that efficiently would mean putting sorted entries into an array

Yeah, that's true. Maybe if we could adapt the path matching function on nanocoap we could get rid of the alphabetical ordering constraint? In any case, if that's needed it would be work for a follow up PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe if we could adapt the path matching function on nanocoap

Sorry, meant the gcoap matching, that's the one that enforces this.

@chrysn
Copy link
Member Author

chrysn commented Oct 26, 2020

Thanks for the review; most points can be trivially addressed with the next batch (which may take a few days though).

The st_atim vs. st_atime and include parts are a bit icky as the otherwise RIOT-style vfs calls fan out to whatever the currently used libc decides to do about POSIX stat properties, but I'll manage and try building for some more platforms.

@stale
Copy link

stale bot commented Jun 2, 2021

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you want me to ignore this issue, please mark it with the "State: don't stale" label. Thank you for your contributions.

@stale stale bot added the State: stale State: The issue / PR has no activity for >185 days label Jun 2, 2021
@stale stale bot closed this Jul 8, 2021
@fjmolinas
Copy link
Contributor

Is this one something that could be revived @chrysn?

@chrysn
Copy link
Member Author

chrysn commented Apr 4, 2022 via email

@benpicco
Copy link
Contributor

benpicco commented Apr 4, 2022

Is the rust server usable from C applications? How big is the overhead compared to the C implementation?

@fjmolinas
Copy link
Contributor

Hm, there is now a file server in the rust-gcoap example. Is there much purpose in duplicating that? (There are some features in here, which are not in there yet; these should be ported over.)

For me this is still useful mainly because don't want to depend on rust: I still have not picked up rust, and I work with people that do not code in rust. But I can understand if you have no interest in pushing this.

@chrysn
Copy link
Member Author

chrysn commented Apr 4, 2022

Is the rust server usable from C applications?

It will be once #16785 (similar to the above #14397 (comment)) and #16833 are done (maybe even only the latter, if it registers somewhere in-tree).

How big is the overhead compared to the C implementation?

That is one reason I wrote the Rust one, but I'm not at feature parity (and then will have to strip down the Rust one again as it can already do some things the C one can't).

Also, it'll be a nice comparison for gcoap and nanocoap; I didn't wrap nanocoap yet, but it's on my rough agenda. (Plus there's a Rust CoAP implementation competing).

I still have not picked up rust, and I work with people that do not code in rust.

That shouldn't be a problem once the above things are closed (any more than not speaking C++ is if a pkg uses that). However, platform support for MIPS, AVR and the pre-RISC-V ESPs.

But I can understand if you have no interest in pushing this.

It should be usable as it is already, just not at the quality level I expect from RIOT components.

@chrysn
Copy link
Member Author

chrysn commented Apr 13, 2022 via email

@miri64
Copy link
Member

miri64 commented Apr 13, 2022

This allows quick revalidation: "I have a representation, but its Max-Age (default 60s) expires. It had tag 1234, so send anything new, or tell me it's still good." -- "2.03 Valid, ETag 1234 is good (for the next 60s)"

See also #17888 for the client-side :-)

fjmolinas pushed a commit to fjmolinas/RIOT that referenced this pull request Apr 14, 2022
fjmolinas pushed a commit to thingsat/RIOT that referenced this pull request Apr 15, 2022
fjmolinas pushed a commit to thingsat/RIOT that referenced this pull request Apr 15, 2022
fjmolinas pushed a commit to thingsat/RIOT that referenced this pull request Apr 15, 2022
@benpicco
Copy link
Contributor

benpicco commented Apr 15, 2022

I rebased this and applied the suggested fixes to my branch but I noticed something off:

$ aiocoap-client coap://[fe80::58b0:8ff:fe67:ad82%tapbr0]/vfs/            
4.04 Not Found

$ aiocoap-client coap://[fe80::58b0:8ff:fe67:ad82%tapbr0]/vfs/const/
application/link-format content was re-formatted
<hello-world>,
<hello-riot>

$ aiocoap-client coap://[fe80::58b0:8ff:fe67:ad82%tapbr0]/vfs/nvm0/
4.04 Not Found

@chrysn
Copy link
Member Author

chrysn commented Apr 15, 2022

$ aiocoap-client coap://[fe80::58b0:8ff:fe67:ad82%tapbr0]/vfs/
4.04 Not Found

That is fine -- at least that was theresolution of #15291. The Rust based server, on directory access, looks both at the directory and at the mount point list, and serves entries from either. Of course, that's more complex on the client side than if just VFS added mount points magically, but then again, the code here is only used for relatively high-level applications (special purpose applications, even when using a file server, might not span mount points in the first place). Moreover, it's kind of practical in the file server to treat mount points special, as they can have df style metadata that regular directories don't have.

(The nvm0 is something different, you found that already).

Comment on lines +129 to +131
request.namebuf[namelength] = '/';
memcpy(&request.namebuf[namelength] + 1, value, optlen);
namelength = newlength;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there really no way in GCoap to provide the handler with the request URL, so that the handler doesn't have to parse the coap packet itself?

@chrysn
Copy link
Member Author

chrysn commented Apr 15, 2022 via email

@benpicco
Copy link
Contributor

benpicco commented Apr 15, 2022

I was thinking of something like

char uri[CONFIG_NANOCOAP_URI_MAX];
coap_get_uri_path(pkt, uri);
[…]
char *sub_path = &uri[strlen(resource->path)]);
resource->handler(pkt, resp_buf, resp_buf_len, sub_path, resource->context);

That would allow us to get rid of the uneconomic strip_path parameter.

@chrysn
Copy link
Member Author

chrysn commented Apr 15, 2022 via email

@benpicco
Copy link
Contributor

benpicco commented Apr 15, 2022

Hm wouldn't it still help if the handler had knowledge of the resource path that it's supposed to handle?

So

resource->handler(pkt, resp_buf, resp_buf_len, resource->path, resource->context);

maybe?

@chrysn
Copy link
Member Author

chrysn commented Apr 15, 2022 via email

@chrysn
Copy link
Member Author

chrysn commented Apr 15, 2022

Ah, sorry, I misunderstood your comment.

Yes, passing the entry to the handler as a whole seems like a viable option; might even just pass resource in rather than ->path and ->context. (For example, that'd allow the handler to tell whether or not it is configured for POST and DELETE, if it'd want to know whether or not to advertise these featuers).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: CoAP Area: Constrained Application Protocol implementations CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR State: stale State: The issue / PR has no activity for >185 days Type: new feature The issue requests / The PR implemements a new feature for RIOT
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants