OT_WEAK void alp_stream_query_tmpl(ot_queue* out_q, void* data_type) { if _PTR_TEST(data_type) { q_writebyte(out_q, ((query_tmpl*)data_type)->code); q_writebyte(out_q, ((query_tmpl*)data_type)->length); if (((query_tmpl*)data_type)->code & 0x80) q_writestring(out_q, ((query_tmpl*)data_type)->mask, ((query_tmpl*)data_type)->length); q_writestring(out_q, ((query_tmpl*)data_type)->value, ((query_tmpl*)data_type)->length); } }
OT_WEAK ot_u16 otapi_put_query_tmpl(ot_u8* status, query_tmpl* query) { q_writebyte(&txq, query->length); q_writebyte(&txq, query->code); if (query->code & 0x80) { q_writestring(&txq, query->mask, query->length); } q_writestring(&txq, query->value, query->length); *status = 1; return q_length(&txq); }
OT_WEAK void alp_stream_dialog_tmpl(ot_queue* out_q, void* data_type) { if _PTR_TEST(data_type) { q_writeshort(out_q, ((dialog_tmpl*)data_type)->timeout); q_writeshort(out_q, ((dialog_tmpl*)data_type)->channels); q_writestring(out_q, ((dialog_tmpl*)data_type)->chanlist, ((dialog_tmpl*)data_type)->channels); } }
OT_WEAK void alp_stream_ack_tmpl(ot_queue* out_q, void* data_type) { if _PTR_TEST(data_type) { q_writebyte(out_q, ((ack_tmpl*)data_type)->count); q_writebyte(out_q, ((ack_tmpl*)data_type)->length); q_writestring(out_q, ((ack_tmpl*)data_type)->list, ((ack_tmpl*)data_type)->count * ((ack_tmpl*)data_type)->length); } }
OT_WEAK void alp_stream_udp_tmpl(ot_queue* out_q, void* data_type) { if _PTR_TEST(data_type) { q_writeshort(out_q, ((udp_tmpl*)data_type)->data_length); q_writebyte(out_q, ((udp_tmpl*)data_type)->dst_port); q_writebyte(out_q, ((udp_tmpl*)data_type)->src_port); q_writestring(out_q, ((udp_tmpl*)data_type)->data, ((udp_tmpl*)data_type)->data_length); } }
void otapi_log(ot_u8 subcode, ot_int length, ot_u8* data) { /// Log raw data that is able to fit in the MPipe output queue. if (otapi_log_header(subcode, length)) { q_writestring(mpipe.alp.outq, data, length); mpipe_send(); } }
void logger_header(ot_u8 id_subcode, ot_int payload_length) { ot_u8 header[] = { 0xDD, 0x00, 0, 0x02, 0x04, 0 }; header[2] = (ot_u8)payload_length; header[5] = id_subcode; q_empty(&otmpout); // output buffer q_writestring(&otmpout, header, 6); }
ot_u16 otapi_put_query_tmpl(ot_u8* status, query_tmpl* query) { /// Test for Anycast and Multicast addressing (query needs one of these) if (m2np.header.addr_ctl & 0x80) { q_writebyte(&txq, query->length); q_writebyte(&txq, query->code); if (query->code & 0x80) { q_writestring(&txq, query->mask, query->length); } q_writestring(&txq, query->value, query->length); *status = 1; return txq.length; } *status = 0; return 0; }
ot_bool m2qp_sig_udp(ot_u8 srcport, ot_u8 dstport, id_tmpl* user_id) { static const char* label[] = { "PongID: ", ", RSSI: ", ", Link: " }; ot_u16 pongval; ot_u8 i; ot_u8 scratch; //1. Read the PONG VAL pongval = q_readshort(&rxq); // Request: Copy PING VAL to PONG if (dstport == 254) { q_writeshort(&txq, pongval); return True; } # if defined(BOARD_eZ430Chronos) // Chronos doesn't have a normal MPipe, so print-out responses on the LCD # else // Response: Compare PING Val to PONG Val and write output to MPipe if ((dstport == 255) && (app.pingval == pongval)) { // Prepare logging header: UTF8 (text log) is subcode 1, dummy length is 0 otapi_log_header(1, 0); // Print out the three parameters for PongLT, one at a time. // If you are new to OpenTag, this is a common example of a state- // based code structure JP likes to use. i = 0; while (1) { q_writestring(mpipe.alp.outq, (ot_u8*)label[i], 8); switch (i++) { case 0: scratch = otutils_bin2hex( mpipe.alp.outq->putcursor, user_id->value, user_id->length ); break; case 1: scratch = otutils_int2dec(mpipe.alp.outq->putcursor, radio.last_rssi); break; case 2: scratch = otutils_int2dec(mpipe.alp.outq->putcursor, dll.last_nrssi); break; case 3: goto m2qp_sig_udp_PRINTDONE; } mpipe.alp.outq->putcursor += scratch; mpipe.alp.outq->length += scratch; } // Close the log file, send it out, return success m2qp_sig_udp_PRINTDONE: otapi_log_direct(); return True; } # endif return False; }
ot_bool alp_load(alp_tmpl* alp, ot_u8* src, ot_int length) { if (alp_is_available(alp, length)) { alp->inq->options.ubyte[0] = 1; q_writestring(alp->inq, src, length); alp->inq->options.ubyte[0] = 0; return True; } return False; }
OT_WEAK void alp_stream_queue(ot_queue* out_q, void* data_type) { if _PTR_TEST(data_type) { ot_int length; length = q_length((ot_queue*)data_type); q_writeshort(out_q, ((ot_queue*)data_type)->alloc); q_writeshort(out_q, ((ot_queue*)data_type)->options.ushort); q_writeshort(out_q, length); q_writestring(out_q, ((ot_queue*)data_type)->front, length); } }
ot_u16 otapi_put_shell_tmpl(ot_u8* status, shell_tmpl* shell) { /// There is no error/exception handling in this implementation, but it is /// possible to add in the future q_writebyte(&txq, shell->req_port); q_writebyte(&txq, shell->resp_port); q_writestring(&txq, shell->data, shell->data_length); *status = 1; return txq.length; }
OT_WEAK ot_u16 otapi_put_udp_tmpl(ot_u8* status, udp_tmpl* udp) { /// There is no error/exception handling in this implementation, but it is /// possible to add in the future q_writebyte(&txq, udp->src_port); q_writebyte(&txq, udp->dst_port); q_writestring(&txq, udp->data, udp->data_length); *status = 1; return q_length(&txq); }
OT_WEAK ot_u16 otapi_put_ack_tmpl(ot_u8* status, ack_tmpl* ack) { ot_int i; ot_u8* data_ptr = ack->list; ot_u8* limit = txq.back - ack->length; for (i=0; (i < ack->count) && (txq.putcursor < limit); \ i+=ack->length, data_ptr+=ack->length ) { q_writestring(&txq, data_ptr, ack->length); } *status = (ot_u8)i; return q_length(&txq); }
OT_WEAK ot_bool alp_proc_logger(alp_tmpl* alp, id_tmpl* user_id) { /// Logger ALP is like ECHO. The input is copied to the output. // Only root can log directly (this is an important security firewall) if (auth_isroot(user_id)) { alp->OUTREC(FLAGS) = *alp->inq->getcursor++; alp->OUTREC(PLEN) = *alp->inq->getcursor++; alp->inq->getcursor+= 2; if (alp->inq != alp->outq) { q_writestring(alp->outq, alp->inq->getcursor, alp->OUTREC(PLEN)); } } return True; }
void em2_decode_data() { static const ot_u8 cmd[] = { 0x01, 0xFF }; ot_u16 grab; em2_decode_data_TOP: grab = spirit1_rxbytes(); if (grab != 0) { if (grab > 24) grab = 24; spirit1_spibus_io(2, grab, (ot_u8*)cmd); q_writestring(&rxq, spirit1.busrx, grab); if (em2.state == 0) { ot_int ext_bytes; em2.state--; em2.bytes = 1 + (ot_int)rxq.front[0]; rxq.front[1] &= ~0x20; // always clear this bit em2.lctl = rxq.front[1]; em2.crc5 = em2_check_crc5(); if (em2.crc5 != 0) { return; } ext_bytes = 0; if (em2.lctl & 0x40) { ext_bytes = em2_rs_init_decode(&rxq); } crc_init_stream(False, em2.bytes-ext_bytes, rxq.getcursor); } crc_calc_nstream(grab); ///@todo we can optimize this also by waiting until crc is done, /// and then verifying that it is not accurate. but we need /// better speed profiling before doing that. # if (M2_FEATURE(RSCODE)) if (em2.lctl & 0x40) { em2_rs_decode(grab); } # endif em2.bytes -= grab; if (em2.bytes > 0) { goto em2_decode_data_TOP; } } }
ot_bool alp_proc_logger(alp_tmpl* alp, id_tmpl* user_id) { /// Logger ALP is like ECHO. The input is copied to the output. // Only root can log directly (this is an important security firewall) if (auth_isroot(user_id) == False) return False; alp->outrec.flags = alp->inrec.flags; alp->outrec.plength = alp->inrec.plength; if (alp->inq != alp->outq) { q_writestring(alp->outq, alp->inq->getcursor, alp->inrec.plength); } return True; }
void otapi_log_hexmsg(ot_int label_len, ot_int data_len, ot_u8* label, ot_u8* data) { /// This creates a "Message" (see otapi_log_msg()) in utf-8 text that includes /// binary data that has been converted to hex on the server side. It is more /// efficient to use otapi_log_msg() with a client that understands Message /// formatting, but if you don't have one of those, this function works. ot_int payload_length = label_len + 1 + (data_len<<1); if (otapi_log_header(7, payload_length)) { q_writestring(mpipe.alp.outq, label, label_len); q_writebyte(mpipe.alp.outq, ' '); payload_length = otutils_bin2hex(data, mpipe.alp.outq->putcursor, data_len); mpipe.alp.outq->putcursor += payload_length; //#mpipe.alp.outq->length += payload_length; mpipe_send(); } }
ot_u16 otapi_put_dialog_tmpl(ot_u8* status, dialog_tmpl* dialog) { if (dialog == NULL) { dll.comm.rx_timeout = (m2qp.cmd.ext & 2) ? 0 : 15; q_writebyte(&txq, 0); } else { // Calculate actual timeout and write timeout code field dll.comm.rx_timeout = otutils_calc_timeout(dialog->timeout); dialog->timeout |= (dialog->channels != 0) ? 0 : 0x80; q_writebyte(&txq, dialog->timeout); // Write response list if (dialog->channels != 0) { dll.comm.rx_channels = dialog->channels; dll.comm.rx_chanlist = dialog->chanlist; q_writestring(&txq, dialog->chanlist, dialog->channels); } } *status = 1; return txq.length; }
OT_WEAK ot_u16 otapi_put_dialog_tmpl(ot_u8* status, dialog_tmpl* dialog) { if (dialog == NULL) { ///@todo "15" is hard-coded timeout. Have this be a constant dll.comm.rx_timeout = (m2qp.cmd.ext & 2) ? 0 : 15; q_writebyte(&txq, (ot_u8)dll.comm.rx_timeout); } else { // Place dialog with timeout dll.comm.rx_timeout = otutils_calc_timeout(dialog->timeout); dialog->timeout |= (dialog->channels == 0) << 7; // 0 or 0x80 q_writebyte(&txq, dialog->timeout); // Write response list if (dialog->channels != 0) { dll.comm.rx_channels = dialog->channels; dll.comm.rx_chanlist = dialog->chanlist; q_writestring(&txq, dialog->chanlist, dialog->channels); } } *status = 1; return q_length(&txq); }
void m2np_header(m2session* session, ot_u8 addressing, ot_u8 nack) { /// Build an M2NP header, which gets forwarded in all cases to M2QP at the /// transport layer, and which has NM2=0, Frame_Type={0,1} /// 1. Prep txq, and write Frame Info & Addr Ctrl Fields (universal) q_start(&txq, 0, 0); txq.back = txq.getcursor + 254; q_writebyte(&txq, 0); // null length (placeholder only) q_writebyte(&txq, 0); // Dummy TX EIRP setting (placeholder only) //q_writeshort(&txq, 0x0000); q_writebyte(&txq, session->subnet); session->netstate |= (addressing) ? 0 : M2_NETFLAG_FIRSTRX; //Set FIRSTRX mode on Unicast m2np.header.fr_info = (session->flags & 0xC0); addressing |= (session->flags & 0x3F); m2np.header.addr_ctl = addressing; m2np.header.fr_info |= nack; m2np.header.fr_info |= M2FI_ENADDR; q_writebyte(&txq, m2np.header.fr_info); /// 2. If required, enable DLLS encryption. AES128 is the only currently /// supported crypto in OpenTag. It will be applied in m2np_footer(). /// If DLLS is enabled, NLS will be forced-off, because both of them /// cannot be active on the same frame. /// @todo Experimental! # if (OT_FEATURE(DLL_SECURITY)) if (m2np.header.fr_info & M2FI_DLLS) { auth_setup(&txq, b00100000, 249); m2np.header.addr_ctl &= ~M2_FLAG_NLS; } # endif /// 3. Write Dialog, Addr Ctrl, and Source Address (always included in M2NP) q_writebyte(&txq, session->dialog_id); q_writebyte(&txq, m2np.header.addr_ctl); m2np_put_deviceid( (ot_bool)(m2np.header.addr_ctl & M2AC_VID) ); /// 4. If required, enabled NLS. The rules are basically the same as DLLS. /// @todo Experimental! # if (OT_FEATURE(NL_SECURITY)) if (m2np.header.addr_ctl & M2_FLAG_NLS) { auth_setup(&txq, b00100000, (txq.putcursor-txq.getcursor)); } # endif /// 5. Apply target address, if unicast enabled, and rebase it from the TX /// Queue, which remains for the duration of the dialog if ((m2np.header.addr_ctl & 0xC0) == 0) { q_writestring(&txq, m2np.rt.dlog.value, m2np.rt.dlog.length); m2np.rt.dlog.value = (txq.putcursor - m2np.rt.dlog.length); } /// 6. Apply Multihop routing template, if unicast or anycast enabled if ((m2np.header.addr_ctl & 0x40) == 0) { ot_u8 hopmask = M2HC_ORIG; ot_u8 id_num = 1; id_tmpl* id = &m2np.rt.orig; q_writebyte(&txq, m2np.rt.hop_code); if ((m2np.rt.hop_code & M2HC_EXT) != 0) { q_writebyte(&txq, m2np.rt.hop_ext); } while (id_num != 0) { if (m2np.rt.hop_code & hopmask) { ot_u8* loc = txq.putcursor; q_writestring(&txq, id->value, id->length); id->value = loc; } hopmask = M2HC_DEST; id += 1; //moves to next id_tmpl (dest) id_num -= 1; } } }
/** Mpipe Subs (Board & Platform dependent) <BR> * ======================================================================== */ void sub_txack_header() { static const ot_u8 ndef_ack_header[] = {0xDD, 0, 0, 2, 0, 0 }; q_empty(mpipe_alp.outq); q_writestring(mpipe_alp.outq, (ot_u8*)ndef_ack_header, 6); }
OT_WEAK void alp_stream_advert_tmpl(ot_queue* out_q, void* data_type) { if _PTR_TEST(data_type) { q_writestring(out_q, (ot_u8*)data_type, 4); q_writeshort(out_q, ((advert_tmpl*)data_type)->duration); } }
void sub_logmsg(ot_int label_len, ot_int data_len, ot_u8* label, ot_u8* data) { q_writestring(mpipe.alp.outq, label, label_len); q_writebyte(mpipe.alp.outq, ' '); q_writestring(mpipe.alp.outq, data, data_len); }
OT_WEAK void alp_stream_command_tmpl(ot_queue* out_q, void* data_type) { if _PTR_TEST(data_type) q_writestring(out_q, (ot_u8*)data_type, 3); }
ot_int sub_parse_response(m2session* session) { /// Only Gateways and Subcontrollers do anything with the response. Generally, /// Gateways might have some sort of logging in the callbacks. Response types /// include: (1) Normal responses to NA2P standard dialog, (2) Arbitrated /// responses to A2P multicast dialog, (3) Responses that are part of 2, 3, 4, /// or 5-way datastream session. ot_u8 test; ot_u8 cmd_opcode; /// Make sure response command opcode matches the last request's opcode /// (this ensures that the response is to our request). cmd_opcode = m2qp.cmd.code & 0x0F; test = q_readbyte(&rxq) & 0x0F; if (test == cmd_opcode) { # if ((M2_FEATURE(DATASTREAM) == ENABLED) && (OT_FEATURE(ALP) == ENABLED)) /// Manage Responses to Request and Propose Datastream if ((cmd_opcode - M2OP_DS_REQUEST) <= 1) { ot_u16 ds_total_bytes = q_readshort(&rxq); // might be removed ot_u8 fr_per_pkt = q_readbyte(&rxq); // might be removed ot_u8 num_frames = rxq.getcursor[0]; // might be removed rxq.getcursor += ((cmd_opcode & 1) == 0); m2dp.out_rec.flags = ALP_FLAG_MB; // ds beginning // Run Callback (if enabled). Callback can override processing // (Callback is not compiled when callbacks are disabled) test = (ot_u8)M2QP_CALLBACK(DSPKT); if ( test ) { m2dp_dsproc(); } ///@todo Might put in some type of return scoring, later return (ot_int)test; } # if (OT_FEATURE(M2QP_CALLBACKS) == ENABLED) else if (cmd_opcode == 10) { test = (ot_u8)M2QP_CALLBACK(DSACK); if ( test ) { ///@todo Prepare the next stream packet } return (ot_int)test; } # endif else if (((m2qp.cmd.code & 0x60) == 0x40) && \ ((txq.back - txq.putcursor) > 48) ) # else //((M2_FEATURE(DATASTREAM) == ENABLED) && (OT_FEATURE(ALP) == ENABLED)) if (((m2qp.cmd.code & 0x60) == 0x40) && \ ((txq.back - txq.putcursor) > 48) ) # endif /// If using A2P, put this responder's ID onto the ACK chain <BR> /// - Reserve some room at the back for query data (48 bytes) <BR> /// - Increment "Number of ACKs" on each use (txq.getcursor[0]) <BR> /// - Run the A2P callback (if enabled) { ///@todo check to make sure NumACKs is 0 on 1st run (might be done) ///@todo Might put in some type of return scoring, later txq.getcursor[0]++; q_writestring(&txq, m2np.rt.dlog.value, m2np.rt.dlog.length); test = (ot_u8)M2QP_CALLBACK(A2P); } /// If nothing else, the response is a normal response (NA2P), so run /// the callback as normal else { test = (ot_u8)M2QP_CALLBACK(STANDARD); } /// Make into 0/-1 form for returning return (ot_int)test - 1; } }
OT_WEAK void alp_stream_session_tmpl(ot_queue* out_q, void* data_type) { if _PTR_TEST(data_type) q_writestring(out_q, (ot_u8*)data_type, 6); }
void sub_build_uhfmsg(ot_int* buffer) { /// This is the routine that builds the DASH7 UDP generic protocol message. /// The protocol has data elements marked by a letter (T, V, R, E, D) that /// signify Temperature, Voltage, RSSI (LF), PaLFi wake Event, and RX Data. /// The elements are fixed/known length. session_tmpl s_tmpl; command_tmpl c_tmpl; ot_u8* data_start; ot_u8 status; // Create a new session: you could change these parameters // Use "CHAN1" for odd events, "CHAN2" for even events s_tmpl.channel = (palfi.wake_event & 1) ? ALERT_CHAN1 : ALERT_CHAN2; s_tmpl.subnetmask = 0; // Use default subnet s_tmpl.flagmask = 0; // Use default app-flags s_tmpl.timeout = 10; // Do CSMA for no more than 10 ticks (~10 ms) otapi_new_session(&s_tmpl); // Broadcast request (takes no 2nd argument) otapi_open_request(ADDR_broadcast, NULL); // Insert Transport-Layer headers c_tmpl.type = CMDTYPE_na2p_request; c_tmpl.opcode = CMD_udp_on_file; c_tmpl.extension= CMDEXT_no_response; otapi_put_command_tmpl(&status, &c_tmpl); otapi_put_dialog_tmpl(&status, NULL); // NULL = defaults // UDP Header q_writebyte(&txq, 255); // Source Port: 255 (custom application port) q_writebyte(&txq, 255); // Destination Port (same value) data_start = txq.putcursor; // Place temperature data q_writebyte(&txq, 'T'); q_writeshort(&txq, buffer[0]); // Place Voltage data q_writebyte(&txq, 'V'); q_writeshort(&txq, buffer[1]); // Place RSSI data q_writebyte(&txq, 'R'); q_writestring(&txq, (ot_u8*)&palfi.rssi1, 3); // Place Action data q_writebyte(&txq, 'E'); q_writebyte(&txq, (ot_int)palfi.wake_event); // Dump some received data if (palfi.wake_event) { q_writebyte(&txq, 'D'); q_writestring(&txq, palfi.rxdata, 8); } // Store this information into the Port 255 file for continuous, automated // reporting by DASH7/OpenTag until it is updated next time. The length of // this information is always 23 bytes. { vlFILE* fp; fp = ISF_open_su(255); if (fp != NULL) { vl_store(fp, 23, data_start); vl_close(fp); } } // Finish Message otapi_close_request(); }
OT_WEAK void alp_stream_id_tmpl(ot_queue* out_q, void* data_type) { if _PTR_TEST(data_type) { q_writebyte(out_q, ((id_tmpl*)data_type)->length); q_writestring(out_q, ((id_tmpl*)data_type)->value, ((id_tmpl*)data_type)->length); } }