diff --git a/RTCP/Cobalt/OutputProc/scripts/CMakeLists.txt b/RTCP/Cobalt/OutputProc/scripts/CMakeLists.txt index 2e8ef0b32738f6b090a511bed86ff7d345c80d55..1d17a3feadaae78d5a33184aedb7a2da6aff0b2c 100644 --- a/RTCP/Cobalt/OutputProc/scripts/CMakeLists.txt +++ b/RTCP/Cobalt/OutputProc/scripts/CMakeLists.txt @@ -3,3 +3,6 @@ if(USE_THREADS) lofar_add_sbin_program(udp-copy udp-copy.c common.c) endif(USE_THREADS) + +add_executable(tbb-crc-test tbb-crc-test.cc) +add_executable(tbb-dumpframes tbb-dumpframes.cc) diff --git a/RTCP/Cobalt/OutputProc/scripts/tbb-crc-test.cc b/RTCP/Cobalt/OutputProc/scripts/tbb-crc-test.cc new file mode 100644 index 0000000000000000000000000000000000000000..f1d6b730619f901143523d03c3a7179b59411342 --- /dev/null +++ b/RTCP/Cobalt/OutputProc/scripts/tbb-crc-test.cc @@ -0,0 +1,296 @@ +/* tbb-crc-test.cpp + * Author: Alexander S. van Amesfoort, ASTRON + * with code based on Python crc routines received from Gijs Schoonderbeek, ASTRON + * Last-modified: July 2012 + * build: g++ -Wall -o tbb-crc-test tbb-crc-test.cc + */ + +#include <stdint.h> +#include <string.h> +#include <endian.h> +#if __BYTE_ORDER != __BIG_ENDIAN && __BYTE_ORDER != __LITTLE_ENDIAN +#error Byte order is neither big endian nor little endian: not supported +#endif +#include <byteswap.h> + +#include <iostream> +#include <fstream> + +#include <boost/crc.hpp> + +using namespace std; + +struct TBB_Header { + uint8_t stationId; + uint8_t rspID; + uint8_t rcuID; + uint8_t sampleFreq; + + uint32_t seqNr; + uint32_t time; + + union { + uint32_t sampleNr; + uint32_t bandsliceNr; + }; + + uint16_t nOfSamplesPerFrame; + uint16_t nOfFreqBands; + + uint8_t bandSel[64]; + + uint16_t spare; + uint16_t crc16; +}; + +#define MAX_TRANSIENT_NSAMPLES 1298 // based on frames stored by TBB and (un)packing +#define DEFAULT_TRANSIENT_NSAMPLES 1024 // int16_t +#define DEFAULT_SPECTRAL_NSAMPLES 487 // complex int16_t +struct TBB_Payload { + // For transient data, we typically receive 1024 samples per frame. + // uint32_t crc comes right after, so cannot easily declare it after data[], hence + 2. + int16_t data[MAX_TRANSIENT_NSAMPLES + 2]; +}; + +struct TBB_Frame { + TBB_Header header; + TBB_Payload payload; +}; + + +// Same truncated polynomials as standard crc16 and crc32, but with initial_remainder=0, final_xor_value=0, reflected_input=false, reflected_remainder_output=false. +// The boost::crc_optimal<> declarations precompute lookup tables, so do not declare inside the checking routine. +static boost::crc_optimal<16, 0x8005 /*, 0, 0, false, false*/> crc16tbbgen; // instead of crc_16_type +//boost::crc_basic<16> crc16tbbgen(0x8005/*, 0, 0, false, false*/); // non-opt variant + +static boost::crc_optimal<32, 0x04C11DB7/*, 0, 0, false, false*/> crc32tbbgen; // instead of crc_32_type +//boost::crc_basic<32> crc32gen(0x04C11DB7/*, 0, 0, false, false*/); // non-opt variant + +/* + * Assumes that the seqNr field in the TBB_Frame at buf has been zeroed. + * Takes a ptr to a complete header. (Drop too small frames earlier.) + */ +static bool crc16tbb_boost(const TBB_Header* header) { + crc16tbbgen.reset(); + + /* + * The header checksum is done like the data, i.e. on 16 bit little endian blocks at a time. + * As with the data, both big and little endian CPUs need to byte swap. + */ + const int16_t* ptr = reinterpret_cast<const int16_t*>(header); + size_t i; + for (i = 0; i < (sizeof(*header) - sizeof(header->crc16)) / sizeof(int16_t); i++) { + int16_t val = __bswap_16(ptr[i]); + crc16tbbgen.process_bytes(&val, sizeof(int16_t)); + } + + // Byte swap the little endian checksum on big endian only. + // It is also possible to process header->crc16 and see if checksum() equals 0. + uint16_t crc16val = header->crc16; +#if __BYTE_ORDER == __BIG_ENDIAN + crc16val = __bswap_16(crc16val); +#endif + return crc16tbbgen.checksum() == crc16val; +} + +/* + * Note: The nsamples arg is without the space taken by the crc32 in payload. (Drop too small frames earlier.) + */ +static bool crc32tbb_boost(const TBB_Payload* payload, size_t nsamples) { + crc32tbbgen.reset(); + + /* + * Both little and big endian CPUs need to byte swap, because the data always arrives + * in little and the boost routines treat it as uint8_t[] (big). + */ + const int16_t* ptr = reinterpret_cast<const int16_t*>(payload->data); + size_t i; + for (i = 0; i < nsamples; i++) { + int16_t val = __bswap_16(ptr[i]); + crc32tbbgen.process_bytes(&val, sizeof(int16_t)); + } + + // Byte swap the little endian checksum on big endian only. + // It is also possible to process crc32val and see if checksum() equals 0. + uint32_t crc32val = *reinterpret_cast<const uint32_t*>(&ptr[nsamples]); +#if __BYTE_ORDER == __BIG_ENDIAN + crc32val = __bswap_32(crc32val); +#endif + return crc32tbbgen.checksum() == crc32val; +} + +#if __BYTE_ORDER != __LITTLE_ENDIAN +#warning Original crc routines were only developed for little endian. Skipping those. +#else +/* + * This code is translated from the Python ref/test code received from Gijs Schoonderbeek. + * It assumes that the seqNr field (buf[1]) has been zeroed. + * Do not call this function with len < 1; reject too small headers earlier. + */ +static uint16_t crc16tbb(const uint16_t* buf, size_t len) { + uint16_t CRC = 0; + const uint32_t CRC_poly = 0x18005; + const uint16_t bits = 16; + uint32_t data = 0; + const uint32_t CRCDIV = (CRC_poly & 0x7fffffff) << 15; + + data = (buf[0] & 0x7fffffff) << 16; + for (uint32_t i = 1; i < len; i++) { + data += buf[i]; + for (uint16_t j = 0; j < bits; j++) { + if ((data & 0x80000000) != 0) { + data = data ^ CRCDIV; + } + data = data & 0x7fffffff; + data = data << 1; + } + } + CRC = data >> 16; + return CRC; +} + +/* + * This code is translated from the Python ref/test code received from Gijs Schoonderbeek. + * It computes a 32 bit result, 16 bits at a time. + * Do not call this function with len < 2; reject too small payloads earlier. + */ +static uint32_t crc32tbb(const uint16_t* buf, size_t len) { + uint32_t CRC = 0; + const uint64_t CRC_poly = 0x104C11DB7ULL; + const uint16_t bits = 16; + uint64_t data = 0; + const uint64_t CRCDIV = (CRC_poly & 0x7fffffffffffULL) << 15; + + data = buf[0]; + data = data & 0x7fffffffffffULL; + data = data << 16; + data = data + buf[1]; + data = data & 0x7fffffffffffULL; + data = data << 16; + uint32_t i = 2; + for ( ; i < len-2; i++) { + data = data + buf[i]; + for (uint32_t j = 0; j < bits; j++) { + if (data & 0x800000000000ULL) { + data = data ^ CRCDIV; + } + data = data & 0x7fffffffffffULL; + data = data << 1; + } + } + + // Do the 32 bit checksum separately. + // Process the two 16 bit halves in reverse order, but keep the i < len cond. + for (buf += 1; i < len; i++, buf -= 2) { + data = data + buf[i]; + for (uint32_t j = 0; j < bits; j++) { + if (data & 0x800000000000ULL) { + data = data ^ CRCDIV; + } + data = data & 0x7fffffffffffULL; + data = data << 1; + } + } + + CRC = (uint32_t)(data >> 16); + return CRC; +} +#endif + +static int verify_crc(TBB_Frame& frame, size_t frameSize) { + int err = 0; + + // Zero sequence number field before verification. + // It is set by TBB after the checksum has been computed. We do not need it later. + frame.header.seqNr = 0; + +#if __BYTE_ORDER == __LITTLE_ENDIAN + uint16_t headercrc = crc16tbb( reinterpret_cast<uint16_t*>(&frame.header), sizeof(TBB_Header) / sizeof(uint16_t) ); + if (headercrc != 0) { + cerr << "crc16tbb(): Incorrect header crc: " << hex << headercrc << endl; + err = 1; + } +#endif + if (!crc16tbb_boost(&frame.header)) { + cerr << "crc16tbb_boost(): Incorrect header crc" << endl; + err = 1; + } + + +#if __BYTE_ORDER == __LITTLE_ENDIAN + uint32_t payloadcrc = crc32tbb( reinterpret_cast<uint16_t*>(frame.payload.data), ( frameSize - sizeof(TBB_Header) ) / sizeof(uint16_t) ); + if (payloadcrc != 0) { + cerr << "crc32tbb(): Incorrect payload crc: " << hex << payloadcrc << endl; + err = 1; + } +#endif + if (!crc32tbb_boost( &frame.payload, ( frameSize - sizeof(TBB_Header) - sizeof(uint32_t) ) / sizeof(int16_t) )) { + cerr << "crc32tbb_boost(): Incorrect payload crc" << endl; + err = 1; + +#if 0 // this guessing doesn't work: the wrong crc32 is different every time, even on the same data + TBB_Payload p; + unsigned i; + for (i = 0; i < 487; i++) { + memcpy(&p, &frame.payload, i * 2 * sizeof(int16_t)); // data + memcpy((char*)&p + i * 2 * sizeof(int16_t), (char*)(&frame.payload.data[2*487 + 2]) - sizeof(uint32_t), sizeof(uint32_t)); // crc32 + if (crc32tbb_boost(&p, 2 * i)) { + cerr << "found it: i=" << i << endl; + break; + } else { + cerr << "doesn't work either: " << i << endl; + } + } +#endif + + } + + return err; +} + +int main(int argc, char* argv[]) { + if (argc < 2) { + cout << "Usage: " << argv[0] << " rawtbbframes.dat" << endl; + return 1; + } + + bool transient; + ifstream iftype(argv[1], ios_base::binary); + if (!iftype) { + cerr << "Failed to open file " << argv[1] << endl; + return 1; + } + TBB_Header header; + iftype.read(reinterpret_cast<char*>(&header), sizeof header); + if (!iftype) { + cerr << "Failed to read first frame to determine transient or spectral mode" << endl; + return 1; + } + iftype.close(); + transient = header.nOfFreqBands == 0; + + + ifstream ifs(argv[1], ios_base::binary); + if (!ifs) { + cerr << "Failed to open file " << argv[1] << endl; + return 1; + } + + int err = 0; + + TBB_Frame frame; + size_t frameSize; + if (transient) { + frameSize = sizeof(TBB_Header) + DEFAULT_TRANSIENT_NSAMPLES * sizeof(int16_t) + sizeof(uint32_t); + } else { // spectral + frameSize = sizeof(TBB_Header) + DEFAULT_SPECTRAL_NSAMPLES * 2 * sizeof(int16_t) + sizeof(uint32_t); + } + + while (ifs.read(reinterpret_cast<char*>(&frame), frameSize)) { + err |= verify_crc(frame, frameSize); + } + + ifs.close(); + return err; +} + diff --git a/RTCP/Cobalt/OutputProc/scripts/tbb-dumpframes.cc b/RTCP/Cobalt/OutputProc/scripts/tbb-dumpframes.cc new file mode 100644 index 0000000000000000000000000000000000000000..7a4404353396c15f9788915dc0276da06b33f96e --- /dev/null +++ b/RTCP/Cobalt/OutputProc/scripts/tbb-dumpframes.cc @@ -0,0 +1,240 @@ +/* tbb-dumpframes.cc + * Author: Alexander S. van Amesfoort, ASTRON + * Last-modified: Jun 2013 + * build: g++ -Wall -o tbb-dumpframes tbb-dumpframes.cc + */ + +#include <stdint.h> +#include <cstdlib> +#include <cstring> + +#include <string> +#include <iostream> +#include <fstream> +#include <sstream> + +using namespace std; + +struct TBB_Header { + uint8_t stationID; // Data source station identifier + uint8_t rspID; // Data source RSP board identifier + uint8_t rcuID; // Data source RCU board identifier + uint8_t sampleFreq; // Sample frequency in MHz of the RCU boards + + uint32_t seqNr; // Used internally by TBB. Set to 0 by RSP (but written again before we receive it) + uint32_t time; // Time instance in seconds of the first sample in payload + // The time field is relative, but if used as UNIX time, uint32_t will wrap at 06:28:15 UTC on 07 Feb 2106 (int32_t wraps at 03:14:08 UTC on 19 Jan 2038). + + union { + // In transient mode indicates sample number of the first payload sample in current seconds interval. + uint32_t sampleNr; + + // In spectral mode indicates frequency band and slice (transform block of 1024 samples) of first payload sample. + uint32_t bandSliceNr; // bandNr[9:0] and sliceNr[31:10]. + // Avoid bit fields, (portable) compilation support is messy. Instead use mask and shift to decode. +#define TBB_BAND_NR_MASK ((1 << 10) - 1) +#define TBB_SLICE_NR_SHIFT 10 + }; + + uint16_t nOfSamplesPerFrame; // Total number of samples in the frame payload + uint16_t nOfFreqBands; // Number of frequency bands for each spectrum in spectral mode. Is set to 0 for transient mode. + + uint8_t bandSel[64]; // Each bit in the band selector field indicates whether the band with the bit index is present in the spectrum or not. + + uint16_t spare; // For future use. Set to 0. + uint16_t crc16; // CRC16 over frame header, with seqNr set to 0. +}; + +void timeToStr(time_t t, char* out, size_t out_sz) { + struct tm *tm = gmtime(&t); + // Format: Mo, 15-06-2009 20:20:00 + strftime(out, out_sz, "%a, %d-%m-%Y %H:%M:%S", tm); +} + +void printHeader(const TBB_Header& h) { + cout << "Station ID: " << (uint32_t)h.stationID << endl; + cout << "RSP ID: " << (uint32_t)h.rspID << endl; + cout << "RCU ID: " << (uint32_t)h.rcuID << endl; + cout << "Sample Freq: " << (uint32_t)h.sampleFreq << endl; + cout << "Seq Nr: " << h.seqNr << endl; + char buf[32]; + timeToStr(h.time, buf, 32); + cout << "Time: " << h.time << " (dd-mm-yyyy: " << buf << " UTC)" << endl; + bool transient = h.nOfFreqBands == 0; + if (transient) { + cout << "Transient" << endl; + cout << "Sample Nr: " << h.sampleNr << endl; + } else { + cout << "Spectral" << endl; + cout << "Band Nr: " << (h.bandSliceNr & TBB_BAND_NR_MASK) << endl; + cout << "Slice Nr: " << (h.bandSliceNr >> TBB_SLICE_NR_SHIFT) << endl; + } + cout << "NSamples/fr: " << h.nOfSamplesPerFrame << endl; + if (!transient) { + cout << "NFreq Bands: " << h.nOfFreqBands << endl; + + bool anyBandsPresent = false; + cout << "Band(s) present(?): "; + for (unsigned i = 0; i < 64; ++i) { + for (unsigned j = 8; j > 0; ) { + --j; + if (h.bandSel[i] & (1 << j)) { + cout << 8 * i + (8-1-j) << " "; + anyBandsPresent = true; + } + } + } + if (!anyBandsPresent) { + cout << "Warning: Spectral data, but no band present!" << endl; + } else { + cout << endl; + } + } + + cout << "Spare (0): " << h.spare << endl; + cout << "crc16: " << h.crc16 << endl; +} + +void printPayload(const int16_t* payload, size_t payload_len) { + size_t data_len = payload_len - sizeof(uint32_t) / sizeof(int16_t); // - crc32 + unsigned i; + + if (data_len == 1024) { // transient has 1024 samples + crc32 + for (i = 0; i < data_len; i++) { + cout << payload[i] << " "; + } + } else { // spectral has up to 487 complex samples + crc32 + for (i = 0; i < data_len; i += 2) { + cout << '(' << payload[i] << ' ' << payload[i+1] << ") "; // assumes data_len is even + } + } + cout << endl; + + cout << "crc32: " << reinterpret_cast<uint32_t*>(payload[i]) << endl; +} + +void printFakeInput() { + TBB_Header hdr0; + + hdr0.stationID = 1; + hdr0.rspID = 2; + hdr0.rcuID = 3; + hdr0.sampleFreq = 200; + hdr0.seqNr = 10000; + hdr0.time = 1380240059; + hdr0.bandSliceNr = (17 << 10) | 11; // sliceNr=17; bandNr is 11 + hdr0.nOfSamplesPerFrame = 487; + hdr0.nOfFreqBands = 487/8 * 7 + 7; // 427, as set in the sb bitmap below + + // subband bitmap + // I'm not 100% if the bits are populated from most to least significant... + int i; + for (i = 0; i < 487/8; i++) + hdr0.bandSel[i] = 0x7f; + hdr0.bandSel[i++] = 0xfe; // remaining 7 bits to cover all 487 meaningful bits + for ( ; i < 64; i++) + hdr0.bandSel[i] = 0; + + hdr0.spare = 0; + hdr0.crc16 = 1; + + printHeader(hdr0); +} + +int main(int argc, char* argv[]) { + bool printData = false; + bool fakeInput = false; + const char* filename = "/dev/stdin"; + int nprinted = 8; + + cout << "Usage: " << argv[0] << " [-d] [-t] [data/tbbdata.raw] [nframes]" << endl; + + int argi = 1; + if (argc > argi) { + if (strcmp(argv[argi], "-d") == 0) { + printData = true; + argi += 1; + } + + if (strcmp(argv[argi], "-t") == 0) { + fakeInput = true; + argi += 1; + } + + if (argc > argi) { + filename = argv[argi]; + argi += 1; + } + + if (argc > argi) { + nprinted = std::atoi(argv[argi]); + argi += 1; + if (nprinted < 0) { + cerr << "Bad nframes argument" << endl; + return 1; + } + } + } + + + if (fakeInput) { + printFakeInput(); + exit(0); + } + + ifstream ifs(filename); + if (!ifs) { + cerr << "Failed to open " << filename << endl; + return 1; + } + + cout << "Default frame size:" << " header=" << sizeof(TBB_Header) << + " transient=" << sizeof(TBB_Header) + 1024 * sizeof(int16_t) + sizeof(uint32_t) << + " spectral=" << sizeof(TBB_Header) + 487 * 2 * sizeof(int16_t) + sizeof(uint32_t) << endl << endl; + + int exit_status = 0; + + // This doesn't work directly with data from message-oriented streams like udp, + // because header and payload need to be read using a single read() under linux. + // We don't need that for dumping data from a file; buffers are separate here. + TBB_Header h; + int16_t* payload = NULL; + for (int i = 0; i < nprinted; i++) { + ifs.read(reinterpret_cast<char*>(&h), sizeof h); + if (!ifs || static_cast<size_t>(ifs.gcount()) < sizeof h) { + cerr << "Failed to read " << sizeof h << " frame header bytes from " << filename << endl; + exit_status = 1; + goto out; + } + + printHeader(h); + + + size_t payload_len = h.nOfSamplesPerFrame; + if (h.nOfFreqBands != 0) { + payload_len *= 2; // spectral has complex nrs, so 2 * int16_t + } + payload_len += sizeof(uint32_t) / sizeof(int16_t); // crc32 + if (payload == NULL) { + // assume this is enough for all future frames; this program is for formatted frame dumps, not for the real thing anyway + payload = new int16_t[payload_len]; // data + crc32 + } + + ifs.read(reinterpret_cast<char*>(payload), payload_len * sizeof(int16_t)); + if (!ifs) { + cerr << "Failed to read " << payload_len * sizeof(int16_t) << " frame payload from " << filename << endl; + exit_status = 1; + goto out; + } + if (printData) { + printPayload(payload, payload_len); + } + + cout << "----------------------------" << endl; + } + +out: // too lazy to use proper objects in this test prog, but avoid mem leaks..... + delete[] payload; + return exit_status; +} +