You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

266 lines
7.0 KiB

Copyright Jeroen Vreeken (, 2017
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <>.
#include "ccs7db.h"
#include "dml_config.h"
#include <dml/dml.h>
#include <dml/dml_crypto.h>
#include <dml/dml_host.h>
#include <dml/dml_packet.h>
#include <dml/dml_poll.h>
#include <stdio.h>
#include <string.h>
static struct dml_crypto_key *dk;
static uint8_t ref_id[DML_ID_SIZE];
static struct dml_host *host;
static struct dml_stream *stream_dv;
static struct ccs7db *db;
uint64_t prev_timestamp = 0;
static struct timespec dml_data_ts_keepalive = { 10, 0 };
static struct timespec dml_data_ts_off = { 0, 100*1000*1000 };
static int watchdog(void *arg);
static void load_ccs7(struct ccs7db *db)
char *value = NULL;
while ((value = dml_config_value("ccs7_file", value, NULL))) {
printf("Load ccs7 file %s\n", value);
ccs7db_csv_load(db, value);
static unsigned long last_id;
static void stream_data_cb(struct dml_host *host, struct dml_stream *ds, uint64_t timestamp, void *data, size_t data_size, void *arg)
if (data_size < 8)
uint8_t *datab = data;
uint8_t *mac = data;
uint8_t *voice = &datab[8];
size_t voice_size = data_size - 8;
int mode = datab[6];
bool state = datab[7] & 0x1;
unsigned long id;
if (ccs7db_mac2id(db, &id, mac))
/* If call cannot be matched to an id use the alias of the
stream, it should be a valid ID as well.
char *alias = dml_stream_alias_get(ds);
if (alias) {
id = atol(alias);
} else {
id = 0;
if (id != last_id) {
last_id = id;
printf("Voice from id: %lu\n", id);
/* TODO: Do something with voice data */
/* Probably first some decoding based on mode
(could be a codec2 mode, a-law, u-law or signed 16bit,
but currently always 8000Hz samplerate)
printf("Got mode %d voice data: %zu bytes @ %p, state: %d\n",
mode, voice_size, voice, state);
void send_data(uint8_t mac[6], void *voice_data, size_t voice_size, int mode, bool state)
size_t size = voice_size + 8;
uint8_t data[size];
struct timespec ts;
uint64_t timestamp;
uint16_t packet_id = dml_stream_data_id_get(stream_dv);
if (!packet_id)
memcpy(data, mac, 6);
data[6] = mode;
data[7] = state;
if (voice_size)
memcpy(data + 8, voice_data, voice_size);
clock_gettime(CLOCK_REALTIME, &ts);
timestamp = dml_ts2timestamp(&ts);
if (timestamp <= prev_timestamp) {
timestamp = prev_timestamp + 1;
prev_timestamp = timestamp;
struct dml_connection *con = dml_host_connection_get(host);
if (con)
dml_packet_send_data(con, packet_id, data, size, timestamp, dk);
dml_poll_timeout(&watchdog, &dml_data_ts_off);
/* TODO: On reception of data:
- Use ccs7db_id2mac to retrieve a encoded call for the id
(Use multicast address on failure)
- Transcode in DML supported mode (any codec2 mode, a-law, u-law or le16)
- call send_data.
static void stream_req_reverse_connect_cb(struct dml_host *host, struct dml_stream *ds, struct dml_stream *ds_rev, int status, void *arg)
bool do_connect = true;
bool do_reject = false;
printf("Received reverse connect request (status=%d)\n", status);
if (do_connect) {
struct dml_crypto_key *key = dml_stream_crypto_get(ds_rev);
bool mime_match = dml_host_mime_filter(host, ds_rev);
if (mime_match && key) {
printf("Respond with connect\n");
dml_host_connect(host, ds_rev);
} else {
printf("Reject: key %p, mime_match: %d\n",
key, mime_match);
do_reject = true;
} else {
do_reject = true;
if (do_reject) {
printf("Reject request (status=%d)\n", DML_STATUS_UNAUTHORIZED);
static void stream_req_reverse_disconnect_cb(struct dml_host *host, struct dml_stream *ds, struct dml_stream *ds_rev, int status, void *arg)
printf("Disconnect request (status=%d)\n", status);
if (dml_stream_data_id_get(ds_rev)) {
printf("Disconnect stream\n");
dml_packet_send_req_disc(dml_host_connection_get(host), dml_stream_id_get(ds_rev));
static int watchdog(void *arg)
printf("No activity, sending state off packet\n");
(uint8_t[6]){ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
NULL, 0, 0, false);
dml_poll_timeout(&watchdog, &dml_data_ts_keepalive);
return 0;
int main(int argc, char **argv)
char *ca;
char *certificate;
char *key;
char *server;
char *name;
char *alias;
char *description;
char *file = "dml_dmr.conf";
uint32_t bps = 64000;
if (argc > 1)
file = argv[1];
if (dml_config_load(file)) {
printf("Failed to load config file %s\n", file);
return -1;
db = ccs7db_create();
name = dml_config_value("name", NULL, "");
alias = dml_config_value("alias", NULL, "0");
description = dml_config_value("description", NULL, "Talkgroup 0");
server = dml_config_value("server", NULL, "localhost");
certificate = dml_config_value("certificate", NULL, "");
key = dml_config_value("key", NULL, "");
ca = dml_config_value("ca", NULL, ".");
if (dml_crypto_init(NULL, ca)) {
fprintf(stderr, "Failed to init crypto\n");
return -1;
if (dml_crypto_load_cert(certificate)) {
printf("Could not load certificate\n");
return -1;
if (!(dk = dml_crypto_private_load(key))) {
printf("Could not load key\n");
return -1;
if (dml_id_gen(ref_id, DML_PACKET_DESCRIPTION_VERSION_0, bps,
DML_MIME_DV_C2, name, alias, description))
return -1;
stream_dv = dml_stream_by_id_alloc(ref_id);
dml_stream_mine_set(stream_dv, true);
dml_stream_crypto_set(stream_dv, dk);
dml_stream_name_set(stream_dv, name);
dml_stream_alias_set(stream_dv, alias);
dml_stream_mime_set(stream_dv, DML_MIME_DV_C2);
dml_stream_description_set(stream_dv, description);
dml_stream_bps_set(stream_dv, bps);
host = dml_host_create(server);
if (!host) {
printf("Could not create host\n");
return -1;
dml_host_mime_filter_set(host, 1, (char*[]){ DML_MIME_DV_C2 });
dml_host_stream_data_cb_set(host, stream_data_cb, NULL);
dml_host_stream_req_reverse_connect_cb_set(host, stream_req_reverse_connect_cb, NULL);
dml_host_stream_req_reverse_disconnect_cb_set(host, stream_req_reverse_disconnect_cb, NULL);
dml_poll_add(&watchdog, NULL, NULL, watchdog);
dml_poll_timeout(&watchdog, &dml_data_ts_keepalive);
//TODO: hook up DMR reception into the main loop
return 0;