blob: 784057b5b0ee99e02f824c6e61e1268b5a51c84f [file] [log] [blame]
/* srp-thread.c
*
* Copyright (c) 2019-2020 Apple Computer, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* srp host API implementation for Thread accessories using OpenThread.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <openthread/ip6.h>
#include <openthread/instance.h>
#include <openthread/thread.h>
#include <openthread/joiner.h>
#include <openthread/message.h>
#include <openthread/udp.h>
#include <openthread/platform/time.h>
#include <openthread/platform/settings.h>
#include "app_scheduler.h"
#include "app_timer.h"
#include "srp.h"
#include "srp-thread.h"
#include "srp-api.h"
#include "dns_sd.h"
#include "HAPPlatformRandomNumber.h"
#include "dns-msg.h"
#include "dns_sd.h"
#include "srp-crypto.h"
APP_TIMER_DEF(m_srp_timer);
#define HAPTIME_FREQUENCY 1000ULL
const char *key_filename = "srp.key";
#define SRP_IO_CONTEXT_MAGIC 0xFEEDFACEFADEBEEFULL // BEES! Everybody gets BEES!
typedef struct io_context io_context_t;
struct io_context {
uint64_t magic_cookie1;
io_context_t *next;
HAPTime wakeup_time;
void *NONNULL srp_context;
otSockAddr sockaddr;
otUdpSocket sock;
srp_wakeup_callback_t wakeup_callback;
srp_datagram_callback_t datagram_callback;
bool sock_active;
uint64_t magic_cookie2;
} *io_contexts;
static otInstance *otThreadInstance;
static int
validate_io_context(io_context_t **dest, void *src)
{
io_context_t *context = src;
if (context->magic_cookie1 == SRP_IO_CONTEXT_MAGIC &&
context->magic_cookie2 == SRP_IO_CONTEXT_MAGIC)
{
*dest = context;
return kDNSServiceErr_NoError;
}
return kDNSServiceErr_BadState;
}
void
datagram_callback(void *context, otMessage *message, const otMessageInfo *messageInfo)
{
static uint8_t *buf;
const int buf_len = 1500;
int length;
io_context_t *io_context;
if (validate_io_context(&io_context, context) == kDNSServiceErr_NoError) {
if (buf == NULL) {
buf = malloc(buf_len);
if (buf == NULL) {
INFO("No memory for read buffer");
return;
}
}
DEBUG("%d bytes received", otMessageGetLength(message) - otMessageGetOffset(message));
length = otMessageRead(message, otMessageGetOffset(message), buf, buf_len - 1);
io_context->datagram_callback(io_context->srp_context, buf, length);
}
}
static void wakeup_callback(void *context);
static void
note_wakeup(const char *what, void *at, uint64_t when)
{
#ifdef VERBOSE_DEBUG_MESSAGES
int microseconds = (int)(when % HAPTIME_FREQUENCY);
HAPTime seconds = when / HAPTIME_FREQUENCY;
int minute = (int)((seconds / 60) % 60);
int hour = (int)((seconds / 3600) % (7 * 24));
int second = (int)(seconds % 60);
DEBUG(PUB_S_SRP " %p at %llu %d:%d:%d.%d", what, at, when, hour, minute, second, microseconds);
#endif
}
static void
compute_wakeup_time(HAPTime now)
{
io_context_t *io_context;
HAPTime next = 0;
uint32_t err;
for (io_context = io_contexts; io_context; io_context = io_context->next) {
if (next == 0 || (io_context->wakeup_time != 0 && io_context->wakeup_time < next)) {
next = io_context->wakeup_time;
}
}
// If we don't have a wakeup to schedule, wake up anyway in ten seconds.
if (next == 0) {
next = now + 10 * HAPTIME_FREQUENCY;
}
note_wakeup("next wakeup", NULL, next);
if (next != 0) {
int milliseconds;
if (next <= now) {
milliseconds = 1;
} else {
milliseconds = (int)((next - now) / (HAPTIME_FREQUENCY / 1000));
}
err = app_timer_start(m_srp_timer, APP_TIMER_TICKS(milliseconds), NULL);
if (err != 0) {
ERROR("app_timer_start returned %lu", err);
}
}
}
static void
wakeup_callback(void *context)
{
io_context_t *io_context;
HAPTime now = HAPPlatformClockGetCurrent(), next = 0;
bool more;
note_wakeup(" wakeup", NULL, now);
do {
more = false;
for (io_context = io_contexts; io_context; io_context = io_context->next) {
if (io_context->wakeup_time != 0 && io_context->wakeup_time < now) {
more = true;
note_wakeup("io wakeup", io_context, io_context->wakeup_time);
io_context->wakeup_time = 0;
io_context->wakeup_callback(io_context->srp_context);
break;
}
note_wakeup("no wakeup", io_context, io_context->wakeup_time);
if (next == 0 || (io_context->wakeup_time != 0 && io_context->wakeup_time < next))
{
next = io_context->wakeup_time;
}
}
} while (more);
compute_wakeup_time(now);
}
int
srp_deactivate_udp_context(void *host_context, void *in_context)
{
io_context_t *io_context, **p_io_contexts;
int err;
err = validate_io_context(&io_context, in_context);
if (err == kDNSServiceErr_NoError) {
for (p_io_contexts = &io_contexts; *p_io_contexts; p_io_contexts = &(*p_io_contexts)->next) {
if (*p_io_contexts == io_context) {
break;
}
}
// If we don't find it on the list, something is wrong.
if (*p_io_contexts == NULL) {
return kDNSServiceErr_Invalid;
}
*p_io_contexts = io_context->next;
io_context->wakeup_time = 0;
if (io_context->sock_active) {
otUdpClose(&io_context->sock);
}
free(io_context);
}
return err;
}
int
srp_connect_udp(void *context, const uint8_t *port, uint16_t address_type, const uint8_t *address, uint16_t addrlen)
{
io_context_t *io_context;
int err, oterr;
err = validate_io_context(&io_context, context);
if (err == kDNSServiceErr_NoError) {
if (address_type != dns_rrtype_aaaa || addrlen != 16) {
ERROR("srp_make_udp_context: invalid address");
return kDNSServiceErr_Invalid;
}
memcpy(&io_context->sockaddr.mAddress, address, 16);
memcpy(&io_context->sockaddr.mPort, port, 2);
#ifdef OT_NETIF_INTERFACE_ID_THREAD
io_context->sockaddr.mScopeId = OT_NETIF_INTERFACE_ID_THREAD;
#endif
oterr = otUdpOpen(otThreadInstance, &io_context->sock, datagram_callback, io_context);
if (oterr != OT_ERROR_NONE) {
ERROR("srp_make_udp_context: otUdpOpen returned %d", oterr);
return kDNSServiceErr_Unknown;
}
oterr = otUdpConnect(&io_context->sock, &io_context->sockaddr);
if (oterr != OT_ERROR_NONE) {
otUdpClose(&io_context->sock);
ERROR("srp_make_udp_context: otUdpConnect returned %d", oterr);
return kDNSServiceErr_Unknown;
}
io_context->sock_active = true;
err = kDNSServiceErr_NoError;
}
return err;
}
int
srp_disconnect_udp(void *context)
{
io_context_t *io_context;
int err;
err = validate_io_context(&io_context, context);
if (err == kDNSServiceErr_NoError && io_context->sock_active) {
otUdpClose(&io_context->sock);
io_context->sock_active = false;
}
return err;
}
int
srp_make_udp_context(void *host_context, void **p_context, srp_datagram_callback_t callback, void *context)
{
io_context_t *io_context = calloc(1, sizeof *io_context);
if (io_context == NULL) {
ERROR("srp_make_udp_context: no memory");
return kDNSServiceErr_NoMemory;
}
io_context->magic_cookie1 = io_context->magic_cookie2 = SRP_IO_CONTEXT_MAGIC;
io_context->datagram_callback = callback;
io_context->srp_context = context;
*p_context = io_context;
io_context->next = io_contexts;
io_contexts = io_context;
return kDNSServiceErr_NoError;
}
int
srp_set_wakeup(void *host_context, void *context, int milliseconds, srp_wakeup_callback_t callback)
{
int err;
io_context_t *io_context;
HAPTime now;
err = validate_io_context(&io_context, context);
if (err == kDNSServiceErr_NoError) {
now = HAPPlatformClockGetCurrent();
io_context->wakeup_time = now + milliseconds * (HAPTIME_FREQUENCY / 1000);
io_context->wakeup_callback = callback;
INFO("%llu (%llu + %dms)", io_context->wakeup_time, now, milliseconds);
compute_wakeup_time(now);
}
return err;
}
int
srp_cancel_wakeup(void *host_context, void *context)
{
int err;
io_context_t *io_context;
err = validate_io_context(&io_context, context);
if (err == kDNSServiceErr_NoError) {
io_context->wakeup_time = 0;
}
return err;
}
int
srp_send_datagram(void *host_context, void *context, void *payload, size_t message_length)
{
int err;
io_context_t *io_context;
otError error;
otMessageInfo messageInfo;
otMessage * message = NULL;
uint8_t *ap;
#ifdef VERBOSE_DEBUG_MESSAGES
int i, j;
char buf[80], *bufp;
char *hexdigits = "01234567689abcdef";
uint8_t *msg = payload;
#endif // VERBOSE_DEBUG_MESSAGES
err = validate_io_context(&io_context, context);
if (err == kDNSServiceErr_NoError) {
memset(&messageInfo, 0, sizeof(messageInfo));
#ifdef OT_NETIF_INTERFACE_ID_THREAD
messageInfo.mInterfaceId = OT_NETIF_INTERFACE_ID_THREAD;
#endif
messageInfo.mPeerPort = io_context->sockaddr.mPort;
messageInfo.mPeerAddr = io_context->sockaddr.mAddress;
ap = (uint8_t *)&io_context->sockaddr.mAddress;
SEGMENTED_IPv6_ADDR_GEN_SRP(ap, ap_buf);
INFO("Sending to " PRI_SEGMENTED_IPv6_ADDR_SRP " port %d", SEGMENTED_IPv6_ADDR_PARAM_SRP(ap, ap_buf),
io_context->sockaddr.mPort);
#ifdef VERBOSE_DEBUG_MESSAGES
for (i = 0; i < message_length; i += 32) {
bufp = buf;
for (j = 0; bufp < buf + sizeof buf && i + j < message_length; j++) {
*bufp++ = hexdigits[msg[i + j] >> 4];
if (bufp < buf + sizeof buf) {
*bufp++ = hexdigits[msg[i + j] % 15];
}
if (bufp < buf + sizeof buf && (j & 1) == 1) {
*bufp++ = ' ';
}
}
*bufp = 0;
DEBUG(PUB_S_SRP, buf);
}
#endif
message = otUdpNewMessage(otThreadInstance, NULL);
if (message == NULL) {
ERROR("srp_send_datagram: otUdpNewMessage returned NULL");
return kDNSServiceErr_NoMemory;
}
error = otMessageAppend(message, payload, message_length);
if (error != OT_ERROR_NONE) {
ERROR("srp_send_datagram: otMessageAppend returned %d", error);
return kDNSServiceErr_NoMemory;
}
error = otUdpSend(&io_context->sock, message, &messageInfo);
if (error != OT_ERROR_NONE) {
ERROR("srp_send_datagram: otUdpSend returned %d", error);
return kDNSServiceErr_Unknown;
}
}
return err;
}
#define KEY_ID 1000
int
srp_load_key_data(void *host_context, const char *key_name,
uint8_t *buffer, uint16_t *length, uint16_t buffer_size)
{
#ifndef DEBUG_CONFLICTS
otError err;
uint16_t rlength = buffer_size;
// Note that at present we ignore the key name: we are only going to have one host key on an
// accessory.
err = otPlatSettingsGet(otThreadInstance, KEY_ID, 0, buffer, &rlength);
if (err != OT_ERROR_NONE) {
*length = 0;
return kDNSServiceErr_NoSuchKey;
}
*length = rlength;
return kDNSServiceErr_NoError;
#else
return kDNSServiceErr_NoSuchKey;
#endif
}
int
srp_store_key_data(void *host_context, const char *name, uint8_t *buffer, uint16_t length)
{
otError err;
err = otPlatSettingsAdd(otThreadInstance, KEY_ID, buffer, length);
if (err != OT_ERROR_NONE) {
ERROR("Unable to store key (length %d): %d", length, err);
return kDNSServiceErr_Unknown;
}
return kDNSServiceErr_NoError;
}
int
srp_reset_key(const char *name, void *host_context)
{
otPlatSettingsDelete(otThreadInstance, KEY_ID);
}
void
register_callback(DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode,
const char *name, const char *regtype, const char *domain, void *context)
{
INFO("Register Reply: %ld " PRI_S_SRP " " PRI_S_SRP " " PRI_S_SRP "\n", errorCode, name == NULL ? "<NULL>" : name,
regtype == NULL ? "<NULL>" : regtype, domain == NULL ? "<NULL>" : domain);
}
void
conflict_callback(const char *hostname)
{
ERROR("Host name conflict: %s", hostname);
}
int
srp_thread_init(otInstance *instance)
{
uint32_t app_err;
DEBUG("In srp_thread_init().");
otThreadInstance = instance;
srp_host_init(otThreadInstance);
app_err = app_timer_create(&m_srp_timer, APP_TIMER_MODE_SINGLE_SHOT, wakeup_callback);
if (app_err != 0) {
ERROR("app_timer_create returned %lu", app_err);
return kDNSServiceErr_Unknown;
}
return kDNSServiceErr_NoError;
}
int
srp_thread_shutdown(otInstance *instance)
{
INFO("In srp_thread_shutdown().");
uint32_t app_err;
app_err = app_timer_stop(m_srp_timer);
if (app_err != 0) {
ERROR("app_timer_stop returned %lu", app_err);
return kDNSServiceErr_Unknown;
}
return kDNSServiceErr_NoError;
}
// Local Variables:
// mode: C
// tab-width: 4
// c-file-style: "bsd"
// c-basic-offset: 4
// fill-column: 108
// indent-tabs-mode: nil
// End: