diff --git a/FMIAXNetwork.cpp b/FMIAXNetwork.cpp index 987e373..28350a6 100644 --- a/FMIAXNetwork.cpp +++ b/FMIAXNetwork.cpp @@ -49,6 +49,7 @@ const unsigned char AST_CONTROL_ANSWER = 4U; const unsigned char AST_CONTROL_OPTION = 11U; const unsigned char AST_CONTROL_KEY = 12U; const unsigned char AST_CONTROL_UNKEY = 13U; +const unsigned char AST_CONTROL_STOP_SOUNDS = 255U; const unsigned char AST_FORMAT_ULAW = 4U; @@ -113,6 +114,7 @@ m_buffer(2000U, "FM Network"), m_status(IAXS_DISCONNECTED), m_retryTimer(1000U, 0U, 500U), m_pingTimer(1000U, 20U), +m_seed(), m_timestamp(), m_sCallNo(0U), m_dCallNo(0U), @@ -166,7 +168,9 @@ bool CFMIAXNetwork::open() m_status = IAXS_CONNECTING; m_retryTimer.start(); - m_dCallNo = 0U; + + m_dCallNo = 0U; + m_rxFrames = 0U; return true; } @@ -176,6 +180,9 @@ bool CFMIAXNetwork::writeStart() if (m_status != IAXS_CONNECTED) return false; + if (!m_enabled) + return false; + return writeKey(true); } @@ -187,7 +194,34 @@ bool CFMIAXNetwork::writeData(const float* data, unsigned int nSamples) if (m_status != IAXS_CONNECTED) return false; - return true; + if (!m_enabled) + return false; + + short audio[300U]; + for (unsigned int i = 0U; i < nSamples; i++) + audio[i] = short(data[i] * 32767.0F + 0.5F); // Changing audio format from float to S16LE + +#if defined(DEBUG_IAX) + LogDebug("IAX audio sent"); +#endif + unsigned short ts = m_timestamp.elapsed(); + + unsigned char buffer[300U]; + + buffer[0U] = (m_sCallNo << 8) & 0xFFU; + buffer[1U] = (m_sCallNo << 0) & 0xFFU; + + buffer[2U] = (ts << 8) & 0xFFU; + buffer[3U] = (ts << 0) & 0xFFU; + + uLawEncode(audio, buffer + 4U, nSamples); + +#if !defined(DEBUG_IAX) + if (m_debug) +#endif + CUtils::dump(1U, "FM IAX Network Data Sent", buffer, 4U + nSamples); + + return m_socket.write(buffer, 4U + nSamples, m_addr, m_addrLen); } bool CFMIAXNetwork::writeEnd() @@ -195,6 +229,9 @@ bool CFMIAXNetwork::writeEnd() if (m_status != IAXS_CONNECTED) return false; + if (!m_enabled) + return false; + return writeKey(false); } @@ -207,7 +244,10 @@ void CFMIAXNetwork::clock(unsigned int ms) writeNew(); break; case IAXS_AUTHORISING: - writeAuth(); + writeAuthRep(); + break; + case IAXS_REGISTERING: + writeRegReq(); break; default: break; @@ -254,12 +294,14 @@ void CFMIAXNetwork::clock(unsigned int ms) #if defined(DEBUG_IAX) LogDebug("IAX PING received"); #endif + m_rxFrames++; writeAck(ts); writePong(ts); } else if (compareFrame(buffer, AST_FRAME_IAX, IAX_COMMAND_PONG)) { #if defined(DEBUG_IAX) LogDebug("IAX PONG received"); #endif + m_rxFrames++; writeAck(ts); } else if (compareFrame(buffer, AST_FRAME_IAX, IAX_COMMAND_ACCEPT)) { #if defined(DEBUG_IAX) @@ -269,7 +311,9 @@ void CFMIAXNetwork::clock(unsigned int ms) if (m_dCallNo == 0U) m_dCallNo = ((buffer[0U] << 8) | (buffer[1U] << 0)) & 0x7FFFU; + m_rxFrames++; writeAck(ts); + m_retryTimer.stop(); } else if (compareFrame(buffer, AST_FRAME_IAX, IAX_COMMAND_REGREJ)) { #if defined(DEBUG_IAX) LogDebug("IAX REGREJ received"); @@ -288,6 +332,59 @@ void CFMIAXNetwork::clock(unsigned int ms) m_status = IAXS_DISCONNECTED; m_retryTimer.stop(); m_pingTimer.stop(); + } else if (compareFrame(buffer, AST_FRAME_CONTROL, AST_CONTROL_RINGING)) { +#if defined(DEBUG_IAX) + LogDebug("IAX RINGING received"); +#endif + m_rxFrames++; + writeAck(ts); + } else if (compareFrame(buffer, AST_FRAME_IAX, IAX_COMMAND_REGAUTH)) { +#if defined(DEBUG_IAX) + LogDebug("IAX REGAUTH received"); +#endif + m_rxFrames++; + + if ((buffer[12U] == IAX_IE_AUTHMETHODS) && + (buffer[15U] == IAX_AUTH_MD5) && + (buffer[16U] == IAX_IE_CHALLENGE)) { + // Grab the destination call number if we don't have it already + if (m_dCallNo == 0U) + m_dCallNo = ((buffer[0U] << 8) | (buffer[1U] << 0)) & 0x7FFFU; + + m_seed = std::string((char*)(buffer + 18U), buffer[17U]); + + m_status = IAXS_REGISTERING; + m_retryTimer.start(); + writeRegReq(); + } + } else if (compareFrame(buffer, AST_FRAME_IAX, IAX_COMMAND_AUTHREQ)) { +#if defined(DEBUG_IAX) + LogDebug("IAX AUTHREQ received"); +#endif + m_rxFrames++; + + if ((buffer[12U] == IAX_IE_AUTHMETHODS) && + (buffer[15U] == IAX_AUTH_MD5) && + (buffer[16U] == IAX_IE_CHALLENGE)) { + // Grab the destination call number if we don't have it already + if (m_dCallNo == 0U) + m_dCallNo = ((buffer[0U] << 8) | (buffer[1U] << 0)) & 0x7FFFU; + + m_seed = std::string((char*)(buffer + 18U), buffer[17U]); + + m_status = IAXS_AUTHORISING; + m_retryTimer.start(); + writeAuthRep(); + } + } else if (compareFrame(buffer, AST_FRAME_IAX, IAX_COMMAND_REGACK)) { +#if defined(DEBUG_IAX) + LogDebug("IAX REGACK received"); +#endif + m_rxFrames++; + writeAck(ts); + + if (m_status == IAXS_CONNECTING) + writeNew(); } else if (compareFrame(buffer, AST_FRAME_IAX, IAX_COMMAND_HANGUP)) { #if defined(DEBUG_IAX) LogDebug("IAX HANGUP received"); @@ -308,15 +405,55 @@ void CFMIAXNetwork::clock(unsigned int ms) writeAck(ts); + m_rxFrames++; m_status = IAXS_CONNECTED; m_retryTimer.stop(); m_pingTimer.start(); + } else if (compareFrame(buffer, AST_FRAME_IAX, IAX_COMMAND_VNAK)) { +#if defined(DEBUG_IAX) + LogDebug("IAX VNAK received"); +#endif + m_rxFrames++; + writeAck(ts); + } else if (compareFrame(buffer, AST_FRAME_CONTROL, AST_CONTROL_STOP_SOUNDS)) { +#if defined(DEBUG_IAX) + LogDebug("IAX STOP SOUNDS received"); +#endif + m_rxFrames++; + writeAck(ts); + } else if (compareFrame(buffer, AST_FRAME_CONTROL, AST_CONTROL_OPTION)) { +#if defined(DEBUG_IAX) + LogDebug("IAX OPTION received"); +#endif + m_rxFrames++; + writeAck(ts); + } else if (compareFrame(buffer, AST_FRAME_TEXT, 0U)) { +#if defined(DEBUG_IAX) + LogDebug("IAX TEXT received - %s", buffer + 12U); +#endif + writeAck(ts); } else if (compareFrame(buffer, AST_FRAME_IAX, IAX_COMMAND_LAGRQ)) { #if defined(DEBUG_IAX) LogDebug("IAX LAGRQ received"); #endif + m_rxFrames++; writeAck(ts); - writeLagRp(); + writeLagRp(ts); + } else if (compareFrame(buffer, AST_FRAME_VOICE, AST_FORMAT_ULAW)) { +#if defined(DEBUG_IAX) + LogDebug("IAX ULAW received"); +#endif + m_rxFrames++; + writeAck(ts); + + if (!m_enabled) + return; + + m_buffer.addData(buffer + 12U, length - 12U); + + short audio[160U]; + ::memset(audio, 0x00U, 160U * sizeof(short)); + writeAudio(audio, 160U); } else if ((buffer[0U] & 0x80U) == 0x00U) { #if defined(DEBUG_IAX) LogDebug("IAX audio received"); @@ -325,6 +462,8 @@ void CFMIAXNetwork::clock(unsigned int ms) return; m_buffer.addData(buffer + 4U, length - 4U); + } else { + CUtils::dump(2U, "Unknown IAX message received", buffer, length); } } @@ -333,7 +472,7 @@ unsigned int CFMIAXNetwork::readData(float* out, unsigned int nOut) assert(out != NULL); assert(nOut > 0U); - unsigned int bytes = m_buffer.dataSize() / sizeof(unsigned short); + unsigned int bytes = m_buffer.dataSize() / sizeof(unsigned char); if (bytes == 0U) return 0U; @@ -341,12 +480,13 @@ unsigned int CFMIAXNetwork::readData(float* out, unsigned int nOut) nOut = bytes; unsigned char buffer[1500U]; - m_buffer.getData(buffer, nOut * sizeof(unsigned short)); + m_buffer.getData(buffer, nOut * sizeof(unsigned char)); - for (unsigned int i = 0U; i < nOut; i++) { - short val = ((buffer[i * 2U + 0U] & 0xFFU) << 0) + ((buffer[i * 2U + 1U] & 0xFFU) << 8); - out[i] = float(val) / 65536.0F; - } + short audio[1500U]; + uLawDecode(buffer, audio, nOut); + + for (unsigned int i = 0U; i < nOut; i++) + out[i] = float(audio[i]) / 65536.0F; return nOut; } @@ -459,8 +599,10 @@ bool CFMIAXNetwork::writeAuthRep() #if defined(DEBUG_IAX) LogDebug("IAX AUTHREP sent"); #endif + std::string password = m_seed + m_password; + char hash[MD5_DIGEST_STRING_LENGTH]; - ::MD5Data((unsigned char*)m_password.c_str(), m_password.size(), hash); + ::MD5Data((unsigned char*)password.c_str(), password.size(), hash); unsigned short sCall = m_sCallNo | 0x8000U; unsigned int ts = m_timestamp.elapsed(); @@ -694,13 +836,12 @@ bool CFMIAXNetwork::writeAck(unsigned int ts) return m_socket.write(buffer, 12U, m_addr, m_addrLen); } -bool CFMIAXNetwork::writeLagRp() +bool CFMIAXNetwork::writeLagRp(unsigned int ts) { #if defined(DEBUG_IAX) LogDebug("IAX LAGRP sent"); #endif unsigned short sCall = m_sCallNo | 0x8000U; - unsigned int ts = m_timestamp.elapsed(); unsigned char buffer[15U]; @@ -778,6 +919,170 @@ bool CFMIAXNetwork::writeHangup() return m_socket.write(buffer, 14U + ::strlen(REASON), m_addr, m_addrLen); } +bool CFMIAXNetwork::writeRegReq() +{ + const unsigned short REFRESH_TIME = 60U; + +#if defined(DEBUG_IAX) + LogDebug("IAX REGREQ sent"); +#endif + unsigned short sCall = m_sCallNo | 0x8000U; + unsigned int ts = m_timestamp.elapsed(); + + unsigned char buffer[70U]; + + buffer[0U] = (sCall << 8) & 0xFFU; + buffer[1U] = (sCall << 0) & 0xFFU; + + buffer[2U] = (m_dCallNo << 8) & 0xFFU; + buffer[3U] = (m_dCallNo << 0) & 0xFFU; + + buffer[4U] = (ts << 24) & 0xFFU; + buffer[5U] = (ts << 16) & 0xFFU; + buffer[6U] = (ts << 8) & 0xFFU; + buffer[7U] = (ts << 0) & 0xFFU; + + buffer[8U] = m_oSeqNo; + + buffer[9U] = m_iSeqNo; + + buffer[10U] = AST_FRAME_IAX; + + buffer[11U] = IAX_COMMAND_REGREQ; + + buffer[12U] = IAX_IE_USERNAME; + buffer[13U] = m_username.size(); + ::memcpy(buffer + 14U, m_username.c_str(), m_username.size()); + + unsigned int offset = 14U + m_username.size(); + + if (m_dCallNo > 0U) { + std::string password = m_seed + m_password; + + char hash[MD5_DIGEST_STRING_LENGTH]; + ::MD5Data((unsigned char*)password.c_str(), password.size(), hash); + + buffer[offset++] = IAX_IE_MD5_RESULT; + buffer[offset++] = MD5_DIGEST_STRING_LENGTH; + + ::memcpy(buffer + offset, hash, MD5_DIGEST_STRING_LENGTH); + offset += MD5_DIGEST_STRING_LENGTH; + } + + buffer[offset++] = IAX_IE_REFRESH; + buffer[offset++] = sizeof(unsigned short); + buffer[offset++] = (REFRESH_TIME >> 8) & 0xFFU; + buffer[offset++] = (REFRESH_TIME >> 0) & 0xFFU; + +#if !defined(DEBUG_IAX) + if (m_debug) +#endif + CUtils::dump(1U, "FM IAX Network Data Sent", buffer, offset); + + m_oSeqNo++; + + return m_socket.write(buffer, offset, m_addr, m_addrLen); +} + +void CFMIAXNetwork::uLawEncode(const short* audio, unsigned char* buffer, unsigned int length) const +{ + assert(audio != NULL); + assert(buffer != NULL); + + const unsigned short MULAW_MAX = 0x1FFFU; + const unsigned short MULAW_BIAS = 33U; + + for (unsigned int i = 0U; i < length; i++) { + unsigned short mask = 0x1000U; + unsigned char sign; + unsigned char position = 12U; + unsigned short number; + + if (audio[i] < 0) { + number = -audio[i]; + sign = 0x80U; + } else { + number = audio[i]; + sign = 0x00U; + } + + number += MULAW_BIAS; + if (number > MULAW_MAX) + number = MULAW_MAX; + + for (; ((number & mask) != mask && position >= 5U); mask >>= 1, position--) + ; + + unsigned char lsb = (number >> (position - 4U)) & 0x0FU; + buffer[i] = ~(sign | ((position - 5U) << 4) | lsb); + } +} + +void CFMIAXNetwork::uLawDecode(const unsigned char* buffer, short* audio, unsigned int length) const +{ + assert(buffer != NULL); + assert(audio != NULL); + + const unsigned short MULAW_BIAS = 33U; + + for (unsigned int i = 0U; i < length; i++) { + bool sign = true; + + unsigned char number = ~buffer[i]; + if (number & 0x80U) { + number &= 0x7FU; + sign = false; + } + + unsigned char position = ((number & 0xF0U) >> 4) + 5U; + short decoded = ((1 << position) | ((number & 0x0FU) << (position - 4U)) + | (1 << (position - 5U))) - MULAW_BIAS; + + audio[i] = sign ? decoded : -decoded; + } +} + +bool CFMIAXNetwork::writeAudio(const short* audio, unsigned int length) +{ +#if defined(DEBUG_IAX) + LogDebug("IAX ULAW sent"); +#endif + unsigned short sCall = m_sCallNo | 0x8000U; + unsigned int ts = m_timestamp.elapsed(); + + unsigned char buffer[300U]; + + buffer[0U] = (sCall << 8) & 0xFFU; + buffer[1U] = (sCall << 0) & 0xFFU; + + buffer[2U] = (m_dCallNo << 8) & 0xFFU; + buffer[3U] = (m_dCallNo << 0) & 0xFFU; + + buffer[4U] = (ts << 24) & 0xFFU; + buffer[5U] = (ts << 16) & 0xFFU; + buffer[6U] = (ts << 8) & 0xFFU; + buffer[7U] = (ts << 0) & 0xFFU; + + buffer[8U] = m_oSeqNo; + + buffer[9U] = m_iSeqNo; + + buffer[10U] = AST_FRAME_VOICE; + + buffer[11U] = AST_FORMAT_ULAW; + + uLawEncode(audio, buffer + 12U, length); + +#if !defined(DEBUG_IAX) + if (m_debug) +#endif + CUtils::dump(1U, "FM IAX Network Data Sent", buffer, 12U + length); + + m_oSeqNo++; + + return m_socket.write(buffer, 12U + length, m_addr, m_addrLen); +} + bool CFMIAXNetwork::compareFrame(const unsigned char* buffer, unsigned char type1, unsigned char type2) const { assert(buffer != NULL); diff --git a/FMIAXNetwork.h b/FMIAXNetwork.h index c5066c9..0afe86d 100644 --- a/FMIAXNetwork.h +++ b/FMIAXNetwork.h @@ -32,6 +32,7 @@ enum IAX_STATUS { IAXS_DISCONNECTED, IAXS_CONNECTING, IAXS_AUTHORISING, + IAXS_REGISTERING, IAXS_CONNECTED }; @@ -72,6 +73,7 @@ private: IAX_STATUS m_status; CTimer m_retryTimer; CTimer m_pingTimer; + std::string m_seed; CStopWatch m_timestamp; unsigned short m_sCallNo; unsigned short m_dCallNo; @@ -90,8 +92,13 @@ private: bool writePing(); bool writePong(unsigned int ts); bool writeAck(unsigned int ts); - bool writeLagRp(); + bool writeLagRp(unsigned int ts); bool writeHangup(); + bool writeRegReq(); + bool writeAudio(const short* audio, unsigned int length); + + void uLawEncode(const short* audio, unsigned char* buffer, unsigned int length) const; + void uLawDecode(const unsigned char* buffer, short* audio, unsigned int length) const; bool compareFrame(const unsigned char* buffer, unsigned char type1, unsigned char type2) const; };