Changed notify API to passing a notification message.
This commit is contained in:
parent
d102d8c607
commit
949ba03bda
|
@ -164,43 +164,51 @@ coap_remove_observer_by_mid(uip_ipaddr_t *addr, uint16_t port, uint16_t mid)
|
||||||
}
|
}
|
||||||
/*-----------------------------------------------------------------------------------*/
|
/*-----------------------------------------------------------------------------------*/
|
||||||
void
|
void
|
||||||
coap_notify_observers(const char *url, int type, uint32_t observe, uint8_t *payload, size_t payload_len)
|
coap_notify_observers(resource_t *resource, uint16_t obs_counter, void *notification)
|
||||||
{
|
{
|
||||||
|
coap_packet_t *const coap_res = (coap_packet_t *) notification;
|
||||||
coap_observer_t* obs = NULL;
|
coap_observer_t* obs = NULL;
|
||||||
|
uint8_t preferred_type = coap_res->type;
|
||||||
|
|
||||||
|
PRINTF("Observing: Notification from %s\n", resource->url);
|
||||||
|
|
||||||
|
/* Iterate over observers. */
|
||||||
for (obs = (coap_observer_t*)list_head(observers_list); obs; obs = obs->next)
|
for (obs = (coap_observer_t*)list_head(observers_list); obs; obs = obs->next)
|
||||||
{
|
{
|
||||||
if (obs->url==url) /* using RESOURCE url pointer as handle */
|
if (obs->url==resource->url) /* using RESOURCE url pointer as handle */
|
||||||
{
|
{
|
||||||
coap_transaction_t *transaction = NULL;
|
coap_transaction_t *transaction = NULL;
|
||||||
|
|
||||||
/*TODO implement special transaction for CON, sharing the same buffer to allow for more observers */
|
/*TODO implement special transaction for CON, sharing the same buffer to allow for more observers. */
|
||||||
|
|
||||||
if ( (transaction = coap_new_transaction(coap_get_mid(), &obs->addr, obs->port)) )
|
if ( (transaction = coap_new_transaction(coap_get_mid(), &obs->addr, obs->port)) )
|
||||||
{
|
{
|
||||||
/* Use CON to check whether client is still there/interested after COAP_OBSERVING_REFRESH_INTERVAL. */
|
PRINTF(" Observer ");
|
||||||
if (stimer_expired(&obs->refresh_timer))
|
|
||||||
{
|
|
||||||
PRINTF("Observing: Refresh client with CON\n");
|
|
||||||
type = COAP_TYPE_CON;
|
|
||||||
stimer_restart(&obs->refresh_timer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* prepare response */
|
|
||||||
coap_packet_t push[1]; /* This way the packet can be treated as pointer as usual. */
|
|
||||||
coap_init_message(push, (coap_message_type_t)type, CONTENT_2_05, transaction->mid );
|
|
||||||
coap_set_header_observe(push, observe);
|
|
||||||
coap_set_header_token(push, obs->token, obs->token_len);
|
|
||||||
coap_set_payload(push, payload, payload_len);
|
|
||||||
transaction->packet_len = coap_serialize_message(push, transaction->packet);
|
|
||||||
|
|
||||||
PRINTF("Observing: Notify from /%s for ", url);
|
|
||||||
PRINT6ADDR(&obs->addr);
|
PRINT6ADDR(&obs->addr);
|
||||||
PRINTF(":%u\n", obs->port);
|
PRINTF(":%u\n", obs->port);
|
||||||
PRINTF(" %.*s\n", payload_len, payload);
|
|
||||||
|
|
||||||
/* Update last MID for RST matching. */
|
/* Update last MID for RST matching. */
|
||||||
obs->last_mid = transaction->mid;
|
obs->last_mid = transaction->mid;
|
||||||
|
|
||||||
|
/* Prepare response */
|
||||||
|
coap_res->mid = transaction->mid;
|
||||||
|
coap_set_header_observe(coap_res, obs_counter);
|
||||||
|
coap_set_header_token(coap_res, obs->token, obs->token_len);
|
||||||
|
|
||||||
|
/* Use CON to check whether client is still there/interested after COAP_OBSERVING_REFRESH_INTERVAL. */
|
||||||
|
if (stimer_expired(&obs->refresh_timer))
|
||||||
|
{
|
||||||
|
PRINTF(" Refreshing with CON\n");
|
||||||
|
coap_res->type = COAP_TYPE_CON;
|
||||||
|
stimer_restart(&obs->refresh_timer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
coap_res->type = preferred_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction->packet_len = coap_serialize_message(coap_res, transaction->packet);
|
||||||
|
|
||||||
coap_send_transaction(transaction);
|
coap_send_transaction(transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,7 +75,7 @@ int coap_remove_observer_by_token(uip_ipaddr_t *addr, uint16_t port, uint8_t *to
|
||||||
int coap_remove_observer_by_url(uip_ipaddr_t *addr, uint16_t port, const char *url);
|
int coap_remove_observer_by_url(uip_ipaddr_t *addr, uint16_t port, const char *url);
|
||||||
int coap_remove_observer_by_mid(uip_ipaddr_t *addr, uint16_t port, uint16_t mid);
|
int coap_remove_observer_by_mid(uip_ipaddr_t *addr, uint16_t port, uint16_t mid);
|
||||||
|
|
||||||
void coap_notify_observers(const char *url, int type, uint32_t observe, uint8_t *payload, size_t payload_len);
|
void coap_notify_observers(resource_t *resource, uint16_t obs_counter, void *notification);
|
||||||
|
|
||||||
void coap_observe_handler(resource_t *resource, void *request, void *response);
|
void coap_observe_handler(resource_t *resource, void *request, void *response);
|
||||||
|
|
||||||
|
|
|
@ -105,6 +105,7 @@ coap_send_transaction(coap_transaction_t *t)
|
||||||
{
|
{
|
||||||
if (t->retrans_counter<COAP_MAX_RETRANSMIT)
|
if (t->retrans_counter<COAP_MAX_RETRANSMIT)
|
||||||
{
|
{
|
||||||
|
/* Not timed out yet. */
|
||||||
PRINTF("Keeping transaction %u\n", t->mid);
|
PRINTF("Keeping transaction %u\n", t->mid);
|
||||||
|
|
||||||
if (t->retrans_counter==0)
|
if (t->retrans_counter==0)
|
||||||
|
@ -118,7 +119,10 @@ coap_send_transaction(coap_transaction_t *t)
|
||||||
PRINTF("Doubled (%u) interval %f\n", t->retrans_counter, (float)t->retrans_timer.timer.interval/CLOCK_SECOND);
|
PRINTF("Doubled (%u) interval %f\n", t->retrans_counter, (float)t->retrans_timer.timer.interval/CLOCK_SECOND);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*FIXME hack, maybe there is a better way, but avoid posting everything to the process */
|
/*FIXME
|
||||||
|
* Hack: Setting timer for responsible process.
|
||||||
|
* Maybe there is a better way, but avoid posting everything to the process.
|
||||||
|
*/
|
||||||
struct process *process_actual = PROCESS_CURRENT();
|
struct process *process_actual = PROCESS_CURRENT();
|
||||||
process_current = transaction_handler_process;
|
process_current = transaction_handler_process;
|
||||||
etimer_restart(&t->retrans_timer); /* interval updated above */
|
etimer_restart(&t->retrans_timer); /* interval updated above */
|
||||||
|
@ -128,7 +132,7 @@ coap_send_transaction(coap_transaction_t *t)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* timeout */
|
/* Timed out. */
|
||||||
PRINTF("Timeout\n");
|
PRINTF("Timeout\n");
|
||||||
restful_response_handler callback = t->callback;
|
restful_response_handler callback = t->callback;
|
||||||
void *callback_data = t->callback_data;
|
void *callback_data = t->callback_data;
|
||||||
|
|
|
@ -75,7 +75,7 @@ struct periodic_resource_s;
|
||||||
typedef void (*restful_handler) (void* request, void* response, uint8_t *buffer, uint16_t preferred_size, int32_t *offset);
|
typedef void (*restful_handler) (void* request, void* response, uint8_t *buffer, uint16_t preferred_size, int32_t *offset);
|
||||||
typedef int (*restful_pre_handler) (struct resource_s *resource, void* request, void* response);
|
typedef int (*restful_pre_handler) (struct resource_s *resource, void* request, void* response);
|
||||||
typedef void (*restful_post_handler) (struct resource_s *resource, void* request, void* response);
|
typedef void (*restful_post_handler) (struct resource_s *resource, void* request, void* response);
|
||||||
typedef int (*restful_periodic_handler) (struct resource_s* resource);
|
typedef void (*restful_periodic_handler) (struct resource_s* resource);
|
||||||
typedef void (*restful_response_handler) (void *data, void* response);
|
typedef void (*restful_response_handler) (void *data, void* response);
|
||||||
|
|
||||||
/* Signature of the rest-engine service function. */
|
/* Signature of the rest-engine service function. */
|
||||||
|
@ -134,6 +134,31 @@ struct rest_implementation_type
|
||||||
unsigned int APPLICATION_X_OBIX_BINARY;
|
unsigned int APPLICATION_X_OBIX_BINARY;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Data structure representing a resource in REST.
|
||||||
|
*/
|
||||||
|
struct resource_s {
|
||||||
|
struct resource_s *next; /* for LIST, points to next resource defined */
|
||||||
|
rest_resource_flags_t flags; /* handled RESTful methods */
|
||||||
|
const char* url; /*handled URL*/
|
||||||
|
const char* attributes; /* link-format attributes */
|
||||||
|
restful_handler handler; /* handler function */
|
||||||
|
restful_pre_handler pre_handler; /* to be called before handler, may perform initializations */
|
||||||
|
restful_post_handler post_handler; /* to be called after handler, may perform finalizations (cleanup, etc) */
|
||||||
|
void* user_data; /* pointer to user specific data */
|
||||||
|
unsigned int benchmark; /* to benchmark resource handler, used for separate response */
|
||||||
|
};
|
||||||
|
typedef struct resource_s resource_t;
|
||||||
|
|
||||||
|
struct periodic_resource_s {
|
||||||
|
struct periodic_resource_s *next; /* for LIST, points to next resource defined */
|
||||||
|
resource_t *resource;
|
||||||
|
uint32_t period;
|
||||||
|
struct etimer periodic_timer;
|
||||||
|
restful_periodic_handler periodic_handler;
|
||||||
|
};
|
||||||
|
typedef struct periodic_resource_s periodic_resource_t;
|
||||||
|
|
||||||
struct rest_implementation {
|
struct rest_implementation {
|
||||||
char *name;
|
char *name;
|
||||||
|
|
||||||
|
@ -199,7 +224,7 @@ struct rest_implementation {
|
||||||
int (* get_post_variable)(void *request, const char *name, const char **value);
|
int (* get_post_variable)(void *request, const char *name, const char **value);
|
||||||
|
|
||||||
/** Send the payload to all subscribers of the resource at url. */
|
/** Send the payload to all subscribers of the resource at url. */
|
||||||
void (* notify_subscribers)(const char *url, int implementation_secific_mode, uint32_t counter, uint8_t *payload, size_t payload_len);
|
void (* notify_subscribers)(resource_t *resource, uint16_t counter, void *notification);
|
||||||
|
|
||||||
/** The handler for resource subscriptions. */
|
/** The handler for resource subscriptions. */
|
||||||
restful_post_handler subscription_handler;
|
restful_post_handler subscription_handler;
|
||||||
|
@ -222,32 +247,6 @@ struct rest_implementation {
|
||||||
*/
|
*/
|
||||||
extern const struct rest_implementation REST;
|
extern const struct rest_implementation REST;
|
||||||
|
|
||||||
/*
|
|
||||||
* Data structure representing a resource in REST.
|
|
||||||
*/
|
|
||||||
struct resource_s {
|
|
||||||
struct resource_s *next; /* for LIST, points to next resource defined */
|
|
||||||
rest_resource_flags_t flags; /* handled RESTful methods */
|
|
||||||
const char* url; /*handled URL*/
|
|
||||||
const char* attributes; /* link-format attributes */
|
|
||||||
restful_handler handler; /* handler function */
|
|
||||||
restful_pre_handler pre_handler; /* to be called before handler, may perform initializations */
|
|
||||||
restful_post_handler post_handler; /* to be called after handler, may perform finalizations (cleanup, etc) */
|
|
||||||
void* user_data; /* pointer to user specific data */
|
|
||||||
unsigned int benchmark; /* to benchmark resource handler, used for separate response */
|
|
||||||
};
|
|
||||||
typedef struct resource_s resource_t;
|
|
||||||
|
|
||||||
struct periodic_resource_s {
|
|
||||||
struct periodic_resource_s *next; /* for LIST, points to next resource defined */
|
|
||||||
resource_t *resource;
|
|
||||||
uint32_t period;
|
|
||||||
struct etimer periodic_timer;
|
|
||||||
restful_periodic_handler periodic_handler;
|
|
||||||
};
|
|
||||||
typedef struct periodic_resource_s periodic_resource_t;
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Macro to define a Resource
|
* Macro to define a Resource
|
||||||
* Resources are statically defined for the sake of efficiency and better memory management.
|
* Resources are statically defined for the sake of efficiency and better memory management.
|
||||||
|
@ -282,7 +281,7 @@ int name##_event_handler(resource_t*)
|
||||||
#define PERIODIC_RESOURCE(name, flags, url, attributes, period) \
|
#define PERIODIC_RESOURCE(name, flags, url, attributes, period) \
|
||||||
void name##_handler(void *, void *, uint8_t *, uint16_t, int32_t *); \
|
void name##_handler(void *, void *, uint8_t *, uint16_t, int32_t *); \
|
||||||
resource_t resource_##name = {NULL, flags, url, attributes, name##_handler, NULL, NULL, NULL}; \
|
resource_t resource_##name = {NULL, flags, url, attributes, name##_handler, NULL, NULL, NULL}; \
|
||||||
int name##_periodic_handler(resource_t*); \
|
void name##_periodic_handler(resource_t*); \
|
||||||
periodic_resource_t periodic_resource_##name = {NULL, &resource_##name, period, {{0}}, name##_periodic_handler}
|
periodic_resource_t periodic_resource_##name = {NULL, &resource_##name, period, {{0}}, name##_periodic_handler}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -225,7 +225,7 @@ separate_handler(void* request, void* response, uint8_t *buffer, uint16_t prefer
|
||||||
PRINTF("(%s %u)\n", coap_req->type==COAP_TYPE_CON?"CON":"NON", coap_req->mid);
|
PRINTF("(%s %u)\n", coap_req->type==COAP_TYPE_CON?"CON":"NON", coap_req->mid);
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
void
|
||||||
separate_periodic_handler(resource_t *resource)
|
separate_periodic_handler(resource_t *resource)
|
||||||
{
|
{
|
||||||
if (separate_active)
|
if (separate_active)
|
||||||
|
@ -256,14 +256,10 @@ separate_periodic_handler(resource_t *resource)
|
||||||
/* The engine will clear the transaction (right after send for NON, after acked for CON). */
|
/* The engine will clear the transaction (right after send for NON, after acked for CON). */
|
||||||
|
|
||||||
separate_active = 0;
|
separate_active = 0;
|
||||||
|
|
||||||
return 1;
|
|
||||||
} else {
|
} else {
|
||||||
PRINTF("ERROR (transaction)\n");
|
PRINTF("ERROR (transaction)\n");
|
||||||
}
|
}
|
||||||
} /* if (separate_active) */
|
} /* if (separate_active) */
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -364,7 +360,7 @@ large_update_handler(void* request, void* response, uint8_t *buffer, uint16_t pr
|
||||||
*offset = -1;
|
*offset = -1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const uint8_t *incoming = NULL;
|
uint8_t *incoming = NULL;
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
|
|
||||||
unsigned int ct = REST.get_header_content_type(request);
|
unsigned int ct = REST.get_header_content_type(request);
|
||||||
|
@ -415,9 +411,8 @@ void
|
||||||
large_create_handler(void* request, void* response, uint8_t *buffer, uint16_t preferred_size, int32_t *offset)
|
large_create_handler(void* request, void* response, uint8_t *buffer, uint16_t preferred_size, int32_t *offset)
|
||||||
{
|
{
|
||||||
coap_packet_t *const coap_req = (coap_packet_t *) request;
|
coap_packet_t *const coap_req = (coap_packet_t *) request;
|
||||||
uint8_t method = REST.get_method_type(request);
|
|
||||||
|
|
||||||
const uint8_t *incoming = NULL;
|
uint8_t *incoming = NULL;
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
|
|
||||||
unsigned int ct = REST.get_header_content_type(request);
|
unsigned int ct = REST.get_header_content_type(request);
|
||||||
|
@ -461,7 +456,7 @@ large_create_handler(void* request, void* response, uint8_t *buffer, uint16_t pr
|
||||||
*/
|
*/
|
||||||
PERIODIC_RESOURCE(obs, METHOD_GET, "obs", "title=\"Observable resource which changes every 5 seconds\";obs;rt=\"observe\"", 5*CLOCK_SECOND);
|
PERIODIC_RESOURCE(obs, METHOD_GET, "obs", "title=\"Observable resource which changes every 5 seconds\";obs;rt=\"observe\"", 5*CLOCK_SECOND);
|
||||||
|
|
||||||
static uint32_t obs_counter = 0;
|
static uint16_t obs_counter = 0;
|
||||||
static char obs_content[16];
|
static char obs_content[16];
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -479,18 +474,20 @@ obs_handler(void* request, void* response, uint8_t *buffer, uint16_t preferred_s
|
||||||
* Additionally, a handler function named [resource name]_handler must be implemented for each PERIODIC_RESOURCE.
|
* Additionally, a handler function named [resource name]_handler must be implemented for each PERIODIC_RESOURCE.
|
||||||
* It will be called by the REST manager process with the defined period.
|
* It will be called by the REST manager process with the defined period.
|
||||||
*/
|
*/
|
||||||
int
|
void
|
||||||
obs_periodic_handler(resource_t *r)
|
obs_periodic_handler(resource_t *r)
|
||||||
{
|
{
|
||||||
|
++obs_counter;
|
||||||
|
|
||||||
PRINTF("TICK /%s\n", r->url);
|
PRINTF("TICK %u for /%s\n", obs_counter, r->url);
|
||||||
obs_counter = obs_counter + 1;
|
|
||||||
|
/* Build notification. */
|
||||||
|
coap_packet_t notification[1]; /* This way the packet can be treated as pointer as usual. */
|
||||||
|
coap_init_message(notification, COAP_TYPE_NON, CONTENT_2_05, 0 );
|
||||||
|
coap_set_payload(notification, obs_content, snprintf(obs_content, sizeof(obs_content), "TICK %u", obs_counter));
|
||||||
|
|
||||||
/* Notify the registered observers with the given message type, observe option, and payload. */
|
/* Notify the registered observers with the given message type, observe option, and payload. */
|
||||||
REST.notify_subscribers(r->url, 1, obs_counter, (uint8_t *)obs_content, snprintf(obs_content, sizeof(obs_content), "TICK %lu", obs_counter));
|
REST.notify_subscribers(r, obs_counter, notification);
|
||||||
/* |-> implementation-specific, e.g. CoAP: 0=CON and 1=NON notification */
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -465,20 +465,23 @@ pushing_handler(void* request, void* response, uint8_t *buffer, uint16_t preferr
|
||||||
* Additionally, a handler function named [resource name]_handler must be implemented for each PERIODIC_RESOURCE.
|
* Additionally, a handler function named [resource name]_handler must be implemented for each PERIODIC_RESOURCE.
|
||||||
* It will be called by the REST manager process with the defined period.
|
* It will be called by the REST manager process with the defined period.
|
||||||
*/
|
*/
|
||||||
int
|
void
|
||||||
pushing_periodic_handler(resource_t *r)
|
pushing_periodic_handler(resource_t *r)
|
||||||
{
|
{
|
||||||
static uint32_t periodic_i = 0;
|
static uint16_t obs_counter = 0;
|
||||||
static char content[16];
|
static char content[11];
|
||||||
|
|
||||||
PRINTF("TICK /%s\n", r->url);
|
++obs_counter;
|
||||||
periodic_i = periodic_i + 1;
|
|
||||||
|
PRINTF("TICK %u for /%s\n", periodic_i, r->url);
|
||||||
|
|
||||||
|
/* Build notification. */
|
||||||
|
coap_packet_t notification[1]; /* This way the packet can be treated as pointer as usual. */
|
||||||
|
coap_init_message(notification, COAP_TYPE_NON, CONTENT_2_05, 0 );
|
||||||
|
coap_set_payload(notification, content, snprintf(content, sizeof(content), "TICK %u", obs_counter));
|
||||||
|
|
||||||
/* Notify the registered observers with the given message type, observe option, and payload. */
|
/* Notify the registered observers with the given message type, observe option, and payload. */
|
||||||
REST.notify_subscribers(r->url, 1, periodic_i, (uint8_t *)content, snprintf(content, sizeof(content), "TICK %lu", periodic_i));
|
REST.notify_subscribers(r, obs_counter, notification);
|
||||||
/* |-> implementation-specific, e.g. CoAP: 0=CON and 1=NON notification */
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -503,21 +506,23 @@ event_handler(void* request, void* response, uint8_t *buffer, uint16_t preferred
|
||||||
|
|
||||||
/* Additionally, a handler function named [resource name]_event_handler must be implemented for each PERIODIC_RESOURCE defined.
|
/* Additionally, a handler function named [resource name]_event_handler must be implemented for each PERIODIC_RESOURCE defined.
|
||||||
* It will be called by the REST manager process with the defined period. */
|
* It will be called by the REST manager process with the defined period. */
|
||||||
int
|
void
|
||||||
event_event_handler(resource_t *r)
|
event_event_handler(resource_t *r)
|
||||||
{
|
{
|
||||||
static uint32_t event_i = 0;
|
static uint16_t event_counter = 0;
|
||||||
static char content[10];
|
static char content[12];
|
||||||
|
|
||||||
PRINTF("EVENT /%s\n", r->url);
|
++event_counter;
|
||||||
++event_i;
|
|
||||||
|
|
||||||
/* Notify registered observers with the given message type, observe option, and payload.
|
PRINTF("TICK %u for /%s\n", event_counter, r->url);
|
||||||
* The token will be set automatically. */
|
|
||||||
|
|
||||||
// FIXME provide a rest_notify_subscribers call; how to manage specific options such as COAP_TYPE?
|
/* Build notification. */
|
||||||
REST.notify_subscribers(r->url, 0, event_i, content, snprintf(content, sizeof(content), "EVENT %lu", event_i));
|
coap_packet_t notification[1]; /* This way the packet can be treated as pointer as usual. */
|
||||||
return 1;
|
coap_init_message(notification, COAP_TYPE_CON, CONTENT_2_05, 0 );
|
||||||
|
coap_set_payload(notification, content, snprintf(content, sizeof(content), "EVENT %u", event_counter));
|
||||||
|
|
||||||
|
/* Notify the registered observers with the given message type, observe option, and payload. */
|
||||||
|
REST.notify_subscribers(r, event_counter, notification);
|
||||||
}
|
}
|
||||||
#endif /* PLATFORM_HAS_BUTTON */
|
#endif /* PLATFORM_HAS_BUTTON */
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue