1 /**
2  * radio.cpp
3  * This file is part of the YATE Project http://YATE.null.ro
4  *
5  * Generic radio interface
6  *
7  * Yet Another Telephony Engine - a fully featured software PBX and IVR
8  * Copyright (C) 2015 Null Team
9  * Copyright (C) 2015 LEGBA Inc
10  *
11  * This software is distributed under multiple licenses;
12  * see the COPYING file in the main directory for licensing
13  * information for this specific distribution.
14  *
15  * This use of this software may be subject to additional restrictions.
16  * See the LEGAL file in the main directory for details.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
21  */
22 
23 #include <yateradio.h>
24 #include <string.h>
25 
26 /*
27 #ifndef htobe32
28 #include <byteswap.h>
29 
30 #ifdef LITTLE_ENDIAN
31 
32 #define htobe16(x) __bswap_16 (x)
33 #define htole16(x) (x)
34 #define be16toh(x) __bswap_16 (x)
35 #define le16toh(x) (x)
36 #define htobe32(x) __bswap_32 (x)
37 #define htole32(x) (x)
38 #define be32toh(x) __bswap_32 (x)
39 #define le32toh(x) (x)
40 #define htobe64(x) __bswap_64 (x)
41 #define htole64(x) (x)
42 #define be64toh(x) __bswap_64 (x)
43 #define le64toh(x) (x)
44 
45 #else
46 
47 #define htobe16(x) (x)
48 #define htole16(x) __bswap_16 (x)
49 #define be16toh(x) (x)
50 #define le16toh(x) __bswap_16 (x)
51 #define htobe32(x) (x)
52 #define htole32(x) __bswap_32 (x)
53 #define be32toh(x) (x)
54 #define le32toh(x) __bswap_32 (x)
55 #define htobe64(x) (x)
56 #define htole64(x) __bswap_64 (x)
57 #define be64toh(x) (x)
58 #define le64toh(x) __bswap_64 (x)
59 
60 #endif
61 
62 #endif // htobe32
63 */
64 #include <sys/endian.h>
65 
66 
67 //#define DEBUG_RADIO_READ
68 
69 #ifdef DEBUG_RADIO_READ
70 #define DebugRadioRead(args...) Debug(this,DebugAll,args)
71 #else
72 #ifdef _WINDOWS
73 #define DebugRadioRead do { break; } while
74 #else
75 #define DebugRadioRead(arg...)
76 #endif
77 #endif // DEBUG_RADIO_READ
78 
79 
80 using namespace TelEngine;
81 
82 #define MAKE_NAME(x) {#x, RadioInterface::x}
83 static const TokenDict s_errorName[] = {
84     MAKE_NAME(HardwareIOError),
85     MAKE_NAME(NotInitialized),
86     MAKE_NAME(NotSupported),
87     MAKE_NAME(NotCalibrated),
88     MAKE_NAME(TooEarly),
89     MAKE_NAME(TooLate),
90     MAKE_NAME(OutOfRange),
91     MAKE_NAME(NotExact),
92     MAKE_NAME(DataLost),
93     MAKE_NAME(Saturation),
94     MAKE_NAME(RFHardwareFail),
95     MAKE_NAME(RFHardwareChange),
96     MAKE_NAME(EnvironmentalFault),
97     MAKE_NAME(InvalidPort),
98     MAKE_NAME(Pending),
99     MAKE_NAME(Cancelled),
100     MAKE_NAME(Failure),
101     MAKE_NAME(Timeout),
102     MAKE_NAME(HardwareNotAvailable),
103     MAKE_NAME(InsufficientSpeed),
104     MAKE_NAME(NoError),
105     {0,0}
106 };
107 #undef MAKE_NAME
108 
RadioCapability()109 RadioCapability::RadioCapability()
110 {
111     ::memset(this,0,sizeof(*this));
112 }
113 
114 
115 // Calculate the length of a sample in elements
sampleLen()116 static inline unsigned int sampleLen()
117 {
118     return 2;
119 }
120 
121 // Calculate the number of 'float' elements in given number of samples
samples2floats(unsigned int nSamples)122 static inline unsigned int samples2floats(unsigned int nSamples)
123 {
124     return nSamples * sampleLen();
125 }
126 
127 // Calculate the length in bytes of a given number of samples
samples2bytes(unsigned int nSamples)128 static inline unsigned int samples2bytes(unsigned int nSamples)
129 {
130     return samples2floats(nSamples) * sizeof(float);
131 }
132 
133 // Advance float buffer by samples
advanceSamples(float * buf,unsigned int nSamples)134 static inline float* advanceSamples(float* buf, unsigned int nSamples)
135 {
136     return buf + samples2floats(nSamples);
137 }
138 
resetSamples(float * buf,unsigned int nSamples)139 static inline void resetSamples(float* buf, unsigned int nSamples)
140 {
141     ::memset(buf,0,samples2bytes(nSamples));
142 }
143 
copySamples(float * dest,unsigned int destOffs,float * src,unsigned int srcOffs,unsigned int nSamples)144 static inline void copySamples(float* dest, unsigned int destOffs,
145     float* src, unsigned int srcOffs, unsigned int nSamples)
146 {
147     ::memcpy(advanceSamples(dest,destOffs),advanceSamples(src,srcOffs),
148 	samples2bytes(nSamples));
149 }
150 
151 // Move samples from buf to offset (in the same buffer)
152 // Copy data in the backward direction to avoid overlap
moveSamples(float * buf,unsigned int offs,unsigned int nSamples)153 static inline void moveSamples(float* buf, unsigned int offs, unsigned int nSamples)
154 {
155     if (!nSamples)
156 	return;
157     unsigned int cp = samples2floats(nSamples);
158     float* src = buf + cp - 1;
159     float* dest = advanceSamples(buf,offs) + cp - 1;
160     for (float* last = src - cp; src != last; --dest, --src)
161 	*dest = *src;
162     resetSamples(buf,offs);
163 }
164 
165 
dump(String & buf)166 String& RadioReadBufs::dump(String& buf)
167 {
168     return buf.printf("\r\n-----\r\ncrt:\t%u(%u)\t%u\t(%p)\r\naux:\t%u(%u)\t%u\t(%p)"
169 	"\r\nextra:\t\t%u\t(%p)\r\n-----",
170 	valid(crt),crt.valid,crt.offs,crt.samples,
171 	valid(aux),aux.valid,aux.offs,aux.samples,
172 	extra.offs,extra.samples);
173 }
174 
175 
RadioInterface(const char * name)176 RadioInterface::RadioInterface(const char* name)
177     : m_lastErr(0), m_totalErr(0), m_radioCaps(0), m_name(name),
178     m_mutex(false,"RadioInterface")
179 {
180     debugName(m_name);
181     ::memset(m_pendingCode,0,sizeof(m_pendingCode));
182 }
183 
184 // Poll for a pending operation
pollPending(unsigned int oper,unsigned int waitMs)185 unsigned int RadioInterface::pollPending(unsigned int oper, unsigned int waitMs)
186 {
187     if (oper >= PendingCount)
188 	return 0;
189     if (waitMs && (Pending == m_pendingCode[oper])) {
190 	unsigned int intervals = (waitMs + Thread::idleMsec() - 1) / Thread::idleMsec();
191 	while ((Pending == m_pendingCode[oper]) && intervals && !Thread::check(false)) {
192 	    Thread::idle();
193 	    intervals--;
194 	}
195     }
196     Lock lck(m_mutex);
197     if (Pending == m_pendingCode[oper])
198 	return Pending;
199     unsigned int code = m_pendingCode[oper];
200     m_pendingCode[oper] = 0;
201     return code;
202 }
203 
204 // NOTE: This method assumes a single port is used
205 // E.g.: a sample is an I/Q pair
206 // If multiple ports are handled the sampleLen() function should handle the number of ports
read(uint64_t & when,RadioReadBufs & bufs,unsigned int & skippedBufs)207 unsigned int RadioInterface::read(uint64_t& when, RadioReadBufs& bufs,
208     unsigned int& skippedBufs)
209 {
210     String tmp;
211     DebugRadioRead(">>> read: ts=" FMT64U " buf_samples=%u [%p]%s",
212 	when,bufs.bufSamples(),this,bufs.dump(tmp).c_str());
213     // Switch buffers
214     if (bufs.full(bufs.crt) && !bufs.aux.offs) {
215 	bufs.crt.reset();
216 	DebugRadioRead("read reset crt [%p]",this);
217     }
218     else if ((!bufs.crt.offs && bufs.aux.offs) || bufs.full(bufs.crt)) {
219 	bool emptyCrt = (bufs.crt.offs == 0);
220 	RadioBufDesc buf = bufs.crt;
221 	bufs.crt = bufs.aux;
222 	if (emptyCrt || !bufs.extra.offs) {
223 	    bufs.aux.samples = buf.samples;
224 	    bufs.aux.reset();
225 	}
226 	else {
227 	    bufs.aux = bufs.extra;
228 	    bufs.extra.samples = buf.samples;
229 	    bufs.extra.reset();
230 	}
231 	// Adjust timestamp with data already in buffer
232 	when += bufs.crt.offs;
233 	if (bufs.full(bufs.crt)) {
234 	    if (!bufs.valid(bufs.crt)) {
235 		skippedBufs = 1;
236 		bufs.crt.reset();
237 	    }
238 	    DebugRadioRead("<<< read ts=" FMT64U " (crt full) [%p]%s",
239 		when,this,bufs.dump(tmp).c_str());
240 	    return 0;
241 	}
242 	DebugRadioRead("read moved aux to crt [%p]%s",
243 	    this,bufs.dump(tmp).c_str());
244     }
245     skippedBufs = 0;
246     unsigned int avail = bufs.bufSamples() - bufs.crt.offs;
247     unsigned int rdSamples = avail;
248     uint64_t ts = when;
249     float* rdBuf = advanceSamples(bufs.crt.samples,bufs.crt.offs);
250     unsigned int code = recv(ts,rdBuf,rdSamples);
251     DebugRadioRead("read: code=%u read=%u/%u [%p]",code,rdSamples,avail,this);
252     if (code || !rdSamples)
253 	return code;
254     if (when == ts) {
255 	when += rdSamples;
256 	bufs.crt.offs += rdSamples;
257 	bufs.crt.valid += rdSamples;
258 	DebugRadioRead("<<< read ts=" FMT64U " OK [%p]%s",
259 	    when,this,bufs.dump(tmp).c_str());
260 	return 0;
261     }
262     // This should never happen !!!
263     if (ts < when) {
264 	Debug(this,DebugFail,
265 	    "Read timestamp in the past by " FMT64U " at " FMT64U " [%p]",
266 	    (when - ts),when,this);
267 	return TooEarly;
268     }
269     // Timestamp is in the future
270     uint64_t diff = ts - when;
271     if (when)
272 	Debug(this,DebugNote,
273 	    "Read timestamp in the future by " FMT64U " at " FMT64U " [%p]",
274 	    diff,when,this);
275     if (diff <= avail) {
276 	// The timestamp difference is inside available space
277 	// Read samples + NULLs won't exceed current + auxiliary buffer
278 	bufs.extra.reset();
279 	// We may copy some data
280 	unsigned int cpSamples = avail - diff;
281 	if (cpSamples > rdSamples)
282 	    cpSamples = rdSamples;
283 	// Copy data to auxiliary buffer if valid
284 	// Do nothing if invalid: we will ignore it at next read
285 	bufs.aux.reset(rdSamples - cpSamples);
286 	if (bufs.aux.offs && bufs.valid(bufs.aux))
287 	    copySamples(bufs.aux.samples,0,rdBuf,cpSamples,bufs.aux.offs);
288 	// Adjust available (used) space: copy samples + NULLs
289 	avail = diff + cpSamples;
290 	bufs.crt.reset(bufs.crt.offs + avail,bufs.crt.valid + cpSamples);
291 	if (bufs.valid(bufs.crt)) {
292 	    if (cpSamples)
293 		moveSamples(rdBuf,diff,cpSamples);
294 	    else
295 		resetSamples(rdBuf,avail);
296 	}
297 	else if (bufs.full(bufs.crt)) {
298 	    // Not enough valid samples in full buffer: skip it
299 	    skippedBufs++;
300 	    bufs.crt.reset();
301 	}
302 	// Adjust timestamp
303 	when += avail;
304     }
305     else {
306 	// Timestamp is outside buffer
307 	uint64_t delta = diff - avail;
308 	skippedBufs = delta / bufs.bufSamples();
309 	if (skippedBufs)
310 	    when += skippedBufs * bufs.bufSamples();
311 	// Advance current timestamp
312 	// Reset data in current buffer or skip it
313 	when += avail;
314 	bufs.crt.offs = bufs.bufSamples();
315 	if (bufs.valid(bufs.crt))
316 	    resetSamples(rdBuf,avail);
317 	else {
318 	    // Not enough valid samples in full buffer: skip it
319 	    skippedBufs++;
320 	    bufs.crt.reset();
321 	}
322 	// Setup the auxiliary buffers
323 	// Set data if valid
324 	// Do nothing with data if invalid: we will ignore them on subsequent reads
325 	unsigned int nullSamples = delta % bufs.bufSamples();
326 	unsigned int len = nullSamples + rdSamples;
327 	if (len <= bufs.bufSamples()) {
328 	    bufs.aux.reset(len,rdSamples);
329 	    bufs.extra.reset();
330 	}
331 	else {
332 	    bufs.aux.reset(bufs.bufSamples(),bufs.bufSamples() - nullSamples);
333 	    bufs.extra.reset(rdSamples - bufs.aux.valid);
334 	}
335 	if (bufs.valid(bufs.aux)) {
336 	    resetSamples(bufs.aux.samples,nullSamples);
337 	    copySamples(bufs.aux.samples,nullSamples,rdBuf,0,bufs.aux.valid);
338 	}
339 	if (bufs.extra.offs && bufs.valid(bufs.extra))
340 	    copySamples(bufs.extra.samples,0,rdBuf,bufs.aux.valid,bufs.extra.valid);
341     }
342     DebugRadioRead("<<< read (ts in future): ts=" FMT64U " skipped_bufs=%u [%p]%s",
343 	when,skippedBufs,this,bufs.dump(tmp).c_str());
344     return 0;
345 }
346 
toString() const347 const String& RadioInterface::toString() const
348 {
349     return m_name;
350 }
351 
completeDevInfo(NamedList & p,bool full,bool retData)352 void RadioInterface::completeDevInfo(NamedList& p, bool full, bool retData)
353 {
354     if (retData)
355 	p.setParam(new NamedPointer("interface",this,m_name));
356     else
357 	p.addParam("interface",m_name);
358 }
359 
setError(NamedList & p,unsigned int code,const char * str)360 void RadioInterface::setError(NamedList& p, unsigned int code, const char* str)
361 {
362     if (!code)
363 	return;
364     p.setParam(YSTRING("code"),String(code));
365     p.setParam(YSTRING("reason"),errorName(code));
366     if (TelEngine::null(str))
367 	p.clearParam(YSTRING("error"));
368     else
369 	p.setParam(YSTRING("error"),str);
370     unsigned int tmp = code & NoAutoRestartMask;
371     p.setParam(YSTRING("canretry"),String::boolText(!tmp));
372     if (!tmp)
373 	p.setParam(YSTRING("fatal"),String::boolText(code & FatalErrorMask));
374 }
375 
errorNameDict()376 const TokenDict* RadioInterface::errorNameDict()
377 {
378     return s_errorName;
379 }
380 
381 
382 //
383 // RadioDataFile
384 //
RadioDataFile(const char * name,bool dropOnError)385 RadioDataFile::RadioDataFile(const char* name, bool dropOnError)
386     : String(name),
387     m_littleEndian(true),
388     m_dropOnError(dropOnError),
389     m_chunkSize(0),
390     m_writeBuf(256)
391 {
392     m_littleEndian = m_header.m_littleEndian;
393 }
394 
~RadioDataFile()395 RadioDataFile::~RadioDataFile()
396 {
397     terminate();
398 }
399 
elementSize(const RadioDataDesc & data)400 static inline unsigned int elementSize(const RadioDataDesc& data)
401 {
402     switch (data.m_elementType) {
403 	case RadioDataDesc::Float:
404 	    return sizeof(float);
405 	case RadioDataDesc::Int16:
406 	    return sizeof(int16_t);
407     }
408     return 0;
409 }
410 
411 // Open a file for read/write. Terminate current data dump if any.
412 // For file write: read the file header
open(const char * fileName,const RadioDataDesc * data,DebugEnabler * dbg,int * error)413 bool RadioDataFile::open(const char* fileName, const RadioDataDesc* data,
414     DebugEnabler* dbg, int* error)
415 {
416     terminate(dbg);
417     if (TelEngine::null(fileName))
418 	return false;
419     const char* fileOper = 0;
420     String hdrError;
421     while (true) {
422 	m_writeBuf.resize(sizeof(m_header));
423 	uint8_t* d = m_writeBuf.data(0);
424 	// Write
425 	if (data) {
426 	    fileOper = "write";
427 	    if ((elementSize(*data) * data->m_sampleLen * data->m_ports) == 0) {
428 		hdrError = "Invalid header data";
429 		break;
430 	    }
431 	    m_header = *data;
432 	    if (!m_file.openPath(fileName,true,false,true,false,true,true)) {
433 		fileOper = "open";
434 		break;
435 	    }
436 	    ::memcpy(d,m_header.m_signature,sizeof(m_header.m_signature));
437 	    d += sizeof(m_header.m_signature);
438 	    *d++ = m_header.m_elementType;
439 	    *d++ = m_header.m_sampleLen;
440 	    *d++ = m_header.m_ports;
441 	    *d++ = m_header.m_tsType;
442 	    *d++ = m_header.m_littleEndian ? 0 : 1;
443 	    int wr = m_file.writeData(m_writeBuf.data(),m_writeBuf.length());
444 	    if (wr == (int)m_writeBuf.length())
445 		fileOper = 0;
446 	    else if (wr >= 0)
447 		hdrError = "Incomplete header write";
448 	    break;
449 	}
450 	// Read
451 	fileOper = "open";
452 	if (!m_file.openPath(fileName))
453 	    break;
454 	fileOper = "read";
455 	int rd = m_file.readData(d,m_writeBuf.length());
456 	if (rd == (int)m_writeBuf.length()) {
457 	    ::memcpy(m_header.m_signature,d,sizeof(m_header.m_signature));
458 	    d += sizeof(m_header.m_signature);
459 	    m_header.m_elementType = *d++;
460 	    m_header.m_sampleLen = *d++;
461 	    m_header.m_ports = *d++;
462 	    m_header.m_tsType = *d++;
463 	    if (*d == 0 || *d == 1) {
464 		m_header.m_littleEndian = (*d == 0);
465 		fileOper = 0;
466 		break;
467 	    }
468 	    hdrError = "Invalid endiannes value";
469 	}
470 	else if (rd >= 0)
471 	    hdrError = "Invalid file size";
472 	break;
473     }
474     if (!fileOper) {
475 	m_chunkSize = elementSize(m_header) * m_header.m_sampleLen * m_header.m_ports;
476 	if (dbg)
477 	    Debug(dbg,DebugAll,"RadioDataFile[%s] opened file '%s' [%p]",
478 		c_str(),fileName,this);
479 	return true;
480     }
481     if (error)
482 	*error = hdrError ? 0 : m_file.error();
483     if (!hdrError) {
484 	hdrError = m_file.error();
485 	String tmp;
486 	Thread::errorString(tmp,m_file.error());
487 	hdrError.append(tmp," - ");
488     }
489     Debug(dbg,DebugNote,"RadioDataFile[%s] file '%s' %s %s failed: %s [%p]",
490 	c_str(),fileName,(data ? "OUT" : "IN"),fileOper,hdrError.safe(),this);
491     terminate();
492     return false;
493 }
494 
write(uint64_t ts,const void * buf,uint32_t len,DebugEnabler * dbg,int * error)495 bool RadioDataFile::write(uint64_t ts, const void* buf, uint32_t len,
496     DebugEnabler* dbg, int* error)
497 {
498     int e = 0;
499     if (error)
500 	*error = 0;
501     else
502 	error = &e;
503     if (!(buf && len))
504 	return false;
505     if (m_chunkSize && (len % m_chunkSize) != 0)
506 	return ioError(true,dbg,0,"Invalid buffer length");
507     m_writeBuf.resize(len + 12);
508     uint8_t* d = m_writeBuf.data(0);
509     if (m_littleEndian == m_header.m_littleEndian) {
510 	*(uint32_t*)d = len;
511 	*(uint64_t*)(d + 4) = ts;
512     }
513     else if (m_littleEndian) {
514 	*(uint32_t*)d = htobe32(len);
515 	*(uint64_t*)(d + 4) = htobe64(ts);
516     }
517     else {
518 	*(uint32_t*)d = htole32(len);
519 	*(uint64_t*)(d + 4) = htole64(ts);
520     }
521     ::memcpy(d + 12,buf,len);
522     int wr = m_file.writeData(m_writeBuf.data(),m_writeBuf.length());
523     if (wr != (int)m_writeBuf.length())
524 	return ioError(true,dbg,error,wr >= 0 ? "Incomplete write" : 0);
525 #ifdef XDEBUG
526     String sHdr, s;
527     sHdr.hexify(d,12,' ');
528     s.hexify(d + 12,wr - 12,' ');
529     Debug(dbg,DebugAll,"RadioDataFile[%s] wrote %d hdr=%s data=%s [%p]",
530 	c_str(),wr,sHdr.c_str(),s.c_str(),this);
531 #endif
532     return true;
533 }
534 
535 // Read a record from file
read(uint64_t & ts,DataBlock & buffer,DebugEnabler * dbg,int * error)536 bool RadioDataFile::read(uint64_t& ts, DataBlock& buffer, DebugEnabler* dbg, int* error)
537 {
538     int e = 0;
539     if (error)
540 	*error = 0;
541     else
542 	error = &e;
543     uint8_t hdr[12];
544     int rd = m_file.readData(hdr,sizeof(hdr));
545     // EOF ?
546     if (rd == 0) {
547 	buffer.resize(0);
548 	return true;
549     }
550     if (rd != sizeof(hdr))
551 	return ioError(false,dbg,error,rd > 0 ? "Incomplete read (invalid size?)" : 0);
552     uint32_t len = 0;
553     uint32_t* u = (uint32_t*)hdr;
554     if (m_littleEndian == m_header.m_littleEndian)
555 	len = *u;
556     else if (m_littleEndian)
557 	len = be32toh(*u);
558     else
559 	len = le32toh(*u);
560     uint64_t* p = (uint64_t*)&hdr[4];
561     if (m_littleEndian == m_header.m_littleEndian)
562 	ts = *p;
563     else if (m_littleEndian)
564 	len = be64toh(*p);
565     else
566 	len = le64toh(*p);
567     buffer.resize(len);
568     if (!len)
569 	return ioError(false,dbg,0,"Empty record");
570     rd = m_file.readData((void*)buffer.data(),len);
571     if (rd != (int)len)
572 	return ioError(false,dbg,error,rd > 0 ? "Incomplete read (invalid size?)" : 0);
573 #ifdef XDEBUG
574     String sHdr, s;
575     sHdr.hexify(hdr,sizeof(hdr),' ');
576     s.hexify((void*)buffer.data(),rd,' ');
577     Debug(dbg,DebugAll,"RadioDataFile[%s] read %d hdr=%s data=%s [%p]",
578 	c_str(),rd + (int)sizeof(hdr),sHdr.c_str(),s.c_str(),this);
579 #endif
580     return true;
581 }
582 
terminate(DebugEnabler * dbg)583 void RadioDataFile::terminate(DebugEnabler* dbg)
584 {
585     if (dbg && valid())
586 	Debug(dbg,DebugAll,"RadioDataFile[%s] closing file [%p]",c_str(),this);
587     m_file.terminate();
588 }
589 
590 // Convert endiannes
fixEndian(DataBlock & buf,unsigned int bytes)591 bool RadioDataFile::fixEndian(DataBlock& buf, unsigned int bytes)
592 {
593     if (!bytes)
594 	return false;
595     unsigned int n = buf.length() / bytes;
596     if (bytes == 2) {
597 	for (uint16_t* p = (uint16_t*)buf.data(); n; n--, p++)
598 #ifdef LITTLE_ENDIAN
599 	    *p = be16toh(*p);
600 #else
601 	    *p = le16toh(*p);
602 #endif
603 	return true;
604     }
605     if (bytes == 4) {
606 	for (uint32_t* p = (uint32_t*)buf.data(); n; n--, p++)
607 #ifdef LITTLE_ENDIAN
608 	    *p = be32toh(*p);
609 #else
610 	    *p = le32toh(*p);
611 #endif
612 	return true;
613     }
614     if (bytes == 8) {
615 	for (uint64_t* p = (uint64_t*)buf.data(); n; n--, p++)
616 #ifdef LITTLE_ENDIAN
617 	    *p = be64toh(*p);
618 #else
619 	    *p = le64toh(*p);
620 #endif
621 	return true;
622     }
623     return false;
624 }
625 
ioError(bool send,DebugEnabler * dbg,int * error,const char * extra)626 bool RadioDataFile::ioError(bool send, DebugEnabler* dbg, int* error, const char* extra)
627 {
628     String s = extra;
629     if (error) {
630 	String tmp;
631 	Thread::errorString(tmp,m_file.error());
632 	tmp.printf("(%d - %s)",m_file.error(),tmp.c_str());
633 	s.append(tmp," ");
634     }
635     Debug(dbg,DebugNote,"RadioDataFile[%s] file %s failed: %s [%p]",
636 	c_str(),(send ? "write" : "read"),s.safe(),this);
637     if (error) {
638 	*error = m_file.error();
639 	if (m_dropOnError)
640 	    terminate();
641     }
642     return false;
643 }
644 
645 /* vi: set ts=8 sw=4 sts=4 noet enc=utf-8: */
646