libcw/dns_sd/dns_sd.cpp
2024-12-01 14:35:24 -05:00

332 lines
10 KiB
C++

//| Copyright: (C) 2020-2024 Kevin Larke <contact AT larke DOT org>
//| License: GNU GPL version 3.0 or above. See the accompanying LICENSE file.
#include "config.h"
#include <stdio.h>
#include <string.h>
#include <assert.h>
#ifdef OS_LINUX
#include <arpa/inet.h>
#endif
#ifdef ARDUINO
#include <Ethernet.h>
#include <utility/w5100.h>
#endif
#include "rpt.h"
#include "dns_sd.h"
#include "dns_sd_const.h"
#include "dns_sd_print.h"
#define DNS_SD_SERVICE_TYPE_STRING "_services._dns-sd._udp"
dns_sd::dns_sd(sendCallback_t sendCbFunc, void* sendCbArg, printCallback_t printCbFunc )
: _sendCbFunc(sendCbFunc),_sendCbArg(sendCbArg),_printCbFunc(printCbFunc),_serviceName(nullptr),_serviceType(nullptr),_serviceDomain(nullptr),_hostName(nullptr),_hostPort(0),_text(nullptr)
{}
dns_sd::dns_sd( sendCallback_t sendCbFunc, void* sendCbArg, printCallback_t printCbFunc, const char* serviceName, const char* serviceType, const char* serviceDomain, const char* hostName, uint32_t hostAddr, uint16_t hostPort, const char* text )
: _sendCbFunc(sendCbFunc),_sendCbArg(sendCbArg),_printCbFunc(printCbFunc),_serviceName(nullptr),_serviceType(nullptr),_serviceDomain(nullptr),_hostName(nullptr),_hostPort(0),_text(nullptr)
{
setup( serviceName, serviceType, serviceDomain, hostName, hostAddr, hostPort, text );
}
dns_sd::~dns_sd()
{
_free();
}
dns_sd::result_t dns_sd::setup( const char* serviceName, const char* serviceType, const char* serviceDomain, const char* hostName, uint32_t hostAddr, uint16_t hostPort, const char* text )
{
_free();
_serviceName = strdup(serviceName);
_serviceType = strdup(serviceType);
_serviceDomain = strdup(serviceDomain);
_hostName = strdup(hostName);
_hostAddr = hostAddr;
_text = strdup(text);
_hostPort = hostPort;
return kOkRC;
}
dns_sd::result_t dns_sd::receive( const void* buf, unsigned bufByteN )
{
_parse((const char*)buf,bufByteN);
return kOkRC;
}
void dns_sd::gen_question()
{
unsigned n = _calc_question_byte_count();
unsigned char *b = (unsigned char*)calloc(1,n);
_format_question(b, n);
_send(b,n);
free(b);
}
void dns_sd::gen_response()
{
unsigned n = _calc_response_byte_count();
unsigned char* b = (unsigned char*)calloc(1,n);
_format_response(b, n);
_send(b,n);
free(b);
}
void dns_sd::_free()
{
if( _serviceName ) free( _serviceName );
if( _serviceType ) free( _serviceType );
if( _serviceDomain) free( _serviceDomain );
if( _hostName) free( _hostName );
if( _text ) free( _text );
}
unsigned dns_sd::_calc_question_byte_count()
{
unsigned n = kHdrBodyByteN;
// Question
n += 1 + strlen(_serviceName) + 1 + strlen(_serviceType) + 1 + strlen(_serviceDomain) + 1 + kQuestionBodyByteN;
// SRV
n += 2 + kRsrcBodyByteN + kSrvBodyByteN + 1 + strlen(_hostName) + 2;
// TXT
n += 2 + kRsrcBodyByteN + strlen(_text) + 1;
return n;
}
void dns_sd::_format_question( unsigned char* buf, unsigned bufByteN )
{
assert( bufByteN > kHdrBodyByteN );
unsigned char* bend = buf + bufByteN;
uint16_t* u = (uint16_t*)buf;
u[0] = htons(0); // transaction id
u[1] = htons(0); // flags
u[2] = htons(1); // question
u[3] = htons(0); // answer
u[4] = htons(2); // name server
u[5] = htons(0); // other
unsigned char* b = (unsigned char*)(u + 6);
// Format question
unsigned char namePtr[] = {0xc0, (unsigned char)(b - buf)};
b = _write_name( b, bend, _serviceName );
//unsigned char typePtr[] = { 0xc0, (unsigned char)(b - buf)};
b = _write_name( b, bend, _serviceType );
unsigned char domainPtr[] = { 0xc0, (unsigned char)(b - buf) };
b = _write_name( b, bend, _serviceDomain, true );
b = _write_uint16( b, bend, kANY_DnsTId );
b = _write_uint16( b, bend, kInClassDnsFl );
// Format SRV name server
b = _write_ptr( b, bend, namePtr ); // name
b = _write_uint16( b, bend, kSRV_DnsTId ); // type
b = _write_uint16( b, bend, kInClassDnsFl ); // class
b = _write_uint32( b, bend, 120 ); // TTL
b = _write_uint16( b, bend, kSrvBodyByteN + strlen(_hostName) + 1 + 2 );
b = _write_uint16( b, bend, 0 ); // priority
b = _write_uint16( b, bend, 0 ); // weight
b = _write_uint16( b, bend, _hostPort ); // port
b = _write_text( b, bend, _hostName ); // host
b = _write_ptr( b, bend, domainPtr ); // host suffix (.local)
// Format TXT name server
b = _write_ptr( b, bend, namePtr ); // name
b = _write_uint16( b, bend, kTXT_DnsTId ); // type
b = _write_uint16( b, bend, kInClassDnsFl ); // class
b = _write_uint32( b, bend, 4500 ); // TTL
b = _write_uint16( b, bend, strlen(_text)+1 ); // dlen
b = _write_text( b, bend, _text ); // text
//assert( b == bend );
//rpt(_printCbFunc,"%i %i : %s\n", b - buf, bend - buf, _text );
}
unsigned char* dns_sd::_write_uint16( unsigned char* b, unsigned char* bend, uint16_t value )
{
assert( bend - b >= (int)sizeof(value));
uint16_t* u = (uint16_t*)b;
u[0] = htons(value);
return (unsigned char*)(u + 1);
}
unsigned char* dns_sd::_write_uint32( unsigned char* b, unsigned char* bend, uint32_t value )
{
assert( bend - b >= (int)sizeof(value));
uint32_t* u = (uint32_t*)b;
u[0] = htonl(value);
return (unsigned char*)(u + 1);
}
unsigned char* dns_sd::_write_ptr( unsigned char* b, unsigned char* bend, const unsigned char ptr[2] )
{
assert( (ptr[0] & 0xc0) == 0xc0 );
assert( bend - b >= 2 );
b[0] = ptr[0];
b[1] = ptr[1];
return b+2;
}
unsigned char* dns_sd::_write_text( unsigned char* b, unsigned char* bend, const char* name )
{ return _write_name( b, bend, name, false, '\n' ); }
unsigned char* dns_sd::_write_name( unsigned char* b, unsigned char* bend, const char* name, bool zeroTermFl, const unsigned char eosChar )
{
unsigned char* b0 = b; // segment length prefix pointer
unsigned n = 0; // segment length
b += 1; // advance past the first segment length byte
// for every name char advance both the src and dst location
for(; *name; ++name, ++b )
{
if( *name == eosChar )
{
*b0 = n; // write the segment length
n = 0; // reset the segment length counter
b0 = b; // reset the segment length prefix pointer
}
else
{
assert( b < bend );
*b = *name; // write a name character
++n; // advance the segment length counter
}
}
*b0 = n; // write the segment length of the last segment
if( zeroTermFl )
{
assert( b < bend );
*b = 0; // write the zero termination
b += 1; //
}
return b;
}
unsigned dns_sd::_calc_response_byte_count()
{
unsigned n = kHdrBodyByteN;
// TXT
n += 1 + strlen(_serviceName) + 1 + strlen(_serviceType) + 1 + strlen(_serviceDomain) + 1 + kRsrcBodyByteN + 1 + strlen(_text);
// PTR
n += 2 + kRsrcBodyByteN + 2;
// SRV
n += 2 + kRsrcBodyByteN + kSrvBodyByteN + 1 + strlen(_hostName) + 2;
// A
n += 2 + kRsrcBodyByteN + kABodyByteN;
// PTR
n += 1 + strlen(DNS_SD_SERVICE_TYPE_STRING) + 2 + kRsrcBodyByteN + 2;
return n;
}
void dns_sd::_format_response( unsigned char* buf, unsigned bufByteN )
{
unsigned char* bend = buf + bufByteN;
uint16_t* u = (uint16_t*)buf;
u[0] = htons(0); // transaction id
u[1] = htons(0x8400); // flags
u[2] = htons(0); // question
u[3] = htons(5); // answer
u[4] = htons(0); // name server
u[5] = htons(0); // other
unsigned char* b = (unsigned char*)(u+6);
// Format TXT resource record
unsigned char namePtr[] = { 0xc0, (unsigned char)(b-buf) };
b = _write_name( b, buf+bufByteN, _serviceName );
unsigned char typePtr[] = { 0xc0, (unsigned char)(b - buf) };
b = _write_name( b, bend, _serviceType );
unsigned char domainPtr[] = { 0xc0, (unsigned char)(b - buf) };
b = _write_name( b, bend, _serviceDomain, true );
b = _write_uint16( b, bend, kTXT_DnsTId ); // type
b = _write_uint16( b, bend, kFlushClassDnsFl | kInClassDnsFl ); // class
b = _write_uint32( b, bend, 4500 ); // TTL
b = _write_uint16( b, bend, strlen(_text)+1 ); // dlen
b = _write_text( b, bend, _text ); // text
// Format a PTR resource record
b = _write_ptr( b, bend, typePtr );
b = _write_uint16( b, bend, kPTR_DnsTId );
b = _write_uint16( b, bend, kInClassDnsFl );
b = _write_uint32( b, bend, 4500 );
b = _write_uint16( b, bend, 2 );
b = _write_ptr( b, bend, namePtr );
// Format SRV response
b = _write_ptr( b, bend, namePtr ); // name
b = _write_uint16( b, bend, kSRV_DnsTId ); // type
b = _write_uint16( b, bend, kFlushClassDnsFl | kInClassDnsFl ); // class
b = _write_uint32( b, bend, 120 ); // TTL
b = _write_uint16( b, bend, kSrvBodyByteN + 1 + strlen(_hostName) + 2 );
b = _write_uint16( b, bend, 0 ); // priority
b = _write_uint16( b, bend, 0 ); // weight
b = _write_uint16( b, bend, _hostPort ); // port
unsigned char hostPtr[] = { 0xc0, (unsigned char)(b - buf) };
b = _write_text( b, bend, _hostName ); // target
b = _write_ptr( b, bend, domainPtr );
// Format A resource record
b = _write_ptr( b, bend, hostPtr );
b = _write_uint16( b, bend, kA_DnsTId ); // type
b = _write_uint16( b, bend, kFlushClassDnsFl | kInClassDnsFl ); // class
b = _write_uint32( b, bend, 120 ); // TTL
b = _write_uint16( b, bend, kABodyByteN );
b = _write_uint32( b, bend, _hostAddr ); // priority
// Format a PTR resource record
b = _write_name( b, bend, DNS_SD_SERVICE_TYPE_STRING );
b = _write_ptr( b, bend, domainPtr );
b = _write_uint16( b, bend, kPTR_DnsTId );
b = _write_uint16( b, bend, kInClassDnsFl );
b = _write_uint32( b, bend, 4500 );
b = _write_uint16( b, bend, 2 );
b = _write_ptr( b, bend, typePtr );
//rpt(_printCbFunc,"%i %i : %s\n", b - buf, bend - buf, _text );
}
void dns_sd::_parse( const char* buf, unsigned bufByteN )
{
//(void)buf;
//(void)bufByteN;
dns_sd_print(_printCbFunc,buf,bufByteN);
}
void dns_sd::_send( const void* buf, unsigned bufByteN )
{
if( _sendCbFunc != nullptr )
_sendCbFunc(_sendCbArg,buf,bufByteN);
}