#include #include #include #include #include #include #include #include #include #include #include #include "fbsh.h" /* * implementation of the function atoi and atof but with additional check for NULL pointer * atoi_check will return fallback value on error * atof will return NAN */ double atof_check (char *s) { if (s != NULL) return (atof(s)); return NAN; } int atoi_check (char *s, int failnumber) { if (s != NULL) return (atoi(s)); return failnumber; } std::string generateMD5 (char* input, int size) { int i; MD5_CTX md5; unsigned char out[MD5_DIGEST_LENGTH]; char txt[32]; std::string md5sum = ""; MD5_Init(&md5); MD5_Update(&md5, input, size); MD5_Final(out, &md5); for ( i = 0; i < MD5_DIGEST_LENGTH; i++) { snprintf (txt, 32, "%02x", out[i]); md5sum += txt; } return md5sum; } FBSmartHome::FBSmartHome() { hostname = ""; SID = "00 not yet set 00"; fsidfile = NULL; } FBSmartHome::~FBSmartHome() { if (fsidfile != NULL) CloseSIDFile(); } /* * try to connect with the last known SID. */ int FBSmartHome::Connect(std::string host) { int len; xmlDocPtr xmldoc; xmlNodePtr xmlnode; hostname = host; // is sid working? len = tcp.WebGetFile(hostname+"/login_sid.lua?sid="+SID, inbuffer, FB_BUFFER, NULL); if (len > FB_BUFFER) len = FB_BUFFER; else if (len < 0) { fprintf (stderr, "%s:%d Error getting challenge from Fritzbox\n", __FILE__, __LINE__); errno = EAI_FAIL; return -1; } inbuffer[len] = '\0'; XMLPrepare(inbuffer, len, &xmldoc, &xmlnode); // // parse for SID and Challange while (xmlnode) { if ((!xmlStrcmp(xmlnode->name, (const xmlChar *)"SessionInfo"))){ xmlNodePtr xmlchild = xmlnode->xmlChildrenNode; while (xmlchild) { if ((!xmlStrcmp(xmlchild->name, (const xmlChar *)"SID"))) SID = (char *)xmlNodeListGetString(xmldoc, xmlchild->children, 1); xmlchild = xmlchild->next; } } xmlnode = xmlnode->next; } xmlFreeDoc(xmldoc); if (SID.compare("0000000000000000") == 0) { hostname = ""; errno = ENOTCONN; return -1; } errno = 0; return 0; } int FBSmartHome::XMLPrepare(char *buffer, int len, xmlDocPtr *xmldoc, xmlNodePtr *xmlnode) { // // read the xml data from memory and select root node. if (xmldoc == NULL || xmlnode == NULL) return -1; *xmldoc = xmlReadMemory(inbuffer, len, "hostname", NULL, 0); if (xmldoc == NULL) { fprintf (stderr, "%s: Could not read XML Document\n", __FUNCTION__); errno = EAI_FAIL; return -1; } *xmlnode = xmlDocGetRootElement(*xmldoc); if (*xmlnode == NULL) { fprintf (stderr, "%s: Empty XML Document\n", __FUNCTION__); xmlFreeDoc(*xmldoc); errno = EOF; return -1; } return 0; } /* * connect with username and password, if connect is successfull the * SID will be returned. */ #define PWSSIZE 512 int FBSmartHome::Connect (std::string host, std::string username, std::string password) { int len; xmlDocPtr xmldoc; xmlNodePtr xmlnode; std::string challenge; std::string formdata; hostname = host; SID = ""; tcp.Close(); // // get challenge from FritzBox len = tcp.WebGetFile(hostname+"/login_sid.lua", inbuffer, FB_BUFFER, NULL); if (len > FB_BUFFER) len = FB_BUFFER; else if (len < 0) { fprintf (stderr, "%s:%d Error getting challenge from Fritzbox\n", __FILE__, __LINE__); errno = EAI_FAIL; return -1; } inbuffer[len] = '\0'; XMLPrepare(inbuffer, len, &xmldoc, &xmlnode); // // parse for SID and Challange while (xmlnode) { if ((!xmlStrcmp(xmlnode->name, (const xmlChar *)"SessionInfo"))){ xmlNodePtr xmlchild = xmlnode->xmlChildrenNode; while (xmlchild) { if ((!xmlStrcmp(xmlchild->name, (const xmlChar *)"SID"))) SID = (char *)xmlNodeListGetString(xmldoc, xmlchild->children, 1); else if ((!xmlStrcmp(xmlchild->name, (const xmlChar *)"Challenge"))) challenge = (char*) xmlNodeListGetString(xmldoc, xmlchild->children, 1); xmlchild = xmlchild->next; } } xmlnode = xmlnode->next; } xmlFreeDoc(xmldoc); // // create Challenge-PSW -> MD5 sum // // // convert to UTF16_LE std::string convertfrom = challenge+"-"+password; char *conv_input = (char *)convertfrom.c_str(); size_t conv_inbytes = strlen (conv_input); size_t conv_size; char convertedpsw[PWSSIZE]; char *conv_output = (char*)convertedpsw; size_t conv_outbytes = PWSSIZE; iconv_t ic = iconv_open("UTF-16LE", "UTF-8"); if ((long int)ic == -1) { fprintf (stderr, "iconv_open error: %s\n", strerror(errno)); return -1; } conv_size = iconv(ic, &conv_input, &conv_inbytes, (char**)&conv_output, &conv_outbytes); if ((long int)conv_size == -1) { fprintf (stderr, "iconv error: %s\n", strerror(errno)); return -1; } iconv_close(ic); // // create password formdata = "response="+challenge+"-"+generateMD5(convertedpsw, conv_output-convertedpsw)+"&username="+username; len = tcp.WebGetFile(hostname+"/login_sid.lua", inbuffer, FB_BUFFER, (char*)formdata.c_str()); if (len > FB_BUFFER) len = FB_BUFFER; else if (len < 0) { errno = EAI_FAIL; return -1; } inbuffer[len] = '\0'; // // parse for SID and Challange XMLPrepare(inbuffer, len, &xmldoc, &xmlnode); while (xmlnode) { if ((!xmlStrcmp(xmlnode->name, (const xmlChar *)"SessionInfo"))){ xmlNodePtr xmlchild = xmlnode->xmlChildrenNode; while (xmlchild) { if ((!xmlStrcmp(xmlchild->name, (const xmlChar *)"SID"))) SID = (char *)xmlNodeListGetString(xmldoc, xmlchild->children, 1); else if ((!xmlStrcmp(xmlchild->name, (const xmlChar *)"Challenge"))) challenge = (char*) xmlNodeListGetString(xmldoc, xmlchild->children, 1); xmlchild = xmlchild->next; } } xmlnode = xmlnode->next; } xmlFreeDoc(xmldoc); errno = 0; return 0; }; /* * read sidfile, if it does not exist create file. */ #define BUFSIZE 1024 int FBSmartHome::UseSIDFile(std::string fn) { char buffer[BUFSIZE]; int i; if ((fsidfile = fopen (fn.c_str(), "r+")) == NULL) { // file not found create file if ((fsidfile = fopen (fn.c_str(), "w")) == NULL) { fprintf (stderr, "error on writing sidfile '%s' : %s\n", fn.c_str(), strerror(errno)); return -1; } } else { // sidfile found and open fseek (fsidfile, 0, SEEK_SET); if (fgets(buffer, BUFSIZE, fsidfile) != NULL) { buffer[BUFSIZE] = '\0'; i = strlen (buffer); if (i > 0 && buffer[i-1] == '\n') buffer[i-1] = '\0'; SID = buffer; } } return 0; } /* * if the sid file is open save current sid. */ int FBSmartHome::CloseSIDFile() { if (fsidfile != NULL) { fseek (fsidfile, 0, SEEK_SET); fprintf (fsidfile, "%s", SID.c_str()); fclose(fsidfile); fsidfile = NULL; } return 0; } int FBSmartHome::XMLReadAttr(FBSmartHomeDevice *dev, std::string objPrefix, xmlDocPtr xmldoc, xmlAttrPtr xmlattr) { char *txt = NULL; while (xmlattr) { if (dev->others.length() > 0) dev->others += "\t"; txt = (char*) xmlNodeListGetString(xmldoc, xmlattr->children, 1); if (txt != NULL) dev->others += objPrefix + "." + (std::string)(char*) xmlattr->name + "=" + txt; else dev->others += (std::string)(char*) xmlattr->name + "="; xmlattr = xmlattr->next; } return 0; } int FBSmartHome::XMLReadOBJ(FBSmartHomeDevice *dev, std::string objPrefix, xmlDocPtr xmldoc, xmlNodePtr xmlobj) { std::string tmp = ""; char *txt = NULL; while (xmlobj) { if (dev->others.length() > 0) dev->others += "\t"; txt = (char*) xmlNodeListGetString(xmldoc, xmlobj->children, 1); if (txt != NULL) dev->others += objPrefix + "." + (std::string)(char*) xmlobj->name + "=" + txt; else if (xmlobj->children) { XMLReadAttr(dev, objPrefix+"."+(std::string)(char*)xmlobj->name, xmldoc, xmlobj->properties); XMLReadOBJ(dev, objPrefix+"."+(std::string)(char*)xmlobj->name, xmldoc, xmlobj->children); } else dev->others += (std::string)(char*) xmlobj->name + "="; xmlobj = xmlobj->next; } return 0; } int FBSmartHome::XMLReadAndAddDevice(std::list *devlist, std::string defType, xmlDocPtr xmldoc, xmlNodePtr xmlnode) { xmlAttrPtr xmlattr; xmlNodePtr child; std::string tmp = ""; char *txt = NULL; FBSmartHomeDevice fbdev; // // reset all fields fbdev.others = ""; fbdev.id = ""; fbdev.name = ""; fbdev.type = defType; // // read attributes xmlattr = xmlnode->properties; while (xmlattr) { if (!xmlStrcmp(xmlattr->name, (const xmlChar *)"identifier")) fbdev.id = (char *) xmlNodeListGetString(xmldoc, xmlattr->children, 1); else { txt = (char*) xmlNodeListGetString(xmldoc, xmlattr->children, 1); if (txt != NULL) fbdev.others += (std::string)(char*) xmlattr->name + "=" + txt + "\t"; else fbdev.others += (std::string)(char*) xmlattr->name + "=\t"; } xmlattr = xmlattr->next; } // // read subelements child = xmlnode->children; while (child) { if (!xmlStrcmp(child->name, (const xmlChar *)"name")) fbdev.name = (char *) xmlNodeListGetString(xmldoc, child->children, 1); else if (!xmlStrcmp(child->name, (const xmlChar *)"button")) { fbdev.type = "button"; XMLReadAndAddDevice(devlist, "button", xmldoc, child); } else if (!xmlStrcmp(child->name, (const xmlChar *)"hkr")) { fbdev.type = (char*)child->name; XMLReadOBJ(&fbdev, "hkr" , xmldoc, child->children); } else { txt = (char*) xmlNodeListGetString(xmldoc, child->children, 1); if (txt != NULL) { fbdev.others += (std::string)(char*) child->name + "=" + txt + "\t"; } else { if (child->children) { XMLReadAttr(&fbdev, (std::string)(char*)child->name, xmldoc, child->properties); XMLReadOBJ(&fbdev, (std::string)(char*)child->name, xmldoc, child->children); } else { fbdev.others += (std::string)(char*) child->name + "=\t"; } } } child = child->next; } devlist->push_back(fbdev); return 0; } /* * retrieve the device list from the fritzbox */ std::list *FBSmartHome::GetDevices() { std::list *devlist = NULL; FBSmartHomeDevice smd; xmlDocPtr xmldoc; xmlNodePtr xmlnode; int len; devlist = new std::list; // retrieve data len = tcp.WebGetFile(hostname+"/webservices/homeautoswitch.lua?sid="+SID+"&switchcmd=getdevicelistinfos", inbuffer, FB_BUFFER, NULL); if (len > FB_BUFFER) len = FB_BUFFER; else if (len < 0) { fprintf (stderr, "%s:%d Error getting challenge from Fritzbox\n", __FILE__, __LINE__); errno = EAI_FAIL; return NULL; } inbuffer[len] = '\0'; XMLPrepare(inbuffer, len, &xmldoc, &xmlnode); // // parse for SID and Challange while (xmlnode) { if ((!xmlStrcmp(xmlnode->name, (const xmlChar *)"devicelist"))){ xmlNodePtr xmlchild = xmlnode->xmlChildrenNode; // // read each device details // while (xmlchild) { if (!xmlStrcmp(xmlchild->name, (const xmlChar *)"device")) XMLReadAndAddDevice(devlist, "", xmldoc, xmlchild); xmlchild = xmlchild->next; } } xmlnode = xmlnode->next; } xmlFreeDoc(xmldoc); if (SID.compare("0000000000000000") == 0) { hostname = ""; errno = ENOTCONN; return NULL; } return devlist; } /* * read data from device and fill out FBSmartHomeHZR structure */ int FBSmartHome::GetHKR(std::string ain, FBSmartHomeHKR *hkr) { std::string s; FBSmartHomeDevice smd; xmlDocPtr xmldoc; xmlNodePtr xmlnode; xmlAttrPtr xmlattr; int len, i; if (hkr == NULL) return -1; for (s = "", i = 0; i < (int)ain.length(); i++) { if (ain[i] == ' ') s += "%20"; else s += ain[i]; } // // read device info // // // retrieve data len = tcp.WebGetFile(hostname+"/webservices/homeautoswitch.lua?sid="+SID+"&switchcmd=getdeviceinfos&ain="+s, inbuffer, FB_BUFFER, NULL); if (len > FB_BUFFER) len = FB_BUFFER; else if (len < 0) { fprintf (stderr, "%s:%d Error getting challenge from Fritzbox\n", __FILE__, __LINE__); errno = EAI_FAIL; return -1; } inbuffer[len] = '\0'; XMLPrepare(inbuffer, len, &xmldoc, &xmlnode); // // parse for SID and Challange while (xmlnode) { if ((!xmlStrcmp(xmlnode->name, (const xmlChar *)"device"))){ xmlNodePtr xmlchild = xmlnode->xmlChildrenNode; xmlattr = xmlnode->properties; while (xmlattr) { if (!xmlStrcmp(xmlattr->name, (const xmlChar *)"identifier")) hkr->id = (char *) xmlNodeListGetString(xmldoc, xmlattr->children, 1); xmlattr = xmlattr->next; } while (xmlchild) { // name if (!xmlStrcmp(xmlchild->name, (const xmlChar *)"name")) hkr->name = (char *) xmlNodeListGetString(xmldoc, xmlchild->children, 1); // temperatur else if (!xmlStrcmp(xmlchild->name, (const xmlChar *)"temperature")) { xmlNodePtr xmlc = xmlchild->children; while(xmlc) { if (!xmlStrcmp(xmlc->name, (const xmlChar *)"celsius")) { hkr->temp = (atof_check((char *) xmlNodeListGetString(xmldoc, xmlc->children, 1))) / 10.0; } else if (!xmlStrcmp(xmlc->name, (const xmlChar *)"offset")) { hkr->offset = atoi_check ((char *) xmlNodeListGetString(xmldoc, xmlc->children, 1), -999); } xmlc = xmlc->next; } } // hkr else if (!xmlStrcmp(xmlchild->name, (const xmlChar *)"hkr")) { xmlNodePtr xmlc = xmlchild->children; while(xmlc) { if (!xmlStrcmp(xmlc->name, (const xmlChar *)"tist")) { hkr->temp_cur = atof_check ((char *) xmlNodeListGetString(xmldoc, xmlc->children, 1)); hkr->temp_cur = hkr->temp_cur / 2.0; } else if (!xmlStrcmp(xmlc->name, (const xmlChar *)"tsoll")) { hkr->temp_set = atof_check ((char *) xmlNodeListGetString(xmldoc, xmlc->children, 1)); hkr->temp_set = hkr->temp_set / 2.0; } else if (!xmlStrcmp(xmlc->name, (const xmlChar *)"absenk")) { hkr->temp_l = atof_check ((char *) xmlNodeListGetString(xmldoc, xmlc->children, 1)); hkr->temp_l = hkr->temp_l / 2.0; } else if (!xmlStrcmp(xmlc->name, (const xmlChar *)"komfort")) { hkr->temp_h = atof_check ((char *) xmlNodeListGetString(xmldoc, xmlc->children, 1)); hkr->temp_h = hkr->temp_h / 2.0; } xmlc = xmlc->next; } } xmlchild = xmlchild->next; } } xmlnode = xmlnode->next; } xmlFreeDoc(xmldoc); // // // // retrieve data len = tcp.WebGetFile(hostname+"/webservices/homeautoswitch.lua?sid="+SID+"&switchcmd=getbasicdevicestats&ain="+s, inbuffer, FB_BUFFER, NULL); if (len > FB_BUFFER) len = FB_BUFFER; else if (len < 0) { fprintf (stderr, "%s:%d Error getting challenge from Fritzbox\n", __FILE__, __LINE__); errno = EAI_FAIL; return -1; } inbuffer[len] = '\0'; XMLPrepare(inbuffer, len, &xmldoc, &xmlnode); // // parse for SID and Challange while (xmlnode) { if ((!xmlStrcmp(xmlnode->name, (const xmlChar *)"devicestats"))){ xmlNodePtr xmlchild = xmlnode->xmlChildrenNode; std::string txt; float f; char *t; while (xmlchild) { // temperatur if (!xmlStrcmp(xmlchild->name, (const xmlChar *)"temperature")) { xmlNodePtr xmlc = xmlchild->children; int count = 0; while(xmlc) { if (!xmlStrcmp(xmlc->name, (const xmlChar *)"stats")) { xmlattr = xmlc->properties; while (xmlattr) { if (!xmlStrcmp(xmlattr->name, (const xmlChar *)"count")) count = atoi_check((char *) xmlNodeListGetString(xmldoc, xmlattr->children, 1), -999); else if (!xmlStrcmp(xmlattr->name, (const xmlChar *)"grid")) hkr-> statsgrid = atoi_check((char *) xmlNodeListGetString(xmldoc, xmlattr->children, 1), -999); xmlattr = xmlattr->next; } txt = ((char *) xmlNodeListGetString(xmldoc, xmlc->children, 1)); hkr->stats.clear(); t = (char*)txt.c_str(); for (int i = 0; t != NULL && i < count; i++) { if (*t == ',') t++; f = atoi_check (t, 0); hkr->stats.push_back(f); t = strchr(t, ','); } } xmlc = xmlc->next; } } xmlchild = xmlchild->next; } } xmlnode = xmlnode->next; } xmlFreeDoc(xmldoc); return 0; } /* * switch device off = 0, on = 1 or toggle = 2 */ int FBSmartHome::SimpleOnOff(std::string ain, int oot) { std::string s; int i, len; if (ain.length() <= 0) return -1; for (s = "", i = 0; i < (int)ain.length(); i++) { if (ain[i] == ' ') s += "%20"; else s += ain[i]; } // // send switch on / off len = tcp.WebGetFile(hostname+"/webservices/homeautoswitch.lua?sid="+SID+"&switchcmd=setsimpleonoff&ain="+s+"&onoff="+to_string(oot), inbuffer, FB_BUFFER, NULL); if (len > FB_BUFFER) len = FB_BUFFER; else if (len < 0) { fprintf (stderr, "%s:%d Error getting challenge from Fritzbox\n", __FILE__, __LINE__); errno = EAI_FAIL; return -1; } inbuffer[len] = '\0'; return 0; } /* * set device level 0...255 (0-100%) */ int FBSmartHome::SetLevel(std::string ain, int level) { std::string s; int i, len; if (ain.length() <= 0) return -1; for (s = "", i = 0; i < (int)ain.length(); i++) { if (ain[i] == ' ') s += "%20"; else s += ain[i]; } // // send switch on / off len = tcp.WebGetFile(hostname+"/webservices/homeautoswitch.lua?sid="+SID+"&switchcmd=setlevel&ain="+s+"&level="+to_string(level), inbuffer, FB_BUFFER, NULL); if (len > FB_BUFFER) len = FB_BUFFER; else if (len < 0) { fprintf (stderr, "%s:%d Error getting challenge from Fritzbox\n", __FILE__, __LINE__); errno = EAI_FAIL; return -1; } inbuffer[len] = '\0'; return 0; } std::list *FBSmartHome::GetColorDefaults (std::string ain) { std::list *lst = new std::list; std::string cmd; std::string s; int i, len; xmlDocPtr xmldoc; xmlNodePtr xmlnode; xmlAttrPtr xmlattr; // // convert space char to %20 to be used in URLs // if (ain.length() <= 0) return lst; for (s = "", i = 0; i < (int)ain.length(); i++) { if (ain[i] == ' ') s += "%20"; else s += ain[i]; } cmd = hostname+"/webservices/homeautoswitch.lua?sid="+SID+"&switchcmd=getcolordefaults&ain="+s; // // send switch on / off len = tcp.WebGetFile(cmd, inbuffer, FB_BUFFER, NULL); if (len > FB_BUFFER) len = FB_BUFFER; else if (len < 0) { fprintf (stderr, "%s:%d Error getting challenge from Fritzbox\n", __FILE__, __LINE__); errno = EAI_FAIL; return lst; } inbuffer[len] = '\0'; // // convert inbuffer to list of values // XMLPrepare(inbuffer, len, &xmldoc, &xmlnode); // // parse for SID and Challange while (xmlnode) { if ((!xmlStrcmp(xmlnode->name, (const xmlChar *)"colordefaults"))){ xmlNodePtr xmlchild = xmlnode->xmlChildrenNode; std::string txt; while (xmlchild) { // hsdefaults if (!xmlStrcmp(xmlchild->name, (const xmlChar *)"hsdefaults")) { xmlNodePtr xmlhs = xmlchild->children; while(xmlhs) { if (!xmlStrcmp(xmlhs->name, (const xmlChar *)"hs")) { xmlNodePtr xmlc = xmlhs->children; while (xmlc) { if (!xmlStrcmp(xmlc->name, (const xmlChar *)"color")) { xmlattr = xmlc->properties; FBSmartHomeLightDefaults def = { -1, -1, -1 }; while (xmlattr) { if (!xmlStrcmp(xmlattr->name, (const xmlChar *)"hue")) def.hue = atoi_check((char *) xmlNodeListGetString(xmldoc, xmlattr->children, 1), 0); else if (!xmlStrcmp(xmlattr->name, (const xmlChar *)"sat")) def.sat = atoi_check((char *) xmlNodeListGetString(xmldoc, xmlattr->children, 1), 0); else if (!xmlStrcmp(xmlattr->name, (const xmlChar *)"val")) def.val = atoi_check((char *) xmlNodeListGetString(xmldoc, xmlattr->children, 1), 0); xmlattr = xmlattr->next; } if (def.hue != -1) lst->push_back(def); } xmlc = xmlc->next; } } xmlhs = xmlhs->next; } } xmlchild = xmlchild->next; } } xmlnode = xmlnode->next; } return lst; } /* * */ int FBSmartHome::SetColor(std::string ain, int hue, int sat, int duration) { std::list *defcolors = NULL; std::list::iterator iter; std::string s; std::string cmd; int i, len; float delta; struct { float delta; int hue; int sat; int val; } selected_color; // // convert space char to %20 to be used in URLs // if (ain.length() <= 0) return -1; for (s = "", i = 0; i < (int)ain.length(); i++) { if (ain[i] == ' ') s += "%20"; else s += ain[i]; } // // need to read defaults and round up to the next closest parameters // defcolors = GetColorDefaults(ain); iter = defcolors->begin(); if (iter != defcolors->end()) { selected_color.hue = iter->hue; selected_color.sat = iter->sat; selected_color.val = iter->val; selected_color.delta = (iter->hue - hue) * (iter->hue - hue) + (iter->sat - sat) * (iter->hue - sat); for (iter++; iter != defcolors->end(); iter++) { delta = (iter->hue - hue) * (iter->hue - hue) + (iter->sat - sat) * (iter->hue - sat); if (delta < selected_color.delta) { selected_color.hue = iter->hue; selected_color.sat = iter->sat; selected_color.val = iter->val; selected_color.delta = delta; } } cmd = hostname+"/webservices/homeautoswitch.lua?sid="+SID+"&switchcmd=setcolor&ain="+s; cmd += "&hue="+to_string(selected_color.hue)+"&saturation="+to_string(selected_color.sat)+"&duration="+to_string(duration); printf ("%s:%d SetColor ain:'%s' hue,sat %d,%d selected hue,sat: %d,%d duration:%d\n", __FILE__, __LINE__, ain.c_str(), hue, sat, selected_color.hue, selected_color.sat, duration); // // send switch on / off len = tcp.WebGetFile(cmd, inbuffer, FB_BUFFER, NULL); if (len > FB_BUFFER) len = FB_BUFFER; else if (len < 0) { fprintf (stderr, "%s:%d Error getting challenge from Fritzbox\n", __FILE__, __LINE__); errno = EAI_FAIL; return -1; } inbuffer[len] = '\0'; } return 0; }