commit 423f02f5dbaa7b68b446483dc25e22b00db8a07e from: cage date: Tue Dec 28 10:33:53 2021 UTC Merge branch 'main' of ssh://omarpolo.com/kamid commit - 7a89af2b85e5db320ff2be364cb6c0cc0473663d commit + 423f02f5dbaa7b68b446483dc25e22b00db8a07e blob - 024927cfcf8dc33f61b3656e18d9a92471c0f326 blob + ad3f4c31452cba5bba00a25368429af6966e770a --- TODO +++ TODO @@ -8,3 +8,9 @@ user and not by root. - tweak iounit so it's always lower than the choosen msize + + - handle message bigger than MAX_IMSGSIZE - IMSG_HEADER_SIZE. One + way to do that would be to *not* use asynchroonus imsgs in client.c + but synchronous I/O: this way, once a message has been processed, + we can just receive the next in the same function (i.e. twrite) and + go ahead. blob - d3484c718bade31e04b234150c8dd206f4ac75d2 blob + ea9b319fd27f64cd69cd2b5366f4fae9b549471e --- client.c +++ client.c @@ -35,6 +35,15 @@ #include "sandbox.h" #include "utils.h" +/* + * XXX: atm is difficult to accept messages bigger than MAX_IMSGSIZE + * minus IMSG_HEADER_SIZE, we need something to split messages into + * chunks and receive them one by the other. + * + * CLIENT_MSIZE is thus the maximum message size we can handle now. + */ +#define CLIENT_MSIZE (MAX_IMSGSIZE - IMSG_HEADER_SIZE) + #define DEBUG_PACKETS 0 /* straight outta /src/usr.bin/ssh/scp.c */ @@ -42,12 +51,6 @@ ((sizeof(type) == 4 && (val) > INT32_MAX) || \ (sizeof(type) == 8 && (val) > INT64_MAX) || \ (sizeof(type) != 4 && sizeof(type) != 8)) - -struct qid { - uint64_t path; - uint32_t vers; - uint8_t type; -}; STAILQ_HEAD(dirhead, dir) dirs; struct dir { @@ -861,7 +864,7 @@ tversion(struct np_msg_header *hdr, const uint8_t *dat /* version matched */ handshaked = 1; - msize = MIN(msize, MSIZE9P); + msize = MIN(msize, CLIENT_MSIZE); client_send_listener(IMSG_MSIZE, &msize, sizeof(msize)); np_version(hdr->tag, msize, VERSION9P); return; @@ -1387,7 +1390,7 @@ tread(struct np_msg_header *hdr, const uint8_t *data, } if (TYPE_OVERFLOW(off_t, off)) { - log_warnx("unexpected size_t size"); + log_warnx("unexpected off_t size"); np_error(hdr->tag, "invalid offset"); return; } @@ -1403,6 +1406,7 @@ tread(struct np_msg_header *hdr, const uint8_t *data, if (off == 0 && f->offset != 0) { rewinddir(f->d); f->offset = 0; + evbuffer_drain(f->evb, EVBUFFER_LENGTH(f->evb)); } if (off != f->offset) { blob - 6b3f71d033c44d0f05037b8cebd54c1c3e79580e blob + c218227956175b3d089238ca1934f43f54e80105 --- ftp.c +++ ftp.c @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -28,11 +29,15 @@ #include #include -#if HAVE_READLINE +#if HAVE_LIBREADLINE #include #include #endif +#ifndef nitems +#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) +#endif + #include "9pclib.h" #include "kamid.h" #include "utils.h" @@ -47,10 +52,16 @@ const char *keypath; struct tls_config *tlsconf; struct tls *ctx; int sock; +struct evbuffer *buf; +struct evbuffer *dirbuf; +uint32_t msize; +int bell; #define PWDFID 0 -#if HAVE_READLINE +#define ASSERT_EMPTYBUF() assert(EVBUFFER_LENGTH(buf) == 0) + +#if HAVE_LIBREADLINE static char * read_line(const char *prompt) { @@ -76,6 +87,9 @@ read_line(const char *prompt) size_t linesize = 0; ssize_t linelen; + printf("%s", prompt); + fflush(stdout); + linelen = getline(&line, &linesize, stdin); if (linelen == -1) return NULL; @@ -96,22 +110,256 @@ usage(int ret) } static void +do_send(void) +{ + ssize_t r; + + while (EVBUFFER_LENGTH(evb) != 0) { + r = tls_write(ctx, EVBUFFER_DATA(evb), EVBUFFER_LENGTH(evb)); + switch (r) { + case TLS_WANT_POLLIN: + case TLS_WANT_POLLOUT: + continue; + case -1: + errx(1, "tls: %s", tls_error(ctx)); + default: + evbuffer_drain(evb, r); + } + } +} + +static void +mustread(void *d, size_t len) +{ + ssize_t r; + + while (len != 0) { + switch (r = tls_read(ctx, d, len)) { + case TLS_WANT_POLLIN: + case TLS_WANT_POLLOUT: + continue; + case -1: + errx(1, "tls: %s", tls_error(ctx)); + default: + d += r; + len -= r; + } + } +} + +static void +recv_msg(void) +{ + uint32_t len; + ssize_t r; + char tmp[BUFSIZ]; + + mustread(&len, sizeof(len)); + len = le32toh(len); + if (len < HEADERSIZE) + errx(1, "read message of invalid length %d", len); + + len -= 4; /* skip the length just read */ + + while (len != 0) { + switch (r = tls_read(ctx, tmp, sizeof(tmp))) { + case TLS_WANT_POLLIN: + case TLS_WANT_POLLOUT: + continue; + case -1: + errx(1, "tls: %s", tls_error(ctx)); + default: + len -= r; + evbuffer_add(buf, tmp, r); + } + } +} + +static uint64_t +np_read64(struct evbuffer *buf) +{ + uint64_t n; + + evbuffer_remove(buf, &n, sizeof(n)); + return le64toh(n); +} + +static uint32_t +np_read32(struct evbuffer *buf) +{ + uint32_t n; + + evbuffer_remove(buf, &n, sizeof(n)); + return le32toh(n); +} + +static uint16_t +np_read16(struct evbuffer *buf) +{ + uint16_t n; + + evbuffer_remove(buf, &n, sizeof(n)); + return le16toh(n); +} + +static uint16_t +np_read8(struct evbuffer *buf) +{ + uint8_t n; + + evbuffer_remove(buf, &n, sizeof(n)); + return n; +} + +static char * +np_readstr(struct evbuffer *buf) +{ + uint16_t len; + char *str; + + len = np_read16(buf); + assert(EVBUFFER_LENGTH(buf) >= len); + + if ((str = calloc(1, len+1)) == NULL) + err(1, "calloc"); + evbuffer_remove(buf, str, len); + return str; +} + +static void +np_read_qid(struct evbuffer *buf, struct qid *qid) +{ + assert(EVBUFFER_LENGTH(buf) >= QIDSIZE); + + qid->type = np_read8(buf); + qid->vers = np_read32(buf); + qid->path = np_read64(buf); +} + +static void +expect(uint8_t type) +{ + uint8_t t; + + t = np_read8(buf); + if (t == type) + return; + + if (t == Terror) { + char *err; + + err = np_readstr(buf); + errx(1, "expected %s, got error %s", + pp_msg_type(type), err); + } + + errx(1, "expected %s, got msg type %s", + pp_msg_type(type), pp_msg_type(t)); +} + +static void +expect2(uint8_t type, uint16_t tag) +{ + uint16_t t; + + expect(type); + + t = np_read16(buf); + if (t == tag) + return; + + errx(1, "expected tag 0x%x, got 0x%x", tag, t); +} + +static void do_version(void) { + char *version; + tversion(VERSION9P, MSIZE9P); - /* TODO: get reply */ + do_send(); + recv_msg(); + expect2(Rversion, NOTAG); + + msize = np_read32(buf); + version = np_readstr(buf); + + if (msize > MSIZE9P) + errx(1, "got unexpected msize: %d", msize); + if (strcmp(version, VERSION9P)) + errx(1, "unexpected 9p version: %s", version); + + free(version); + ASSERT_EMPTYBUF(); } static void do_attach(const char *path) { + const char *user; + struct qid qid; + if (path == NULL) path = "/"; + if ((user = getenv("USER")) == NULL) + user = "flan"; - /* TODO: do attach */ + tattach(PWDFID, NOFID, user, path); + do_send(); + recv_msg(); + expect2(Rattach, iota_tag); + np_read_qid(buf, &qid); + + ASSERT_EMPTYBUF(); +} + +static uint32_t +do_open(uint32_t fid, uint8_t mode) +{ + struct qid qid; + uint32_t iounit; + + topen(fid, mode); + do_send(); + recv_msg(); + expect2(Ropen, iota_tag); + + np_read_qid(buf, &qid); + iounit = np_read32(buf); + + ASSERT_EMPTYBUF(); + + return iounit; } static void +do_clunk(uint32_t fid) +{ + tclunk(fid); + do_send(); + recv_msg(); + expect2(Rclunk, iota_tag); + + ASSERT_EMPTYBUF(); +} + +static void +dup_fid(int fid, int nfid) +{ + uint16_t nwqid; + + twalk(fid, nfid, NULL, 0); + do_send(); + recv_msg(); + expect2(Rwalk, iota_tag); + + nwqid = np_read16(buf); + assert(nwqid == 0); + + ASSERT_EMPTYBUF(); +} + +static void do_connect(const char *connspec, const char *path) { int handshake; @@ -165,13 +413,175 @@ do_connect(const char *connspec, const char *path) free(host); } +static void +cmd_bell(int argc, const char **argv) +{ + if (argc == 0) { + bell = !bell; + if (bell) + puts("bell mode enabled"); + else + puts("bell mode disabled"); + return; + } + + if (argc != 1) + goto usage; + + if (!strcmp(*argv, "on")) { + bell = 1; + puts("bell mode enabled"); + return; + } + + if (!strcmp(*argv, "off")) { + bell = 0; + puts("bell mode disabled"); + return; + } + +usage: + printf("bell [on | off]\n"); +} + +static void +cmd_bye(int argc, const char **argv) +{ + log_warnx("bye\n"); + exit(0); +} + +static void +cmd_ls(int argc, const char **argv) +{ + uint64_t off = 0; + uint32_t len; + + if (argc != 0) { + printf("ls don't take arguments (yet)\n"); + return; + } + + dup_fid(PWDFID, 1); + do_open(1, KOREAD); + + evbuffer_drain(dirbuf, EVBUFFER_LENGTH(dirbuf)); + + for (;;) { + tread(1, off, BUFSIZ); + do_send(); + recv_msg(); + expect2(Rread, iota_tag); + + len = np_read32(buf); + if (len == 0) + break; + + evbuffer_add_buffer(dirbuf, buf); + off += len; + + ASSERT_EMPTYBUF(); + } + + while (EVBUFFER_LENGTH(dirbuf) != 0) { + struct qid qid; + uint64_t len; + uint16_t size; + char *name; + + size = np_read16(dirbuf); + assert(size <= EVBUFFER_LENGTH(dirbuf)); + + np_read16(dirbuf); /* skip type */ + np_read32(dirbuf); /* skip dev */ + + np_read_qid(dirbuf, &qid); + printf("%s ", pp_qid_type(qid.type)); + + np_read32(dirbuf); /* skip mode */ + np_read32(dirbuf); /* skip atime */ + np_read32(dirbuf); /* skip mtime */ + + len = np_read64(dirbuf); + printf("%llu ", (unsigned long long)len); + + name = np_readstr(dirbuf); + printf("%s\n", name); + free(name); + + free(np_readstr(dirbuf)); /* skip uid */ + free(np_readstr(dirbuf)); /* skip gid */ + free(np_readstr(dirbuf)); /* skip muid */ + } + + do_clunk(1); +} + +static void +cmd_verbose(int argc, const char **argv) +{ + if (argc == 0) { + log_setverbose(!log_getverbose()); + if (log_getverbose()) + puts("verbose mode enabled"); + else + puts("verbose mode disabled"); + return; + } + + if (argc != 1) + goto usage; + + if (!strcmp(*argv, "on")) { + log_setverbose(1); + puts("verbose mode enabled"); + return; + } + + if (!strcmp(*argv, "off")) { + log_setverbose(0); + puts("verbose mode disabled"); + return; + } + +usage: + printf("verbose [on | off]\n"); +} + +static void +excmd(int argc, const char **argv) +{ + struct cmd { + const char *name; + void (*fn)(int, const char **); + } cmds[] = { + {"bell", cmd_bell}, + {"bye", cmd_bye}, + {"ls", cmd_ls}, + {"quit", cmd_bye}, + {"verbose", cmd_verbose}, + }; + size_t i; + + if (argc == 0) + return; + for (i = 0; i < nitems(cmds); ++i) { + if (!strcmp(cmds[i].name, *argv)) { + cmds[i].fn(argc-1, argv+1); + return; + } + } + + log_warnx("unknown command %s", *argv); +} + int main(int argc, char **argv) { int ch; log_init(1, LOG_DAEMON); - log_setverbose(1); + log_setverbose(0); log_procinit(getprogname()); while ((ch = getopt(argc, argv, "C:cK:")) != -1) { @@ -198,14 +608,36 @@ main(int argc, char **argv) if ((evb = evbuffer_new()) == NULL) fatal("evbuffer_new"); + if ((buf = evbuffer_new()) == NULL) + fatal("evbuffer_new"); + + if ((dirbuf = evbuffer_new()) == NULL) + fatal("evbuferr_new"); + do_connect(argv[0], argv[1]); + /* cmd_ls(0, NULL); */ + for (;;) { - char *line; + int argc = 0; + char *line, *argv[16] = {0}, **ap; - if ((line = read_line("ftp> ")) == NULL) + if ((line = read_line("kamiftp> ")) == NULL) break; - printf("read: %s\n", line); + + for (argc = 0, ap = argv; ap < &argv[15] && + (*ap = strsep(&line, " \t")) != NULL;) { + if (**ap != '\0') + ap++, argc++; + } + excmd(argc, (const char **)argv); + + if (bell) { + printf("\a"); + fflush(stdout); + } + + free(line); } printf("\n"); blob - 9fa99a3c7244b99f835976cae7bb1dc48e7cdfda blob + 9675e6ac175f820b3eec23853740698a562b017b --- kamid.h +++ kamid.h @@ -151,6 +151,12 @@ struct np_msg_header { uint16_t tag; }; +struct qid { + uint64_t path; + uint32_t vers; + uint8_t type; +}; + /* useful constants */ #define HEADERSIZE (4 + 1 + 2) #define VERSION9P "9P2000" blob - 1b88cb698ca30ff9bb277d2af0697b6fa3075734 blob + bbb82ddbd18431c0c4cc0cc05d9e9273e5bf4d26 --- kamiftp.1 +++ kamiftp.1 @@ -49,8 +49,29 @@ Specify the path to the client certificate .Ar key to be used during the TLS handshake. .El +.Pp +The following commands are recognized by +.Nm : +.Bl -tag -width Ds +.It Ic bell Oo Cm on | off Oc +Request terminal to sound a bell after each command. +Without arguments toggle the current state. +.It Ic bye +Terminate the session. +Synomym of +.Ic quit . +.It Ic ls +List the file in the current directory +.It Ic quit +Terminate the session. +Synomym of +.Ic bye . +.It Ic verbose Oo Cm on | off Oc +Print verbose information. +Without arguments toggle the current state. +.El .Sh SEE ALSO -.Xr 9p 7 +.Xr 9p 7 , .Xr kamid 8 .Sh AUTHORS The blob - 6c7171faab707741aba6595e903666e9a77964eb blob + 504329156629b6374f7a18342d97b66bfbb16659 --- kamirepl.c +++ kamirepl.c @@ -84,7 +84,6 @@ static void excmd_read(const char ** , int); static void excmd_write(const char **, int); static void excmd(const char **, int); -static const char *pp_qid_type(uint8_t); static void pp_qid(const uint8_t *, uint32_t); static void pp_msg(uint32_t, uint8_t, uint16_t, const uint8_t *); static void handle_9p(const uint8_t *, size_t); @@ -702,23 +701,6 @@ excmd(const char **argv, int argc) } log_warnx("Unknown command %s", *argv); -} - -static const char * -pp_qid_type(uint8_t type) -{ - switch (type) { - case QTDIR: return "dir"; - case QTAPPEND: return "append-only"; - case QTEXCL: return "exclusive"; - case QTMOUNT: return "mounted-channel"; - case QTAUTH: return "authentication"; - case QTTMP: return "non-backed-up"; - case QTSYMLINK: return "symlink"; - case QTFILE: return "file"; - } - - return "unknown"; } static void blob - 1e9b8c93e6bd2a5ee92763875fd80ccd05987ef8 blob + 6da049b0758a48894158408f942e91823baa3ade --- ninepscript.5 +++ ninepscript.5 @@ -69,10 +69,17 @@ It evaluates to the value of the variable or constant scope. .It comparison The syntax is -.Ql Ar expression Cm == Ar expression +.Bd -literal -offset Ds +.Ar expression Cm == Ar expression +.Ar expression Cm <= Ar expression +.Ed +.Pp and yields a true value if the two expressions are considered to be +respectively .Sq equal -or a false value otherwise. +or +.Sq lesser equal , +a false value otherwise. Two values are equal if they are both number and represent the same value .Pq regardless of the size @@ -168,9 +175,9 @@ Execute .Ar procedure with the given .Ar arguments . -.It Ic assert Ar expression +.It Ic assert Ar comparison Evaluate -.Ar expression +.Ar comparison and if it not yields a true-ish value terminate the current running test and mark it as failed. Multiple assertion can be done in one single @@ -178,15 +185,15 @@ Multiple assertion can be done in one single block using the following syntax: .Bd -literal -offset Ds .Ic assert ( - expression_1 - expression_2 + comparison_1 + comparison_2 ... - expression_n + comparison_n ) .Ed .Pp Note that newlines are mandatory after every -.Ar expression +.Ar comparison in this case. .It Ic should-fail Ar expression Op : Ar reason Evaluate blob - e856da804353a10ea38daeea0e7396e071ecbc58 blob + 3b22364359c6f834886ffda7912b580339df0524 --- np.y +++ np.y @@ -163,7 +163,9 @@ literal : STRING { $$ = op_lit_str($1); } * interested in checking all the possibilities here. */ cexpr : literal | varref | funcall | faccess ; -check : cexpr '=' '=' cexpr { $$ = op_cmp_eq($1, $4); } ; +check : cexpr '=' '=' cexpr { $$ = op_cmp_eq($1, $4); } + | cexpr '<' '=' cexpr { $$ = op_cmp_leq($1, $4); } + ; expr : literal | funcall | varref | check | cast | faccess | vargs ; blob - 68fd63cc1792b8a8158fe5a817c2794afa5ff875 blob + c5b25c870e154f06143d64be5bab83c66694c253 --- regress/lib.9ps +++ regress/lib.9ps @@ -33,7 +33,7 @@ proc mount(fid, path) { assert ( m.type == Rversion m.tag == notag - m.msize == msize + m.msize <= msize # m.version == version ) blob - 08f401c4930cd4e4cf94ab0ca4ece3cff6e04a93 blob + 8c80fde28a266bf7c9947cfd555b284fd9d5f801 --- regress/misc-suite.9ps +++ regress/misc-suite.9ps @@ -18,7 +18,7 @@ testing "multiple attach" dir "./root" { assert ( m.type == Rversion m.tag == notag - m.msize == msize + m.msize <= msize ) fid1 = 0 blob - 4fb5bc0910430077c25c774212922762678ad98f blob + 8b480c2ff8bd568c4adadc410eed846e8f7696d2 --- script.c +++ script.c @@ -428,8 +428,9 @@ free_op(struct op *op) free_op_rec(op->v.cast.expr); break; case OP_CMP_EQ: - free_op_rec(op->v.cmp_eq.a); - free_op_rec(op->v.cmp_eq.b); + case OP_CMP_LEQ: + free_op_rec(op->v.bin_cmp.a); + free_op_rec(op->v.bin_cmp.b); break; case OP_FACCESS: free_op_rec(op->v.faccess.expr); @@ -517,9 +518,21 @@ op_cmp_eq(struct op *a, struct op *b) struct op *op; op = newop(OP_CMP_EQ); - op->v.cmp_eq.a = a; - op->v.cmp_eq.b = b; + op->v.bin_cmp.a = a; + op->v.bin_cmp.b = b; + + return op; +} + +struct op * +op_cmp_leq(struct op *a, struct op *b) +{ + struct op *op; + op = newop(OP_CMP_LEQ); + op->v.bin_cmp.a = a; + op->v.bin_cmp.b = b; + return op; } @@ -677,7 +690,15 @@ val_eq(struct value *a, struct value *b) case V_SYM: return !strcmp(a->v.str, b->v.str); } + + return 0; +} +int +val_leq(struct value *a, struct value *b) +{ + if (val_isnum(a) && val_isnum(b)) + return val_tonum(a) <= val_tonum(b); return 0; } @@ -879,10 +900,15 @@ pp_op(struct op *op) } break; case OP_CMP_EQ: - pp_op(op->v.cmp_eq.a); + pp_op(op->v.bin_cmp.a); printf(" == "); - pp_op(op->v.cmp_eq.b); + pp_op(op->v.bin_cmp.b); break; + case OP_CMP_LEQ: + pp_op(op->v.bin_cmp.a); + printf(" <= "); + pp_op(op->v.bin_cmp.b); + break; case OP_FACCESS: pp_op(op->v.faccess.expr); printf(".%s", op->v.faccess.field); @@ -1052,15 +1078,25 @@ eval(struct op *op) break; case OP_CMP_EQ: - if ((ret = eval(op->v.cmp_eq.a)) != EVAL_OK) + if ((ret = eval(op->v.bin_cmp.a)) != EVAL_OK) return ret; - if ((ret = eval(op->v.cmp_eq.b)) != EVAL_OK) + if ((ret = eval(op->v.bin_cmp.b)) != EVAL_OK) return ret; popv(&b); popv(&a); pushbool(val_eq(&a, &b)); + break; + + case OP_CMP_LEQ: + if ((ret = eval(op->v.bin_cmp.a)) != EVAL_OK) + return ret; + if ((ret = eval(op->v.bin_cmp.b)) != EVAL_OK) + return ret; + popv(&b); + popv(&a); + pushbool(val_leq(&a, &b)); break; case OP_FACCESS: blob - 8e094a0a1f2b051994a39fe747d5803f68d7bb85 blob + f7b6a07ae2b5f8ecc35bea64130cf0b6af2af2c0 --- script.h +++ script.h @@ -69,6 +69,7 @@ enum { OP_VAR, OP_CAST, OP_CMP_EQ, + OP_CMP_LEQ, OP_FACCESS, OP_SFAIL, OP_VARGS, @@ -99,7 +100,7 @@ struct op { struct { struct op *a; struct op *b; - } cmp_eq; + } bin_cmp; struct { struct op *expr; char *field; @@ -177,6 +178,7 @@ struct op *op_var(char *); struct op *op_lit_str(char *); struct op *op_lit_num(uint64_t); struct op *op_cmp_eq(struct op *, struct op *); +struct op *op_cmp_leq(struct op *, struct op *); struct op *op_cast(struct op *, int); struct op *op_faccess(struct op *, char *); struct op *op_sfail(struct op *, char *); @@ -190,6 +192,7 @@ int val_trueish(struct value *); int val_isnum(struct value *); int64_t val_tonum(struct value *); int val_eq(struct value *, struct value *); +int val_leq(struct value *, struct value *); int val_cast(struct value *, int); int val_faccess(struct value *, const char *, struct value *); void pp_op(struct op *); blob - 066fdb3d2905942ccc7328d712194ff6a03fc1a8 blob + 50b83e74a8d469e6fa1c0906a70fe5fd7fd61ca9 --- utils.c +++ utils.c @@ -102,6 +102,23 @@ pp_msg_type(uint8_t type) } } +const char * +pp_qid_type(uint8_t type) +{ + switch (type) { + case QTDIR: return "dir"; + case QTAPPEND: return "append-only"; + case QTEXCL: return "exclusive"; + case QTMOUNT: return "mounted-channel"; + case QTAUTH: return "authentication"; + case QTTMP: return "non-backed-up"; + case QTSYMLINK: return "symlink"; + case QTFILE: return "file"; + } + + return "unknown"; +} + static void hexdump_ppline(int x, uint8_t *data, size_t len) { blob - 78cf40028b8b5683ce038ac20a23e0920073697f blob + 55b0bbe929a564d5e891da783db89f48371265d8 --- utils.h +++ utils.h @@ -27,6 +27,7 @@ char *xstrdup(const char *); void *xmemdup(const void *, size_t); const char *pp_msg_type(uint8_t); +const char *pp_qid_type(uint8_t); void hexdump(const char *, uint8_t *data, size_t len);