1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
18134: nanocoap_link_format: add helper function to parse Link Format r=kaspar030 a=benpicco



18738: nanocoap_sock: implement nanocoap_sock_delete() r=maribu a=benpicco



18939: gnrc_ipv6_nib: clean up _resolve_addr() r=maribu a=benpicco



19118: sys/ztimer: ztimer_mock: guard ztimer_ondemand static functions r=kaspar030 a=kaspar030



Co-authored-by: Benjamin Valentin <benpicco@beuth-hochschule.de>
Co-authored-by: Benjamin Valentin <benjamin.valentin@ml-pa.com>
Co-authored-by: Benjamin Valentin <benjamin.valentin@bht-berlin.de>
Co-authored-by: Kaspar Schleiser <kaspar@schleiser.de>
This commit is contained in:
bors[bot] 2023-01-10 11:25:32 +00:00 committed by GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 384 additions and 144 deletions

View File

@ -724,6 +724,10 @@ ifneq (,$(filter nanocoap_cache,$(USEMODULE)))
USEMODULE += hashes
endif
ifneq (,$(filter nanocoap_link_format,$(USEMODULE)))
USEMODULE += fmt
endif
ifneq (,$(filter nanocoap_vfs,$(USEMODULE)))
USEMODULE += nanocoap_sock
USEMODULE += vfs

View File

@ -0,0 +1,72 @@
/*
* Copyright (C) 2022 ML!PA Consulting GmbH
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup net_nanosock
* @brief NanoCoAP Link Format helper functions
*
* @{
*
* @file
* @brief NanoCoAP Link Format ([RFC 6690](https://www.rfc-editor.org/rfc/rfc6690.html))
* helper functions
*
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
*/
#ifndef NET_NANOCOAP_LINK_FORMAT_H
#define NET_NANOCOAP_LINK_FORMAT_H
#include "net/nanocoap_sock.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Callback function called for each resource on the directory
*
* @param[in] entry Resource entry from the server
* @param[in] ctx Optional function context
*
* @returns 0 on success
* @returns <0 on error
*/
typedef int (*coap_link_format_handler_t)(char *entry, void *ctx);
/**
* @brief Downloads the resource behind @p path via blockwise GET
*
* @param[in] sock Connection to the server
* @param[in] path path of the resource
* @param[in] cb Callback to execute for each resource entry
* @param[in] arg Optional callback argument
*
* @returns 0 on success
* @returns <0 on error
*/
int nanocoap_link_format_get(nanocoap_sock_t *sock, const char *path,
coap_link_format_handler_t cb, void *arg);
/**
* @brief Downloads the resource behind @p url via blockwise GET
*
* @param[in] url URL to the resource
* @param[in] cb Callback to execute for each resource entry
* @param[in] arg Optional callback argument
*
* @returns 0 on success
* @returns <0 on error
*/
int nanocoap_link_format_get_url(const char *url,
coap_link_format_handler_t cb, void *arg);
#ifdef __cplusplus
}
#endif
#endif /* NET_NANOCOAP_LINK_FORMAT_H */
/** @} */

View File

@ -327,6 +327,27 @@ ssize_t nanocoap_sock_post_url(const char *url,
const void *request, size_t len,
void *response, size_t len_max);
/**
* @brief Simple synchronous CoAP (confirmable) DELETE
*
* @param[in] sock socket to use for the request
* @param[in] path remote path to delete
*
* @returns 0 on success
* @returns <0 on error
*/
ssize_t nanocoap_sock_delete(nanocoap_sock_t *sock, const char *path);
/**
* @brief Simple synchronous CoAP (confirmable) DELETE for URL
*
* @param[in] url URL of the resource that should be deleted
*
* @returns 0 on success
* @returns <0 on error
*/
ssize_t nanocoap_sock_delete_url(const char *url);
/**
* @brief Performs a blockwise coap get request on a socket.
*

View File

@ -0,0 +1,109 @@
/*
* Copyright (C) 2022 ML!PA Consulting GmbH
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup net_nanocoap
* @{
*
* @file
* @brief NanoCoAP Link Format parser
*
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
*
* @}
*/
#include "fmt.h"
#include "net/nanocoap/link_format.h"
#include "net/nanocoap_sock.h"
#include "net/sock/util.h"
struct dir_list_ctx {
char *buf;
char *cur;
char *end;
coap_link_format_handler_t cb;
void *ctx;
char esc_buf[2];
uint8_t esc_idx;
};
static int _dirlist_cb(void *arg, size_t offset, uint8_t *buf, size_t len, int more)
{
(void)offset;
struct dir_list_ctx *ctx = arg;
char *end = (char *)buf + len;
for (char *c = (char *)buf; c != end; ++c) {
/* start of escape sequence */
if (*c == '%') {
ctx->esc_idx = 1;
continue;
}
if (ctx->esc_idx) {
/* fill escape buffer */
ctx->esc_buf[ctx->esc_idx - 1] = *c;
if (++ctx->esc_idx == 3) {
ctx->esc_idx = 0;
*c = scn_u32_hex(ctx->esc_buf, 2);
} else {
continue;
}
}
if (*c == ',' || ctx->cur == ctx->end) {
int res;
*ctx->cur = 0;
res = ctx->cb(ctx->buf, ctx->ctx);
ctx->cur = ctx->buf;
if (res < 0) {
return res;
}
} else {
*ctx->cur++ = *c;
}
}
if (!more) {
*ctx->cur = 0;
return ctx->cb(ctx->buf, ctx->ctx);
}
return 0;
}
int nanocoap_link_format_get(nanocoap_sock_t *sock, const char *path,
coap_link_format_handler_t cb, void *arg)
{
char buffer[CONFIG_NANOCOAP_QS_MAX];
struct dir_list_ctx ctx = {
.buf = buffer,
.end = buffer + sizeof(buffer),
.cur = buffer,
.cb = cb,
.ctx = arg,
};
return nanocoap_sock_get_blockwise(sock, path, CONFIG_NANOCOAP_BLOCKSIZE_DEFAULT,
_dirlist_cb, &ctx);
}
int nanocoap_link_format_get_url(const char *url, coap_link_format_handler_t cb, void *arg)
{
nanocoap_sock_t sock;
int res = nanocoap_sock_url_connect(url, &sock);
if (res) {
return res;
}
res = nanocoap_link_format_get(&sock, sock_urlpath(url), cb, arg);
nanocoap_sock_close(&sock);
return res;
}

View File

@ -425,6 +425,38 @@ ssize_t nanocoap_sock_post_url(const char *url,
return _sock_put_post_url(url, COAP_METHOD_POST, request, len, response, len_max);
}
ssize_t nanocoap_sock_delete(nanocoap_sock_t *sock, const char *path)
{
/* buffer for CoAP header */
uint8_t buffer[CONFIG_NANOCOAP_BLOCK_HEADER_MAX];
uint8_t *pktpos = buffer;
coap_pkt_t pkt = {
.hdr = (void *)pktpos,
};
pktpos += coap_build_hdr(pkt.hdr, COAP_TYPE_CON, NULL, 0, COAP_METHOD_DELETE, _get_id());
pktpos += coap_opt_put_uri_path(pktpos, 0, path);
pkt.payload = pktpos;
return nanocoap_sock_request_cb(sock, &pkt, NULL, NULL);
}
ssize_t nanocoap_sock_delete_url(const char *url)
{
nanocoap_sock_t sock;
int res = nanocoap_sock_url_connect(url, &sock);
if (res) {
return res;
}
res = nanocoap_sock_delete(&sock, sock_urlpath(url));
nanocoap_sock_close(&sock);
return res;
}
ssize_t nanocoap_request(coap_pkt_t *pkt, const sock_udp_ep_t *local,
const sock_udp_ep_t *remote, size_t len)
{

View File

@ -249,7 +249,7 @@ bool _is_reachable(_nib_onl_entry_t *entry);
#define _set_reachable(netif, nce) (void)netif; (void)nce
#define _get_nud_state(nbr) (GNRC_IPV6_NIB_NC_INFO_NUD_STATE_UNMANAGED)
#define _set_nud_state(netif, nce, state) (void)netif; (void)nbr; (void)state
#define _set_nud_state(netif, nce, state) (void)netif; (void)nce; (void)state
#define _is_reachable(entry) (true)
#endif /* CONFIG_GNRC_IPV6_NIB_ARSM || defined(DOXYGEN) */

View File

@ -1251,25 +1251,88 @@ static void _handle_nbr_adv(gnrc_netif_t *netif, const ipv6_hdr_t *ipv6,
}
}
#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_QUEUE_PKT)
static gnrc_pktqueue_t *_alloc_queue_entry(gnrc_pktsnip_t *pkt)
{
#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_QUEUE_PKT)
for (int i = 0; i < CONFIG_GNRC_IPV6_NIB_NUMOF; i++) {
if (_queue_pool[i].pkt == NULL) {
_queue_pool[i].pkt = pkt;
return &_queue_pool[i];
}
}
#else
(void)pkt;
#endif /* CONFIG_GNRC_IPV6_NIB_QUEUE_PKT */
return NULL;
}
#endif /* CONFIG_GNRC_IPV6_NIB_QUEUE_PKT */
static bool _resolve_addr_from_nc(_nib_onl_entry_t *entry, gnrc_netif_t *netif,
gnrc_ipv6_nib_nc_t *nce)
{
if (entry == NULL) {
return false;
}
if (IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_ARSM)) {
if (!_is_reachable(entry)) {
return false;
}
if (_get_nud_state(entry) == GNRC_IPV6_NIB_NC_INFO_NUD_STATE_STALE) {
_set_nud_state(netif, entry, GNRC_IPV6_NIB_NC_INFO_NUD_STATE_DELAY);
_evtimer_add(entry, GNRC_IPV6_NIB_DELAY_TIMEOUT,
&entry->nud_timeout, NDP_DELAY_FIRST_PROBE_MS);
}
}
_nib_nc_get(entry, nce);
return true;
}
static bool _enqueue_for_resolve(gnrc_netif_t *netif, gnrc_pktsnip_t *pkt,
_nib_onl_entry_t *entry)
{
if (!IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_QUEUE_PKT) ||
_get_nud_state(entry) != GNRC_IPV6_NIB_NC_INFO_NUD_STATE_INCOMPLETE) {
gnrc_icmpv6_error_dst_unr_send(ICMPV6_ERROR_DST_UNR_ADDR, pkt);
gnrc_pktbuf_release_error(pkt, EHOSTUNREACH);
return true;
}
gnrc_pktqueue_t *queue_entry = _alloc_queue_entry(pkt);
if (queue_entry == NULL) {
DEBUG("nib: can't allocate entry for packet queue "
"dropping packet\n");
gnrc_pktbuf_release(pkt);
return false;
}
if (netif != NULL) {
gnrc_pktsnip_t *netif_hdr = gnrc_netif_hdr_build(NULL, 0, NULL, 0);
if (netif_hdr == NULL) {
DEBUG("nib: can't allocate netif header for queue\n");
gnrc_pktbuf_release(pkt);
queue_entry->pkt = NULL;
return false;
}
gnrc_netif_hdr_set_netif(netif_hdr->data, netif);
queue_entry->pkt = gnrc_pkt_prepend(queue_entry->pkt, netif_hdr);
}
#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_QUEUE_PKT)
gnrc_pktqueue_add(&entry->pktqueue, queue_entry);
#else
(void)entry;
#endif
return true;
}
static bool _resolve_addr(const ipv6_addr_t *dst, gnrc_netif_t *netif,
gnrc_pktsnip_t *pkt, gnrc_ipv6_nib_nc_t *nce,
_nib_onl_entry_t *entry)
{
bool res = false;
if ((netif != NULL) && (netif->device_type == NETDEV_TYPE_SLIP)) {
/* XXX: Linux doesn't do neighbor discovery for SLIP so no use sending
* NS and since SLIP doesn't have link-layer addresses anyway, we can
@ -1279,107 +1342,57 @@ static bool _resolve_addr(const ipv6_addr_t *dst, gnrc_netif_t *netif,
nce->l2addr_len = 0;
return true;
}
#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_ARSM)
if ((entry != NULL) && _is_reachable(entry)) {
if (_get_nud_state(entry) == GNRC_IPV6_NIB_NC_INFO_NUD_STATE_STALE) {
_set_nud_state(netif, entry, GNRC_IPV6_NIB_NC_INFO_NUD_STATE_DELAY);
_evtimer_add(entry, GNRC_IPV6_NIB_DELAY_TIMEOUT,
&entry->nud_timeout, NDP_DELAY_FIRST_PROBE_MS);
}
DEBUG("nib: resolve address %s%%%u from neighbor cache\n",
ipv6_addr_to_str(addr_str, &entry->ipv6, sizeof(addr_str)),
_nib_onl_get_if(entry));
_nib_nc_get(entry, nce);
res = true;
}
#else /* CONFIG_GNRC_IPV6_NIB_ARSM */
if (entry != NULL) {
DEBUG("nib: resolve address %s%%%u from neighbor cache\n",
ipv6_addr_to_str(addr_str, &entry->ipv6, sizeof(addr_str)),
_nib_onl_get_if(entry));
_nib_nc_get(entry, nce);
res = true;
}
#endif /* CONFIG_GNRC_IPV6_NIB_ARSM */
else if (!(res = _resolve_addr_from_ipv6(dst, netif, nce))) {
#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_ARSM)
bool reset = false;
#endif /* CONFIG_GNRC_IPV6_NIB_ARSM */
DEBUG("nib: resolve address %s by probing neighbors\n",
ipv6_addr_to_str(addr_str, dst, sizeof(addr_str)));
/* first check if address is cached */
if (_resolve_addr_from_nc(entry, netif, nce)) {
DEBUG("nib: resolve address %s%%%u from neighbor cache\n",
ipv6_addr_to_str(addr_str, &entry->ipv6, sizeof(addr_str)),
_nib_onl_get_if(entry));
return true;
}
/* directly resolve address if it uses 6lo addressing mode */
if (_resolve_addr_from_ipv6(dst, netif, nce)) {
DEBUG("nib: resolve l2 address from IPv6 address\n");
return true;
}
bool reset = false;
DEBUG("nib: resolve address %s by probing neighbors\n",
ipv6_addr_to_str(addr_str, dst, sizeof(addr_str)));
if (entry == NULL) {
entry = _nib_nc_add(dst, netif ? netif->pid : 0,
GNRC_IPV6_NIB_NC_INFO_NUD_STATE_INCOMPLETE);
if (entry == NULL) {
entry = _nib_nc_add(dst, (netif != NULL) ? netif->pid : 0,
GNRC_IPV6_NIB_NC_INFO_NUD_STATE_INCOMPLETE);
if (entry == NULL) {
DEBUG("nib: can't resolve address, neighbor cache full\n");
gnrc_pktbuf_release(pkt);
return false;
}
#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_ROUTER)
if (netif != NULL) {
_call_route_info_cb(netif,
GNRC_IPV6_NIB_ROUTE_INFO_TYPE_NSC,
dst,
(void *)GNRC_IPV6_NIB_NC_INFO_NUD_STATE_INCOMPLETE);
}
#endif /* CONFIG_GNRC_IPV6_NIB_ROUTER */
#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_ARSM)
reset = true;
#endif /* CONFIG_GNRC_IPV6_NIB_ARSM */
DEBUG("nib: can't resolve address, neighbor cache full\n");
gnrc_pktbuf_release(pkt);
return false;
}
#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_ARSM)
else if (_get_nud_state(entry) == GNRC_IPV6_NIB_NC_INFO_NUD_STATE_UNREACHABLE) {
/* reduce back-off to possibly resolve neighbor sooner again */
entry->ns_sent = 3;
if (IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_ROUTER) && netif != NULL) {
_call_route_info_cb(netif,
GNRC_IPV6_NIB_ROUTE_INFO_TYPE_NSC,
dst,
(void *)GNRC_IPV6_NIB_NC_INFO_NUD_STATE_INCOMPLETE);
}
#endif /* CONFIG_GNRC_IPV6_NIB_ARSM */
if (pkt != NULL) {
#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_QUEUE_PKT)
if (_get_nud_state(entry) == GNRC_IPV6_NIB_NC_INFO_NUD_STATE_INCOMPLETE) {
gnrc_pktqueue_t *queue_entry = _alloc_queue_entry(pkt);
if (queue_entry != NULL) {
if (netif != NULL) {
gnrc_pktsnip_t *netif_hdr = gnrc_netif_hdr_build(
NULL, 0, NULL, 0
);
if (netif_hdr == NULL) {
DEBUG("nib: can't allocate netif header for queue\n");
gnrc_pktbuf_release(pkt);
queue_entry->pkt = NULL;
return false;
}
gnrc_netif_hdr_set_netif(netif_hdr->data, netif);
queue_entry->pkt = gnrc_pkt_prepend(queue_entry->pkt,
netif_hdr);
}
gnrc_pktqueue_add(&entry->pktqueue, queue_entry);
}
else {
DEBUG("nib: can't allocate entry for packet queue "
"dropping packet\n");
gnrc_pktbuf_release(pkt);
return false;
}
}
/* pkt != NULL already checked above */
else {
gnrc_icmpv6_error_dst_unr_send(ICMPV6_ERROR_DST_UNR_ADDR,
pkt);
gnrc_pktbuf_release_error(pkt, EHOSTUNREACH);
}
#else /* CONFIG_GNRC_IPV6_NIB_QUEUE_PKT */
gnrc_icmpv6_error_dst_unr_send(ICMPV6_ERROR_DST_UNR_ADDR,
pkt);
gnrc_pktbuf_release_error(pkt, EHOSTUNREACH);
#endif /* CONFIG_GNRC_IPV6_NIB_QUEUE_PKT */
}
#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_ARSM)
_probe_nbr(entry, reset);
#endif /* CONFIG_GNRC_IPV6_NIB_ARSM */
reset = true;
}
return res;
#if IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_ARSM)
else if (_get_nud_state(entry) == GNRC_IPV6_NIB_NC_INFO_NUD_STATE_UNREACHABLE) {
/* reduce back-off to possibly resolve neighbor sooner again */
entry->ns_sent = 3;
}
#endif
/* queue packet as we have to do address resolution first */
if (pkt != NULL && !_enqueue_for_resolve(netif, pkt, entry)) {
return false;
}
if (IS_ACTIVE(CONFIG_GNRC_IPV6_NIB_ARSM)) {
_probe_nbr(entry, reset);
}
return false;
}
static void _handle_snd_na(gnrc_pktsnip_t *pkt)

View File

@ -225,6 +225,7 @@ endif
ifneq (,$(filter shell_cmd_nanocoap_vfs,$(USEMODULE)))
USEMODULE += nanocoap_vfs
USEMODULE += vfs_util
USEMODULE += nanocoap_link_format
endif
ifneq (,$(filter shell_cmd_netstats_neighbor,$(USEMODULE)))
USEMODULE += netstats_neighbor

View File

@ -23,8 +23,10 @@
#include <string.h>
#include <inttypes.h>
#include "net/nanocoap/link_format.h"
#include "net/nanocoap_sock.h"
#include "net/nanocoap_vfs.h"
#include "shell.h"
#include "vfs_default.h"
#include "vfs_util.h"
@ -48,6 +50,21 @@ static bool _is_dir(const char *url)
return url[len - 1] == '/';
}
static int _resource_cb(char *entry, void *ctx)
{
(void)ctx;
char *start = strchr(entry, '<');
if (start) {
char *end = strchr(entry, '>');
*end = '\0';
entry = start + 1;
}
puts(entry);
return 0;
}
static int _print_cb(void *arg, size_t offset, uint8_t *buf, size_t len, int more)
{
(void)arg;
@ -61,41 +78,6 @@ static int _print_cb(void *arg, size_t offset, uint8_t *buf, size_t len, int mor
return 0;
}
static int _print_dir_cb(void *arg, size_t offset, uint8_t *buf, size_t len, int more)
{
(void)offset;
(void)more;
struct dir_list_ctx *ctx = arg;
char *end = (char *)buf + len;
for (char *c = (char *)buf; c < end; ++c) {
if (ctx->cur) {
if (*c == '>' || ctx->cur == ctx->end) {
*ctx->cur = 0;
puts(ctx->buf);
ctx->cur = NULL;
} else {
*ctx->cur++ = *c;
}
} else if (*c == '<') {
ctx->cur = ctx->buf;
}
}
return 0;
}
static int _print_dir(const char *url, char *buf, size_t len)
{
struct dir_list_ctx ctx = {
.buf = buf,
.end = buf + len,
};
return nanocoap_get_blockwise_url(url, CONFIG_NANOCOAP_BLOCKSIZE_DEFAULT,
_print_dir_cb, &ctx);
}
static int _nanocoap_get_handler(int argc, char **argv)
{
int res;
@ -109,7 +91,7 @@ static int _nanocoap_get_handler(int argc, char **argv)
}
if (_is_dir(url) && argc < 3) {
res = _print_dir(url, buffer, sizeof(buffer));
res = nanocoap_link_format_get_url(url, _resource_cb, NULL);
if (res) {
printf("Request failed: %s\n", strerror(-res));
}

View File

@ -119,6 +119,7 @@ static void ztimer_mock_op_cancel(ztimer_clock_t *clock)
self->armed = 0;
}
#if MODULE_ZTIMER_ONDEMAND
static void ztimer_mock_op_start(ztimer_clock_t *clock)
{
ztimer_mock_t *self = (ztimer_mock_t *)clock;
@ -138,6 +139,7 @@ static void ztimer_mock_op_stop(ztimer_clock_t *clock)
DEBUG("zmock_stop: %3u\n", self->calls.stop);
self->running = 0;
}
#endif
static const ztimer_ops_t ztimer_mock_ops = {
.set = ztimer_mock_op_set,

View File

@ -183,7 +183,7 @@ static int _blockwise_cb(void *arg, size_t offset, uint8_t *buf, size_t len, int
int nanotest_client_url_cmd(int argc, char **argv)
{
/* Ordered like the RFC method code numbers, but off by 1. GET is code 0. */
const char *method_codes[] = {"get", "post", "put"};
const char *method_codes[] = { "get", "post", "put", "delete" };
int res;
if (argc < 3) {
@ -202,11 +202,12 @@ int nanotest_client_url_cmd(int argc, char **argv)
}
switch (code_pos) {
case 0:
return nanocoap_get_blockwise_url(argv[2], COAP_BLOCKSIZE_32,
_blockwise_cb, NULL);
case 1:
case 2:
case COAP_METHOD_GET - 1:
res = nanocoap_get_blockwise_url(argv[2], COAP_BLOCKSIZE_32,
_blockwise_cb, NULL);
break;
case COAP_METHOD_POST - 1:
case COAP_METHOD_PUT - 1:
;
char response[32];
nanocoap_sock_t sock;
@ -229,6 +230,9 @@ int nanotest_client_url_cmd(int argc, char **argv)
printf("response: %s\n", response);
}
break;
case COAP_METHOD_DELETE - 1:
res = nanocoap_sock_delete_url(argv[2]);
break;
default:
printf("TODO: implement %s request\n", method_codes[code_pos]);
return -1;
@ -240,7 +244,7 @@ int nanotest_client_url_cmd(int argc, char **argv)
return res;
error:
printf("usage: %s <get|post|put> <url> [data]\n", argv[0]);
printf("usage: %s <get|post|put|delete> <url> [data]\n", argv[0]);
return -1;
}