Repo created
This commit is contained in:
parent
6e9a0d01ce
commit
7ee9806fba
2415 changed files with 312708 additions and 2 deletions
67
webserver/build.gradle
Normal file
67
webserver/build.gradle
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import com.android.build.gradle.internal.tasks.LibraryJniLibsTask
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdk 33
|
||||
ndkVersion '25.2.9519653'
|
||||
namespace 'org.adaway.webserver'
|
||||
|
||||
defaultConfig {
|
||||
minSdk 26
|
||||
targetSdk 33
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
prefab = true
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
path 'jni/Android.mk'
|
||||
}
|
||||
}
|
||||
|
||||
sonarqube {
|
||||
skipProject = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.android.ndk.thirdparty:openssl:1.1.1q-beta-1'
|
||||
}
|
||||
|
||||
/**
|
||||
* Task to rename executables from hello_world to libhello_world_exec.so
|
||||
* If they look like libraries, they are packaged in the apk and deployed on the device in the lib folder!
|
||||
*
|
||||
* Help with files: https://docs.gradle.org/current/userguide/working_with_files.html
|
||||
*/
|
||||
def renameJniLibAsExecutable = tasks.register('renameJniLibAsExecutable', Copy) {
|
||||
def flavor = gradle.startParameter.taskRequests.toString().containsIgnoreCase("debug") ? "debug" : "release"
|
||||
def buildFolder = file("${buildDir}/intermediates/ndkBuild/${flavor}/obj/local/")
|
||||
|
||||
dependsOn ":${project.name}:externalNativeBuild${flavor.substring(0, 1).toUpperCase()}${flavor.substring(1)}"
|
||||
|
||||
from(buildFolder) {
|
||||
exclude "**/objs"
|
||||
exclude "**/objs-debug"
|
||||
exclude "**/*.a"
|
||||
exclude "**/*.d"
|
||||
exclude "**/*.o"
|
||||
exclude "**/*.so"
|
||||
exclude "**/*.txt"
|
||||
}
|
||||
|
||||
into buildFolder
|
||||
|
||||
rename '(.+)', 'lib$1_exec.so'
|
||||
|
||||
eachFile {
|
||||
println "Replacing ${it.getPath()} from ${flavor} ndkBuild directory"
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType(LibraryJniLibsTask).configureEach {
|
||||
dependsOn renameJniLibAsExecutable
|
||||
}
|
||||
39
webserver/jni/Android.mk
Normal file
39
webserver/jni/Android.mk
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
|
||||
#
|
||||
# Build stub library in order to declare shared library to include to AAR
|
||||
#
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE := webserver_exec
|
||||
LOCAL_SRC_FILES := stub.c
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
|
||||
|
||||
#
|
||||
# Build webserver executable
|
||||
#
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_CFLAGS := -D MG_ENABLE_IPV6 -DMG_TLS=MG_TLS_OPENSSL -std=c99 -O2 -W -Wall -lcrypto -lssl -pthread -pipe $(COPT)
|
||||
LOCAL_MODULE := webserver
|
||||
LOCAL_SRC_FILES := webserver.c mongoose/mongoose.c
|
||||
LOCAL_SHARED_LIBRARIES := ssl crypto
|
||||
LOCAL_LDLIBS := -llog
|
||||
|
||||
include $(BUILD_EXECUTABLE)
|
||||
|
||||
#
|
||||
# NOTE: Stub library binary is replaced by webserver executable at the end of ndkbuild by gradle build script
|
||||
#
|
||||
|
||||
|
||||
#
|
||||
# Import OpenSSL (ssl and crypto) libraries from prefab
|
||||
#
|
||||
$(call import-module,prefab/openssl)
|
||||
2
webserver/jni/Application.mk
Normal file
2
webserver/jni/Application.mk
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
APP_ABI := all
|
||||
APP_STL := c++_shared
|
||||
16
webserver/jni/mongoose/LICENSE
Normal file
16
webserver/jni/mongoose/LICENSE
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
Copyright (c) 2004-2013 Sergey Lyubka
|
||||
Copyright (c) 2013-2024 Cesanta Software Limited
|
||||
All rights reserved
|
||||
|
||||
This software is dual-licensed: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 as
|
||||
published by the Free Software Foundation. For the terms of this
|
||||
license, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
You are free to use this software under the terms of the GNU General
|
||||
Public License, but WITHOUT ANY WARRANTY; without even the implied
|
||||
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
Alternatively, you can license this software under a commercial
|
||||
license, as set out in <https://mongoose.ws/licensing/>.
|
||||
178
webserver/jni/mongoose/README.md
Normal file
178
webserver/jni/mongoose/README.md
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
# Mongoose - Embedded Web Server / Embedded Network Library
|
||||
|
||||
[](https://opensource.org/licenses/gpl-2.0.php)
|
||||
[](https://github.com/cesanta/mongoose/actions)
|
||||
[](https://codecov.io/gh/cesanta/mongoose)
|
||||
[](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:mongoose)
|
||||
|
||||
Mongoose is a network library for C/C++. It provides event-driven non-blocking
|
||||
APIs for TCP, UDP, HTTP, WebSocket, MQTT, and other protocols. It is designed
|
||||
for connecting devices and bringing them online. On the market since 2004, used
|
||||
by vast number of open source and commercial products - it even runs on the
|
||||
International Space Station! Mongoose makes embedded network programming fast,
|
||||
robust, and easy. Features include:
|
||||
|
||||
- Cross-platform:
|
||||
- works on Linux/UNIX, MacOS, Windows, Android
|
||||
- works on STM32, NXP, ESP32, NRF52, TI, Microchip, and other
|
||||
- write code once - and it'll work everywhere
|
||||
- ideal for the unification of the network infrastructure code across company
|
||||
- Built-in protocols: plain TCP/UDP, SNTP, HTTP, MQTT, Websocket, and other
|
||||
- Asynchronous DNS resolver
|
||||
- Tiny static and run-time footprint
|
||||
- Source code is both ISO C and ISO C++ compliant
|
||||
- Easy to integrate: just copy [mongoose.c](https://raw.githubusercontent.com/cesanta/mongoose/master/mongoose.c)
|
||||
and [mongoose.h](https://raw.githubusercontent.com/cesanta/mongoose/master/mongoose.h) files to your source tree
|
||||
- Built-in TCP/IP stack with drivers for bare metal or RTOS systems
|
||||
- Available drivers: STM32F, STM32H; NXP RT1xxx; TI TM4C; Microchip SAME54; Wiznet W5500
|
||||
- A complete Web device dashboard on bare metal ST Nucleo boards is only 6 files
|
||||
- For comparison, a CubeIDE generated HTTP example is 400+ files
|
||||
- Can run on top of an existing TCP/IP stack with BSD API, e.g. lwIP, Zephyr, Azure, etc
|
||||
- Built-in TLS 1.3 ECC stack. Also can use external TLS libraries - mbedTLS, OpenSSL, or other
|
||||
- Does not depend on any other software to implement networking
|
||||
- Built-in firmware updates for STM32 H5, STM32 H7
|
||||
|
||||
See https://mongoose.ws/ for complete documentation, videos, case studies, etc.
|
||||
|
||||
## Usage Examples
|
||||
|
||||
Below are quick snippets that should give an idea how simple the API is and
|
||||
how easy it is to create applications with it.
|
||||
|
||||
Create a simple web server that serves a directory. The behavior of the
|
||||
HTTP server is specified by its event handler function:
|
||||
|
||||
```c
|
||||
#include "mongoose.h" // To build, run: cc main.c mongoose.c
|
||||
|
||||
// HTTP server event handler function
|
||||
void ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
if (ev == MG_EV_HTTP_MSG) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
struct mg_http_serve_opts opts = { .root_dir = "./web_root/" };
|
||||
mg_http_serve_dir(c, hm, &opts);
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
struct mg_mgr mgr; // Declare event manager
|
||||
mg_mgr_init(&mgr); // Initialise event manager
|
||||
mg_http_listen(&mgr, "http://0.0.0.0:8000", ev_handler, NULL); // Setup listener
|
||||
for (;;) { // Run an infinite event loop
|
||||
mg_mgr_poll(&mgr, 1000);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
HTTP server implements a REST API that returns current time. JSON formatting:
|
||||
```c
|
||||
static void ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
if (ev == MG_EV_HTTP_MSG) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
if (mg_http_match_uri(hm, "/api/time/get")) {
|
||||
mg_http_reply(c, 200, "", "{%m:%lu}\n", MG_ESC("time"), time(NULL));
|
||||
} else {
|
||||
mg_http_reply(c, 500, "", "{%m:%m}\n", MG_ESC("error"), MG_ESC("Unsupported URI"));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
MQTT client that subscribes to a topic `device1/rx` and
|
||||
echoes incoming messages to `device1/tx`:
|
||||
|
||||
```c
|
||||
#include "mongoose.h"
|
||||
|
||||
static const char *s_mqtt_url = "mqtt://broker.hivemq.com:1883";
|
||||
static struct mg_connection *s_mqtt_conn = NULL;
|
||||
|
||||
// MQTT connection event handler function
|
||||
static void ev_handler(struct mg_connection *c, int ev, void *ev_data) {
|
||||
if (ev == MG_EV_OPEN) {
|
||||
MG_INFO(("%lu created, connecting to %s ...", c->id, s_mqtt_url));
|
||||
} else if (ev == MG_EV_MQTT_OPEN) {
|
||||
struct mg_mqtt_opts opts = {.qos = 1, .topic = mg_str("device1/rx")};
|
||||
mg_mqtt_sub(c, &opts);
|
||||
MG_INFO(("%lu connected, subscribing to %s", c->id, opts.topic.buf));
|
||||
} else if (ev == MG_EV_MQTT_MSG) {
|
||||
char response[100];
|
||||
struct mg_mqtt_message *mm = (struct mg_mqtt_message *) ev_data;
|
||||
struct mg_mqtt_opts opts = {.qos = 1, .topic = mg_str("device1/tx")};
|
||||
mg_snprintf(response, sizeof(response), "Received [%.*s] / [%.*s]",
|
||||
mm->topic.len, mm->topic.buf, mm->data.len, mm->data.buf);
|
||||
opts.message = mg_str(response);
|
||||
mg_mqtt_pub(c, &opts);
|
||||
} else if (ev == MG_EV_CLOSE) {
|
||||
MG_INFO(("%u closing", c->id));
|
||||
s_mqtt_conn = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Reconnection timer function. If we get disconnected, reconnect again
|
||||
static void timer_fn(void *arg) {
|
||||
struct mg_mgr *mgr = (struct mg_mgr *) arg;
|
||||
if (s_mqtt_conn == NULL) {
|
||||
struct mg_mqtt_opts opts = {.clean = true};
|
||||
s_mqtt_conn = mg_mqtt_connect(mgr, s_mqtt_url, &opts, ev_handler, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
struct mg_mgr mgr; // Mongoose event manager. Holds all connections
|
||||
mg_mgr_init(&mgr); // Initialise event manager
|
||||
mg_timer_add(&mgr, 3000, MG_TIMER_REPEAT | MG_TIMER_RUN_NOW, timer_fn, &mgr);
|
||||
for (;;) {
|
||||
mg_mgr_poll(&mgr, 1000); // Infinite event loop
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## Commercial use
|
||||
- Mongoose is used by hundreds of businesses, from Fortune500 giants like
|
||||
Siemens, Schneider Electric, Broadcom, Bosch, Google, Samsung, Qualcomm, Caterpillar to the small businesses
|
||||
- Used to solve a wide range of business needs, like implementing Web UI
|
||||
interface on devices, RESTful API services, telemetry data exchange, remote
|
||||
control for a product, remote software updates, remote monitoring, and others
|
||||
- Deployed to hundreds of millions devices in production environment worldwide
|
||||
- See [Case Studies](https://mongoose.ws/case-studies/) from our respected
|
||||
customers like [Schneider Electric](https://mongoose.ws/case-studies/schneider-electric/) (industrial automation), [Broadcom](https://mongoose.ws/case-studies/broadcom/) (semiconductors), [Pilz](https://mongoose.ws/case-studies/pilz/) (industrial automation), and others
|
||||
- See [Testimonials](https://mongoose.ws/testimonials/) from engineers that integrated Mongoose in their commercial products
|
||||
- We provide [Evaluation and Commercial licensing](https://mongoose.ws/licensing/), [support](https://mongoose.ws/support/), consultancy and [integration
|
||||
services](https://mongoose.ws/integration/) - don't hesitate to [contact us](https://mongoose.ws/contact/)
|
||||
|
||||
|
||||
## Security
|
||||
|
||||
We take security seriously:
|
||||
1. Mongoose repository runs a
|
||||
[continuous integration test powered by GitHub](https://github.com/cesanta/mongoose/actions),
|
||||
which runs through hundreds of unit tests on every commit to the repository.
|
||||
Our [unit tests](https://github.com/cesanta/mongoose/tree/master/test)
|
||||
are built with modern address sanitizer technologies, which help to find
|
||||
security vulnerabilities early
|
||||
2. Mongoose repository is integrated into Google's
|
||||
[oss-fuzz continuous fuzzer](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:mongoose)
|
||||
which scans for potential vulnerabilities continuously
|
||||
3. We receive periodic vulnerability reports from the independent security
|
||||
groups like
|
||||
[Cisco Talos](https://www.cisco.com/c/en/us/products/security/talos.html),
|
||||
[Microsoft Security Response Center](https://www.microsoft.com/en-us/msrc),
|
||||
[MITRE Corporation](https://www.mitre.org/),
|
||||
[Compass Security](https://www.compass-security.com/en/) and others.
|
||||
In case of the vulnerability found, we act according to the industry best
|
||||
practice: hold on to the publication, fix the software and notify all our
|
||||
customers that have an appropriate subscription
|
||||
4. Some of our customers (for example NASA)
|
||||
have specific security requirements and run independent security audits,
|
||||
of which we get notified and in case of any issue, act similar to (3).
|
||||
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions are welcome! Please follow the guidelines below:
|
||||
|
||||
- Sign [Cesanta CLA](https://cesanta.com/cla.html) and send GitHub pull request
|
||||
- Make sure that PRs have only one commit, and deal with one issue only
|
||||
17594
webserver/jni/mongoose/mongoose.c
Normal file
17594
webserver/jni/mongoose/mongoose.c
Normal file
File diff suppressed because it is too large
Load diff
3013
webserver/jni/mongoose/mongoose.h
Normal file
3013
webserver/jni/mongoose/mongoose.h
Normal file
File diff suppressed because it is too large
Load diff
0
webserver/jni/stub.c
Normal file
0
webserver/jni/stub.c
Normal file
159
webserver/jni/webserver.c
Normal file
159
webserver/jni/webserver.c
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <android/log.h>
|
||||
#include <errno.h>
|
||||
#include "mongoose/mongoose.h"
|
||||
|
||||
#define THIS_FILE "WebServer"
|
||||
|
||||
// TODO Test difference between 127.0.0.1 + [::1] and localhost
|
||||
#define HTTP_URL "http://localhost:80"
|
||||
#define HTTPS_URL "https://localhost:443"
|
||||
|
||||
#define OOM_ADJ_PATH "/proc/self/oom_score_adj"
|
||||
#define OOM_ADJ_NOKILL -17
|
||||
|
||||
static int s_sig_num = 0;
|
||||
|
||||
struct settings {
|
||||
bool init;
|
||||
struct mg_tls_opts tls_opts;
|
||||
char test_path[100];
|
||||
char icon_path[100];
|
||||
bool icon;
|
||||
bool debug;
|
||||
};
|
||||
|
||||
static void fn(struct mg_connection *c, int ev, void *ev_data) {
|
||||
if (ev == MG_EV_HTTP_MSG && c->fn_data != NULL) {
|
||||
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
|
||||
struct settings *s = (struct settings *) c->fn_data;
|
||||
if (mg_strcmp(hm->uri, mg_str("/internal-test")) == 0) {
|
||||
struct mg_http_serve_opts opts;
|
||||
memset(&opts, 0, sizeof(opts));
|
||||
mg_http_serve_file(c, hm, s->test_path, &opts);
|
||||
} else if (s->icon) {
|
||||
struct mg_http_serve_opts opts;
|
||||
memset(&opts, 0, sizeof(opts));
|
||||
mg_http_serve_file(c, hm, s->icon_path, &opts);
|
||||
} else {
|
||||
mg_http_reply(c, 200, "", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void tls_fn(struct mg_connection *c, int ev, void *ev_data) {
|
||||
if (ev == MG_EV_ACCEPT && c->fn_data != NULL) {
|
||||
struct settings *s = (struct settings *) c->fn_data;
|
||||
mg_tls_init(c, &s->tls_opts);
|
||||
} else {
|
||||
fn(c, ev, ev_data);
|
||||
}
|
||||
}
|
||||
|
||||
static void signal_handler(int sig_num) {
|
||||
signal(sig_num, signal_handler);
|
||||
s_sig_num = sig_num;
|
||||
}
|
||||
|
||||
/*
|
||||
* Tell the kernel's out-of-memory killer to avoid this process.
|
||||
*/
|
||||
void oom_adjust_setup(void) {
|
||||
FILE *fp;
|
||||
int oom_score = INT_MIN;
|
||||
if ((fp = fopen(OOM_ADJ_PATH, "r+")) != NULL) {
|
||||
if (fscanf(fp, "%d", &oom_score) != 1)
|
||||
__android_log_print(ANDROID_LOG_INFO, THIS_FILE, "error reading %s: %s", OOM_ADJ_PATH,
|
||||
strerror(errno));
|
||||
else {
|
||||
rewind(fp);
|
||||
if (fprintf(fp, "%d\n", OOM_ADJ_NOKILL) <= 0)
|
||||
__android_log_print(ANDROID_LOG_INFO, THIS_FILE, "error writing %s: %s",
|
||||
OOM_ADJ_PATH, strerror(errno));
|
||||
else
|
||||
__android_log_print(ANDROID_LOG_INFO, THIS_FILE, "Set %s from %d to %d",
|
||||
OOM_ADJ_PATH, oom_score, OOM_ADJ_NOKILL);
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
|
||||
struct settings parse_cli_parameters(int argc, char *argv[]) {
|
||||
struct settings s = {
|
||||
.init = false,
|
||||
.tls_opts = { 0 },
|
||||
.icon = false,
|
||||
.debug = false
|
||||
};
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "--resources") == 0 && i < argc - 1) {
|
||||
char *resource_path = argv[++i];
|
||||
// Initialize TLS options
|
||||
char cert_path[100];
|
||||
char key_path[100];
|
||||
strcpy(cert_path, resource_path);
|
||||
strcat(cert_path, "/localhost-2410.crt");
|
||||
strcpy(key_path, resource_path);
|
||||
strcat(key_path, "/localhost-2410.key");
|
||||
s.tls_opts.cert = mg_file_read(&mg_fs_posix, cert_path);
|
||||
s.tls_opts.key = mg_file_read(&mg_fs_posix, key_path);
|
||||
// Initialize resource paths
|
||||
strcpy(s.icon_path, resource_path);
|
||||
strcat(s.icon_path, "/icon.svg");
|
||||
strcpy(s.test_path, resource_path);
|
||||
strcat(s.test_path, "/test.html");
|
||||
s.init = true;
|
||||
} else if (strcmp(argv[i], "--icon") == 0) {
|
||||
s.icon = true;
|
||||
} else if (strcmp(argv[i], "--debug") == 0) {
|
||||
s.debug = true;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
struct mg_mgr mgr;
|
||||
struct mg_connection *http_connection;
|
||||
struct mg_connection *https_connection;
|
||||
|
||||
struct settings s = parse_cli_parameters(argc, argv);
|
||||
if (!s.init) {
|
||||
__android_log_print(ANDROID_LOG_FATAL, THIS_FILE, "Missing parameters.");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (s.debug) {
|
||||
__android_log_print(ANDROID_LOG_FATAL, THIS_FILE, "Debug mode activated.");
|
||||
mg_log_set(MG_LL_DEBUG);
|
||||
}
|
||||
|
||||
oom_adjust_setup();
|
||||
|
||||
mg_mgr_init(&mgr);
|
||||
http_connection = mg_http_listen(&mgr, HTTP_URL, fn, &s);
|
||||
if (http_connection == NULL) {
|
||||
__android_log_print(ANDROID_LOG_FATAL, THIS_FILE, "Failed to listen on http port.");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
https_connection = mg_http_listen(&mgr, HTTPS_URL, tls_fn, &s);
|
||||
if (https_connection == NULL) {
|
||||
__android_log_print(ANDROID_LOG_FATAL, THIS_FILE, "Failed to listen on https port.");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
signal(SIGINT, signal_handler);
|
||||
signal(SIGTERM, signal_handler);
|
||||
|
||||
__android_log_print(ANDROID_LOG_INFO, THIS_FILE, "Starting server.");
|
||||
while (s_sig_num == 0) {
|
||||
mg_mgr_poll(&mgr, 1000);
|
||||
}
|
||||
|
||||
mg_mgr_free(&mgr);
|
||||
__android_log_print(ANDROID_LOG_INFO, THIS_FILE, "Stopping server on signal %d.", s_sig_num);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
1
webserver/src/main/AndroidManifest.xml
Normal file
1
webserver/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1 @@
|
|||
<manifest />
|
||||
Loading…
Add table
Add a link
Reference in a new issue