diff options
Diffstat (limited to 'vdr-smarttvweb')
-rwxr-xr-x[-rw-r--r--] | vdr-smarttvweb/httpresource.c | 349 | ||||
-rwxr-xr-x[-rw-r--r--] | vdr-smarttvweb/httpresource.h | 7 | ||||
-rw-r--r-- | vdr-smarttvweb/smarttvweb.c | 2 | ||||
-rwxr-xr-x[-rw-r--r--] | vdr-smarttvweb/stvw_cfg.c | 4 | ||||
-rwxr-xr-x[-rw-r--r--] | vdr-smarttvweb/stvw_cfg.h | 17 | ||||
-rw-r--r-- | vdr-smarttvweb/url.c | 46 | ||||
-rw-r--r-- | vdr-smarttvweb/url.h | 3 |
7 files changed, 364 insertions, 64 deletions
diff --git a/vdr-smarttvweb/httpresource.c b/vdr-smarttvweb/httpresource.c index dc09329..5ad8d34 100644..100755 --- a/vdr-smarttvweb/httpresource.c +++ b/vdr-smarttvweb/httpresource.c @@ -50,7 +50,7 @@ #include <vdr/epg.h> #endif -#define SERVER "SmartTvWeb/0.1" +#define SERVER "SmartTvWeb/0.2" #define PROTOCOL "HTTP/1.1" #define RFC1123FMT "%a, %d %b %Y %H:%M:%S GMT" @@ -67,6 +67,18 @@ using namespace std; +class cResumeEntry { + public: + string mFilename; + float mResume; + + friend ostream& operator<<(ostream& out, const cResumeEntry& o) { + out << "mFilename= " << o.mFilename << " mResume= " << o.mResume << endl; + return out; + }; + cResumeEntry():mFilename(), mResume(-1.0) {}; +}; + struct sVdrFileEntry { uint64_t sSize; @@ -86,6 +98,15 @@ struct sTimerEntry { sTimerEntry(string t, time_t s, int d) : name(t), startTime(s), duration(d) {}; }; +// 8 Byte Per Entry +struct tIndexPes { + uint32_t offset; + uchar type; + uchar number; + uint16_t reserved; + }; + + // 8 Byte per entry struct tIndexTs { uint64_t offset:40; // up to 1TB per file (not using long long int here - must definitely be exactly 64 bit!) @@ -94,6 +115,8 @@ struct tIndexTs { uint16_t number:16; // up to 64K files per recording }; + + union tIndexRead { struct tIndexTs in; char buf[8]; @@ -339,6 +362,13 @@ int cHttpResource::processRequest() { sendEpgXml( &statbuf); return OKAY; } + if (mPath.compare("/resume.xml") == 0) { + if (handleHeadRequest() != 0) + return OKAY; + + sendResumeXml( &statbuf); + return OKAY; + } #endif if (mPath.compare("/media.xml") == 0) { @@ -547,7 +577,7 @@ int cHttpResource::fillDataBlk() { return ERROR; } to_read = ((mRemLength > MAXLEN) ? MAXLEN : mRemLength); - + mBlkLen = fread(mBlkData, 1, to_read, mFile); mRemLength -= mBlkLen; @@ -559,7 +589,8 @@ int cHttpResource::fillDataBlk() { return OKAY; } - if (mBlkLen != MAXLEN) { + // if (mBlkLen != MAXLEN) { // thlo verify + if (mBlkLen != to_read) { fclose(mFile); mFile = NULL; mVdrIdx ++; @@ -580,7 +611,7 @@ int cHttpResource::fillDataBlk() { } // Error: Open next file failed if (mBlkLen == 0) { - to_read = ((mRemLength > MAXLEN) ? MAXLEN : mRemLength); + to_read = ((mRemLength > MAXLEN) ? MAXLEN : mRemLength); mBlkLen = fread(mBlkData, 1, to_read, mFile); mRemLength -= mBlkLen; } @@ -628,9 +659,11 @@ int cHttpResource::parseResume(cResumeEntry &entry, string &id) { bool done = false; size_t cur_pos = 0; - bool have_devid = false; - bool have_title = false; - bool have_start = false; + // The asset_id should the the filename, which is provided by the link element in the xml + // the link is url-safe encoded. + + // bool have_devid = false; + bool have_filename = false; bool have_resume = false; while (!done) { @@ -643,21 +676,24 @@ int cHttpResource::parseResume(cResumeEntry &entry, string &id) { string attr= mPayload.substr(cur_pos, (pos_col- cur_pos)); string val = mPayload.substr(pos_col +1, (pos - pos_col-1)); - if (attr== "devid") { + /* if (attr== "devid") { have_devid = true; id = val; } - else if (attr == "title") { - have_title = true; - entry.mTitle = val; - } - else if (attr == "start") { - have_start = true; - entry.mStartTime = atoi(val.c_str()); + else */ + if (attr == "filename") { + have_filename = true; + entry.mFilename = cUrlEncode::doXmlSaveDecode(val); + *(mLog->log())<< DEBUGPREFIX + << " filename= " << entry.mFilename + << endl; } else if (attr == "resume") { have_resume = true; - entry.mResume = atoi(val.c_str()); + entry.mResume = atof(val.c_str()); + *(mLog->log())<< DEBUGPREFIX + << " mResume= " << entry.mResume + << endl; } else { *(mLog->log())<< DEBUGPREFIX @@ -669,7 +705,7 @@ int cHttpResource::parseResume(cResumeEntry &entry, string &id) { if (cur_pos >= mPayload.size()) done= true; } - if (have_resume && have_start && have_title && have_devid) + if (have_resume && have_filename ) return OKAY; else return ERROR; @@ -706,7 +742,6 @@ int cHttpResource::handlePost() { Resume support: Key for recordings: Title plus start time. Value: Current PlayTime in Sec Structure: Either, the plugin reads the list from file when the first client connects - Question: How to get files into /var/lib/vdr/plugins First: Create a list of resumes (use vdr cList ). Write the list to file @@ -731,10 +766,26 @@ int cHttpResource::handlePost() { *(mLog->log())<< DEBUGPREFIX << " Resume: id= " << dev_id << " resume= " << entry << endl; +#ifndef STANDALONE + cRecording *rec = Recordings.GetByName(entry.mFilename.c_str()); + if (rec == NULL) { + //Error 404 + sendError(404, "Not Found", NULL, "Failed to find recording."); + return OKAY; + } + cResumeFile resume(entry.mFilename.c_str(), rec->IsPesRecording()); + *(mLog->log())<< DEBUGPREFIX + << " Resume: " << entry.mFilename + << " saving Index= " << int(entry.mResume * rec->FramesPerSecond() ) + << " mResume= " <<entry.mResume + << " fpr= " << rec->FramesPerSecond() + << endl; + + resume.Save(int(entry.mResume * rec->FramesPerSecond() )); +#endif } sendHeaders(200, "OK", NULL, NULL, -1, -1); - return OKAY; } @@ -869,7 +920,7 @@ int cHttpResource::sendDir(struct stat *statbuf) { } -int cHttpResource::writeXmlItem(string name, string link, string programme, string desc, string guid, time_t start, int dur, int fps, int is_pes) { +int cHttpResource::writeXmlItem(string name, string link, string programme, string desc, string guid, time_t start, int dur, double fps, int is_pes, int is_new) { string hdr = ""; char f[400]; @@ -902,7 +953,7 @@ int cHttpResource::writeXmlItem(string name, string link, string programme, stri hdr += "</duration>\n"; if (fps != -1) - snprintf(f, sizeof(f), "<fps>%d</fps>\n", fps); + snprintf(f, sizeof(f), "<fps>%.2f</fps>\n", fps); else snprintf(f, sizeof(f), "<fps>unknown</fps>\n"); hdr += f; @@ -924,6 +975,23 @@ int cHttpResource::writeXmlItem(string name, string link, string programme, stri break; } + switch (is_new){ + case -1: + // unknown + hdr += "<isnew>unknown</isnew>\n"; + break; + case 0: + // true + hdr += "<isnew>true</isnew>\n"; + break; + case 1: + // false + hdr += "<isnew>false</isnew>\n"; + break; + default: + break; + } + hdr += "</item>\n"; *mResponseMessage += hdr; @@ -1244,6 +1312,7 @@ int cHttpResource::sendMediaSegment (struct stat *statbuf) { if(buffered_indexes <= 0 ) { *(mLog->log())<<DEBUGPREFIX << " issue while reading" << endl; + delete[] index_buf; sendError(404, "Not Found", NULL, "Failed to read Index file"); return OKAY; } @@ -1391,7 +1460,7 @@ int cHttpResource::sendMediaXml (struct stat *statbuf) { snprintf(pathbuf, sizeof(pathbuf), "http://%s:%d%s", mServerAddr.c_str(), mServerPort, cUrlEncode::doUrlSaveEncode(entries[i].sPath).c_str()); if (writeXmlItem(cUrlEncode::doXmlSaveEncode(entries[i].sName), pathbuf, "NA", "NA", "-", - entries[i].sStart, -1, -1, -1) == ERROR) + entries[i].sStart, -1, -1, -1, -1) == ERROR) return ERROR; } @@ -1595,7 +1664,7 @@ int cHttpResource::sendChannelsXml (struct stat *statbuf) { string c_name = (group_sep != "") ? (group_sep + "~" + channel->Name()) : channel->Name(); // if (writeXmlItem(channel->Name(), link, title, desc, *(channel->GetChannelID()).ToString(), start_time, duration) == ERROR) - if (writeXmlItem(c_name, link, title, desc, *(channel->GetChannelID()).ToString(), start_time, duration, -1, -1) == ERROR) + if (writeXmlItem(c_name, link, title, desc, *(channel->GetChannelID()).ToString(), start_time, duration, -1, -1, -1) == ERROR) return ERROR; } @@ -1611,6 +1680,49 @@ int cHttpResource::sendChannelsXml (struct stat *statbuf) { return OKAY; } +int cHttpResource::sendResumeXml (struct stat *statbuf) { +#ifndef STANDALONE + + mResponseMessage = new string(); + *mResponseMessage = ""; + mResponseMessagePos = 0; + mContentType = MEMBLOCK; + + mConnState = SERVING; + + char f[400]; + + cResumeEntry entry; + string id; + parseResume(entry, id); + + + cRecording *rec = Recordings.GetByName(entry.mFilename.c_str()); + if (rec == NULL) { + //Error 404 + sendError(404, "Not Found", NULL, "Failed to find recording."); + return OKAY; + } + cResumeFile resume(entry.mFilename.c_str(), rec->IsPesRecording()); + + *(mLog->log())<< DEBUGPREFIX + << " resume request for " << entry.mFilename + << " resume= " << resume.Read() + << " (" << resume.Read() *1.0 / rec->FramesPerSecond() << "sec)" + << endl; + + *mResponseMessage += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; + *mResponseMessage += "<resume>\n"; + snprintf(f, sizeof(f), "%.02f", resume.Read() *1.0 / rec->FramesPerSecond()); + *mResponseMessage += f; + *mResponseMessage += "</resume>\n"; + + sendHeaders(200, "OK", NULL, "application/xml", mResponseMessage->size(), statbuf->st_mtime); + + return OKAY; +#endif +} + int cHttpResource::sendRecordingsXml(struct stat *statbuf) { #ifndef STANDALONE @@ -1787,8 +1899,11 @@ int cHttpResource::sendRecordingsXml(struct stat *statbuf) { } - if (writeXmlItem(cUrlEncode::doXmlSaveEncode(recording->Name()), link, "NA", desc, "-", - recording->Start(), rec_dur, recording->FramesPerSecond(), (recording->IsPesRecording() ? 0: 1)) == ERROR) + if (writeXmlItem(cUrlEncode::doXmlSaveEncode(recording->Name()), link, "NA", desc, + cUrlEncode::doXmlSaveEncode(recording->FileName()).c_str(), + recording->Start(), rec_dur, recording->FramesPerSecond(), + (recording->IsPesRecording() ? 0: 1), (recording->IsNew() ? 0: 1)) == ERROR) + // Better Internal Server Error return ERROR; } @@ -1803,24 +1918,174 @@ int cHttpResource::sendRecordingsXml(struct stat *statbuf) { return OKAY; } -void cHttpResource::checkForTimeRequest() { - return; +bool cHttpResource::isTimeRequest(struct stat *statbuf) { - /* vector<sQueryAVP> avps; + vector<sQueryAVP> avps; parseQueryLine(&avps); - string time = ""; + string time_str = ""; + float time = 0.0; - if (getQueryAttributeValue(&avps, "time", time) == OKAY){ + if (getQueryAttributeValue(&avps, "time", time_str) != OKAY){ + return false; + } + time = atof(time_str.c_str()); + *(mLog->log())<< DEBUGPREFIX + << " Found a Time Parameter: " << time + << endl; + + mDir = mPath; + cRecording *rec = Recordings.GetByName(mPath.c_str()); + if (rec == NULL) { *(mLog->log())<< DEBUGPREFIX - << " Found a Time Parameter: " << time - << endl; + << " Error: Did not find recording= " << mPath << endl; + sendError(404, "Not Found", NULL, "File not found."); + return true; + } + + double fps = rec->FramesPerSecond(); + double dur = rec->NumFrames() * fps; + bool is_pes = rec->IsPesRecording(); + if (dur < time) { + sendError(400, "Bad Request", NULL, "Time to large."); + return true; + } + + int start_frame = int(time * fps) -25; + + FILE* idx_file= NULL; + + if (is_pes){ + idx_file = fopen((mDir +"/index.vdr").c_str(), "r"); + // sendError(400, "Bad Request", NULL, "PES not yet supported."); + // return true; + } + else { + idx_file = fopen((mDir +"/index").c_str(), "r"); + } + + if (idx_file == NULL){ + *(mLog->log()) << DEBUGPREFIX + << " failed to open idx file = "<< (mDir +"/index").c_str() + << endl; + sendError(404, "Not Found", NULL, "Failed to open Index file"); + return OKAY; + } + + int buffered_frames = 50; + char *index_buf = new char[8 *buffered_frames]; // 50 indexes + + *(mLog->log()) << DEBUGPREFIX + << " seeking to start_frame= " << start_frame + << " fps= " << fps << endl; + fseek(idx_file, start_frame * 8, SEEK_SET); + + int buffered_indexes = fread(index_buf, 8, (buffered_frames), idx_file); + + fclose(idx_file); + + if(buffered_indexes <= 0 ) { + *(mLog->log())<<DEBUGPREFIX + << " issue while reading, buffered_indexes <=0" << endl; + delete[] index_buf; + sendError(404, "Not Found", NULL, "Failed to read Index file"); + return OKAY; } - */ - // First, I need to get the fps value - // then I determine the frame number mathcing the time - // the I go into index and find the byte offset and the vdr idx - // Then I need to shortcut the calc in sendVdrDir + *(mLog->log()) << DEBUGPREFIX + << " Finding I-Frame now" << endl; + + + bool found_it = false; + int i = 0; + + uint32_t offset = 0; + int idx =0; + int type =0; + + for (i= 0; i < buffered_indexes; i++){ + if (is_pes) { + tIndexPes in_read_pes; + memcpy (&in_read_pes, &(index_buf[i*8]), 8); + type = in_read_pes.type == 1; + idx = in_read_pes.number; + offset = in_read_pes.offset; + } + else{ + tIndexTs in_read_ts; + memcpy (&in_read_ts, &(index_buf[i*8]), 8); + type = in_read_ts.independent; + idx = in_read_ts.number; + offset = in_read_ts.offset; + } + + *(mLog->log()) << DEBUGPREFIX + << " Frame= " << i + << " idx= "<< idx + << " offset= " << offset + << " type= " << type + << endl; + if (type){ + found_it = true; + break; + } + } + + if (!found_it) { + delete[] index_buf; + sendError(404, "Not Found", NULL, "Failed to read Index file"); + return OKAY; + } + + mVdrIdx = idx; + + *(mLog->log()) << DEBUGPREFIX + << " idx= "<< mVdrIdx + << " offset= " << offset + << endl; + + delete[] index_buf; + + char pathbuf[4096]; + uint64_t file_size = 0; + bool more_to_go = true; + int vdr_idx = mVdrIdx; + while (more_to_go) { + snprintf(pathbuf, sizeof(pathbuf), mFileStructure.c_str(), mPath.c_str(), vdr_idx); + if (stat(pathbuf, statbuf) >= 0) { + *(mLog->log())<< " found for " << pathbuf << endl; + file_size += statbuf->st_size; + } + else { + more_to_go = false; + } + vdr_idx ++; + } + mRemLength = file_size - offset; + + snprintf(pathbuf, sizeof(pathbuf), mFileStructure.c_str(), mPath.c_str(), mVdrIdx); + + *(mLog->log()) << DEBUGPREFIX + << " Opening Path= " + << pathbuf << endl; + if (openFile(pathbuf) != OKAY) { + sendError(403, "Forbidden", NULL, "Access denied."); + return true; + } + + *(mLog->log()) << DEBUGPREFIX + << " Done. Start Streaming " + << endl; + + fseek(mFile, offset, SEEK_SET); + + if (rangeHdr.isRangeRequest) { + snprintf(pathbuf, sizeof(pathbuf), "Content-Range: bytes 0-%lld/%lld", (mRemLength -1), mRemLength); + sendHeaders(206, "Partial Content", pathbuf, "video/mpeg", mRemLength, statbuf->st_mtime); + } + else { + sendHeaders(200, "OK", NULL, "video/mpeg", mRemLength, -1); + } + return true; } int cHttpResource::sendVdrDir(struct stat *statbuf) { @@ -1839,8 +2104,6 @@ int cHttpResource::sendVdrDir(struct stat *statbuf) { checkRecording(); - // The range request functions are activated, when a time header is detected - // checkForTimeRequest(); mVdrIdx = 1; mFileStructure = "%s/%03d.vdr"; @@ -1851,6 +2114,14 @@ int cHttpResource::sendVdrDir(struct stat *statbuf) { #endif } + // The range request functions are activated, when a time header is detected + if (isTimeRequest(statbuf)) { + *(mLog->log())<< DEBUGPREFIX + << " isTimeRequest is true" + << endl; + return OKAY; + } + // --- looup all vdr files in the dir --- while (more_to_go) { vdr_idx ++; diff --git a/vdr-smarttvweb/httpresource.h b/vdr-smarttvweb/httpresource.h index bb0df3a..fef59ce 100644..100755 --- a/vdr-smarttvweb/httpresource.h +++ b/vdr-smarttvweb/httpresource.h @@ -123,7 +123,7 @@ class cHttpResource { bool mAcceptRanges; cRange rangeHdr; unsigned long long mFileSize; - uint mRemLength; + uint64_t mRemLength; FILE *mFile; int mVdrIdx; string mFileStructure; @@ -144,6 +144,7 @@ class cHttpResource { int sendVdrDir(struct stat *statbuf); int sendRecordingsXml (struct stat *statbuf); int sendChannelsXml (struct stat *statbuf); + int sendResumeXml (struct stat *statbuf); int sendEpgXml (struct stat *statbuf); int sendMediaXml (struct stat *statbuf); @@ -163,7 +164,7 @@ class cHttpResource { const char *getMimeType(const char *name); string getConnStateName(); void checkRecording(); - void checkForTimeRequest(); + bool isTimeRequest(struct stat *statbuf); int parseRangeHeaderValue(string); int parseHttpRequestLine(string); int parseHttpHeaderLine (string); @@ -174,6 +175,6 @@ class cHttpResource { int getQueryAttributeValue(vector<sQueryAVP> *avps, string id, string &val); int openFile(const char *name); - int writeXmlItem(string title, string link, string programme, string desc, string guid, time_t start, int dur, int fps, int is_pes); + int writeXmlItem(string title, string link, string programme, string desc, string guid, time_t start, int dur, double fps, int is_pes, int is_new); }; #endif diff --git a/vdr-smarttvweb/smarttvweb.c b/vdr-smarttvweb/smarttvweb.c index 34d87ee..f64b52e 100644 --- a/vdr-smarttvweb/smarttvweb.c +++ b/vdr-smarttvweb/smarttvweb.c @@ -41,7 +41,7 @@ #include "smarttvfactory.h" -static const char *VERSION = "0.9.1"; +static const char *VERSION = "0.9.2"; static const char *DESCRIPTION = "SmartTV Web Server"; diff --git a/vdr-smarttvweb/stvw_cfg.c b/vdr-smarttvweb/stvw_cfg.c index a7a5e58..b624040 100644..100755 --- a/vdr-smarttvweb/stvw_cfg.c +++ b/vdr-smarttvweb/stvw_cfg.c @@ -122,10 +122,10 @@ void cSmartTvConfig::readConfig() { myfile.close(); } + /* cResumes* cSmartTvConfig::readConfig(string f) { - /* string line; ifstream myfile ("example.txt"); @@ -156,9 +156,9 @@ cResumes* cSmartTvConfig::readConfig(string f) { return NULL; } -*/ // open the file // read the lines return NULL; } +*/ diff --git a/vdr-smarttvweb/stvw_cfg.h b/vdr-smarttvweb/stvw_cfg.h index 3b440b1..1f124fb 100644..100755 --- a/vdr-smarttvweb/stvw_cfg.h +++ b/vdr-smarttvweb/stvw_cfg.h @@ -32,19 +32,8 @@ using namespace std; -class cResumeEntry { - public: - string mTitle; - time_t mStartTime; // title is not unique - int mResume; - time_t mLastViewed; - - friend ostream& operator<<(ostream& out, const cResumeEntry& o) { - out << "mTitle= " << o.mTitle << " mStartTime= " << o.mStartTime << " mResume= " << o.mResume << endl; - return out; - }; -}; +/* class cResumes { public: cResumes(string t) : mDevice(t) {}; @@ -53,7 +42,7 @@ class cResumes { string mDevice; }; - +*/ class cSmartTvConfig { private: string mConfigDir; @@ -73,7 +62,7 @@ class cSmartTvConfig { void readConfig(); - cResumes* readConfig(string); + // cResumes* readConfig(string); string getLogFile() { return mLogFile; }; string getMediaFolder() { return mMediaFolder; }; diff --git a/vdr-smarttvweb/url.c b/vdr-smarttvweb/url.c index 75b5817..38f6af2 100644 --- a/vdr-smarttvweb/url.c +++ b/vdr-smarttvweb/url.c @@ -1,7 +1,7 @@ /* * url.c: VDR on Smart TV plugin * - * Copyright (C) 2012 Thorsten Lohmar + * Copyright (C) 2012 T. Lohmar * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -24,7 +24,7 @@ #include"url.h" - +//http://www.blooberry.com/indexdot/html/topics/urlencoding.htm string cUrlEncode::doUrlSaveEncode(string in) { string res = ""; unsigned char num = 0; @@ -120,7 +120,6 @@ string cUrlEncode::doUrlSaveDecode(string input) { return res; } - string cUrlEncode::doXmlSaveEncode(string in) { string res = ""; unsigned char num = 0; @@ -136,7 +135,7 @@ string cUrlEncode::doXmlSaveEncode(string in) { num = in[idx]; switch (num) { case 0x26: // '&': - res += "&"; + res += "&"; break; case 0x27: // '\'': res += "'"; @@ -184,6 +183,45 @@ string cUrlEncode::doXmlSaveEncode(string in) { return res; } +string cUrlEncode::doXmlSaveDecode(string input) { + string res = ""; + unsigned int idx = 0; + while (idx < input.size()){ + if (input[idx] == '&') { + if (input.compare(idx, 4, "<") == 0){ + res += "<"; + idx += 4; + } + else if (input.compare(idx, 4, ">") == 0){ + res += ">"; + idx += 4; + } + else if (input.compare(idx, 5, "&") == 0){ + res += "&"; + idx += 5; + } + else if (input.compare(idx, 6, """) == 0){ + res += "\""; + idx += 6; + } + else if (input.compare(idx, 6, "'") == 0){ + res += "\'"; + idx += 6; + } + else { + // ERROR + idx = input.size(); + res = ""; + } + } + else { + res += input[idx]; + idx ++; + } + + } + return res; +} string cUrlEncode::removeEtChar(string line, bool xml) { bool done = false; diff --git a/vdr-smarttvweb/url.h b/vdr-smarttvweb/url.h index 4695f8b..4656a2a 100644 --- a/vdr-smarttvweb/url.h +++ b/vdr-smarttvweb/url.h @@ -1,7 +1,7 @@ /* * url.h: VDR on Smart TV plugin * - * Copyright (C) 2012 Thorsten Lohmar + * Copyright (C) 2012 T. Lohmar * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -38,6 +38,7 @@ class cUrlEncode { static string doUrlSaveDecode (string); static string doXmlSaveEncode (string); + static string doXmlSaveDecode (string); static string removeEtChar(string line, bool xml=true); static string hexDump(char *line, int line_len); static string hexDump(string); |