====== Protocole DPlus ====== :!: Page encore en cours d'édition :!: Il n'y a pas de spécification publique du protocole DPlus. Comme me l'a dit G4KLX: "None exists. The implementation is the specification." Je n'ai pas encore établi la parenté exacte du protocole, mais l'OM qui semble le plus calé est AA4RC. Il faut que j'embête un de ces 4 pour voir s'il a plus d'infos. Ce que j'ai donc déduit pour l'instant: * Le premier octet correspond à la longueur en octets du datagramme, cet octet est inclus dans le décompte. * Le deuxième octet correspond à un identifiant de type. * Quelques types de trames en dessous à partir du code source de dxrfd, d'xlxd et d'OpenDV. ===== Frame types ===== enum DPLUS_TYPE { DP_NONE, DP_HEADER, DP_AMBE, DP_POLL, DP_CONNECT }; Frame type detection in OpenDV bool CDPlusProtocolHandler::readPackets() { m_type = DP_NONE; // No more data? int length = m_socket.read(m_buffer, BUFFER_LENGTH, m_yourAddress, m_yourPort); if (length <= 0) return false; m_length = length; if (m_buffer[2] != 'D' || m_buffer[3] != 'S' || m_buffer[4] != 'V' || m_buffer[5] != 'T') { switch (m_length) { case 3U: m_type = DP_POLL; return false; case 5U: case 8U: case 28U: m_type = DP_CONNECT; return false; default: // An unknown type // CUtils::dump(wxT("Unknown packet type from D-Plus"), m_buffer, m_length); return true; } } else { // Header or data packet type? if (m_buffer[0] == 0x3A && m_buffer[1] == 0x80) { m_type = DP_HEADER; return false; } else if (m_buffer[0] == 0x1D && m_buffer[1] == 0x80) { m_type = DP_AMBE; return false; } else if (m_buffer[0] == 0x20 && m_buffer[1] == 0x80) { m_type = DP_AMBE; return false; } else { // An unknown type CUtils::dump(wxT("Unknown packet type from D-Plus"), m_buffer, m_length); return true; } } } ==== DV frame ==== **__Type:__** 0x80 Contient une trame de streaming D-Star au [[d-star:stream|format de flux UDP utilisé en D-Star]]. === DV header === unsigned int CHeaderData::getDPlusData(unsigned char* data, unsigned int length, bool check) const { wxASSERT(data != NULL); wxASSERT(length >= 58U); data[0] = 0x3A; data[1] = 0x80; data[2] = 'D'; data[3] = 'S'; data[4] = 'V'; data[5] = 'T'; data[6] = 0x10; data[7] = 0x00; data[8] = 0x00; data[9] = 0x00; data[10] = 0x20; data[11] = m_band1; data[12] = m_band2; data[13] = m_band3; data[14] = m_id % 256U; // Unique session id data[15] = m_id / 256U; data[16] = 0x80; data[17] = 0x00; // Flags 1, 2, and 3 data[18] = 0x00; data[19] = 0x00; ::memcpy(data + 20U, m_rptCall2, LONG_CALLSIGN_LENGTH); ::memcpy(data + 28U, m_rptCall1, LONG_CALLSIGN_LENGTH); ::memcpy(data + 36U, m_yourCall, LONG_CALLSIGN_LENGTH); ::memcpy(data + 44U, m_myCall1, LONG_CALLSIGN_LENGTH); ::memcpy(data + 52U, m_myCall2, SHORT_CALLSIGN_LENGTH); if (check) { CCCITTChecksum csum; csum.update(data + 17, 4U * LONG_CALLSIGN_LENGTH + SHORT_CALLSIGN_LENGTH + 3U); csum.result(data + 56); } else { data[56] = 0xFF; data[57] = 0xFF; } return 58U; } === DV data (AMBE) === unsigned int CAMBEData::getDPlusData(unsigned char* data, unsigned int length) const { wxASSERT(data != NULL); wxASSERT(length >= 32U); if (isEnd()) { data[0] = 0x20; data[1] = 0x80; } else { data[0] = 0x1D; data[1] = 0x80; } data[2] = 'D'; data[3] = 'S'; data[4] = 'V'; data[5] = 'T'; data[6] = 0x20; data[7] = 0x00; data[8] = 0x00; data[9] = 0x00; data[10] = 0x20; data[11] = m_band1; data[12] = m_band2; data[13] = m_band3; data[14] = m_id % 256U; // Unique session id data[15] = m_id / 256U; data[16] = m_outSeq; if (isEnd()) { ::memcpy(data + 17U, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); ::memcpy(data + 26U, END_PATTERN_BYTES, END_PATTERN_LENGTH_BYTES); // Add the end flag return 17U + DV_FRAME_MAX_LENGTH_BYTES; } else { // All other cases, just copy the payload ::memcpy(data + 17U, m_data, DV_FRAME_LENGTH_BYTES); return 17U + DV_FRAME_LENGTH_BYTES; } } ==== Connection notification ==== La commande est utilisée pour signaler son la connexion et la déconnexion d'un client au réflecteur. **__Type:__** 0x00 05 00 24 00 XX ^ Valeur ^ Nature de la commande ^ | 0 | Connexion | | 1 | Déconnexion | FIXME: trouver l'utilité des octets 2 et 3 ==== Keepalive frame ==== **__Type:__** 0x60 { 0x03, 0x60, 0x00 } ==== Command frame ==== La commande a l'air bien plus complète que ce qui est simplement présenté dans le code de G4KLX: dans dxrfd, on a plein d'autres données. **__Type:__** 0xC0, soit 192. Valeurs constatées pour l'octet 2: ^ Valeur ^ Nature de la requete ^ | 3 | Version | | 4 | Login request | | 5 | Connected user list | | 6 | Linked repeaters | | 7 | LH (//ambiguous, need to dig that//) | | 8 | Local date | === Keepalive frame === unsigned int CPollData::getDPlusData(unsigned char *data, unsigned int length) const { wxASSERT(data != NULL); wxASSERT(length >= 3U); data[0U] = 0x03; data[1U] = 0x60; data[2U] = 0x00; return 3U; } === Connection frame === Connection type enum CD_TYPE { CT_LINK1, // Connection CT_LINK2, // Link request CT_UNLINK, // Disconnection CT_ACK, // Link acknowledgement CT_NAK // Link denial }; * Long linking request length is 28 * Linking response length is 8 == Response format == When the linking was successful, the server MUST send back the value "OKRW" in bytes 5-8. If the link request wasn't satisfiable, the server can use any value which is not beginning by "OK". Observed values are "FAIL" in dxrfd, "BUSY" in xlxd. bool CConnectData::setDPlusData(const unsigned char* data, unsigned int length, const in_addr& yourAddress, unsigned int yourPort, unsigned int myPort) { wxASSERT(data != NULL); wxASSERT(length >= 5U); wxASSERT(yourPort > 0U); switch (length) { case 5U: switch (data[4U]) { case 0x01: m_type = CT_LINK1; break; case 0x00: m_type = CT_UNLINK; break; } break; case 8U: { wxString reply((const char*)(data + 4U), wxConvLocal, 4U); wxLogMessage(wxT("D-Plus reply is %.4s"), reply.c_str()); if (::memcmp(data + 4U, "OKRW", 4U) == 0) m_type = CT_ACK; else m_type = CT_NAK; } break; case 28U: m_repeater = wxString((const char*)(data + 4U), wxConvLocal); m_type = CT_LINK2; break; default: return false; } m_yourAddress = yourAddress; m_yourPort = yourPort; m_myPort = myPort; return true; }