Updated to support brand new External Server Interface
This commit is contained in:
parent
ee57e48d53
commit
4ea54ae61a
5 changed files with 129 additions and 94 deletions
183
CronosAgent.c
183
CronosAgent.c
|
@ -45,11 +45,11 @@
|
|||
#define ASN_CONSTRUCTOR 0x20
|
||||
#endif
|
||||
|
||||
// #include "RA-RTP.h"
|
||||
#ifndef RA_RTP_H
|
||||
#define RTP_PROTOCOL_VERSION 2
|
||||
#define RTP_CONTROL_VERSION_SHIFT 6
|
||||
#define RTP_HEADER_MINIMAL_LENGTH 12
|
||||
// #include "KAIROS-HAM.h"
|
||||
#ifndef KAIROS_HAM_H
|
||||
#define KAIROS_HAM_DEFAULT_PORT 65472
|
||||
#define KAIROS_HAM_RECORD_ID 0xcbfaded8
|
||||
#define KAIROS_EXCHANGE_HEADER_LENGTH 12
|
||||
#endif
|
||||
|
||||
// #include "RemoteControl.h"
|
||||
|
@ -99,8 +99,7 @@
|
|||
#define MODE_SYSLOG (1 << 1)
|
||||
#define MODE_DAEMON (1 << 2)
|
||||
|
||||
#define MEDIA_PORT_COUNT (REWIND_TYPE_ANALOG_DATA - REWIND_TYPE_SLOT_1_VOICE + 1)
|
||||
#define EVENT_LIST_LENGTH (4 + 1 + 3 + MEDIA_PORT_COUNT)
|
||||
#define EVENT_LIST_LENGTH (4 + 1 + 4)
|
||||
|
||||
#define BUFFER_SIZE 4096
|
||||
#define EXPIRATION_TIME 20
|
||||
|
@ -136,7 +135,7 @@ int main(int argc, const char* argv[])
|
|||
// Parameters of repeater
|
||||
|
||||
int proxyTrapPort = 162;
|
||||
int proxyBasePort = 40000;
|
||||
int proxyMediaPort = KAIROS_HAM_DEFAULT_PORT;
|
||||
|
||||
uint32_t repeaterNumber = 0;
|
||||
int repeaterControlPort = REMOTE_DEFAULT_PORT;
|
||||
|
@ -155,7 +154,7 @@ int main(int argc, const char* argv[])
|
|||
{ "server-password", required_argument, NULL, 'w' },
|
||||
{ "server-address", required_argument, NULL, 's' },
|
||||
{ "server-port", required_argument, NULL, 'p' },
|
||||
{ "base-port", required_argument, NULL, 'b' },
|
||||
{ "media-port", required_argument, NULL, 'b' },
|
||||
{ "trap-port", required_argument, NULL, 't' },
|
||||
{ "service-mode", required_argument, NULL, 'm' },
|
||||
{ NULL, 0, NULL, 0 }
|
||||
|
@ -190,7 +189,7 @@ int main(int argc, const char* argv[])
|
|||
break;
|
||||
|
||||
case 'b':
|
||||
proxyBasePort = strtol(optarg, NULL, 10);
|
||||
proxyMediaPort = strtol(optarg, NULL, 10);
|
||||
break;
|
||||
|
||||
case 't':
|
||||
|
@ -215,8 +214,8 @@ int main(int argc, const char* argv[])
|
|||
" --repeater-port <port number of interface Remote Control>\n"
|
||||
" --server-password <access password of BrandMeister DMR Server>\n"
|
||||
" --server-address <domain name of BrandMeister DMR Server>\n"
|
||||
" --server-port <port number of RadioActivity Console>\n"
|
||||
" --base-port <base port for RTP Media>\n"
|
||||
" --server-port <local port for BrandMeister DMR Server>\n"
|
||||
" --media-port <media port for KAIROS External Servier>\n"
|
||||
" --trap-port <port for SNMP Traps>\n"
|
||||
" --service-mode <set of bits>\n"
|
||||
" bit 0 - print to standard output\n"
|
||||
|
@ -287,8 +286,8 @@ int main(int argc, const char* argv[])
|
|||
// Initialize proxy sockets
|
||||
|
||||
int trapHandle;
|
||||
int mediaHandle;
|
||||
int remoteHandle;
|
||||
int mediaHandles[MEDIA_PORT_COUNT];
|
||||
struct sockaddr_in proxySocketAddress;
|
||||
|
||||
proxySocketAddress.sin_family = AF_INET;
|
||||
|
@ -312,17 +311,13 @@ int main(int argc, const char* argv[])
|
|||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
for (size_t index = 0; index < MEDIA_PORT_COUNT; index ++)
|
||||
proxySocketAddress.sin_port = htons(proxyMediaPort);
|
||||
mediaHandle = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if((mediaHandle < 0) ||
|
||||
(bind(mediaHandle, (struct sockaddr*)&proxySocketAddress, sizeof(proxySocketAddress)) < 0))
|
||||
{
|
||||
proxySocketAddress.sin_port = htons(proxyBasePort + index * 10);
|
||||
int handle = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if((handle < 0) ||
|
||||
(bind(handle, (struct sockaddr*)&proxySocketAddress, sizeof(proxySocketAddress)) < 0))
|
||||
{
|
||||
print("Error opening port for RTP Media\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
mediaHandles[index] = handle;
|
||||
print("Error opening port for KAIROS External Server\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Initialize uplink socket
|
||||
|
@ -343,6 +338,11 @@ int main(int argc, const char* argv[])
|
|||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Configure socket options
|
||||
|
||||
int value = true;
|
||||
setsockopt(remoteHandle, IPPROTO_IP, IP_PKTINFO, &value, sizeof(value));
|
||||
|
||||
#ifdef __linux__
|
||||
|
||||
// Initialize timer handle
|
||||
|
@ -378,12 +378,9 @@ int main(int argc, const char* argv[])
|
|||
|
||||
pollHandle = epoll_create(EVENT_LIST_LENGTH);
|
||||
|
||||
for (size_t index = 0; index < MEDIA_PORT_COUNT; index ++)
|
||||
{
|
||||
event.events = EPOLLIN;
|
||||
event.data.fd = mediaHandles[index];
|
||||
epoll_ctl(pollHandle, EPOLL_CTL_ADD, event.data.fd, &event);
|
||||
}
|
||||
event.events = EPOLLIN;
|
||||
event.data.fd = mediaHandle;
|
||||
epoll_ctl(pollHandle, EPOLL_CTL_ADD, event.data.fd, &event);
|
||||
|
||||
event.events = EPOLLIN;
|
||||
event.data.fd = trapHandle;
|
||||
|
@ -429,11 +426,8 @@ int main(int argc, const char* argv[])
|
|||
|
||||
queueHandle = kqueue();
|
||||
|
||||
for (size_t index = 0; index < MEDIA_PORT_COUNT; index ++)
|
||||
{
|
||||
EV_SET(change, mediaHandles[index], EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, 0);
|
||||
change ++;
|
||||
}
|
||||
EV_SET(change, mediaHandle, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, 0);
|
||||
change ++;
|
||||
|
||||
EV_SET(change, uplinkHandle, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, 0);
|
||||
change ++;
|
||||
|
@ -479,7 +473,14 @@ int main(int argc, const char* argv[])
|
|||
|
||||
size_t passwordLength = strlen(serverPassword);
|
||||
time_t watchDog = now.tv_sec + EXPIRATION_TIME;
|
||||
uint32_t sequenceNumber = 0;
|
||||
|
||||
uint32_t sequenceNumbers[] =
|
||||
{
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
};
|
||||
|
||||
// Main loop
|
||||
|
||||
|
@ -530,13 +531,10 @@ int main(int argc, const char* argv[])
|
|||
uint16_t type = le16toh(incomingBuffer->type);
|
||||
size_t length = le16toh(incomingBuffer->length);
|
||||
|
||||
if ((type >= REWIND_TYPE_SLOT_1_VOICE) &&
|
||||
(type <= REWIND_TYPE_ANALOG_DATA))
|
||||
if (type == REWIND_TYPE_EXTERNAL_SERVER)
|
||||
{
|
||||
size_t index = type - REWIND_TYPE_SLOT_1_VOICE;
|
||||
repeaterSocketAddress.sin_port = htons(proxyBasePort + index * 10);
|
||||
sendto(mediaHandles[index], incomingBuffer->data, length, 0, (struct sockaddr*)&repeaterSocketAddress, sizeof(struct sockaddr_in));
|
||||
|
||||
repeaterSocketAddress.sin_port = htons(proxyMediaPort);
|
||||
sendto(mediaHandle, incomingBuffer->data, length, 0, (struct sockaddr*)&repeaterSocketAddress, sizeof(struct sockaddr_in));
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -544,7 +542,6 @@ int main(int argc, const char* argv[])
|
|||
{
|
||||
repeaterSocketAddress.sin_port = htons(repeaterControlPort);
|
||||
sendto(remoteHandle, incomingBuffer->data, length, 0, (struct sockaddr*)&repeaterSocketAddress, sizeof(struct sockaddr_in));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -563,7 +560,7 @@ int main(int argc, const char* argv[])
|
|||
SHA256(incomingBuffer->data, length + passwordLength, outgoingBuffer->data);
|
||||
|
||||
outgoingBuffer->type = htole16(REWIND_TYPE_AUTHENTICATION);
|
||||
outgoingBuffer->number = htole32(++ sequenceNumber);
|
||||
outgoingBuffer->number = htole32(++ sequenceNumbers[0]);
|
||||
outgoingBuffer->length = htole16(SHA256_DIGEST_LENGTH);
|
||||
|
||||
sendto(uplinkHandle, outgoingBuffer, sizeof(struct RewindData) + SHA256_DIGEST_LENGTH, 0, serverAddress->ai_addr, serverAddress->ai_addrlen);
|
||||
|
@ -591,30 +588,29 @@ int main(int argc, const char* argv[])
|
|||
}
|
||||
}
|
||||
|
||||
// Handle packet of RTP
|
||||
// Handle packet of External Server
|
||||
|
||||
for (size_t index = 0; index < MEDIA_PORT_COUNT; index ++)
|
||||
if (CHECK(event, mediaHandle))
|
||||
{
|
||||
if (CHECK(event, mediaHandles[index]))
|
||||
{
|
||||
struct sockaddr_in address;
|
||||
socklen_t size = sizeof(address);
|
||||
uint8_t* buffer = (uint8_t*)outgoingBuffer->data;
|
||||
size_t length = recvfrom(mediaHandles[index], buffer, BUFFER_SIZE, 0, (struct sockaddr*)&address, &size);
|
||||
struct sockaddr_in address;
|
||||
socklen_t size = sizeof(address);
|
||||
uint8_t* buffer = (uint8_t*)outgoingBuffer->data;
|
||||
size_t length = recvfrom(mediaHandle, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&address, &size);
|
||||
|
||||
if ((size == sizeof(struct sockaddr_in)) &&
|
||||
(address.sin_addr.s_addr == repeaterSocketAddress.sin_addr.s_addr) &&
|
||||
((buffer[0] >> RTP_CONTROL_VERSION_SHIFT) == RTP_PROTOCOL_VERSION) &&
|
||||
(length >= RTP_HEADER_MINIMAL_LENGTH))
|
||||
{
|
||||
outgoingBuffer->type = htole16(index + REWIND_TYPE_SLOT_1_VOICE);
|
||||
outgoingBuffer->number = htole32(++ sequenceNumber);
|
||||
outgoingBuffer->length = htole16(length);
|
||||
length += sizeof(struct RewindData);
|
||||
sendto(uplinkHandle, outgoingBuffer, length, 0, serverAddress->ai_addr, serverAddress->ai_addrlen);
|
||||
}
|
||||
break;
|
||||
if ((size == sizeof(struct sockaddr_in)) &&
|
||||
(address.sin_addr.s_addr == repeaterSocketAddress.sin_addr.s_addr) &&
|
||||
(length >= KAIROS_EXCHANGE_HEADER_LENGTH) &&
|
||||
(le32toh(*(uint32_t*)buffer) == KAIROS_HAM_RECORD_ID))
|
||||
{
|
||||
uint16_t number = ++ sequenceNumbers[buffer[8] & 3];
|
||||
outgoingBuffer->type = htole16(REWIND_TYPE_EXTERNAL_SERVER);
|
||||
outgoingBuffer->flags = htole16(buffer[8] < 4);
|
||||
outgoingBuffer->number = htole32(number);
|
||||
outgoingBuffer->length = htole16(length);
|
||||
length += sizeof(struct RewindData);
|
||||
sendto(uplinkHandle, outgoingBuffer, length, 0, serverAddress->ai_addr, serverAddress->ai_addrlen);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle packet of Remote Control
|
||||
|
@ -622,22 +618,62 @@ int main(int argc, const char* argv[])
|
|||
if (CHECK(event, remoteHandle))
|
||||
{
|
||||
struct sockaddr_in address;
|
||||
socklen_t size = sizeof(address);
|
||||
uint8_t* buffer = (uint8_t*)outgoingBuffer->data;
|
||||
size_t length = recvfrom(remoteHandle, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&address, &size);
|
||||
|
||||
if ((size == sizeof(struct sockaddr_in)) &&
|
||||
struct iovec vector;
|
||||
vector.iov_base = buffer;
|
||||
vector.iov_len = BUFFER_SIZE;
|
||||
|
||||
struct msghdr message;
|
||||
message.msg_name = &address;
|
||||
message.msg_namelen = sizeof(address);
|
||||
message.msg_iov = &vector;
|
||||
message.msg_iovlen = 1;
|
||||
message.msg_control = alloca(BUFFER_SIZE);
|
||||
message.msg_controllen = BUFFER_SIZE;
|
||||
message.msg_flags = 0;
|
||||
|
||||
size_t length = recvmsg(remoteHandle, &message, 0);
|
||||
|
||||
if ((message.msg_namelen == sizeof(struct sockaddr_in)) &&
|
||||
(address.sin_addr.s_addr == repeaterSocketAddress.sin_addr.s_addr) &&
|
||||
(buffer[0] == REMOTE_STX) &&
|
||||
(buffer[length - 1] == REMOTE_ETX) &&
|
||||
(length >= 8))
|
||||
{
|
||||
outgoingBuffer->type = htole16(REWIND_TYPE_REMOTE_CONTROL);
|
||||
outgoingBuffer->number = htole32(++ sequenceNumber);
|
||||
outgoingBuffer->number = htole32(++ sequenceNumbers[0]);
|
||||
outgoingBuffer->length = htole16(length);
|
||||
length += sizeof(struct RewindData);
|
||||
sendto(uplinkHandle, outgoingBuffer, length, 0, serverAddress->ai_addr, serverAddress->ai_addrlen);
|
||||
|
||||
// Transmit destination address of received packet
|
||||
// to make correct messages for External Server interface
|
||||
|
||||
struct cmsghdr* control = CMSG_FIRSTHDR(&message);
|
||||
while (control != NULL)
|
||||
{
|
||||
if (control->cmsg_type == IP_PKTINFO)
|
||||
{
|
||||
struct in_pktinfo* information = (struct in_pktinfo*)CMSG_DATA(control);
|
||||
|
||||
size_t length = sizeof(struct RewindAddressData);
|
||||
struct RewindAddressData* data = (struct RewindAddressData*)outgoingBuffer->data;
|
||||
data->address = information->ipi_addr;
|
||||
data->port = htons(proxyMediaPort);
|
||||
|
||||
outgoingBuffer->type = htole16(REWIND_TYPE_ADDRESS_NOTICE);
|
||||
outgoingBuffer->number = htole32(++ sequenceNumbers[0]);
|
||||
outgoingBuffer->length = htobe16(length);
|
||||
length += sizeof(struct RewindData);
|
||||
sendto(uplinkHandle, outgoingBuffer, length, 0, serverAddress->ai_addr, serverAddress->ai_addrlen);
|
||||
|
||||
break;
|
||||
}
|
||||
control = CMSG_NXTHDR(&message, control);
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -656,7 +692,7 @@ int main(int argc, const char* argv[])
|
|||
(length >= 20))
|
||||
{
|
||||
outgoingBuffer->type = htole16(REWIND_TYPE_SNMP_TRAP);
|
||||
outgoingBuffer->number = htole32(++ sequenceNumber);
|
||||
outgoingBuffer->number = htole32(++ sequenceNumbers[0]);
|
||||
outgoingBuffer->length = htole16(length);
|
||||
length += sizeof(struct RewindData);
|
||||
sendto(uplinkHandle, outgoingBuffer, length, 0, serverAddress->ai_addr, serverAddress->ai_addrlen);
|
||||
|
@ -692,7 +728,7 @@ int main(int argc, const char* argv[])
|
|||
data->number = htole32(repeaterNumber);
|
||||
|
||||
outgoingBuffer->type = htole16(REWIND_TYPE_KEEP_ALIVE);
|
||||
outgoingBuffer->number = htole32(++ sequenceNumber);
|
||||
outgoingBuffer->number = htole32(++ sequenceNumbers[0]);
|
||||
outgoingBuffer->length = htobe16(length);
|
||||
length += sizeof(struct RewindData);
|
||||
sendto(uplinkHandle, outgoingBuffer, length, 0, serverAddress->ai_addr, serverAddress->ai_addrlen);
|
||||
|
@ -713,7 +749,7 @@ int main(int argc, const char* argv[])
|
|||
{
|
||||
#endif
|
||||
outgoingBuffer->type = htole16(REWIND_TYPE_CLOSE);
|
||||
outgoingBuffer->number = htole32(++ sequenceNumber);
|
||||
outgoingBuffer->number = htole32(++ sequenceNumbers[0]);
|
||||
outgoingBuffer->length = 0;
|
||||
sendto(uplinkHandle, outgoingBuffer, sizeof(struct RewindData), 0, serverAddress->ai_addr, serverAddress->ai_addrlen);
|
||||
#ifdef __linux__
|
||||
|
@ -740,14 +776,9 @@ int main(int argc, const char* argv[])
|
|||
|
||||
close(uplinkHandle);
|
||||
close(remoteHandle);
|
||||
close(mediaHandle);
|
||||
close(trapHandle);
|
||||
|
||||
for (size_t index = 0; index < MEDIA_PORT_COUNT; index ++)
|
||||
{
|
||||
int handle = mediaHandles[index];
|
||||
close(handle);
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
close(pollHandle);
|
||||
close(timerHandle);
|
||||
|
|
15
Makefile
15
Makefile
|
@ -2,11 +2,8 @@ BUILD := $(shell date -u +%Y%m%d-%H%M%S)
|
|||
OS := $(shell uname -s)
|
||||
|
||||
PREFIX = $(DESTDIR)/opt/CronosAgent
|
||||
TOOLKIT = ../..
|
||||
|
||||
DIRECTORIES = \
|
||||
$(TOOLKIT)/Common \
|
||||
$(TOOLKIT)/KAIROS
|
||||
DIRECTORIES =
|
||||
|
||||
ifeq ($(OS), Linux)
|
||||
LIBRARIES += \
|
||||
|
@ -46,14 +43,14 @@ install:
|
|||
install -D -d $(PREFIX)
|
||||
install -o root -g root cronosagent $(PREFIX)
|
||||
install -o root -g root cronosagent.sh $(PREFIX)
|
||||
install -o root -g root cronosagent-init $(PREFIX)
|
||||
install -m 644 -o root -g root Main/brandmeister-monit $(PREFIX)
|
||||
install -m 644 -o root -g root Main/brandmeister.plist $(PREFIX)
|
||||
install -m 644 -o root -g root Main/brandmeister.service $(PREFIX)
|
||||
# install -o root -g root cronosagent-init $(PREFIX)
|
||||
# install -m 644 -o root -g root cronosagent-monit $(PREFIX)
|
||||
# install -m 644 -o root -g root cronosagent.plist $(PREFIX)
|
||||
install -m 644 -o root -g root cronosagent.service $(PREFIX)
|
||||
|
||||
clean:
|
||||
rm -f $(PREREQUISITES) $(OBJECTS) cronosagent
|
||||
rm -f *.d $(TOOLKIT)/*/*.d
|
||||
rm -f *.d
|
||||
|
||||
version:
|
||||
echo "#define VERSION $(shell svn info | grep -E "^Revision:" | grep -o -E "[0-9]+")" > Version.h
|
||||
|
|
21
Rewind.h
21
Rewind.h
|
@ -2,6 +2,7 @@
|
|||
#define REWIND_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
|
@ -29,15 +30,13 @@ extern "C"
|
|||
#define REWIND_TYPE_REPORT (REWIND_CLASS_SYSTEM_CONSOLE + 0)
|
||||
|
||||
#define REWIND_TYPE_BUSY_NOTICE (REWIND_CLASS_SERVER_NOTICE + 0)
|
||||
#define REWIND_TYPE_ADDRESS_NOTICE (REWIND_CLASS_SERVER_NOTICE + 1)
|
||||
|
||||
#define REWIND_TYPE_REMOTE_CONTROL (REWIND_CLASS_KAIROS_DATA + 0)
|
||||
#define REWIND_TYPE_SNMP_TRAP (REWIND_CLASS_KAIROS_DATA + 1)
|
||||
#define REWIND_TYPE_SLOT_1_VOICE (REWIND_CLASS_KAIROS_DATA + 2)
|
||||
#define REWIND_TYPE_SLOT_2_VOICE (REWIND_CLASS_KAIROS_DATA + 3)
|
||||
#define REWIND_TYPE_ANALOG_VOICE (REWIND_CLASS_KAIROS_DATA + 4)
|
||||
#define REWIND_TYPE_SLOT_1_DATA (REWIND_CLASS_KAIROS_DATA + 5)
|
||||
#define REWIND_TYPE_SLOT_2_DATA (REWIND_CLASS_KAIROS_DATA + 6)
|
||||
#define REWIND_TYPE_ANALOG_DATA (REWIND_CLASS_KAIROS_DATA + 7)
|
||||
#define REWIND_TYPE_EXTERNAL_SERVER (REWIND_CLASS_KAIROS_DATA + 0)
|
||||
#define REWIND_TYPE_REMOTE_CONTROL (REWIND_CLASS_KAIROS_DATA + 1)
|
||||
#define REWIND_TYPE_SNMP_TRAP (REWIND_CLASS_KAIROS_DATA + 2)
|
||||
|
||||
#define REWIND_FLAG_REAL_TIME (1 << 0)
|
||||
|
||||
struct RewindVersionData
|
||||
{
|
||||
|
@ -45,6 +44,12 @@ struct RewindVersionData
|
|||
char version[0]; // Software version
|
||||
};
|
||||
|
||||
struct RewindAddressData
|
||||
{
|
||||
struct in_addr address;
|
||||
uint16_t port;
|
||||
};
|
||||
|
||||
struct RewindData
|
||||
{
|
||||
char sign[REWIND_SIGN_LENGTH];
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
<array>
|
||||
<string>6</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
</dict>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#/bin/bash
|
||||
#!/bin/bash
|
||||
|
||||
REPEATER_NUMBER=250304
|
||||
REPEATER_ADDRESS=172.33.20.136
|
||||
|
|
Loading…
Add table
Reference in a new issue